/// <summary> /// Manages the actual write to the HttpListenerContext Response. /// </summary> /// <param name="ctx">HttpListenerContext object.</param> /// <param name="response">Response to write to the output.</param> private static void WriteToOutput(HttpListenerContext ctx, RestfulResponse response) { if (response != null) { ctx.Response.StatusCode = (int)response.StatusCode; // Propagate the headers back in the response. if (response.Headers != null) { foreach (var header in response.Headers) { ctx.Response.Headers.Add(header.Key, header.Value); } } var responseString = response.Body; if (!string.IsNullOrEmpty(responseString)) { var buf = Encoding.UTF8.GetBytes(responseString); ctx.Response.ContentLength64 = buf.Length; ctx.Response.OutputStream.Write(buf, 0, buf.Length); } } }
/// <summary> /// This is where the good stuff happens. Incoming requests are Resolved using the /// EndPointGroup. This is where there is room for potential optimizations; there are /// two reflection calls happening at runtime for each request. /// 1) An instantiation of the BaseModule - this is because each BaseModule has a /// responseHeaders dictionary that cannot be shared. Obviously, some scheme that /// avoids reflection can be devised. /// 2) The actual invocation of the MethodInfo. /// </summary> /// <param name="request">An IRequest object encapsulating the request.</param> /// <returns>A RestfulResponse object that can be serialized and written to the /// response.</returns> public RestfulResponse ResolveRequest(IRequest request) { SidModule moduleInstance = null; try { // Resolve the endpoint the request is for. Regex pathRegex; var endPoint = ResolveEndPoint(request, out pathRegex); // Instantiate the module. moduleInstance = Activator.CreateInstance(endPoint.ModuleType) as SidModule; moduleInstance.RestfulRequest = request; request.Match = GetPathRegexMatch(request, endPoint, pathRegex); // Determine if it's an OPTIONS request or not. var optionsRequest = request.HttpMethod == HttpMethod.Options; // Create CORS-specific headers if appropriate, or throw Forbidden exception. DoCORSHandling(request, moduleInstance.ResponseHeaders, endPoint, optionsRequest); // Get the arguments and execute the method. var args = GetArguments(request, endPoint, moduleInstance); var ret = endPoint.MethodInfo.Invoke(moduleInstance, args); // Create the Access-Control-Expose-Headers header, if the endpoint added any // response headers. CopyResponseHeadersToAccessControlExposeHeadersKey(optionsRequest, moduleInstance.ResponseHeaders); // Serialize the return value. var json = ret != null ? JsonConvert.SerializeObject(ret) : null; // Construct a RestfulResponse object from the return value. var successResponse = new RestfulResponse { StatusCode = HttpStatusCode.OK, Body = json, Headers = moduleInstance.ResponseHeaders, }; return successResponse; } catch (Exception ex) { return HandleDispatchException(ex, moduleInstance); } }
/// <summary> /// This shouldn't get hit; RestfulMethodDispatcher.ResolveResponse is supposed to /// catch errors, but if something slips through, this is a stopgap. /// This is equivalent to the Nancy 'Oh Noes!....page'. /// </summary> /// <param name="ex"></param> /// <param name="ctx"></param> private static void HandleDispatchException(Exception ex, HttpListenerContext ctx) { var errMsg = string.Format("RestfulMethodDispatcher incorrectly threw an untrapped" + " error = {0}, request = {1} stack = {2}", ex.Message, ctx.Request.RawUrl, ex.StackTrace); log.Error(errMsg); var error = new RestfulResponse{ StatusCode = HttpStatusCode.InternalServerError, Body = errMsg, }; WriteToOutput(ctx, error); }