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.
30 using System
.Collections
;
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
;
42 namespace MonoTests
.Remoting
{
44 public class HttpServerChannelTests
{
45 HttpServerChannel serverChannel
;
49 public void StartHttpServer ()
51 if (serverChannel
!= null)
54 serverChannel
= new HttpServerChannel ("HttpServerChannelTests", port
);
55 ChannelServices
.RegisterChannel (serverChannel
);
57 RemotingConfiguration
.RegisterWellKnownServiceType (
58 typeof (RemoteObject
), "RemoteObject.rem",
59 WellKnownObjectMode
.Singleton
);
63 public void StopHttpServer ()
65 ChannelServices
.UnregisterChannel (serverChannel
);
68 struct ParseURLTestCase
{
69 public readonly string input
;
70 public readonly string retval
;
71 public readonly string objectURI
;
73 public ParseURLTestCase (string s0
, string s1
, string s2
)
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"),
96 [Test
] // HttpChannel.Parse ()
97 [Ignore ("Fails on MS")]
98 public void ParseURL ()
103 channel
= new HttpChannel ();
105 for (i
= 0; i
< ParseURLTests
.Length
; i
++) {
106 string retval
, objectURI
;
108 retval
= channel
.Parse (ParseURLTests
[i
].input
, out objectURI
);
110 Assert
.AreEqual (ParseURLTests
[i
].retval
, retval
);
111 Assert
.AreEqual (ParseURLTests
[i
].objectURI
, objectURI
);
115 static void Send (NetworkStream stream
, string str
)
117 byte [] buf
= Encoding
.ASCII
.GetBytes (str
);
122 static void Send (NetworkStream stream
, byte[] buf
)
124 //Console.Write ("C: ");
125 //DumpByteArray (buf, 3);
126 //Console.Write ("\n");
128 stream
.Write (buf
, 0, buf
.Length
);
131 static int Receive (NetworkStream stream
, int chunks
, out byte[] buf
)
133 byte[] buffer
= new byte [4096];
137 if ((n
= stream
.Read (buffer
, nread
, buffer
.Length
- nread
)) > 0)
141 } while (n
> 0 && chunks
> 0);
143 //Console.Write ("S: ");
145 buf
= new byte [nread
];
147 for (int i
= 0; i
< nread
; i
++)
150 //DumpByteArray (buf, 3);
151 //Console.Write ("\n");
153 //Console.Write ("(null)\n");
160 static string ByteArrayToString (byte[] buf
, int indent
)
162 StringBuilder sb
= new StringBuilder ();
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') {
169 } else if (buf
[i
] == '\n') {
171 for (int j
= 0; j
< indent
; j
++)
174 sb
.Append (String
.Format ("\\x{0:x2}", buf
[i
]));
178 return sb
.ToString ();
181 static void DumpByteArray (byte[] buf
, int indent
)
183 Console
.Write (ByteArrayToString (buf
, indent
));
186 static int GetResponseContentOffset (byte[] response
)
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')
197 static bool ResponseMatches (byte[] expected
, byte[] actual
)
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')
210 //Console.WriteLine ("\nFirst line of actual response did not match");
214 if (expected
[i
] == '\n')
218 if (i
>= actual
.Length
) {
219 //Console.WriteLine ("Actual response too short");
223 // now compare the content
224 i
= GetResponseContentOffset (expected
);
225 j
= GetResponseContentOffset (actual
);
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");
234 if (i
< expected
.Length
) {
235 //Console.WriteLine ("Expected more content data...");
239 if (j
< actual
.Length
) {
240 //Console.WriteLine ("Got too much content data in the server response");
247 static void CreateBinaryMethodInvoke (string assemblyName
, string objectName
, string methodName
, out byte[] content
)
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 };
256 content
= new byte [lead
.Length
+ 1 + methodName
.Length
+ 1 + 1 + text
.Length
+ 1];
257 lead
.CopyTo (content
, 0);
260 buf
= Encoding
.ASCII
.GetBytes (methodName
);
261 content
[index
++] = (byte) buf
.Length
;
262 buf
.CopyTo (content
, index
);
265 content
[index
++] = (byte) 0x12;
267 buf
= Encoding
.ASCII
.GetBytes (text
);
268 content
[index
++] = (byte) buf
.Length
;
269 buf
.CopyTo (content
, index
);
272 content
[index
] = (byte) 0x0b;
276 [Category ("NotWorking")] // the faked request content string might be wrong?
277 public void TestBinaryTransport ()
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
;
285 CreateBinaryMethodInvoke (assemblyName
, typeof (RemoteObject
).FullName
, "ReturnOne", out content
);
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
));
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
);
301 Assert
.IsNotNull (outbuf
, "Server continuation response is null");
302 Assert
.IsTrue (ResponseMatches (buf
, outbuf
), "Unexpected server continuation response:\n" + ByteArrayToString (outbuf
, 0));
304 // send our content data
305 Send (stream
, content
);
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,
330 Receive (stream
, 2, out outbuf
);
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));
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 ()
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
;
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" +
359 "</SOAP-ENV:Body>\r\n" +
360 "</SOAP-ENV:Envelope>", methodName
, typeof (RemoteObject
).FullName
, assemblyName
);
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
);
372 Send (stream
, headers
);
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
);
378 Assert
.IsNotNull (outbuf
, "Server continuation response is null");
379 Assert
.IsTrue (ResponseMatches (buf
, outbuf
), "Unexpected server continuation response:\n" + ByteArrayToString (outbuf
, 0));
381 // send our content data
382 Send (stream
, content
);
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
);
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
);
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
);
418 buf
= Encoding
.ASCII
.GetBytes (headers
+ content
);
420 Receive (stream
, 2, out outbuf
);
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));
428 object mutex
= new object ();
431 void MultiClientStart ()
435 // the prupose of this is just to block until all clients have been created
440 RemoteObject remObj
= new RemoteObject ();
442 rv
= remObj
.Increment ();
444 // make sure the value returned hasn't been returned to another thread as well
446 Assert
.IsTrue (!retvals
[rv
], "RemoteObject.Increment() has already returned " + rv
);
452 [Category ("NotWorking")] // disabled as it got not working by NUnit upgrade to 2.4.8
453 public void MultiClientConnection ()
455 int num_clients
= 20;
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
);
464 WellKnownClientTypeEntry remoteType
= new WellKnownClientTypeEntry (
465 typeof (RemoteObject
), "http://127.0.0.1:9090/RemoteObject.rem");
466 RemotingConfiguration
.RegisterWellKnownClientType (remoteType
);
468 // start a bunch of clients...
469 Thread
[]clients
= new Thread
[num_clients
];
470 retvals
= new bool [num_clients
];
473 for (int i
= 0; i
< num_clients
; i
++) {
474 clients
[i
] = new Thread (MultiClientStart
);
480 // wait for all clients to finish...
481 for (int i
= 0; i
< num_clients
; i
++)
484 for (int i
= 0; i
< num_clients
; i
++)
485 Assert
.IsTrue (retvals
[i
], "RemoteObject.Incrememnt() didn't return a value of " + i
);
488 ChannelServices
.UnregisterChannel (clientChannel
);