svn path=/trunk/mcs/; revision=113894
[mono.git] / mcs / class / System.Runtime.Remoting / Test / HttpServerChannelTests.cs
1 //
2 // HttpServerChannelTest.cs
3 //      - Unit tests for System.Runtime.Remoting.Channels.Http
4 //
5 // Author: Jeffrey Stedfast <fejj@novell.com>
6 //
7 // Copyright (C) 2008 Novell, Inc (http://www.novell.com)
8 //
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:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
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.
27 //
28
29 using System;
30 using System.IO;
31 using System.Net;
32 using System.Text;
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;
40
41 namespace MonoTests.Remoting {
42         [TestFixture]
43         public class HttpServerChannelTests {
44                 HttpServerChannel serverChannel;
45                 int port = 9090;
46                 
47                 [TestFixtureSetUp]
48                 public void StartHttpServer ()
49                 {
50                         if (serverChannel != null)
51                                 return;
52                         
53                         serverChannel = new HttpServerChannel ("HttpServerChannelTests", port);
54                         ChannelServices.RegisterChannel (serverChannel);
55                         
56                         RemotingConfiguration.RegisterWellKnownServiceType (
57                                 typeof (RemoteObject), "RemoteObject.rem", 
58                                 WellKnownObjectMode.Singleton);
59                 }
60                 
61                 [TestFixtureTearDown]
62                 public void StopHttpServer ()
63                 {
64                         ChannelServices.UnregisterChannel (serverChannel);
65                 }
66                 
67                 struct ParseURLTestCase {
68                         public readonly string input;
69                         public readonly string retval;
70                         public readonly string objectURI;
71                         
72                         public ParseURLTestCase (string s0, string s1, string s2)
73                         {
74                                 input = s0;
75                                 retval = s1;
76                                 objectURI = s2;
77                         }
78                 };
79                 
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"),
93                 };
94                 
95                 [Test] // HttpChannel.Parse ()
96                 public void ParseURL ()
97                 {
98                         HttpChannel channel;
99                         int i;
100                         
101                         channel = new HttpChannel ();
102                         
103                         for (i = 0; i < ParseURLTests.Length; i++) {
104                                 string retval, objectURI;
105                                 
106                                 retval = channel.Parse (ParseURLTests[i].input, out objectURI);
107                                 
108                                 Assert.AreEqual (ParseURLTests[i].retval, retval);
109                                 Assert.AreEqual (ParseURLTests[i].objectURI, objectURI);
110                         }
111                 }
112                 
113                 static void Send (NetworkStream stream, string str)
114                 {
115                         byte [] buf = Encoding.ASCII.GetBytes (str);
116                         
117                         Send (stream, buf);
118                 }
119                 
120                 static void Send (NetworkStream stream, byte[] buf)
121                 {
122                         //Console.Write ("C: ");
123                         //DumpByteArray (buf, 3);
124                         //Console.Write ("\n");
125                         
126                         stream.Write (buf, 0, buf.Length);
127                 }
128                 
129                 static int Receive (NetworkStream stream, int chunks, out byte[] buf)
130                 {
131                         byte[] buffer = new byte [4096];
132                         int n, nread = 0;
133                         
134                         do {
135                                 if ((n = stream.Read (buffer, nread, buffer.Length - nread)) > 0)
136                                         nread += n;
137                                 
138                                 chunks--;
139                         } while (n > 0 && chunks > 0);
140                         
141                         //Console.Write ("S: ");
142                         if (nread > 0) {
143                                 buf = new byte [nread];
144                                 
145                                 for (int i = 0; i < nread; i++)
146                                         buf[i] = buffer[i];
147                                 
148                                 //DumpByteArray (buf, 3);
149                                 //Console.Write ("\n");
150                         } else {
151                                 //Console.Write ("(null)\n");
152                                 buf = null;
153                         }
154                         
155                         return nread;
156                 }
157                 
158                 static string ByteArrayToString (byte[] buf, int indent)
159                 {
160                         StringBuilder sb = new StringBuilder ();
161                         
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') {
166                                         sb.Append ("\\r");
167                                 } else if (buf[i] == '\n') {
168                                         sb.Append ("\\n\n");
169                                         for (int j = 0; j < indent; j++)
170                                                 sb.Append (' ');
171                                 } else {
172                                         sb.Append (String.Format ("\\x{0:x2}", buf[i]));
173                                 }
174                         }
175                         
176                         return sb.ToString ();
177                 }
178                 
179                 static void DumpByteArray (byte[] buf, int indent)
180                 {
181                         Console.Write (ByteArrayToString (buf, indent));
182                 }
183                 
184                 static int GetResponseContentOffset (byte[] response)
185                 {
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')
189                                         return i + 3;
190                         }
191                         
192                         return -1;
193                 }
194                 
195                 static bool ResponseMatches (byte[] expected, byte[] actual)
196                 {
197                         int i, j;
198                         
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')
206                                                 continue;
207                                         
208                                         //Console.WriteLine ("\nFirst line of actual response did not match");
209                                         return false;
210                                 }
211                                 
212                                 if (expected[i] == '\n')
213                                         break;
214                         }
215                         
216                         if (i >= actual.Length) {
217                                 //Console.WriteLine ("Actual response too short");
218                                 return false;
219                         }
220                         
221                         // now compare the content
222                         i = GetResponseContentOffset (expected);
223                         j = GetResponseContentOffset (actual);
224                         
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");
228                                         return false;
229                                 }
230                         }
231                         
232                         if (i < expected.Length) {
233                                 //Console.WriteLine ("Expected more content data...");
234                                 return false;
235                         }
236                         
237                         if (j < actual.Length) {
238                                 //Console.WriteLine ("Got too much content data in the server response");
239                                 return false;
240                         }
241                         
242                         return true;
243                 }
244                 
245                 static void CreateBinaryMethodInvoke (string assemblyName, string objectName, string methodName, out byte[] content)
246                 {
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 };
251                         byte[] buf;
252                         int index;
253                         
254                         content = new byte [lead.Length + 1 + methodName.Length + 1 + 1 + text.Length + 1];
255                         lead.CopyTo (content, 0);
256                         index = lead.Length;
257                         
258                         buf = Encoding.ASCII.GetBytes (methodName);
259                         content[index++] = (byte) buf.Length;
260                         buf.CopyTo (content, index);
261                         index += buf.Length;
262                         
263                         content[index++] = (byte) 0x12;
264                         
265                         buf = Encoding.ASCII.GetBytes (text);
266                         content[index++] = (byte) buf.Length;
267                         buf.CopyTo (content, index);
268                         index += buf.Length;
269                         
270                         content[index] = (byte) 0x0b;
271                 }
272                 
273                 [Test]
274                 [Category ("NotWorking")] // the faked request content string might be wrong?
275                 public void TestBinaryTransport ()
276                 {
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;
282                         
283                         CreateBinaryMethodInvoke (assemblyName, typeof (RemoteObject).FullName, "ReturnOne", out content);
284                         
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));
294                         
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);
298                         
299                         Assert.IsNotNull (outbuf, "Server continuation response is null");
300                         Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server continuation response:\n" + ByteArrayToString (outbuf, 0));
301                         
302                         // send our content data
303                         Send (stream, content);
304                         
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, 
326                                            0x0b };
327                         
328                         Receive (stream, 2, out outbuf);
329                         
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));
332                         
333                         stream.Close ();
334                 }
335                 
336                 [Test]
337                 public void TestSoapTransport ()
338                 {
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;
345                         byte[] buf, outbuf;
346                         
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" +
355                                                  "</i2:{0}>\r\n" +
356                                                  "</SOAP-ENV:Body>\r\n" +
357                                                  "</SOAP-ENV:Envelope>", methodName, typeof (RemoteObject).FullName, assemblyName);
358                         
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);
368                         
369                         Send (stream, headers);
370                         
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);
374                         
375                         Assert.IsNotNull (outbuf, "Server continuation response is null");
376                         Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server continuation response:\n" + ByteArrayToString (outbuf, 0));
377                         
378                         // send our content data
379                         Send (stream, content);
380                         
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);
395 #else
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);
409 #endif
410                         
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);
414                         
415                         buf = Encoding.ASCII.GetBytes (headers + content);
416                         
417                         Receive (stream, 2, out outbuf);
418                         
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));
421                         
422                         stream.Close ();
423                 }
424                 
425                 object mutex = new object ();
426                 bool []retvals;
427                 
428                 void MultiClientStart ()
429                 {
430                         int rv = 0;
431                         
432                         // the prupose of this is just to block until all clients have been created
433                         lock (mutex) {
434                                 rv++;
435                         }
436                         
437                         RemoteObject remObj = new RemoteObject ();
438                         
439                         rv = remObj.Increment ();
440                         
441                         // make sure the value returned hasn't been returned to another thread as well
442                         lock (retvals) {
443                                 Assert.IsTrue (!retvals[rv], "RemoteObject.Increment() has already returned " + rv);
444                                 retvals[rv] = true;
445                         }
446                 }
447                 
448                 [Test]
449                 public void MultiClientConnection ()
450                 {
451                         int num_clients = 20;
452                         
453                         HttpClientChannel clientChannel = new HttpClientChannel ("MultiClientConnection", null);
454                         ChannelServices.RegisterChannel (clientChannel);
455                         
456                         WellKnownClientTypeEntry remoteType = new WellKnownClientTypeEntry (
457                                 typeof (RemoteObject), "http://127.0.0.1:9090/RemoteObject.rem");
458                         RemotingConfiguration.RegisterWellKnownClientType (remoteType);
459                         
460                         // start a bunch of clients...
461                         Thread []clients = new Thread [num_clients];
462                         retvals = new bool [num_clients];
463                         
464                         lock (mutex) {
465                                 for (int i = 0; i < num_clients; i++) {
466                                         clients[i] = new Thread (MultiClientStart);
467                                         clients[i].Start ();
468                                         retvals[i] = false;
469                                 }
470                         }
471                         
472                         // wait for all clients to finish...
473                         for (int i = 0; i < num_clients; i++)
474                                 clients[i].Join ();
475                         
476                         ChannelServices.UnregisterChannel (clientChannel);
477                         
478                         for (int i = 0; i < num_clients; i++)
479                                 Assert.IsTrue (retvals[i], "RemoteObject.Incrememnt() didn't return a value of " + i);
480                 }
481         }
482 }