svn path=/trunk/mcs/; revision=113894
[mono.git] / mcs / class / System.Runtime.Remoting / MonoHttp / HttpListener.cs
1 #define EMBEDDED_IN_1_0
2
3 //
4 // System.Net.HttpListener
5 //
6 // Author:
7 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
8 //
9 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 #if !NET_2_0
32
33 using System.Collections;
34 using System.Threading;
35 //TODO: logging
36 using System; using System.Net; namespace MonoHttp {
37         internal class HttpListener : IDisposable {
38                 AuthenticationSchemes auth_schemes;
39                 HttpListenerPrefixCollection prefixes;
40                 AuthenticationSchemeSelector auth_selector; 
41                 string realm;
42                 bool ignore_write_exceptions;
43                 bool unsafe_ntlm_auth;
44                 bool listening;
45                 bool disposed;
46
47                 Hashtable registry;   // Dictionary<HttpListenerContext,HttpListenerContext> 
48                 ArrayList ctx_queue;  // List<HttpListenerContext> ctx_queue;
49                 ArrayList wait_queue; // List<ListenerAsyncResult> wait_queue;
50
51                 public HttpListener ()
52                 {
53                         prefixes = new HttpListenerPrefixCollection (this);
54                         registry = new Hashtable ();
55                         ctx_queue = new ArrayList ();
56                         wait_queue = new ArrayList ();
57                         auth_schemes = AuthenticationSchemes.Anonymous;
58                 }
59
60                 // TODO: Digest, NTLM and Negotiate require ControlPrincipal
61                 public AuthenticationSchemes AuthenticationSchemes {
62                         get { return auth_schemes; }
63                         set {
64                                 CheckDisposed ();
65                                 auth_schemes = value;
66                         }
67                 }
68
69                 //TODO: when is this called?
70                 public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate {
71                         get { return auth_selector; }
72                         set {
73                                 CheckDisposed ();
74                                 auth_selector = value;
75                         }
76                 }
77
78                 public bool IgnoreWriteExceptions {
79                         get { return ignore_write_exceptions; }
80                         set {
81                                 CheckDisposed ();
82                                 ignore_write_exceptions = value;
83                         }
84                 }
85
86                 public bool IsListening {
87                         get { return listening; }
88                 }
89
90                 public static bool IsSupported {
91                         get { return true; }
92                 }
93
94                 public HttpListenerPrefixCollection Prefixes {
95                         get {
96                                 CheckDisposed ();
97                                 return prefixes;
98                         }
99                 }
100
101                 // TODO: use this
102                 public string Realm {
103                         get { return realm; }
104                         set {
105                                 CheckDisposed ();
106                                 realm = value;
107                         }
108                 }
109
110                 [MonoTODO ("Support for NTLM needs some loving.")]
111                 public bool UnsafeConnectionNtlmAuthentication {
112                         get { return unsafe_ntlm_auth; }
113                         set {
114                                 CheckDisposed ();
115                                 unsafe_ntlm_auth = value;
116                         }
117                 }
118
119                 public void Abort ()
120                 {
121                         if (disposed)
122                                 return;
123
124                         if (!listening) {
125                                 return;
126                         }
127
128                         Close (true);
129                 }
130
131                 public void Close ()
132                 {
133                         if (disposed)
134                                 return;
135
136                         if (!listening) {
137                                 disposed = true;
138                                 return;
139                         }
140
141                         Close (false);
142                         disposed = true;
143                 }
144
145                 void Close (bool force)
146                 {
147                         CheckDisposed ();
148                         EndPointManager.RemoveListener (this);
149                         Cleanup (force);
150                 }
151
152                 void Cleanup (bool close_existing)
153                 {
154                         lock (registry) {
155                                 if (close_existing) {
156                                         foreach (HttpListenerContext context in registry.Keys) {
157                                                 context.Connection.Close ();
158                                         }
159                                         registry.Clear (); // Just in case.
160                                 }
161
162                                 lock (ctx_queue) {
163                                         foreach (HttpListenerContext context in ctx_queue)
164                                                 context.Connection.Close ();
165
166                                         ctx_queue.Clear ();
167                                 }
168
169                                 lock (wait_queue) {
170                                         foreach (ListenerAsyncResult ares in wait_queue) {
171                                                 ares.Complete ("Listener was closed.");
172                                         }
173                                         wait_queue.Clear ();
174                                 }
175                         }
176                 }
177
178                 public IAsyncResult BeginGetContext (AsyncCallback callback, Object state)
179                 {
180                         CheckDisposed ();
181                         if (!listening)
182                                 throw new InvalidOperationException ("Please, call Start before using this method.");
183
184                         ListenerAsyncResult ares = new ListenerAsyncResult (callback, state);
185
186                         // lock wait_queue early to avoid race conditions
187                         lock (wait_queue) {
188                                 lock (ctx_queue) {
189                                         HttpListenerContext ctx = GetContextFromQueue ();
190                                         if (ctx != null) {
191                                                 ares.Complete (ctx, true);
192                                                 return ares;
193                                         }
194                                 }
195
196                                 wait_queue.Add (ares);
197                         }
198
199                         return ares;
200                 }
201
202                 public HttpListenerContext EndGetContext (IAsyncResult asyncResult)
203                 {
204                         CheckDisposed ();
205                         if (asyncResult == null)
206                                 throw new ArgumentNullException ("asyncResult");
207
208                         ListenerAsyncResult ares = asyncResult as ListenerAsyncResult;
209                         if (ares == null)
210                                 throw new ArgumentException ("Wrong IAsyncResult.", "asyncResult");
211
212                         if (!ares.IsCompleted)
213                                 ares.AsyncWaitHandle.WaitOne ();
214
215                         lock (wait_queue) {
216                                 int idx = wait_queue.IndexOf (ares);
217                                 if (idx >= 0)
218                                         wait_queue.RemoveAt (idx);
219                         }
220
221                         HttpListenerContext context = ares.GetContext ();
222                         if (auth_schemes != AuthenticationSchemes.Anonymous) {
223                                 context.ParseAuthentication ();
224                         }
225                         return context; // This will throw on error.
226                 }
227
228                 public HttpListenerContext GetContext ()
229                 {
230                         // The prefixes are not checked when using the async interface!?
231                         if (prefixes.Count == 0)
232                                 throw new InvalidOperationException ("Please, call AddPrefix before using this method.");
233
234                         IAsyncResult ares = BeginGetContext (null, null);
235                         return EndGetContext (ares);
236                 }
237
238                 public void Start ()
239                 {
240                         CheckDisposed ();
241                         if (listening)
242                                 return;
243
244                         EndPointManager.AddListener (this);
245                         listening = true;
246                 }
247
248                 public void Stop ()
249                 {
250                         CheckDisposed ();
251                         listening = false;
252                         Close (false);
253                 }
254
255                 void IDisposable.Dispose ()
256                 {
257                         if (disposed)
258                                 return;
259
260                         Close (true); //TODO: Should we force here or not?
261                         disposed = true;
262                 }
263
264                 internal void CheckDisposed ()
265                 {
266                         if (disposed)
267                                 throw new ObjectDisposedException (GetType ().ToString ());
268                 }
269
270                 // Must be called with a lock on ctx_queue
271                 HttpListenerContext GetContextFromQueue ()
272                 {
273                         if (ctx_queue.Count == 0)
274                                 return null;
275
276                         HttpListenerContext context = (HttpListenerContext) ctx_queue [0];
277                         ctx_queue.RemoveAt (0);
278                         return context;
279                 }
280
281                 internal void RegisterContext (HttpListenerContext context)
282                 {
283                         try {
284                                 Monitor.Enter (registry);
285                                 registry [context] = context;
286                                 Monitor.Enter (wait_queue);
287                                 Monitor.Enter (ctx_queue);
288                                 if (wait_queue.Count == 0) {
289                                         ctx_queue.Add (context);
290                                 } else {
291                                         ListenerAsyncResult ares = (ListenerAsyncResult) wait_queue [0];
292                                         wait_queue.RemoveAt (0);
293                                         ares.Complete (context);
294                                 }
295                         } finally {
296                                 Monitor.Exit (ctx_queue);
297                                 Monitor.Exit (wait_queue);
298                                 Monitor.Exit (registry);
299                         }
300                 }
301
302                 internal void UnregisterContext (HttpListenerContext context)
303                 {
304                         try {
305                                 Monitor.Enter (registry);
306                                 Monitor.Enter (ctx_queue);
307                                 int idx = ctx_queue.IndexOf (context);
308                                 if (idx >= 0)
309                                         ctx_queue.RemoveAt (idx);
310                                 registry.Remove (context);
311                         } finally {
312                                 Monitor.Exit (ctx_queue);
313                                 Monitor.Exit (registry);
314                         }
315                 }
316         }
317 }
318 #endif
319