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