New test.
[mono.git] / mcs / class / System / System.Net / HttpListener.cs
1 //
2 // System.Net.HttpListener
3 //
4 // Author:
5 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
6 //
7 // Copyright (c) 2005 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 #if NET_2_0
29 using System.Collections.Generic;
30 using System.Threading;
31 //TODO: logging
32 namespace System.Net {
33         public sealed class HttpListener : IDisposable {
34                 AuthenticationSchemes auth_schemes;
35                 HttpListenerPrefixCollection prefixes;
36                 AuthenticationSchemeSelector auth_selector; 
37                 string realm;
38                 bool ignore_write_exceptions;
39                 bool unsafe_ntlm_auth;
40                 bool listening;
41                 bool disposed;
42
43                 Dictionary<HttpListenerContext,HttpListenerContext> registry;
44                 List<HttpListenerContext> ctx_queue;
45                 List<ListenerAsyncResult> wait_queue;
46
47                 public HttpListener ()
48                 {
49                         prefixes = new HttpListenerPrefixCollection (this);
50                         registry = new Dictionary<HttpListenerContext,HttpListenerContext> ();
51                         ctx_queue = new List<HttpListenerContext> ();
52                         wait_queue = new List<ListenerAsyncResult> ();
53                         auth_schemes = AuthenticationSchemes.Anonymous;
54                 }
55
56                 // TODO: Digest, NTLM and Negotiate require ControlPrincipal
57                 public AuthenticationSchemes AuthenticationSchemes {
58                         get { return auth_schemes; }
59                         set {
60                                 CheckDisposed ();
61                                 auth_schemes = value;
62                         }
63                 }
64
65                 //TODO: when is this called?
66                 public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate {
67                         get { return auth_selector; }
68                         set {
69                                 CheckDisposed ();
70                                 auth_selector = value;
71                         }
72                 }
73
74                 public bool IgnoreWriteExceptions {
75                         get { return ignore_write_exceptions; }
76                         set {
77                                 CheckDisposed ();
78                                 ignore_write_exceptions = value;
79                         }
80                 }
81
82                 public bool IsListening {
83                         get { return listening; }
84                 }
85
86                 public static bool IsSupported {
87                         get { return true; }
88                 }
89
90                 public HttpListenerPrefixCollection Prefixes {
91                         get {
92                                 CheckDisposed ();
93                                 return prefixes;
94                         }
95                 }
96
97                 // TODO: use this
98                 public string Realm {
99                         get { return realm; }
100                         set {
101                                 CheckDisposed ();
102                                 realm = value;
103                         }
104                 }
105
106                 [MonoTODO ("Support for NTLM needs some loving.")]
107                 public bool UnsafeConnectionNtlmAuthentication {
108                         get { return unsafe_ntlm_auth; }
109                         set {
110                                 CheckDisposed ();
111                                 unsafe_ntlm_auth = value;
112                         }
113                 }
114
115                 public void Abort ()
116                 {
117                         if (disposed)
118                                 return;
119
120                         if (!listening) {
121                                 disposed = true;
122                                 return;
123                         }
124
125                         Close (true);
126                 }
127
128                 public void Close ()
129                 {
130                         if (disposed)
131                                 return;
132
133                         if (!listening) {
134                                 disposed = true;
135                                 return;
136                         }
137
138                         Close (false);
139                 }
140
141                 void Close (bool force)
142                 {
143                         CheckDisposed ();
144                         EndPointManager.RemoveListener (this);
145                         Cleanup (force);
146                         disposed = true;
147                 }
148
149                 void Cleanup (bool close_existing)
150                 {
151                         lock (registry) {
152                                 if (close_existing) {
153                                         foreach (HttpListenerContext context in registry.Keys) {
154                                                 context.Connection.Close ();
155                                         }
156                                         registry.Clear (); // Just in case.
157                                 }
158
159                                 lock (ctx_queue) {
160                                         foreach (HttpListenerContext context in ctx_queue)
161                                                 context.Connection.Close ();
162
163                                         ctx_queue.Clear ();
164                                 }
165
166                                 lock (wait_queue) {
167                                         foreach (ListenerAsyncResult ares in wait_queue) {
168                                                 ares.Complete ("Listener was closed.");
169                                         }
170                                         wait_queue.Clear ();
171                                 }
172                         }
173                 }
174
175                 public IAsyncResult BeginGetContext (AsyncCallback callback, Object state)
176                 {
177                         CheckDisposed ();
178                         if (!listening)
179                                 throw new InvalidOperationException ("Please, call Start before using this method.");
180
181                         ListenerAsyncResult ares = new ListenerAsyncResult (callback, state);
182
183                         // lock wait_queue early to avoid race conditions
184                         lock (wait_queue) {
185                                 lock (ctx_queue) {
186                                         HttpListenerContext ctx = GetContextFromQueue ();
187                                         if (ctx != null) {
188                                                 ares.Complete (ctx, true);
189                                                 return ares;
190                                         }
191                                 }
192
193                                 wait_queue.Add (ares);
194                         }
195
196                         return ares;
197                 }
198
199                 public HttpListenerContext EndGetContext (IAsyncResult asyncResult)
200                 {
201                         CheckDisposed ();
202                         if (asyncResult == null)
203                                 throw new ArgumentNullException ("asyncResult");
204
205                         ListenerAsyncResult ares = asyncResult as ListenerAsyncResult;
206                         if (ares == null)
207                                 throw new ArgumentException ("Wrong IAsyncResult.", "asyncResult");
208
209                         if (!ares.IsCompleted)
210                                 ares.AsyncWaitHandle.WaitOne ();
211
212                         lock (wait_queue) {
213                                 int idx = wait_queue.IndexOf (ares);
214                                 if (idx >= 0)
215                                         wait_queue.RemoveAt (idx);
216                         }
217
218                         return ares.GetContext (); // This will throw on error.
219                 }
220
221                 public HttpListenerContext GetContext ()
222                 {
223                         // The prefixes are not checked when using the async interface!?
224                         if (prefixes.Count == 0)
225                                 throw new InvalidOperationException ("Please, call AddPrefix before using this method.");
226
227                         IAsyncResult ares = BeginGetContext (null, null);
228                         return EndGetContext (ares);
229                 }
230
231                 public void Start ()
232                 {
233                         CheckDisposed ();
234                         if (listening)
235                                 return;
236
237                         EndPointManager.AddListener (this);
238                         listening = true;
239                 }
240
241                 public void Stop ()
242                 {
243                         CheckDisposed ();
244                         listening = false;
245                         Close (false);
246                 }
247
248                 void IDisposable.Dispose ()
249                 {
250                         if (disposed)
251                                 return;
252
253                         disposed = true;
254                         Close (true); //TODO: Should we force here or not?
255                 }
256
257                 internal void CheckDisposed ()
258                 {
259                         if (disposed)
260                                 throw new ObjectDisposedException (GetType ().ToString ());
261                 }
262
263                 // Must be called with a lock on ctx_queue
264                 HttpListenerContext GetContextFromQueue ()
265                 {
266                         if (ctx_queue.Count == 0)
267                                 return null;
268
269                         HttpListenerContext context = ctx_queue [0];
270                         ctx_queue.RemoveAt (0);
271                         return context;
272                 }
273
274                 internal void RegisterContext (HttpListenerContext context)
275                 {
276                         try {
277                                 Monitor.Enter (registry);
278                                 registry [context] = context;
279                                 Monitor.Enter (wait_queue);
280                                 Monitor.Enter (ctx_queue);
281                                 if (wait_queue.Count == 0) {
282                                         ctx_queue.Add (context);
283                                 } else {
284                                         ListenerAsyncResult ares = wait_queue [0];
285                                         wait_queue.RemoveAt (0);
286                                         ares.Complete (context);
287                                 }
288                         } finally {
289                                 Monitor.Exit (ctx_queue);
290                                 Monitor.Exit (wait_queue);
291                                 Monitor.Exit (registry);
292                         }
293                 }
294
295                 internal void UnregisterContext (HttpListenerContext context)
296                 {
297                         try {
298                                 Monitor.Enter (registry);
299                                 Monitor.Enter (ctx_queue);
300                                 int idx = ctx_queue.IndexOf (context);
301                                 if (idx >= 0)
302                                         ctx_queue.RemoveAt (idx);
303                                 registry.Remove (context);
304                         } finally {
305                                 Monitor.Exit (ctx_queue);
306                                 Monitor.Exit (registry);
307                         }
308                 }
309         }
310 }
311 #endif
312