#region Copyright notice and license // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // http://github.com/jskeet/dotnet-protobufs/ // Original C++/Java/Python code: // http://code.google.com/p/protobuf/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endregion using System; using Google.ProtocolBuffers; using Google.ProtocolBuffers.Serialization.Http; using Google.ProtocolBuffers.TestProtos; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; using Google.ProtocolBuffers.Serialization; using System.Text; namespace Google.ProtocolBuffers { /// /// This class verifies the correct code is generated from unittest_rpc_interop.proto and provides a small demonstration /// of using the new IRpcDispatch to write a client/server /// [TestClass] public class TestRpcForMimeTypes { /// /// A sample implementation of the ISearchService for testing /// private class ExampleSearchImpl : ISearchService { SearchResponse ISearchService.Search(SearchRequest searchRequest) { if (searchRequest.CriteriaCount == 0) { throw new ArgumentException("No criteria specified.", new InvalidOperationException()); } SearchResponse.Builder resp = SearchResponse.CreateBuilder(); foreach (string criteria in searchRequest.CriteriaList) { resp.AddResults( SearchResponse.Types.ResultItem.CreateBuilder().SetName(criteria).SetUrl("http://search.com"). Build()); } return resp.Build(); } SearchResponse ISearchService.RefineSearch(RefineSearchRequest refineSearchRequest) { SearchResponse.Builder resp = refineSearchRequest.PreviousResults.ToBuilder(); foreach (string criteria in refineSearchRequest.CriteriaList) { resp.AddResults( SearchResponse.Types.ResultItem.CreateBuilder().SetName(criteria).SetUrl("http://refine.com"). Build()); } return resp.Build(); } } /// /// An example extraction of the wire protocol /// private interface IHttpTransfer { void Execute(string method, string contentType, Stream input, string acceptType, Stream output); } /// /// An example of a server responding to a web/http request /// private class ExampleHttpServer : IHttpTransfer { public readonly MessageFormatOptions Options = new MessageFormatOptions { ExtensionRegistry = ExtensionRegistry.Empty, FormattedOutput = true, XmlReaderOptions = XmlReaderOptions.ReadNestedArrays, XmlReaderRootElementName = "request", XmlWriterOptions = XmlWriterOptions.OutputNestedArrays, XmlWriterRootElementName = "response" }; private readonly IRpcServerStub _stub; public ExampleHttpServer(ISearchService implementation) { //on the server, we create a dispatch to call the appropriate method by name IRpcDispatch dispatch = new SearchService.Dispatch(implementation); //we then wrap that dispatch in a server stub which will deserialize the wire bytes to the message //type appropriate for the method name being invoked. _stub = new SearchService.ServerStub(dispatch); } void IHttpTransfer.Execute(string method, string contentType, Stream input, string acceptType, Stream output) { //3.5: _stub.HttpCallMethod( Extensions.HttpCallMethod(_stub, method, Options, contentType, input, acceptType, output ); } } /// /// An example of a client sending a wire request /// private class ExampleClient : IRpcDispatch { public readonly MessageFormatOptions Options = new MessageFormatOptions { ExtensionRegistry = ExtensionRegistry.Empty, FormattedOutput = true, XmlReaderOptions = XmlReaderOptions.ReadNestedArrays, XmlReaderRootElementName = "response", XmlWriterOptions = XmlWriterOptions.OutputNestedArrays, XmlWriterRootElementName = "request" }; private readonly IHttpTransfer _wire; private readonly string _mimeType; public ExampleClient(IHttpTransfer wire, string mimeType) { _wire = wire; _mimeType = mimeType; } TMessage IRpcDispatch.CallMethod(string method, IMessageLite request, IBuilderLite response) { MemoryStream input = new MemoryStream(); MemoryStream output = new MemoryStream(); //Write to _mimeType format Extensions.WriteTo(request, Options, _mimeType, input); input.Position = 0; _wire.Execute(method, _mimeType, input, _mimeType, output); //Read from _mimeType format output.Position = 0; Extensions.MergeFrom(response, Options, _mimeType, output); return response.Build(); } } /// /// Test sending and recieving messages via text/json /// [TestMethod] public void TestClientServerWithJsonFormat() { ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl()); //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting IHttpTransfer wire = server; ISearchService client = new SearchService(new ExampleClient(wire, "text/json")); //now the client has a real, typed, interface to work with: SearchResponse result = client.Search(SearchRequest.CreateBuilder().AddCriteria("Test").Build()); Assert.AreEqual(1, result.ResultsCount); Assert.AreEqual("Test", result.ResultsList[0].Name); Assert.AreEqual("http://search.com", result.ResultsList[0].Url); //The test part of this, call the only other method result = client.RefineSearch( RefineSearchRequest.CreateBuilder().SetPreviousResults(result).AddCriteria("Refine").Build()); Assert.AreEqual(2, result.ResultsCount); Assert.AreEqual("Test", result.ResultsList[0].Name); Assert.AreEqual("http://search.com", result.ResultsList[0].Url); Assert.AreEqual("Refine", result.ResultsList[1].Name); Assert.AreEqual("http://refine.com", result.ResultsList[1].Url); } /// /// Test sending and recieving messages via text/json /// [TestMethod] public void TestClientServerWithXmlFormat() { ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl()); //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting IHttpTransfer wire = server; ISearchService client = new SearchService(new ExampleClient(wire, "text/xml")); //now the client has a real, typed, interface to work with: SearchResponse result = client.Search(SearchRequest.CreateBuilder().AddCriteria("Test").Build()); Assert.AreEqual(1, result.ResultsCount); Assert.AreEqual("Test", result.ResultsList[0].Name); Assert.AreEqual("http://search.com", result.ResultsList[0].Url); //The test part of this, call the only other method result = client.RefineSearch( RefineSearchRequest.CreateBuilder().SetPreviousResults(result).AddCriteria("Refine").Build()); Assert.AreEqual(2, result.ResultsCount); Assert.AreEqual("Test", result.ResultsList[0].Name); Assert.AreEqual("http://search.com", result.ResultsList[0].Url); Assert.AreEqual("Refine", result.ResultsList[1].Name); Assert.AreEqual("http://refine.com", result.ResultsList[1].Url); } /// /// Test sending and recieving messages via text/json /// [TestMethod] public void TestClientServerWithProtoFormat() { ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl()); //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting IHttpTransfer wire = server; ISearchService client = new SearchService(new ExampleClient(wire, "application/x-protobuf")); //now the client has a real, typed, interface to work with: SearchResponse result = client.Search(SearchRequest.CreateBuilder().AddCriteria("Test").Build()); Assert.AreEqual(1, result.ResultsCount); Assert.AreEqual("Test", result.ResultsList[0].Name); Assert.AreEqual("http://search.com", result.ResultsList[0].Url); //The test part of this, call the only other method result = client.RefineSearch( RefineSearchRequest.CreateBuilder().SetPreviousResults(result).AddCriteria("Refine").Build()); Assert.AreEqual(2, result.ResultsCount); Assert.AreEqual("Test", result.ResultsList[0].Name); Assert.AreEqual("http://search.com", result.ResultsList[0].Url); Assert.AreEqual("Refine", result.ResultsList[1].Name); Assert.AreEqual("http://refine.com", result.ResultsList[1].Url); } /// /// Test sending and recieving messages via text/json /// [TestMethod] public void TestClientServerWithCustomFormat() { ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl()); //Setup our custom mime-type format as the only format supported: server.Options.MimeInputTypes.Clear(); server.Options.MimeInputTypes.Add("foo/bar", CodedInputStream.CreateInstance); server.Options.MimeOutputTypes.Clear(); server.Options.MimeOutputTypes.Add("foo/bar", CodedOutputStream.CreateInstance); //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting IHttpTransfer wire = server; ExampleClient exclient = new ExampleClient(wire, "foo/bar"); //Add our custom mime-type format exclient.Options.MimeInputTypes.Add("foo/bar", CodedInputStream.CreateInstance); exclient.Options.MimeOutputTypes.Add("foo/bar", CodedOutputStream.CreateInstance); ISearchService client = new SearchService(exclient); //now the client has a real, typed, interface to work with: SearchResponse result = client.Search(SearchRequest.CreateBuilder().AddCriteria("Test").Build()); Assert.AreEqual(1, result.ResultsCount); Assert.AreEqual("Test", result.ResultsList[0].Name); Assert.AreEqual("http://search.com", result.ResultsList[0].Url); //The test part of this, call the only other method result = client.RefineSearch( RefineSearchRequest.CreateBuilder().SetPreviousResults(result).AddCriteria("Refine").Build()); Assert.AreEqual(2, result.ResultsCount); Assert.AreEqual("Test", result.ResultsList[0].Name); Assert.AreEqual("http://search.com", result.ResultsList[0].Url); Assert.AreEqual("Refine", result.ResultsList[1].Name); Assert.AreEqual("http://refine.com", result.ResultsList[1].Url); } /// /// Test sending and recieving messages via text/json /// [TestMethod] public void TestServerWithUriFormat() { ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl()); //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting IHttpTransfer wire = server; MemoryStream input = new MemoryStream(Encoding.UTF8.GetBytes("?Criteria=Test&Criteria=Test+of%20URI")); MemoryStream output = new MemoryStream(); //Call the server wire.Execute("Search", MessageFormatOptions.ContentFormUrlEncoded, input, MessageFormatOptions.ContentTypeProtoBuffer, output ); SearchResponse result = SearchResponse.ParseFrom(output.ToArray()); Assert.AreEqual(2, result.ResultsCount); Assert.AreEqual("Test", result.ResultsList[0].Name); Assert.AreEqual("http://search.com", result.ResultsList[0].Url); Assert.AreEqual("Test of URI", result.ResultsList[1].Name); Assert.AreEqual("http://search.com", result.ResultsList[1].Url); } /// /// Test sending and recieving messages via text/json /// [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void TestInvalidMimeType() { ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl()); //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting IHttpTransfer wire = server; MemoryStream input = new MemoryStream(); MemoryStream output = new MemoryStream(); //Call the server wire.Execute("Search", "bad/mime", input, MessageFormatOptions.ContentTypeProtoBuffer, output ); Assert.Fail(); } /// /// Test sending and recieving messages via text/json /// [TestMethod] public void TestDefaultMimeType() { ExampleHttpServer server = new ExampleHttpServer(new ExampleSearchImpl()); //obviously if this was a 'real' transport we would not use the server, rather the server would be listening, the client transmitting IHttpTransfer wire = server; MemoryStream input = new MemoryStream(new SearchRequest.Builder().AddCriteria("Test").Build().ToByteArray()); MemoryStream output = new MemoryStream(); //With this default set, any invalid/unknown mime-type will be mapped to use that format server.Options.DefaultContentType = MessageFormatOptions.ContentTypeProtoBuffer; wire.Execute("Search", "foo", input, "bar", output ); SearchResponse result = SearchResponse.ParseFrom(output.ToArray()); Assert.AreEqual(1, result.ResultsCount); Assert.AreEqual("Test", result.ResultsList[0].Name); Assert.AreEqual("http://search.com", result.ResultsList[0].Url); } } }