2
// AsyncTests.HttpClientTests.Addin.Server
5
// Martin Baulig (martin.baulig@gmail.com)
7
// Copyright 2012 Xamarin Inc. (http://www.xamarin.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.
34
using System.Net.Http;
35
using System.Diagnostics;
36
using System.Reflection;
37
using System.Collections.Generic;
38
using System.Threading;
39
using System.Threading.Tasks;
40
using NUnit.Framework;
42
namespace AsyncTests.HttpClientTests.Addin
48
static Server instance;
49
HttpListener listener;
50
Dictionary<string, RequestHandler> handlerByName;
51
Dictionary<MethodInfo, RequestHandler> handlerByMethod;
52
TaskCompletionSource<object> startTcs;
53
TaskCompletionSource<object> stopTcs;
54
Dictionary<int, Exception> exceptions;
61
Server (Assembly assembly, string prefix)
63
this.assembly = assembly;
66
random = new Random ();
67
exceptions = new Dictionary<int, Exception> ();
69
handlerByName = new Dictionary<string, RequestHandler> ();
70
handlerByMethod = new Dictionary<MethodInfo, RequestHandler> ();
72
startTcs = new TaskCompletionSource<object> ();
73
stopTcs = new TaskCompletionSource<object> ();
75
listener = new HttpListener ();
76
listener.Prefixes.Add (prefix);
78
listener.AuthenticationSchemeSelectorDelegate = SelectAuthentication;
81
thread = new Thread (() => ServerMain ());
85
AuthenticationSchemes SelectAuthentication (HttpListenerRequest request)
87
var path = request.Url.AbsolutePath.Substring (1);
88
if (!handlerByName.ContainsKey (path))
89
return AuthenticationSchemes.Anonymous;
91
var handler = handlerByName [path];
92
return handler.Attribute.Authentication;
95
internal static void Log (string message, params object[] args)
97
Debug.WriteLine (string.Format (message, args), "Server");
105
} catch (Exception ex) {
106
startTcs.SetException (ex);
110
startTcs.SetResult (null);
115
} catch (Exception ex) {
118
Log ("SERVER EXCEPTION: {0} {1}", running, ex);
122
Log ("Server exiting.");
123
stopTcs.SetResult (null);
135
public static Uri URI {
136
get { return new Uri (instance.prefix); }
139
public static string Host {
143
return string.Format ("{0}:{1}", uri.Host, uri.Port);
149
public static bool IsLocal {
150
get { return URI.IsLoopback; }
153
public static bool IsRunning {
154
get { return instance != null; }
157
public static async Task Start (Assembly assembly, string prefix)
159
if (instance != null)
161
instance = new Server (assembly, prefix);
162
await instance.startTcs.Task;
163
Log ("Started server: {0}", prefix);
166
public static async Task Stop ()
168
if (instance != null) {
169
await instance.Shutdown ();
176
foreach (var type in assembly.GetTypes ()) {
181
void CheckType (Type type)
183
var attr = type.GetCustomAttribute<AsyncTestFixtureAttribute> ();
187
var bf = BindingFlags.Public | BindingFlags.Static;
188
foreach (var method in type.GetMethods (bf)) {
189
var mattr = method.GetCustomAttribute<RequestHandlerAttribute> ();
193
if (CheckMethod (method, mattr))
195
Log ("ERROR: Invalid method: {0}.{1}", type.FullName, method.Name);
198
foreach (var nested in type.GetNestedTypes ()) {
203
bool CheckMethod (MethodInfo method, RequestHandlerAttribute attr)
205
if ((method.ReturnType != typeof (void)) &&
206
!method.ReturnType.Equals (typeof (Task)))
209
var pinfo = method.GetParameters ();
210
if (pinfo.Length != 1)
213
if (!pinfo [0].ParameterType.Equals (typeof(ServerContext)))
216
var handler = new RequestHandler (method, attr);
217
handlerByName.Add (handler.Name, handler);
218
handlerByMethod.Add (method, handler);
224
public RequestHandlerAttribute Attribute {
229
public bool IsAsync {
234
public RequestHandlerDelegate Handler {
239
public AsyncRequestHandlerDelegate AsyncHandler {
244
public MethodInfo Method {
246
return IsAsync ? AsyncHandler.Method : Handler.Method;
255
public RequestHandler (MethodInfo method, RequestHandlerAttribute attr)
258
if (method.ReturnType == typeof (void)) {
259
Handler = (RequestHandlerDelegate)Delegate.CreateDelegate (
260
typeof (RequestHandlerDelegate), method);
263
AsyncHandler = (AsyncRequestHandlerDelegate)Delegate.CreateDelegate (
264
typeof (AsyncRequestHandlerDelegate), method);
267
Name = method.DeclaringType.FullName.Replace ('.', '/') + '/' + method.Name;
270
public override string ToString ()
272
return string.Format ("[RequestHandler: {0}]", Name);
278
var context = await listener.GetContextAsync ();
279
var url = context.Request.Url;
281
var path = url.AbsolutePath.Substring (1);
282
if (path == string.Empty) {
283
PrintIndex (context);
284
context.Response.Close ();
286
} else if (path.Equals ("favicon.ico")) {
287
context.Response.StatusCode = 404;
288
context.Response.Close ();
292
if (!handlerByName.ContainsKey (path)) {
293
Log ("Unknown URL: {0}", path);
294
Error (context.Response, "Invalid URL: '{0}'", path);
298
var handler = handlerByName [path];
300
var sctx = new ServerContext (context);
302
ServerContext.Register (sctx);
304
await sctx.Invoke (handler.Name, handler.Method);
305
} catch (Exception ex) {
306
if (!sctx.SetException (ex))
307
Exception (context.Response, ex);
310
context.Response.Close ();
317
int CreateExceptionId ()
322
} while (exceptions.ContainsKey (id));
326
static readonly string ExceptionHeaderName = typeof (Server).FullName + ".Exception";
328
void Exception (HttpListenerResponse response, Exception exception)
330
var id = CreateExceptionId ();
331
exceptions [id] = exception;
332
response.AddHeader (ExceptionHeaderName, id.ToString ());
333
if (exception == null)
335
response.StatusCode = 500;
336
using (var writer = new StreamWriter (response.OutputStream)) {
337
writer.WriteLine (string.Format ("EXCEPTION: {0}", exception));
342
public static Exception GetException (HttpResponseMessage response)
344
var exception = ServerContext.GetException (response);
345
if (exception != null)
348
if (!response.Headers.Contains (ExceptionHeaderName))
351
var value = response.Headers.GetValues (ExceptionHeaderName).First ();
352
var id = int.Parse (value);
353
return instance.exceptions [id];
359
public static void CheckException (HttpResponseMessage response)
361
var exc = GetException (response);
366
void Error (HttpListenerResponse response, string format, params object[] args)
368
response.StatusCode = 500;
369
using (var writer = new StreamWriter (response.OutputStream)) {
370
writer.WriteLine (format, args);
375
public static Uri GetUri (RequestHandlerDelegate dlg)
377
var handler = instance.handlerByMethod [dlg.Method];
379
throw new InvalidOperationException ();
381
return GetUri (handler);
384
public static Uri GetAsyncUri (AsyncRequestHandlerDelegate dlg)
386
var handler = instance.handlerByMethod [dlg.Method];
388
throw new InvalidOperationException ();
390
return GetUri (handler);
393
static Uri GetUri (RequestHandler handler)
395
return new Uri (new Uri (instance.prefix), handler.Name);
398
void PrintIndex (HttpListenerContext context)
400
context.Response.StatusCode = 200;
401
using (var writer = new StreamWriter (context.Response.OutputStream)) {
402
writer.WriteLine ("<html><head><title>Test Server</title><head><body>");
403
writer.WriteLine ("<p>Registered tests:");
404
writer.WriteLine ("<p><ul>");
405
foreach (var handler in handlerByName.Keys) {
406
var uri = new Uri (new Uri (prefix), handler);
407
writer.WriteLine ("<li><a href=\"{0}\">{1}</a>",
408
uri.AbsoluteUri, handler);
410
writer.WriteLine ("</ul>");
411
writer.WriteLine ("</body></html>");