2 // HttpListener2Test.cs
3 // - Unit tests for System.Net.HttpListener - connection testing
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Collections.Generic;
32 using System.Globalization;
35 using System.Net.Sockets;
37 using System.Threading;
38 using NUnit.Framework;
40 // ***************************************************************************************
41 // NOTE: when adding prefixes, make then unique per test, as MS might take 'some time' to
42 // unregister it even after explicitly closing the listener.
43 // ***************************************************************************************
44 namespace MonoTests.System.Net {
47 [Ignore ("The class HttpListener is not supported")]
49 public class HttpListener2Test {
50 public class MyNetworkStream : NetworkStream {
51 public MyNetworkStream (Socket sock) : base (sock, true)
55 public Socket GetSocket ()
61 public static HttpListener CreateAndStartListener (string prefix)
63 HttpListener listener = new HttpListener ();
64 listener.Prefixes.Add (prefix);
69 public static HttpListener CreateAndStartListener (string prefix, AuthenticationSchemes authSchemes)
71 HttpListener listener = new HttpListener ();
72 listener.AuthenticationSchemes = authSchemes;
73 listener.Prefixes.Add (prefix);
78 public static MyNetworkStream CreateNS (int port)
80 Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
81 sock.Connect (new IPEndPoint (IPAddress.Loopback, port));
82 return new MyNetworkStream (sock);
85 public static void Send (Stream stream, string str)
87 byte [] bytes = Encoding.ASCII.GetBytes (str);
88 stream.Write (bytes, 0, bytes.Length);
91 public static string Receive (Stream stream, int size)
93 byte [] bytes = new byte [size];
94 int nread = stream.Read (bytes, 0, size);
95 return Encoding.ASCII.GetString (bytes, 0, nread);
98 public static string ReceiveWithTimeout (Stream stream, int size, int timeout, out bool timed_out)
100 byte [] bytes = new byte [size];
101 IAsyncResult ares = stream.BeginRead (bytes, 0, size, null, null);
102 timed_out = !ares.AsyncWaitHandle.WaitOne (timeout, false);
105 int nread = stream.EndRead (ares);
106 return Encoding.ASCII.GetString (bytes, 0, nread);
109 public static HttpListenerContext GetContextWithTimeout (HttpListener listener, int timeout, out bool timed_out)
111 IAsyncResult ares = listener.BeginGetContext (null, null);
112 timed_out = !ares.AsyncWaitHandle.WaitOne (timeout, false);
115 return listener.EndGetContext (ares);
121 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test1/");
122 NetworkStream ns = CreateNS (9000);
123 Send (ns, "GET / HTTP/1.1\r\n\r\n"); // No host
124 string response = Receive (ns, 512);
127 Assert.IsTrue (response.StartsWith ("HTTP/1.1 400"));
133 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test2/");
134 NetworkStream ns = CreateNS (9000);
135 Send (ns, "GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n"); // no prefix
136 string response = Receive (ns, 512);
139 Assert.IsTrue (response.StartsWith ("HTTP/1.1 400"));
145 StringBuilder bad = new StringBuilder ();
146 for (int i = 0; i < 33; i++){
148 bad.Append ((char) i);
168 foreach (char b in bad.ToString ()){
169 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test3/");
170 NetworkStream ns = CreateNS (9000);
171 Send (ns, String.Format ("MA{0} / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n", b)); // bad method
173 string response = Receive (ns, 512);
176 Assert.AreEqual (true, response.StartsWith ("HTTP/1.1 400"), String.Format ("Failed on {0}", (int) b));
183 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test4/");
184 NetworkStream ns = CreateNS (9000);
185 Send (ns, "POST /test4/ HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n"); // length required
186 string response = Receive (ns, 512);
189 Assert.IsTrue (response.StartsWith ("HTTP/1.1 411"));
195 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test5/");
196 NetworkStream ns = CreateNS (9000);
197 Send (ns, "POST / HTTP/1.1\r\nHost: 127.0.0.1\r\nTransfer-Encoding: pepe\r\n\r\n"); // not implemented
198 string response = Receive (ns, 512);
201 Assert.IsTrue (response.StartsWith ("HTTP/1.1 501"));
207 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test6/");
208 NetworkStream ns = CreateNS (9000);
209 // not implemented! This is against the RFC. Should be a bad request/length required
210 Send (ns, "POST /test6/ HTTP/1.1\r\nHost: 127.0.0.1\r\nTransfer-Encoding: identity\r\n\r\n");
211 string response = Receive (ns, 512);
214 Assert.IsTrue (response.StartsWith ("HTTP/1.1 501"));
220 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test7/");
221 NetworkStream ns = CreateNS (9000);
222 Send (ns, "POST /test7/ HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 3\r\n\r\n123");
223 HttpListenerContext ctx = listener.GetContext ();
224 Send (ctx.Response.OutputStream, "%%%OK%%%");
225 string response = Receive (ns, 1024);
228 Assert.IsTrue (response.StartsWith ("HTTP/1.1 200"));
229 Assert.IsTrue (-1 != response.IndexOf ("Transfer-Encoding: chunked"));
235 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test8/");
236 NetworkStream ns = CreateNS (9000);
237 // Just like Test7, but 1.0
238 Send (ns, "POST /test8/ HTTP/1.0\r\nHost: 127.0.0.1\r\nContent-Length: 3\r\n\r\n123");
239 HttpListenerContext ctx = listener.GetContext ();
240 Send (ctx.Response.OutputStream, "%%%OK%%%");
241 string response = Receive (ns, 512);
244 Assert.IsTrue (response.StartsWith ("HTTP/1.1 200"));
245 Assert.IsTrue (-1 == response.IndexOf ("Transfer-Encoding: chunked"));
251 // 1.0 + "Transfer-Encoding: chunked"
252 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test9/");
253 NetworkStream ns = CreateNS (9000);
254 Send (ns, "POST /test9/ HTTP/1.0\r\nHost: 127.0.0.1\r\nTransfer-Encoding: chunked\r\n\r\n3\r\n123\r\n0\r\n\r\n");
256 string response = ReceiveWithTimeout (ns, 512, 1000, out timeout);
257 Assert.IsFalse (timeout);
260 Assert.IsTrue (response.StartsWith ("HTTP/1.1 411"));
264 public void Test10 ()
266 // Same as Test9, but now we shutdown the socket for sending.
267 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test10/");
268 MyNetworkStream ns = CreateNS (9000);
269 Send (ns, "POST /test10/ HTTP/1.0\r\nHost: 127.0.0.1\r\nTransfer-Encoding: chunked\r\n\r\n3\r\n123\r\n0\r\n\r\n");
270 ns.GetSocket ().Shutdown (SocketShutdown.Send);
272 string response = ReceiveWithTimeout (ns, 512, 1000, out timeout);
273 Assert.IsFalse (timeout);
276 Assert.IsTrue (response.StartsWith ("HTTP/1.1 411"));
280 public void Test11 ()
283 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test11/");
284 MyNetworkStream ns = CreateNS (9000);
285 Send (ns, "POST /test11/ HTTP/0.9\r\nHost: 127.0.0.1\r\n\r\n123");
286 ns.GetSocket ().Shutdown (SocketShutdown.Send);
287 string input = Receive (ns, 512);
290 Assert.IsTrue (input.StartsWith ("HTTP/1.1 400"));
294 public void Test12 ()
297 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test12/");
298 MyNetworkStream ns = CreateNS (9000);
299 Send (ns, "POST /test12/ HTTP/0.9\r\nHost: 127.0.0.1\r\nContent-Length: 3\r\n\r\n123");
300 ns.GetSocket ().Shutdown (SocketShutdown.Send);
301 string input = Receive (ns, 512);
304 Assert.IsTrue (input.StartsWith ("HTTP/1.1 400"));
308 public void Test13 ()
311 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test13/");
312 MyNetworkStream ns = CreateNS (9000);
313 Send (ns, "GEt /test13/ HTTP/0.9\r\nHost: 127.0.0.1\r\n\r\n");
314 ns.GetSocket ().Shutdown (SocketShutdown.Send);
315 string input = Receive (ns, 512);
318 Assert.IsTrue (input.StartsWith ("HTTP/1.1 400"));
321 HttpListenerRequest test14_request;
322 ManualResetEvent test_evt;
325 public void Test14 ()
327 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test14/");
328 MyNetworkStream ns = CreateNS (9000);
329 Send (ns, "POST /test14/ HTTP/1.0\r\nHost: 127.0.0.1\r\nContent-Length: 3\r\n\r\n123");
330 HttpListenerContext c = listener.GetContext ();
331 test14_request = c.Request;
332 test_evt = new ManualResetEvent (false);
333 Thread thread = new Thread (ReadToEnd);
335 if (test_evt.WaitOne (3000, false) == false) {
338 Assert.IsTrue (false, "Timed out");
341 Assert.AreEqual ("123", read_to_end, "Did not get the expected input.");
349 using (StreamReader r = new StreamReader (test14_request.InputStream)) {
350 read_to_end = r.ReadToEnd ();
356 public void Test15 ()
358 // 2 separate writes -> 2 packets. Body size > 8kB
359 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test15/");
360 MyNetworkStream ns = CreateNS (9000);
361 Send (ns, "POST /test15/ HTTP/1.0\r\nHost: 127.0.0.1\r\nContent-Length: 8888\r\n\r\n");
363 string data = new string ('a', 8888);
365 HttpListenerContext c = listener.GetContext ();
366 HttpListenerRequest req = c.Request;
367 using (StreamReader r = new StreamReader (req.InputStream)) {
368 read_to_end = r.ReadToEnd ();
370 Assert.AreEqual (read_to_end.Length, data.Length, "Wrong length");
371 Assert.IsTrue (data == read_to_end, "Wrong data");
377 public void Test16 ()
379 // 1 single write with headers + body (size > 8kB)
380 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test16/");
381 MyNetworkStream ns = CreateNS (9000);
382 StringBuilder sb = new StringBuilder ();
383 sb.Append ("POST /test16/ HTTP/1.0\r\nHost: 127.0.0.1\r\nContent-Length: 8888\r\n\r\n");
384 string eights = new string ('b', 8888);
386 string data = sb.ToString ();
388 HttpListenerContext c = listener.GetContext ();
389 HttpListenerRequest req = c.Request;
390 using (StreamReader r = new StreamReader (req.InputStream)) {
391 read_to_end = r.ReadToEnd ();
393 Assert.AreEqual (read_to_end.Length, read_to_end.Length, "Wrong length");
394 Assert.IsTrue (eights == read_to_end, "Wrong data");
400 public void Test17 ()
402 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/test17/");
403 NetworkStream ns = CreateNS (9000);
404 Send (ns, "RANDOM /test17/ HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 3\r\n\r\n123");
405 HttpListenerContext ctx = listener.GetContext ();
406 Send (ctx.Response.OutputStream, "%%%OK%%%");
407 string response = Receive (ns, 1024);
410 Assert.IsTrue (response.StartsWith ("HTTP/1.1 200"));
411 Assert.IsTrue (-1 != response.IndexOf ("Transfer-Encoding: chunked"));
415 public void Test_MultipleClosesOnOuputStreamAllowed ()
417 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/MultipleCloses/");
418 NetworkStream ns = CreateNS (9000);
419 Send (ns, "GET /MultipleCloses/ HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n");
421 HttpListenerContext ctx = listener.GetContext ();
422 ctx.Response.OutputStream.Close ();
423 ctx.Response.OutputStream.Close ();
424 ctx.Response.OutputStream.Close ();
425 ctx.Response.Close ();
431 NetworkStream ns = CreateNS (9000);
432 Send (ns, "GET /SendCookie/ HTTP/1.1\r\nHost: 127.0.0.1\r\n"+
433 "Cookie:$Version=\"1\"; "+
434 "Cookie1=Value1; $Path=\"/\"; "+
435 "CookieM=ValueM; $Path=\"/p2\"; $Domain=\"test\"; $Port=\"99\";"+
436 "Cookie2=Value2; $Path=\"/foo\";"+
445 public void ReceiveCookiesFromClient ()
447 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/SendCookie/");
448 Thread clientThread = new Thread (new ThreadStart (SendCookie));
449 clientThread.Start ();
451 HttpListenerContext context = listener.GetContext();
452 HttpListenerRequest request = context.Request;
454 Assert.AreEqual (3, request.Cookies.Count, "#1");
455 foreach (Cookie c in request.Cookies) {
456 if (c.Name == "Cookie1") {
457 Assert.AreEqual ("Value1", c.Value, "#2");
458 Assert.AreEqual ("\"/\"", c.Path, "#3");
459 Assert.AreEqual (0, c.Port.Length, "#4");
460 Assert.AreEqual (0, c.Domain.Length, "#5");
461 } else if (c.Name == "CookieM") {
462 Assert.AreEqual ("ValueM", c.Value, "#6");
463 Assert.AreEqual ("\"/p2\"", c.Path, "#7");
464 Assert.AreEqual ("\"99\"", c.Port, "#8");
465 Assert.AreEqual ("\"test\"", c.Domain, "#9");
466 } else if (c.Name == "Cookie2") {
467 Assert.AreEqual ("Value2", c.Value, "#10");
468 Assert.AreEqual ("\"/foo\"", c.Path, "#11");
469 Assert.AreEqual (0, c.Port.Length, "#12");
470 Assert.AreEqual (0, c.Domain.Length, "#13");
472 Assert.Fail ("Invalid cookie name " + c.Name);
478 private object _lock = new Object();
479 private string cookieResponse;
481 void ReceiveCookie () {
483 NetworkStream ns = CreateNS (9000);
484 Send (ns, "GET /ReceiveCookie/ HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n");
485 cookieResponse = Receive (ns, 512);
490 public void SendCookiestoClient ()
492 HttpListener listener = CreateAndStartListener ("http://127.0.0.1:9000/ReceiveCookie/");
493 Thread clientThread = new Thread (new ThreadStart (ReceiveCookie));
494 clientThread.Start ();
496 HttpListenerContext context = listener.GetContext();
497 HttpListenerRequest request = context.Request;
498 HttpListenerResponse response = context.Response;
500 Cookie cookie = new Cookie ();
501 cookie.Name = "Name0";
502 cookie.Value = "Value0";
503 cookie.Domain = "blue";
504 cookie.Path = "/path/";
505 cookie.Port = "\"80\"";
507 response.Cookies.Add (cookie);
509 string responseString = "<HTML><BODY>----</BODY></HTML>";
510 byte[] buffer = Encoding.UTF8.GetBytes(responseString);
511 response.ContentLength64 = buffer.Length;
512 Stream output = response.OutputStream;
513 output.Write(buffer, 0, buffer.Length);
518 bool foundCookie = false;
519 foreach (String str in cookieResponse.Split ('\n')) {
520 if (!str.StartsWith ("Set-Cookie2"))
522 Dictionary<string, String> dic = new Dictionary<string, String>();
523 foreach (String p in str.Substring (str.IndexOf (":") + 1).Split (';')) {
524 String[] parts = p.Split('=');
525 dic.Add (parts [0].Trim (), parts [1].Trim ());
527 Assert.AreEqual ("Value0", dic ["Name0"], "#1");
528 Assert.AreEqual ("blue", dic ["Domain"], "#2");
529 Assert.AreEqual ("\"/path/\"", dic ["Path"], "#3");
530 Assert.AreEqual ("\"80\"", dic ["Port"], "#4");
531 Assert.AreEqual ("1", dic ["Version"], "#5");
535 Assert.IsTrue (foundCookie, "#6");
542 public void MultiResponses ()
544 Thread srv = new Thread (new ThreadStart (EchoServer));
548 for (int i = 0; i < 10; i++) {
549 string payload = string.Format (CultureInfo.InvariantCulture,
552 HttpWebRequest req = (HttpWebRequest) WebRequest.Create (
553 "http://localhost:8888/foobar/");
554 req.ServicePoint.Expect100Continue = false;
555 req.ServicePoint.UseNagleAlgorithm = false;
557 StreamWriter w = new StreamWriter (req.GetRequestStream ());
558 w.WriteLine (payload);
561 HttpWebResponse resp = (HttpWebResponse) req.GetResponse ();
562 StreamReader r = new StreamReader (resp.GetResponseStream ());
563 Assert.AreEqual ("Hello, " + payload + "!", r.ReadToEnd ().Trim ());
573 HttpListener listener = new HttpListener ();
574 listener.Prefixes.Add ("http://*:8888/foobar/");
577 manualReset = new ManualResetEvent (false);
579 IAsyncResult result = listener.BeginGetContext (
580 new AsyncCallback (EchoCallback), listener);
581 manualReset.WaitOne ();
584 void EchoCallback (IAsyncResult result)
586 HttpListener listener = (HttpListener) result.AsyncState;
587 HttpListenerContext context = listener.EndGetContext (result);
588 HttpListenerRequest req = context.Request;
589 StreamReader r = new StreamReader (req.InputStream);
590 string reqBody = r.ReadToEnd ().Trim ();
592 HttpListenerResponse resp = context.Response;
593 StreamWriter o = new StreamWriter (resp.OutputStream);
594 o.WriteLine ("Hello, " + reqBody + "!");
597 listener.BeginGetContext (new AsyncCallback (EchoCallback), listener);
600 private ManualResetEvent manualReset;
605 public class HttpListenerBugs {
607 public void TestNonChunkedAsync ()
609 HttpListener listener = HttpListener2Test.CreateAndStartListener ("http://127.0.0.1:9123/");
611 listener.BeginGetContext (callback, listener);
613 HttpListener2Test.MyNetworkStream ns = HttpListener2Test.CreateNS (9123);
614 string message = "<script>\n"+
615 " <!-- register the blueprint for our show-headers service -->\n"+
616 " <action verb=\"POST\" path=\"/host/register\">\n" +
618 " <assembly>dream.tutorial.show-headers</assembly>\n" +
619 " <class>MindTouch.Dream.Tutorial.ShowHeadersService</class>\n" +
623 " <!-- instantiate it -->\n" +
624 " <action verb=\"POST\" path=\"/host/start\">\n" +
626 " <path>show-headers</path>\n" +
627 " <class>MindTouch.Dream.Tutorial.ShowHeadersService</class>\n" +
631 string s = String.Format ("POST / HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: {0}\r\n\r\n{1}",
632 message.Length, message);
633 HttpListener2Test.Send (ns, s);
635 string response = HttpListener2Test.ReceiveWithTimeout (ns, 1024, 3000, out timedout);
637 Assert.IsFalse (timedout);
640 void callback (IAsyncResult ar)
642 HttpListener l = (HttpListener) ar.AsyncState;
644 HttpListenerContext c = l.EndGetContext (ar);
645 HttpListenerRequest request = c.Request;
647 StreamReader r = new StreamReader (request.InputStream);
648 string sr =r.ReadToEnd ();
649 HttpListener2Test.Send (c.Response.OutputStream, "Miguel is love");