Merge pull request #1870 from saper/langinfo_h
[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.Collections;
31 using System.IO;
32 using System.Net;
33 using System.Text;
34 using System.Reflection;
35 using System.Net.Sockets;
36 using System.Runtime.Remoting;
37 using System.Runtime.Remoting.Channels;
38 using System.Runtime.Remoting.Channels.Http;
39 using System.Threading;
40 using NUnit.Framework;
41
42 namespace MonoTests.Remoting {
43         [TestFixture]
44         public class HttpServerChannelTests {
45                 HttpServerChannel serverChannel;
46                 int port = 9090;
47                 
48                 [TestFixtureSetUp]
49                 public void StartHttpServer ()
50                 {
51                         if (serverChannel != null)
52                                 return;
53                         
54                         serverChannel = new HttpServerChannel ("HttpServerChannelTests", port);
55                         ChannelServices.RegisterChannel (serverChannel);
56                         
57                         RemotingConfiguration.RegisterWellKnownServiceType (
58                                 typeof (RemoteObject), "RemoteObject.rem", 
59                                 WellKnownObjectMode.Singleton);
60                 }
61                 
62                 [TestFixtureTearDown]
63                 public void StopHttpServer ()
64                 {
65                         ChannelServices.UnregisterChannel (serverChannel);
66                 }
67                 
68                 struct ParseURLTestCase {
69                         public readonly string input;
70                         public readonly string retval;
71                         public readonly string objectURI;
72                         
73                         public ParseURLTestCase (string s0, string s1, string s2)
74                         {
75                                 input = s0;
76                                 retval = s1;
77                                 objectURI = s2;
78                         }
79                 };
80                 
81                 ParseURLTestCase[] ParseURLTests = new ParseURLTestCase[] {
82                         //new ParseURLTestCase ("http:", "http:", null),  // KnownFailure but works on Microsoft's .NET
83                         new ParseURLTestCase ("http://", "http://", null),
84                         new ParseURLTestCase ("http:localhost", null, null),
85                         new ParseURLTestCase ("ftp://localhost", null, null),
86                         new ParseURLTestCase ("http://localhost", "http://localhost", null),
87                         new ParseURLTestCase ("hTtP://localhost", "hTtP://localhost", null),
88                         new ParseURLTestCase ("https://localhost", "https://localhost", null),
89                         new ParseURLTestCase ("http://localhost:/", "http://localhost:", "/"),
90                         new ParseURLTestCase ("http://localhost:9090", "http://localhost:9090", null),
91                         new ParseURLTestCase ("http://localhost:9090/", "http://localhost:9090", "/"),
92                         new ParseURLTestCase ("http://localhost:9090/RemoteObject.rem", "http://localhost:9090", "/RemoteObject.rem"),
93                         new ParseURLTestCase ("http://localhost:q24691247abc1297/RemoteObject.rem", "http://localhost:q24691247abc1297", "/RemoteObject.rem"),
94                 };
95                 
96                 [Test] // HttpChannel.Parse ()
97                 [Category ("NotWorking")] // Fails on MS
98                 public void ParseURL ()
99                 {
100                         HttpChannel channel;
101                         int i;
102                         
103                         channel = new HttpChannel ();
104                         
105                         for (i = 0; i < ParseURLTests.Length; i++) {
106                                 string retval, objectURI;
107                                 
108                                 retval = channel.Parse (ParseURLTests[i].input, out objectURI);
109                                 
110                                 Assert.AreEqual (ParseURLTests[i].retval, retval);
111                                 Assert.AreEqual (ParseURLTests[i].objectURI, objectURI);
112                         }
113                 }
114                 
115                 static void Send (NetworkStream stream, string str)
116                 {
117                         byte [] buf = Encoding.ASCII.GetBytes (str);
118                         
119                         Send (stream, buf);
120                 }
121                 
122                 static void Send (NetworkStream stream, byte[] buf)
123                 {
124                         //Console.Write ("C: ");
125                         //DumpByteArray (buf, 3);
126                         //Console.Write ("\n");
127                         
128                         stream.Write (buf, 0, buf.Length);
129                 }
130                 
131                 static int Receive (NetworkStream stream, int chunks, out byte[] buf)
132                 {
133                         byte[] buffer = new byte [4096];
134                         int n, nread = 0;
135                         
136                         do {
137                                 if ((n = stream.Read (buffer, nread, buffer.Length - nread)) > 0)
138                                         nread += n;
139                                 
140                                 chunks--;
141                         } while (n > 0 && chunks > 0);
142                         
143                         //Console.Write ("S: ");
144                         if (nread > 0) {
145                                 buf = new byte [nread];
146                                 
147                                 for (int i = 0; i < nread; i++)
148                                         buf[i] = buffer[i];
149                                 
150                                 //DumpByteArray (buf, 3);
151                                 //Console.Write ("\n");
152                         } else {
153                                 //Console.Write ("(null)\n");
154                                 buf = null;
155                         }
156                         
157                         return nread;
158                 }
159                 
160                 static string ByteArrayToString (byte[] buf, int indent)
161                 {
162                         StringBuilder sb = new StringBuilder ();
163                         
164                         for (int i = 0; i < buf.Length; i++) {
165                                 if (!Char.IsControl ((char) buf[i])) {
166                                         sb.Append ((char) buf[i]);
167                                 } else if (buf[i] == '\r') {
168                                         sb.Append ("\\r");
169                                 } else if (buf[i] == '\n') {
170                                         sb.Append ("\\n\n");
171                                         for (int j = 0; j < indent; j++)
172                                                 sb.Append (' ');
173                                 } else {
174                                         sb.Append (String.Format ("\\x{0:x2}", buf[i]));
175                                 }
176                         }
177                         
178                         return sb.ToString ();
179                 }
180                 
181                 static void DumpByteArray (byte[] buf, int indent)
182                 {
183                         Console.Write (ByteArrayToString (buf, indent));
184                 }
185                 
186                 static int GetResponseContentOffset (byte[] response)
187                 {
188                         for (int i = 0; i < response.Length - 3; i++) {
189                                 if (response[i + 0] == '\r' && response[i + 1] == '\n' &&
190                                     response[i + 2] == '\r' && response[i + 3] == '\n')
191                                         return i + 3;
192                         }
193                         
194                         return -1;
195                 }
196                 
197                 static bool ResponseMatches (byte[] expected, byte[] actual)
198                 {
199                         int i, j;
200                         
201                         // First, we compare the first line of the response - they should match
202                         for (i = 0; i < expected.Length && i < actual.Length; i++) {
203                                 if (actual[i] != expected[i]) {
204                                         // HTTP/1.1 vs HTTP/1.0
205                                         if (i == 7 && expected[0] == 'H' && expected[1] == 'T' && expected[2] == 'T' &&
206                                             expected[3] == 'P' && expected[4] == '/' && expected[5] == '1' && expected[6] == '.' &&
207                                             expected[7] == '1' && actual[7] == '0')
208                                                 continue;
209                                         
210                                         //Console.WriteLine ("\nFirst line of actual response did not match");
211                                         return false;
212                                 }
213                                 
214                                 if (expected[i] == '\n')
215                                         break;
216                         }
217                         
218                         if (i >= actual.Length) {
219                                 //Console.WriteLine ("Actual response too short");
220                                 return false;
221                         }
222                         
223                         // now compare the content
224                         i = GetResponseContentOffset (expected);
225                         j = GetResponseContentOffset (actual);
226                         
227                         for ( ; i < expected.Length && j < actual.Length; i++, j++) {
228                                 if (actual[j] != expected[i]) {
229                                         //Console.WriteLine ("Content of actual response did not match");
230                                         return false;
231                                 }
232                         }
233                         
234                         if (i < expected.Length) {
235                                 //Console.WriteLine ("Expected more content data...");
236                                 return false;
237                         }
238                         
239                         if (j < actual.Length) {
240                                 //Console.WriteLine ("Got too much content data in the server response");
241                                 return false;
242                         }
243                         
244                         return true;
245                 }
246                 
247                 static void CreateBinaryMethodInvoke (string assemblyName, string objectName, string methodName, out byte[] content)
248                 {
249                         string text = String.Format ("{0}, {1}, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", objectName, assemblyName);
250                         byte[] lead = new byte [] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
251                                                     0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
252                                                     0x00, 0x15, 0x11, 0x00, 0x00, 0x00, 0x12 };
253                         byte[] buf;
254                         int index;
255                         
256                         content = new byte [lead.Length + 1 + methodName.Length + 1 + 1 + text.Length + 1];
257                         lead.CopyTo (content, 0);
258                         index = lead.Length;
259                         
260                         buf = Encoding.ASCII.GetBytes (methodName);
261                         content[index++] = (byte) buf.Length;
262                         buf.CopyTo (content, index);
263                         index += buf.Length;
264                         
265                         content[index++] = (byte) 0x12;
266                         
267                         buf = Encoding.ASCII.GetBytes (text);
268                         content[index++] = (byte) buf.Length;
269                         buf.CopyTo (content, index);
270                         index += buf.Length;
271                         
272                         content[index] = (byte) 0x0b;
273                 }
274                 
275                 [Test]
276                 [Category ("NotWorking")] // the faked request content string might be wrong?
277                 public void TestBinaryTransport ()
278                 {
279                         string assemblyName = Assembly.GetExecutingAssembly ().GetName ().Name;
280                         Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
281                         sock.Connect (new IPEndPoint (IPAddress.Loopback, port));
282                         NetworkStream stream = new NetworkStream (sock);
283                         byte[] content, buf, outbuf;
284                         
285                         CreateBinaryMethodInvoke (assemblyName, typeof (RemoteObject).FullName, "ReturnOne", out content);
286                         
287                         // send our POST request
288                         Send (stream, String.Format ("POST /RemoteObject.rem HTTP/1.1\r\n" +
289                                                      "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" +
290                                                      "Content-Type: application/octet-stream\r\n" +
291                                                      "Host: 127.0.0.1:{0}\r\n" +
292                                                      "Content-Length: {1}\r\n" +
293                                                      "Expect: 100-continue\r\n" +
294                                                      "Connection: Keep-Alive\r\n" +
295                                                      "\r\n", port, content.Length));
296                         
297                         // create our expected response buffer
298                         buf = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
299                         Receive (stream, 1, out outbuf);
300                         
301                         Assert.IsNotNull (outbuf, "Server continuation response is null");
302                         Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server continuation response:\n" + ByteArrayToString (outbuf, 0));
303                         
304                         // send our content data
305                         Send (stream, content);
306                         
307                         // create our expected response buffer
308                         buf = new byte[] { 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 
309                                            0x20, 0x32, 0x30, 0x30, 0x20, 0x4f, 0x4b, 0x0d, 
310                                            0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 
311                                            0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 
312                                            0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 
313                                            0x6f, 0x6e, 0x2f, 0x6f, 0x63, 0x74, 0x65, 0x74, 
314                                            0x2d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x0d, 
315                                            0x0a, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 
316                                            0x20, 0x4d, 0x53, 0x20, 0x2e, 0x4e, 0x45, 0x54, 
317                                            0x20, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x69, 0x6e, 
318                                            0x67, 0x2c, 0x20, 0x4d, 0x53, 0x20, 0x2e, 0x4e, 
319                                            0x45, 0x54, 0x20, 0x43, 0x4c, 0x52, 0x20, 0x32, 
320                                            0x2e, 0x30, 0x2e, 0x35, 0x30, 0x37, 0x32, 0x37, 
321                                            0x2e, 0x31, 0x34, 0x33, 0x33, 0x0d, 0x0a, 0x43, 
322                                            0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x4c, 
323                                            0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x20, 0x32, 
324                                            0x38, 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x00, 0x00, 
325                                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 
326                                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x11, 
327                                            0x08, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 
328                                            0x0b };
329                         
330                         Receive (stream, 2, out outbuf);
331                         
332                         Assert.IsNotNull (outbuf, "Server method-invoke response is null");
333                         Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server method-invoke response:\n" + ByteArrayToString (outbuf, 0));
334                         
335                         stream.Close ();
336                 }
337                 
338                 [Test]
339                 [Category ("NotWorking")] // The test itself passes, but the runtime goes infinite loop at end of nunit-console udner 2.4.8.
340                 public void TestSoapTransport ()
341                 {
342                         string assemblyName = Assembly.GetExecutingAssembly ().GetName ().Name;
343                         Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
344                         sock.Connect (new IPEndPoint (IPAddress.Loopback, port));
345                         NetworkStream stream = new NetworkStream (sock);
346                         string methodName = "ReturnOne";
347                         string headers, content;
348                         byte[] buf, outbuf;
349                         
350                         content = String.Format ("<SOAP-ENV:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
351                                                  "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
352                                                  "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" " +
353                                                  "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
354                                                  "xmlns:clr=\"http://schemas.microsoft.com/soap/encoding/clr/1.0/\" " +
355                                                  "SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
356                                                  "<SOAP-ENV:Body>\r\n" +
357                                                  "<i2:{0} id=\"ref-1\" xmlns:i2=\"http://schemas.microsoft.com/clr/nsassem/{1}/{2}\">\r\n" +
358                                                  "</i2:{0}>\r\n" +
359                                                  "</SOAP-ENV:Body>\r\n" +
360                                                  "</SOAP-ENV:Envelope>", methodName, typeof (RemoteObject).FullName, assemblyName);
361                         
362                         headers = String.Format ("POST /RemoteObject.rem HTTP/1.1\r\n" +
363                                                  "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" +
364                                                  "Content-Type: text/xml; charset=\"utf-8\"\r\n" +
365                                                  "SOAPAction: \"http://schemas.microsoft.com/clr/nsassem/{0}/{1}#{2}\"\r\n" +
366                                                  "Host: 127.0.0.1:{3}\r\n" +
367                                                  "Content-Length: {4}\r\n" +
368                                                  "Expect: 100-continue\r\n" +
369                                                  "Connection: Keep-Alive\r\n" +
370                                                  "\r\n", typeof (RemoteObject).FullName, assemblyName, methodName, port, content.Length);
371                         
372                         Send (stream, headers);
373                         
374                         // create our expected response buffer
375                         buf = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
376                         Receive (stream, 1, out outbuf);
377                         
378                         Assert.IsNotNull (outbuf, "Server continuation response is null");
379                         Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server continuation response:\n" + ByteArrayToString (outbuf, 0));
380                         
381                         // send our content data
382                         Send (stream, content);
383                         
384                         // create our expected response buffer
385 #if MICROSOFT_DOTNET_SERVER
386                         content = String.Format ("<SOAP-ENV:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
387                                                  "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
388                                                  "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" " +
389                                                  "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
390                                                  "xmlns:clr=\"http://schemas.microsoft.com/soap/encoding/clr/1.0\" " +
391                                                  "SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
392                                                  "<SOAP-ENV:Body>\r\n" +
393                                                  "<i2:{0}Response id=\"ref-1\" xmlns:i2=\"http://schemas.microsoft.com/clr/nsassem/{1}/{2}\">\r\n" +
394                                                  "<return>1</return>\r\n" +
395                                                  "</i2:{0}Response>\r\n" +
396                                                  "</SOAP-ENV:Body>\r\n" +
397                                                  "</SOAP-ENV:Envelope>\r\n", methodName, typeof (RemoteObject).FullName, assemblyName);
398 #else
399                         //slight differences in formatting
400                         content = String.Format ("<SOAP-ENV:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
401                                                  "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
402                                                  "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" " +
403                                                  "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
404                                                  "xmlns:clr=\"http://schemas.microsoft.com/clr/\" " +
405                                                  "SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n" +
406                                                  "  <SOAP-ENV:Body>\n" +
407                                                  "    <i2:{0}Response id=\"ref-1\" xmlns:i2=\"http://schemas.microsoft.com/clr/nsassem/{1}/{2}\">\n" +
408                                                  "      <return xsi:type=\"xsd:int\">1</return>\n" +
409                                                  "    </i2:{0}Response>\n" +
410                                                  "  </SOAP-ENV:Body>\n" +
411                                                  "</SOAP-ENV:Envelope>", methodName, typeof (RemoteObject).FullName, assemblyName);
412 #endif
413                         
414                         headers = String.Format ("HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=\"utf-8\"\r\n" +
415                                                  "Server: MS .NET Remoting, MS .NET CLR 2.0.50727.1433\r\n" +
416                                                  "Content-Length: {0}\r\n\r\n", content.Length);
417                         
418                         buf = Encoding.ASCII.GetBytes (headers + content);
419                         
420                         Receive (stream, 2, out outbuf);
421                         
422                         Assert.IsNotNull (outbuf, "Server method-invoke response is null");
423                         Assert.IsTrue (ResponseMatches (buf, outbuf), "Unexpected server method-invoke response:\n" + ByteArrayToString (outbuf, 0));
424                         
425                         stream.Close ();
426                 }
427                 
428                 object mutex = new object ();
429                 bool []retvals;
430                 
431                 void MultiClientStart ()
432                 {
433                         int rv = 0;
434                         
435                         // the prupose of this is just to block until all clients have been created
436                         lock (mutex) {
437                                 rv++;
438                         }
439                         
440                         RemoteObject remObj = new RemoteObject ();
441                         
442                         rv = remObj.Increment ();
443                         
444                         // make sure the value returned hasn't been returned to another thread as well
445                         lock (retvals) {
446                                 Assert.IsTrue (!retvals[rv], "RemoteObject.Increment() has already returned " + rv);
447                                 retvals[rv] = true;
448                         }
449                 }
450                 
451                 [Test]
452                 [Category ("NotWorking")] // disabled as it got not working by NUnit upgrade to 2.4.8
453                 public void MultiClientConnection ()
454                 {
455                         int num_clients = 20;
456                         
457                         Hashtable options = new Hashtable ();
458                         options ["timeout"] = 10000; // 10s
459                         options ["name"] = "MultiClientConnection"; // 10s
460                         HttpClientChannel clientChannel = new HttpClientChannel (options, null);
461                         ChannelServices.RegisterChannel (clientChannel);
462                         try {
463                         
464                         WellKnownClientTypeEntry remoteType = new WellKnownClientTypeEntry (
465                                 typeof (RemoteObject), "http://127.0.0.1:9090/RemoteObject.rem");
466                         RemotingConfiguration.RegisterWellKnownClientType (remoteType);
467                         
468                         // start a bunch of clients...
469                         Thread []clients = new Thread [num_clients];
470                         retvals = new bool [num_clients];
471                         
472                         lock (mutex) {
473                                 for (int i = 0; i < num_clients; i++) {
474                                         clients[i] = new Thread (MultiClientStart);
475                                         clients[i].Start ();
476                                         retvals[i] = false;
477                                 }
478                         }
479                         
480                         // wait for all clients to finish...
481                         for (int i = 0; i < num_clients; i++)
482                                 clients[i].Join ();
483                         
484                         for (int i = 0; i < num_clients; i++)
485                                 Assert.IsTrue (retvals[i], "RemoteObject.Incrememnt() didn't return a value of " + i);
486
487                         } finally {
488                         ChannelServices.UnregisterChannel (clientChannel);
489                         }
490                 }
491         }
492 }