2 // HttpServerChannelTest.cs
3 // - Unit tests for System.Runtime.Remoting.Channels.Http
5 // Author: Jeffrey Stedfast <fejj@novell.com>
7 // Copyright (C) 2008 Novell, Inc (http://www.novell.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Reflection;
34 using System.Net.Sockets;
35 using System.Runtime.Remoting;
36 using System.Runtime.Remoting.Channels;
37 using System.Runtime.Remoting.Channels.Http;
38 using System.Threading;
39 using NUnit.Framework;
41 namespace MonoTests.Remoting {
43 public class HttpServerChannelTests {
44 HttpServerChannel serverChannel;
48 public void StartHttpServer ()
50 if (serverChannel != null)
53 serverChannel = new HttpServerChannel ("HttpServerChannelTests", port);
54 ChannelServices.RegisterChannel (serverChannel);
56 RemotingConfiguration.RegisterWellKnownServiceType (
57 typeof (RemoteObject), "RemoteObject.rem",
58 WellKnownObjectMode.Singleton);
62 public void StopHttpServer ()
64 ChannelServices.UnregisterChannel (serverChannel);
67 struct ParseURLTestCase {
68 public readonly string input;
69 public readonly string retval;
70 public readonly string objectURI;
72 public ParseURLTestCase (string s0, string s1, string s2)
80 ParseURLTestCase[] ParseURLTests = new ParseURLTestCase[] {
81 //new ParseURLTestCase ("http:", "http:", null), // KnownFailure but works on Microsoft's .NET
82 new ParseURLTestCase ("http://", "http://", null),
83 new ParseURLTestCase ("http:localhost", null, null),
84 new ParseURLTestCase ("ftp://localhost", null, null),
85 new ParseURLTestCase ("http://localhost", "http://localhost", null),
86 new ParseURLTestCase ("hTtP://localhost", "hTtP://localhost", null),
87 new ParseURLTestCase ("https://localhost", "https://localhost", null),
88 new ParseURLTestCase ("http://localhost:/", "http://localhost:", "/"),
89 new ParseURLTestCase ("http://localhost:9090", "http://localhost:9090", null),
90 new ParseURLTestCase ("http://localhost:9090/", "http://localhost:9090", "/"),
91 new ParseURLTestCase ("http://localhost:9090/RemoteObject.rem", "http://localhost:9090", "/RemoteObject.rem"),
92 new ParseURLTestCase ("http://localhost:q24691247abc1297/RemoteObject.rem", "http://localhost:q24691247abc1297", "/RemoteObject.rem"),
95 [Test] // HttpChannel.Parse ()
96 public void ParseURL ()
101 channel = new HttpChannel ();
103 for (i = 0; i < ParseURLTests.Length; i++) {
104 string retval, objectURI;
106 retval = channel.Parse (ParseURLTests[i].input, out objectURI);
108 Assert.AreEqual (ParseURLTests[i].retval, retval);
109 Assert.AreEqual (ParseURLTests[i].objectURI, objectURI);
113 static void Send (NetworkStream stream, string str)
115 byte [] buf = Encoding.ASCII.GetBytes (str);
120 static void Send (NetworkStream stream, byte[] buf)
122 //Console.Write ("C: ");
123 //DumpByteArray (buf, 3);
124 //Console.Write ("\n");
126 stream.Write (buf, 0, buf.Length);
129 static int Receive (NetworkStream stream, int chunks, out byte[] buf)
131 byte[] buffer = new byte [4096];
135 if ((n = stream.Read (buffer, nread, buffer.Length - nread)) > 0)
139 } while (n > 0 && chunks > 0);
141 //Console.Write ("S: ");
143 buf = new byte [nread];
145 for (int i = 0; i < nread; i++)
148 //DumpByteArray (buf, 3);
149 //Console.Write ("\n");
151 //Console.Write ("(null)\n");
158 static string ByteArrayToString (byte[] buf, int indent)
160 StringBuilder sb = new StringBuilder ();
162 for (int i = 0; i < buf.Length; i++) {
163 if (!Char.IsControl ((char) buf[i])) {
164 sb.Append ((char) buf[i]);
165 } else if (buf[i] == '\r') {
167 } else if (buf[i] == '\n') {
169 for (int j = 0; j < indent; j++)
172 sb.Append (String.Format ("\\x{0:x2}", buf[i]));
176 return sb.ToString ();
179 static void DumpByteArray (byte[] buf, int indent)
181 Console.Write (ByteArrayToString (buf, indent));
184 static int GetResponseContentOffset (byte[] response)
186 for (int i = 0; i < response.Length - 3; i++) {
187 if (response[i + 0] == '\r' && response[i + 1] == '\n' &&
188 response[i + 2] == '\r' && response[i + 3] == '\n')
195 static bool ResponseMatches (byte[] expected, byte[] actual)
199 // First, we compare the first line of the response - they should match
200 for (i = 0; i < expected.Length && i < actual.Length; i++) {
201 if (actual[i] != expected[i]) {
202 // HTTP/1.1 vs HTTP/1.0
203 if (i == 7 && expected[0] == 'H' && expected[1] == 'T' && expected[2] == 'T' &&
204 expected[3] == 'P' && expected[4] == '/' && expected[5] == '1' && expected[6] == '.' &&
205 expected[7] == '1' && actual[7] == '0')
208 //Console.WriteLine ("\nFirst line of actual response did not match");
212 if (expected[i] == '\n')
216 if (i >= actual.Length) {
217 //Console.WriteLine ("Actual response too short");
221 // now compare the content
222 i = GetResponseContentOffset (expected);
223 j = GetResponseContentOffset (actual);
225 for ( ; i < expected.Length && j < actual.Length; i++, j++) {
226 if (actual[j] != expected[i]) {
227 //Console.WriteLine ("Content of actual response did not match");
232 if (i < expected.Length) {
233 //Console.WriteLine ("Expected more content data...");
237 if (j < actual.Length) {
238 //Console.WriteLine ("Got too much content data in the server response");
245 static void CreateBinaryMethodInvoke (string assemblyName, string objectName, string methodName, out byte[] content)
247 string text = String.Format ("{0}, {1}, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", objectName, assemblyName);
248 byte[] lead = new byte [] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
249 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
250 0x00, 0x15, 0x11, 0x00, 0x00, 0x00, 0x12 };
254 content = new byte [lead.Length + 1 + methodName.Length + 1 + 1 + text.Length + 1];
255 lead.CopyTo (content, 0);
258 buf = Encoding.ASCII.GetBytes (methodName);
259 content[index++] = (byte) buf.Length;
260 buf.CopyTo (content, index);
263 content[index++] = (byte) 0x12;
265 buf = Encoding.ASCII.GetBytes (text);
266 content[index++] = (byte) buf.Length;
267 buf.CopyTo (content, index);
270 content[index] = (byte) 0x0b;
274 [Category ("NotWorking")] // the faked request content string might be wrong?
275 public void TestBinaryTransport ()
277 string assemblyName = Assembly.GetExecutingAssembly ().GetName ().Name;
278 Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
279 sock.Connect (new IPEndPoint (IPAddress.Loopback, port));
280 NetworkStream stream = new NetworkStream (sock);
281 byte[] content, buf, outbuf;
283 CreateBinaryMethodInvoke (assemblyName, typeof (RemoteObject).FullName, "ReturnOne", out content);
285 // send our POST request
286 Send (stream, String.Format ("POST /RemoteObject.rem HTTP/1.1\r\n" +
287 "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" +
288 "Content-Type: application/octet-stream\r\n" +
289 "Host: 127.0.0.1:{0}\r\n" +
290 "Content-Length: {1}\r\n" +
291 "Expect: 100-continue\r\n" +
292 "Connection: Keep-Alive\r\n" +
293 "\r\n", port, content.Length));
295 // create our expected response buffer
296 buf = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
297 Receive (stream, 1, out outbuf);
299 Assert.IsNotNull (outbuf, "Server continuation response is null");
300 Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server continuation response:\n" + ByteArrayToString (outbuf, 0));
302 // send our content data
303 Send (stream, content);
305 // create our expected response buffer
306 buf = new byte[] { 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31,
307 0x20, 0x32, 0x30, 0x30, 0x20, 0x4f, 0x4b, 0x0d,
308 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
309 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61,
310 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69,
311 0x6f, 0x6e, 0x2f, 0x6f, 0x63, 0x74, 0x65, 0x74,
312 0x2d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x0d,
313 0x0a, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a,
314 0x20, 0x4d, 0x53, 0x20, 0x2e, 0x4e, 0x45, 0x54,
315 0x20, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x69, 0x6e,
316 0x67, 0x2c, 0x20, 0x4d, 0x53, 0x20, 0x2e, 0x4e,
317 0x45, 0x54, 0x20, 0x43, 0x4c, 0x52, 0x20, 0x32,
318 0x2e, 0x30, 0x2e, 0x35, 0x30, 0x37, 0x32, 0x37,
319 0x2e, 0x31, 0x34, 0x33, 0x33, 0x0d, 0x0a, 0x43,
320 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x4c,
321 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x20, 0x32,
322 0x38, 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x00, 0x00,
323 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
324 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x11,
325 0x08, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00,
328 Receive (stream, 2, out outbuf);
330 Assert.IsNotNull (outbuf, "Server method-invoke response is null");
331 Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server method-invoke response:\n" + ByteArrayToString (outbuf, 0));
337 public void TestSoapTransport ()
339 string assemblyName = Assembly.GetExecutingAssembly ().GetName ().Name;
340 Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
341 sock.Connect (new IPEndPoint (IPAddress.Loopback, port));
342 NetworkStream stream = new NetworkStream (sock);
343 string methodName = "ReturnOne";
344 string headers, content;
347 content = String.Format ("<SOAP-ENV:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
348 "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
349 "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" " +
350 "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
351 "xmlns:clr=\"http://schemas.microsoft.com/soap/encoding/clr/1.0/\" " +
352 "SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
353 "<SOAP-ENV:Body>\r\n" +
354 "<i2:{0} id=\"ref-1\" xmlns:i2=\"http://schemas.microsoft.com/clr/nsassem/{1}/{2}\">\r\n" +
356 "</SOAP-ENV:Body>\r\n" +
357 "</SOAP-ENV:Envelope>", methodName, typeof (RemoteObject).FullName, assemblyName);
359 headers = String.Format ("POST /RemoteObject.rem HTTP/1.1\r\n" +
360 "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" +
361 "Content-Type: text/xml; charset=\"utf-8\"\r\n" +
362 "SOAPAction: \"http://schemas.microsoft.com/clr/nsassem/{0}/{1}#{2}\"\r\n" +
363 "Host: 127.0.0.1:{3}\r\n" +
364 "Content-Length: {4}\r\n" +
365 "Expect: 100-continue\r\n" +
366 "Connection: Keep-Alive\r\n" +
367 "\r\n", typeof (RemoteObject).FullName, assemblyName, methodName, port, content.Length);
369 Send (stream, headers);
371 // create our expected response buffer
372 buf = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
373 Receive (stream, 1, out outbuf);
375 Assert.IsNotNull (outbuf, "Server continuation response is null");
376 Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server continuation response:\n" + ByteArrayToString (outbuf, 0));
378 // send our content data
379 Send (stream, content);
381 // create our expected response buffer
382 #if MICROSOFT_DOTNET_SERVER
383 content = String.Format ("<SOAP-ENV:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
384 "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
385 "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" " +
386 "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
387 "xmlns:clr=\"http://schemas.microsoft.com/soap/encoding/clr/1.0\" " +
388 "SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
389 "<SOAP-ENV:Body>\r\n" +
390 "<i2:{0}Response id=\"ref-1\" xmlns:i2=\"http://schemas.microsoft.com/clr/nsassem/{1}/{2}\">\r\n" +
391 "<return>1</return>\r\n" +
392 "</i2:{0}Response>\r\n" +
393 "</SOAP-ENV:Body>\r\n" +
394 "</SOAP-ENV:Envelope>\r\n", methodName, typeof (RemoteObject).FullName, assemblyName);
396 // Mono's HttpServer chunks the response
397 content = String.Format ("27e\r\n<SOAP-ENV:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
398 "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
399 "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" " +
400 "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
401 "xmlns:clr=\"http://schemas.microsoft.com/clr/\" " +
402 "SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n" +
403 " <SOAP-ENV:Body>\n" +
404 " <i2:{0}Response id=\"ref-1\" xmlns:i2=\"http://schemas.microsoft.com/clr/nsassem/{1}/{2}\">\n" +
405 " <return xsi:type=\"xsd:int\">1</return>\n" +
406 " </i2:{0}Response>\n" +
407 " </SOAP-ENV:Body>\n" +
408 "</SOAP-ENV:Envelope>\r\n0\r\n\r\n", methodName, typeof (RemoteObject).FullName, assemblyName);
411 headers = String.Format ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=\"utf-8\"\r\n" +
412 "Server: MS .NET Remoting, MS .NET CLR 2.0.50727.1433\r\n" +
413 "Content-Length: {0}\r\n\r\n", content.Length);
415 buf = Encoding.ASCII.GetBytes (headers + content);
417 Receive (stream, 2, out outbuf);
419 Assert.IsNotNull (outbuf, "Server method-invoke response is null");
420 Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server method-invoke response:\n" + ByteArrayToString (outbuf, 0));
425 object mutex = new object ();
428 void MultiClientStart ()
432 // the prupose of this is just to block until all clients have been created
437 RemoteObject remObj = new RemoteObject ();
439 rv = remObj.Increment ();
441 // make sure the value returned hasn't been returned to another thread as well
443 Assert.IsTrue (!retvals[rv], "RemoteObject.Increment() has already returned " + rv);
449 public void MultiClientConnection ()
451 int num_clients = 20;
453 HttpClientChannel clientChannel = new HttpClientChannel ("MultiClientConnection", null);
454 ChannelServices.RegisterChannel (clientChannel);
456 WellKnownClientTypeEntry remoteType = new WellKnownClientTypeEntry (
457 typeof (RemoteObject), "http://127.0.0.1:9090/RemoteObject.rem");
458 RemotingConfiguration.RegisterWellKnownClientType (remoteType);
460 // start a bunch of clients...
461 Thread []clients = new Thread [num_clients];
462 retvals = new bool [num_clients];
465 for (int i = 0; i < num_clients; i++) {
466 clients[i] = new Thread (MultiClientStart);
472 // wait for all clients to finish...
473 for (int i = 0; i < num_clients; i++)
476 ChannelServices.UnregisterChannel (clientChannel);
478 for (int i = 0; i < num_clients; i++)
479 Assert.IsTrue (retvals[i], "RemoteObject.Incrememnt() didn't return a value of " + i);