// // HttpServerChannelTest.cs // - Unit tests for System.Runtime.Remoting.Channels.Http // // Author: Jeffrey Stedfast // // Copyright (C) 2008 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.IO; using System.Net; using System.Text; using System.Reflection; using System.Net.Sockets; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using System.Threading; using NUnit.Framework; namespace MonoTests.Remoting { [TestFixture] public class HttpServerChannelTests { HttpServerChannel serverChannel; int port = 9090; [TestFixtureSetUp] public void StartHttpServer () { if (serverChannel != null) return; serverChannel = new HttpServerChannel ("HttpServerChannelTests", port); ChannelServices.RegisterChannel (serverChannel); RemotingConfiguration.RegisterWellKnownServiceType ( typeof (RemoteObject), "RemoteObject.rem", WellKnownObjectMode.Singleton); } [TestFixtureTearDown] public void StopHttpServer () { ChannelServices.UnregisterChannel (serverChannel); } struct ParseURLTestCase { public readonly string input; public readonly string retval; public readonly string objectURI; public ParseURLTestCase (string s0, string s1, string s2) { input = s0; retval = s1; objectURI = s2; } }; ParseURLTestCase[] ParseURLTests = new ParseURLTestCase[] { //new ParseURLTestCase ("http:", "http:", null), // KnownFailure but works on Microsoft's .NET new ParseURLTestCase ("http://", "http://", null), new ParseURLTestCase ("http:localhost", null, null), new ParseURLTestCase ("ftp://localhost", null, null), new ParseURLTestCase ("http://localhost", "http://localhost", null), new ParseURLTestCase ("hTtP://localhost", "hTtP://localhost", null), new ParseURLTestCase ("https://localhost", "https://localhost", null), new ParseURLTestCase ("http://localhost:/", "http://localhost:", "/"), new ParseURLTestCase ("http://localhost:9090", "http://localhost:9090", null), new ParseURLTestCase ("http://localhost:9090/", "http://localhost:9090", "/"), new ParseURLTestCase ("http://localhost:9090/RemoteObject.rem", "http://localhost:9090", "/RemoteObject.rem"), new ParseURLTestCase ("http://localhost:q24691247abc1297/RemoteObject.rem", "http://localhost:q24691247abc1297", "/RemoteObject.rem"), }; [Test] // HttpChannel.Parse () public void ParseURL () { HttpChannel channel; int i; channel = new HttpChannel (); for (i = 0; i < ParseURLTests.Length; i++) { string retval, objectURI; retval = channel.Parse (ParseURLTests[i].input, out objectURI); Assert.AreEqual (ParseURLTests[i].retval, retval); Assert.AreEqual (ParseURLTests[i].objectURI, objectURI); } } static void Send (NetworkStream stream, string str) { byte [] buf = Encoding.ASCII.GetBytes (str); Send (stream, buf); } static void Send (NetworkStream stream, byte[] buf) { //Console.Write ("C: "); //DumpByteArray (buf, 3); //Console.Write ("\n"); stream.Write (buf, 0, buf.Length); } static int Receive (NetworkStream stream, int chunks, out byte[] buf) { byte[] buffer = new byte [4096]; int n, nread = 0; do { if ((n = stream.Read (buffer, nread, buffer.Length - nread)) > 0) nread += n; chunks--; } while (n > 0 && chunks > 0); //Console.Write ("S: "); if (nread > 0) { buf = new byte [nread]; for (int i = 0; i < nread; i++) buf[i] = buffer[i]; //DumpByteArray (buf, 3); //Console.Write ("\n"); } else { //Console.Write ("(null)\n"); buf = null; } return nread; } static string ByteArrayToString (byte[] buf, int indent) { StringBuilder sb = new StringBuilder (); for (int i = 0; i < buf.Length; i++) { if (!Char.IsControl ((char) buf[i])) { sb.Append ((char) buf[i]); } else if (buf[i] == '\r') { sb.Append ("\\r"); } else if (buf[i] == '\n') { sb.Append ("\\n\n"); for (int j = 0; j < indent; j++) sb.Append (' '); } else { sb.Append (String.Format ("\\x{0:x2}", buf[i])); } } return sb.ToString (); } static void DumpByteArray (byte[] buf, int indent) { Console.Write (ByteArrayToString (buf, indent)); } static int GetResponseContentOffset (byte[] response) { for (int i = 0; i < response.Length - 3; i++) { if (response[i + 0] == '\r' && response[i + 1] == '\n' && response[i + 2] == '\r' && response[i + 3] == '\n') return i + 3; } return -1; } static bool ResponseMatches (byte[] expected, byte[] actual) { int i, j; // First, we compare the first line of the response - they should match for (i = 0; i < expected.Length && i < actual.Length; i++) { if (actual[i] != expected[i]) { // HTTP/1.1 vs HTTP/1.0 if (i == 7 && expected[0] == 'H' && expected[1] == 'T' && expected[2] == 'T' && expected[3] == 'P' && expected[4] == '/' && expected[5] == '1' && expected[6] == '.' && expected[7] == '1' && actual[7] == '0') continue; //Console.WriteLine ("\nFirst line of actual response did not match"); return false; } if (expected[i] == '\n') break; } if (i >= actual.Length) { //Console.WriteLine ("Actual response too short"); return false; } // now compare the content i = GetResponseContentOffset (expected); j = GetResponseContentOffset (actual); for ( ; i < expected.Length && j < actual.Length; i++, j++) { if (actual[j] != expected[i]) { //Console.WriteLine ("Content of actual response did not match"); return false; } } if (i < expected.Length) { //Console.WriteLine ("Expected more content data..."); return false; } if (j < actual.Length) { //Console.WriteLine ("Got too much content data in the server response"); return false; } return true; } static void CreateBinaryMethodInvoke (string assemblyName, string objectName, string methodName, out byte[] content) { string text = String.Format ("{0}, {1}, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", objectName, assemblyName); byte[] lead = new byte [] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x11, 0x00, 0x00, 0x00, 0x12 }; byte[] buf; int index; content = new byte [lead.Length + 1 + methodName.Length + 1 + 1 + text.Length + 1]; lead.CopyTo (content, 0); index = lead.Length; buf = Encoding.ASCII.GetBytes (methodName); content[index++] = (byte) buf.Length; buf.CopyTo (content, index); index += buf.Length; content[index++] = (byte) 0x12; buf = Encoding.ASCII.GetBytes (text); content[index++] = (byte) buf.Length; buf.CopyTo (content, index); index += buf.Length; content[index] = (byte) 0x0b; } [Test] [Category ("NotWorking")] // the faked request content string might be wrong? public void TestBinaryTransport () { string assemblyName = Assembly.GetExecutingAssembly ().GetName ().Name; Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sock.Connect (new IPEndPoint (IPAddress.Loopback, port)); NetworkStream stream = new NetworkStream (sock); byte[] content, buf, outbuf; CreateBinaryMethodInvoke (assemblyName, typeof (RemoteObject).FullName, "ReturnOne", out content); // send our POST request Send (stream, String.Format ("POST /RemoteObject.rem HTTP/1.1\r\n" + "User-Agent: Mozilla/4.0+(compatible; MSIE 6.0; Windows 6.0.6000.0; MS .NET Remoting; MS .NET CLR 2.0.50727.1433 )\r\n" + "Content-Type: application/octet-stream\r\n" + "Host: 127.0.0.1:{0}\r\n" + "Content-Length: {1}\r\n" + "Expect: 100-continue\r\n" + "Connection: Keep-Alive\r\n" + "\r\n", port, content.Length)); // create our expected response buffer buf = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n"); Receive (stream, 1, out outbuf); Assert.IsNotNull (outbuf, "Server continuation response is null"); Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server continuation response:\n" + ByteArrayToString (outbuf, 0)); // send our content data Send (stream, content); // create our expected response buffer buf = new byte[] { 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x20, 0x32, 0x30, 0x30, 0x20, 0x4f, 0x4b, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x0d, 0x0a, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x20, 0x4d, 0x53, 0x20, 0x2e, 0x4e, 0x45, 0x54, 0x20, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x2c, 0x20, 0x4d, 0x53, 0x20, 0x2e, 0x4e, 0x45, 0x54, 0x20, 0x43, 0x4c, 0x52, 0x20, 0x32, 0x2e, 0x30, 0x2e, 0x35, 0x30, 0x37, 0x32, 0x37, 0x2e, 0x31, 0x34, 0x33, 0x33, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x38, 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x11, 0x08, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x0b }; Receive (stream, 2, out outbuf); Assert.IsNotNull (outbuf, "Server method-invoke response is null"); Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server method-invoke response:\n" + ByteArrayToString (outbuf, 0)); stream.Close (); } [Test] public void TestSoapTransport () { string assemblyName = Assembly.GetExecutingAssembly ().GetName ().Name; Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sock.Connect (new IPEndPoint (IPAddress.Loopback, port)); NetworkStream stream = new NetworkStream (sock); string methodName = "ReturnOne"; string headers, content; byte[] buf, outbuf; content = String.Format ("\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "", methodName, typeof (RemoteObject).FullName, assemblyName); headers = String.Format ("POST /RemoteObject.rem HTTP/1.1\r\n" + "User-Agent: Mozilla/4.0+(compatible; MSIE 6.0; Windows 6.0.6000.0; MS .NET Remoting; MS .NET CLR 2.0.50727.1433 )\r\n" + "Content-Type: text/xml; charset=\"utf-8\"\r\n" + "SOAPAction: \"http://schemas.microsoft.com/clr/nsassem/{0}/{1}#{2}\"\r\n" + "Host: 127.0.0.1:{3}\r\n" + "Content-Length: {4}\r\n" + "Expect: 100-continue\r\n" + "Connection: Keep-Alive\r\n" + "\r\n", typeof (RemoteObject).FullName, assemblyName, methodName, port, content.Length); Send (stream, headers); // create our expected response buffer buf = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n"); Receive (stream, 1, out outbuf); Assert.IsNotNull (outbuf, "Server continuation response is null"); Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server continuation response:\n" + ByteArrayToString (outbuf, 0)); // send our content data Send (stream, content); // create our expected response buffer #if MICROSOFT_DOTNET_SERVER content = String.Format ("\r\n" + "\r\n" + "\r\n" + "1\r\n" + "\r\n" + "\r\n" + "\r\n", methodName, typeof (RemoteObject).FullName, assemblyName); #else //slight differences in formatting content = String.Format ("\n" + " \n" + " \n" + " 1\n" + " \n" + " \n" + "", methodName, typeof (RemoteObject).FullName, assemblyName); #endif headers = String.Format ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=\"utf-8\"\r\n" + "Server: MS .NET Remoting, MS .NET CLR 2.0.50727.1433\r\n" + "Content-Length: {0}\r\n\r\n", content.Length); buf = Encoding.ASCII.GetBytes (headers + content); Receive (stream, 2, out outbuf); Assert.IsNotNull (outbuf, "Server method-invoke response is null"); Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server method-invoke response:\n" + ByteArrayToString (outbuf, 0)); stream.Close (); } object mutex = new object (); bool []retvals; void MultiClientStart () { int rv = 0; // the prupose of this is just to block until all clients have been created lock (mutex) { rv++; } RemoteObject remObj = new RemoteObject (); rv = remObj.Increment (); // make sure the value returned hasn't been returned to another thread as well lock (retvals) { Assert.IsTrue (!retvals[rv], "RemoteObject.Increment() has already returned " + rv); retvals[rv] = true; } } [Test] public void MultiClientConnection () { int num_clients = 20; HttpClientChannel clientChannel = new HttpClientChannel ("MultiClientConnection", null); ChannelServices.RegisterChannel (clientChannel); WellKnownClientTypeEntry remoteType = new WellKnownClientTypeEntry ( typeof (RemoteObject), "http://127.0.0.1:9090/RemoteObject.rem"); RemotingConfiguration.RegisterWellKnownClientType (remoteType); // start a bunch of clients... Thread []clients = new Thread [num_clients]; retvals = new bool [num_clients]; lock (mutex) { for (int i = 0; i < num_clients; i++) { clients[i] = new Thread (MultiClientStart); clients[i].Start (); retvals[i] = false; } } // wait for all clients to finish... for (int i = 0; i < num_clients; i++) clients[i].Join (); ChannelServices.UnregisterChannel (clientChannel); for (int i = 0; i < num_clients; i++) Assert.IsTrue (retvals[i], "RemoteObject.Incrememnt() didn't return a value of " + i); } } }