[System*] Add category for tests that use API that require BSD sockets.
[mono.git] / mcs / class / System / Test / System.Net / HttpListenerTest.cs
1 //
2 // HttpListenerTest.cs
3 //      - Unit tests for System.Net.HttpListener
4 //
5 // Author:
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
9 //
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:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
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.
28 //
29 using System;
30 using System.IO;
31 using System.Net;
32 using System.Net.Sockets;
33 using System.Threading;
34 using System.Threading.Tasks;
35 using NUnit.Framework;
36 using MonoTests.Helpers;
37
38 namespace MonoTests.System.Net {
39         [TestFixture]
40         [Category ("RequiresBSDSockets")]
41         public class HttpListenerTest {
42
43                 int port;
44
45                 [SetUp]
46                 public void SetUp () {
47                         port = NetworkHelpers.FindFreePort ();
48                 }
49
50                 [Test]
51                 public void DefaultProperties ()
52                 {
53                         HttpListener listener = new HttpListener ();
54                         Assert.AreEqual (AuthenticationSchemes.Anonymous, listener.AuthenticationSchemes, "#01");
55                         Assert.AreEqual (null, listener.AuthenticationSchemeSelectorDelegate, "#02");
56                         Assert.AreEqual (false, listener.IgnoreWriteExceptions, "#03");
57                         Assert.AreEqual (false, listener.IsListening, "#03");
58                         Assert.AreEqual (0, listener.Prefixes.Count, "#04");
59                         Assert.AreEqual (null, listener.Realm, "#05");
60                         Assert.AreEqual (false, listener.UnsafeConnectionNtlmAuthentication, "#06");
61                 }
62
63                 [Test]
64                 public void Start1 ()
65                 {
66                         HttpListener listener = new HttpListener ();
67                         listener.Start ();
68                 }
69
70                 [Test]
71                 public void Stop1 ()
72                 {
73                         HttpListener listener = new HttpListener ();
74                         listener.Stop ();
75                 }
76
77                 [Test]
78                 [ExpectedException (typeof (InvalidOperationException))]
79                 public void GetContext1 ()
80                 {
81                         HttpListener listener = new HttpListener ();
82                         // "Please call Start () before calling this method"
83                         listener.GetContext ();
84                 }
85
86                 [Test]
87                 [ExpectedException (typeof (InvalidOperationException))]
88                 public void GetContext2 ()
89                 {
90                         HttpListener listener = new HttpListener ();
91                         listener.Start ();
92                         // "Please call AddPrefix () before calling this method"
93                         listener.GetContext ();
94                 }
95
96                 [Test]
97                 [ExpectedException (typeof (InvalidOperationException))]
98                 public void BeginGetContext1 ()
99                 {
100                         HttpListener listener = new HttpListener ();
101                         // "Please call Start () before calling this method"
102                         listener.BeginGetContext (null, null);
103                 }
104
105                 [Test]
106                 public void BeginGetContext2 ()
107                 {
108                         HttpListener listener = new HttpListener ();
109                         listener.Start ();
110                         // One would expect this to fail as BeginGetContext1 does not fail and
111                         // calling EndGetContext will wait forever.
112                         // Lame. They should check that we have no prefixes.
113                         IAsyncResult ares = listener.BeginGetContext (null, null);
114                         Assert.IsFalse (ares.IsCompleted);
115                 }
116
117                 private bool CanOpenPort(int port)
118                 {
119                         try
120                         {
121                                 using(Socket socket = new Socket (AddressFamily.InterNetwork,
122                                         SocketType.Stream,
123                                         ProtocolType.Tcp))
124                                 {
125                                         socket.Bind (new IPEndPoint (IPAddress.Loopback, port));
126                                         socket.Listen(1);
127                                 }
128                         }
129                         catch(Exception) {
130                                 //Can be AccessDeniedException(ports 80/443 need root access) or
131                                 //SocketException because other application is listening
132                                 return false;
133                         }
134                         return true;
135                 }
136
137                 [Test]
138                 public void DefaultHttpPort ()
139                 {
140                         if (!CanOpenPort (80))
141                                 Assert.Ignore ("Can not open port 80 skipping test.");
142                         using(HttpListener listener = new HttpListener ())
143                         {
144                                 listener.Prefixes.Add ("http://127.0.0.1/");
145                                 listener.Start ();
146                                 Assert.IsFalse (CanOpenPort (80), "HttpListener is not listening on port 80.");
147                         }
148                 }
149
150                 [Test]
151                 public void DefaultHttpsPort ()
152                 {
153                         if (!CanOpenPort (443))
154                                 Assert.Ignore ("Can not open port 443 skipping test.");
155                         using(HttpListener listener = new HttpListener ())
156                         {
157                                 listener.Prefixes.Add ("https://127.0.0.1/");
158                                 listener.Start ();
159                                 Assert.IsFalse (CanOpenPort (443), "HttpListener is not listening on port 443.");
160                         }
161                 }
162
163                 [Test]
164                 public void TwoListeners_SameAddress ()
165                 {
166                         if (!CanOpenPort (port))
167                                 Assert.Ignore ("port");
168                         HttpListener listener1 = new HttpListener ();
169                         listener1.Prefixes.Add ("http://127.0.0.1:" + port + "/");
170                         HttpListener listener2 = new HttpListener ();
171                         listener2.Prefixes.Add ("http://127.0.0.1:" + port + "/hola/");
172                         listener1.Start ();
173                         listener2.Start ();
174                 }
175
176                 [Test]
177                 [ExpectedException (typeof (HttpListenerException))]
178                 public void TwoListeners_SameURL ()
179                 {
180                         if (!CanOpenPort (port))
181                                 Assert.Ignore ("port");
182                         HttpListener listener1 = new HttpListener ();
183                         listener1.Prefixes.Add ("http://127.0.0.1:" + port + "/hola/");
184                         HttpListener listener2 = new HttpListener ();
185                         listener2.Prefixes.Add ("http://127.0.0.1:" + port + "/hola/");
186                         listener1.Start ();
187                         listener2.Start ();
188                 }
189
190                 [Test]
191                 [ExpectedException (typeof (HttpListenerException))]
192                 public void MultipleSlashes ()
193                 {
194                         if (!CanOpenPort (port))
195                                 Assert.Ignore ("port");
196                         HttpListener listener = new HttpListener ();
197                         listener.Prefixes.Add ("http://localhost:" + port + "/hola////");
198                         // this one throws on Start(), not when adding it.
199                         listener.Start ();
200                 }
201
202                 [Test]
203                 [ExpectedException (typeof (HttpListenerException))]
204                 public void PercentSign ()
205                 {
206                         if (!CanOpenPort (port))
207                                 Assert.Ignore ("port");
208                         HttpListener listener = new HttpListener ();
209                         listener.Prefixes.Add ("http://localhost:" + port + "/hola%3E/");
210                         // this one throws on Start(), not when adding it.
211                         listener.Start ();
212                 }
213
214                 [Test]
215                 public void CloseBeforeStart ()
216                 {
217                         HttpListener listener = new HttpListener ();
218                         listener.Close ();
219                 }
220
221                 [Test]
222                 public void CloseTwice ()
223                 {
224                         if (!CanOpenPort (port))
225                                 Assert.Ignore ("port");
226                         HttpListener listener = new HttpListener ();
227                         listener.Prefixes.Add ("http://localhost:" + port + "/hola/");
228                         listener.Start ();
229                         listener.Close ();
230                         listener.Close ();
231                 }
232
233                 [Test]
234                 public void StartStopStart ()
235                 {
236                         if (!CanOpenPort (port))
237                                 Assert.Ignore ("port");
238                         HttpListener listener = new HttpListener ();
239                         listener.Prefixes.Add ("http://localhost:" + port + "/hola/");
240                         listener.Start ();
241                         listener.Stop ();
242                         listener.Start ();
243                         listener.Close ();
244                 }
245
246                 [Test]
247                 public void StartStopDispose ()
248                 {
249                         if (!CanOpenPort (port))
250                                 Assert.Ignore ("port");
251                         using (HttpListener listener = new HttpListener ()){
252                                 listener.Prefixes.Add ("http://localhost:" + port + "/hola/");
253                                 listener.Start ();
254                                 listener.Stop ();
255                         }
256                 }
257                 
258                 [Test]
259                 public void AbortBeforeStart ()
260                 {
261                         HttpListener listener = new HttpListener ();
262                         listener.Abort ();
263                 }
264
265                 [Test]
266                 public void AbortTwice ()
267                 {
268                         if (!CanOpenPort (port))
269                                 Assert.Ignore ("port");
270                         HttpListener listener = new HttpListener ();
271                         listener.Prefixes.Add ("http://localhost:" + port + "/hola/");
272                         listener.Start ();
273                         listener.Abort ();
274                         listener.Abort ();
275                 }
276
277                 [Test]
278                 public void PropertiesWhenClosed1 ()
279                 {
280                         HttpListener listener = new HttpListener ();
281                         listener.Close ();
282                         Assert.AreEqual (AuthenticationSchemes.Anonymous, listener.AuthenticationSchemes, "#01");
283                         Assert.AreEqual (null, listener.AuthenticationSchemeSelectorDelegate, "#02");
284                         Assert.AreEqual (false, listener.IgnoreWriteExceptions, "#03");
285                         Assert.AreEqual (false, listener.IsListening, "#03");
286                         Assert.AreEqual (null, listener.Realm, "#05");
287                         Assert.AreEqual (false, listener.UnsafeConnectionNtlmAuthentication, "#06");
288                 }
289
290                 [Test]
291                 [ExpectedException (typeof (ObjectDisposedException))]
292                 public void PropertiesWhenClosed2 ()
293                 {
294                         HttpListener listener = new HttpListener ();
295                         listener.Close ();
296                         HttpListenerPrefixCollection p = listener.Prefixes;
297                 }
298
299                 [Test]
300                 [ExpectedException (typeof (ObjectDisposedException))]
301                 public void PropertiesWhenClosedSet1 ()
302                 {
303                         HttpListener listener = new HttpListener ();
304                         listener.Close ();
305                         listener.AuthenticationSchemes = AuthenticationSchemes.None;
306                 }
307
308                 [Test]
309                 [ExpectedException (typeof (ObjectDisposedException))]
310                 public void PropertiesWhenClosedSet2 ()
311                 {
312                         HttpListener listener = new HttpListener ();
313                         listener.Close ();
314                         listener.AuthenticationSchemeSelectorDelegate = null;
315                 }
316
317                 [Test]
318                 [ExpectedException (typeof (ObjectDisposedException))]
319                 public void PropertiesWhenClosedSet3 ()
320                 {
321                         HttpListener listener = new HttpListener ();
322                         listener.Close ();
323                         listener.IgnoreWriteExceptions = true;
324                 }
325
326                 [Test]
327                 [ExpectedException (typeof (ObjectDisposedException))]
328                 public void PropertiesWhenClosedSet4 ()
329                 {
330                         HttpListener listener = new HttpListener ();
331                         listener.Close ();
332                         listener.Realm = "hola";
333                 }
334
335                 [Test]
336                 [ExpectedException (typeof (ObjectDisposedException))]
337                 public void PropertiesWhenClosedSet5 ()
338                 {
339                         HttpListener listener = new HttpListener ();
340                         listener.Close ();
341                         listener.UnsafeConnectionNtlmAuthentication = true;
342                 }
343
344                 [Test]
345                 public void PropertiesWhenClosed3 ()
346                 {
347                         HttpListener listener = new HttpListener ();
348                         listener.Close ();
349                         Assert.IsFalse (listener.IsListening);
350                 }
351
352                 [Test]
353                 public void CloseWhileBegin ()
354                 {
355                         HttpListener listener = new HttpListener ();
356                         listener.Prefixes.Add ("http://127.0.0.1:" + NetworkHelpers.FindFreePort () + "/closewhilebegin/");
357                         listener.Start ();
358                         CallMe cm = new CallMe ();
359                         listener.BeginGetContext (cm.Callback, listener);
360                         listener.Close ();
361                         if (false == cm.Event.WaitOne (3000, false))
362                                 Assert.Fail ("This should not time out.");
363                         Assert.IsNotNull (cm.Error);
364                         Assert.AreEqual (typeof (ObjectDisposedException), cm.Error.GetType (), "Exception type");
365                         cm.Dispose ();
366                 }
367
368                 [Test]
369                 public void AbortWhileBegin ()
370                 {
371                         HttpListener listener = new HttpListener ();
372                         listener.Prefixes.Add ("http://127.0.0.1:" + NetworkHelpers.FindFreePort () + "/abortwhilebegin/");
373                         listener.Start ();
374                         CallMe cm = new CallMe ();
375                         listener.BeginGetContext (cm.Callback, listener);
376                         listener.Abort ();
377                         if (false == cm.Event.WaitOne (3000, false))
378                                 Assert.Fail ("This should not time out.");
379                         Assert.IsNotNull (cm.Error);
380                         Assert.AreEqual (typeof (ObjectDisposedException), cm.Error.GetType (), "Exception type");
381                         cm.Dispose ();
382                 }
383
384                 [Test]
385                 [ExpectedException (typeof (HttpListenerException))]
386                 public void CloseWhileGet ()
387                 {
388                         // "System.Net.HttpListener Exception : The I/O operation has been aborted
389                         // because of either a thread exit or an application request
390                         //   at System.Net.HttpListener.GetContext()
391                         //   at MonoTests.System.Net.HttpListenerTest.CloseWhileGet()
392
393                         HttpListener listener = new HttpListener ();
394                         listener.Prefixes.Add ("http://127.0.0.1:" + NetworkHelpers.FindFreePort () + "/closewhileget/");
395                         listener.Start ();
396                         RunMe rm = new RunMe (1000, new ThreadStart (listener.Close), new object [0]);
397                         rm.Start ();
398                         HttpListenerContext ctx = listener.GetContext ();
399                 }
400
401                 [Test]
402                 [ExpectedException (typeof (HttpListenerException))]
403                 public void AbortWhileGet ()
404                 {
405                         // "System.Net.HttpListener Exception : The I/O operation has been aborted
406                         // because of either a thread exit or an application request
407                         //   at System.Net.HttpListener.GetContext()
408                         //   at MonoTests.System.Net.HttpListenerTest.CloseWhileGet()
409
410                         HttpListener listener = new HttpListener ();
411                         listener.Prefixes.Add ("http://127.0.0.1:" + NetworkHelpers.FindFreePort () + "/abortwhileget/");
412                         listener.Start ();
413                         RunMe rm = new RunMe (1000, new ThreadStart (listener.Abort), new object [0]);
414                         rm.Start ();
415                         HttpListenerContext ctx = listener.GetContext ();
416                 }
417
418                 class RunMe {
419                         Delegate d;
420                         int delay_ms;
421                         object [] args;
422                         public object Result;
423
424                         public RunMe (int delay_ms, Delegate d, object [] args)
425                         {
426                                 this.delay_ms = delay_ms;
427                                 this.d = d;
428                                 this.args = args;
429                         }
430
431                         public void Start ()
432                         {
433                                 Thread th = new Thread (new ThreadStart (Run));
434                                 th.Start ();
435                         }
436
437                         void Run ()
438                         {
439                                 Thread.Sleep (delay_ms);
440                                 Result = d.DynamicInvoke (args);
441                         }
442                 }
443
444                 class CallMe {
445                         public ManualResetEvent Event = new ManualResetEvent (false);
446                         public bool Called;
447                         public HttpListenerContext Context;
448                         public Exception Error;
449
450                         public void Reset ()
451                         {
452                                 Called = false;
453                                 Context = null;
454                                 Error = null;
455                                 Event.Reset ();
456                         }
457
458                         public void Callback (IAsyncResult ares)
459                         {
460                                 Called = true;
461                                 if (ares == null) {
462                                         Error = new ArgumentNullException ("ares");
463                                         return;
464                                 }
465                                 
466                                 try {
467                                         HttpListener listener = (HttpListener) ares.AsyncState;
468                                         Context = listener.EndGetContext (ares);
469                                 } catch (Exception e) {
470                                         Error = e;
471                                 }
472                                 Event.Set ();
473                         }
474
475                         public void Dispose ()
476                         {
477                                 Event.Close ();
478                         }
479                 }
480
481                 [Test]
482                 public void ConnectionReuse ()
483                 {
484                         var uri = "http://localhost:" + NetworkHelpers.FindFreePort () + "/";
485
486                         HttpListener listener = new HttpListener ();
487                         listener.Prefixes.Add (uri);
488                         listener.Start ();
489
490                         IPEndPoint expectedIpEndPoint = CreateListenerRequest (listener, uri);
491
492                         Assert.AreEqual (expectedIpEndPoint, CreateListenerRequest (listener, uri), "reuse1");
493                         Assert.AreEqual (expectedIpEndPoint, CreateListenerRequest (listener, uri), "reuse2");
494                 }
495
496                 public IPEndPoint CreateListenerRequest (HttpListener listener, string uri)
497                 {
498                         IPEndPoint ipEndPoint = null;
499                         var mre = new ManualResetEventSlim ();
500                         listener.BeginGetContext (result => {
501                                 ipEndPoint = ListenerCallback (result);
502                                 mre.Set ();
503                         }, listener);
504
505                         var request = (HttpWebRequest) WebRequest.Create (uri);
506                         request.Method = "POST";
507
508                         // We need to write something
509                         request.GetRequestStream ().Write (new byte [] {(byte)'a'}, 0, 1);
510                         request.GetRequestStream ().Dispose ();
511
512                         // Send request, socket is created or reused.
513                         var response = request.GetResponse ();
514
515                         // Close response so socket can be reused.
516                         response.Close ();
517
518                         mre.Wait ();
519
520                         return ipEndPoint;
521                 }
522
523                 public static IPEndPoint ListenerCallback (IAsyncResult result)
524                 {
525                         var listener = (HttpListener) result.AsyncState;
526                         var context = listener.EndGetContext (result);
527                         var clientEndPoint = context.Request.RemoteEndPoint;
528
529                         // Disposing InputStream should not avoid socket reuse
530                         context.Request.InputStream.Dispose ();
531
532                         // Close OutputStream to send response
533                         context.Response.OutputStream.Close ();
534
535                         return clientEndPoint;
536                 }
537                 
538                 [Test]
539                 public void HttpClientIsDisconnectedCheckForWriteException()
540                 {
541                         string uri = "http://localhost:" + NetworkHelpers.FindFreePort () + "/";
542
543                         AutoResetEvent exceptionOccuredEvent = new AutoResetEvent (false);
544                         HttpListener listener = new HttpListener {
545                                 IgnoreWriteExceptions = false
546                         };
547                         listener.Prefixes.Add (uri);
548                         listener.Start ();
549                         listener.BeginGetContext (result =>
550                         {
551                                 HttpListenerContext context = listener.EndGetContext (result);
552                                 context.Response.SendChunked = true;
553                                 context.Request.InputStream.Close ();
554                                 
555                                 var bytes = new byte [1024];
556                                 using(Stream outputStream = context.Response.OutputStream) {
557                                         try {
558                                                 while (true) 
559                                                         outputStream.Write (bytes, 0, bytes.Length);
560                                         } catch {
561                                                 exceptionOccuredEvent.Set ();
562                                         }
563                                 }
564                         }, null);
565
566                         Task.Factory.StartNew (() =>
567                         {
568                                 var webRequest = (HttpWebRequest)WebRequest.Create (uri);
569                                 webRequest.Method = "POST";
570                                 webRequest.KeepAlive = false;
571                                 Stream requestStream = webRequest.GetRequestStream ();
572                                 requestStream.WriteByte (1);
573                                 requestStream.Close ();
574                                 using (WebResponse response = webRequest.GetResponse ())
575                                 using (Stream stream = response.GetResponseStream ()) {
576                                         byte[] clientBytes = new byte [1024];
577                                         Assert.IsNotNull (stream, "#01");
578                                         stream.Read (clientBytes, 0, clientBytes.Length);
579                                 }
580                         });
581
582                         Assert.IsTrue (exceptionOccuredEvent.WaitOne (15 * 1000), "#02");
583                 }
584         }
585 }
586