/// <summary> /// Invoke event handlers. /// </summary> /// <param name="callbacks">Callbacks to invoke.</param> /// <param name="context">Http listener context.</param> /// <returns>True if we should continue handling this request, false if we need to stop handling it.</returns> private bool InvokeCallbacks(HandleRequestsCallback callbacks, ServeritoContext context) { // invoke callback try { callbacks?.Invoke(context); } // on break callbacks do nothing catch (BreakCallbacks) { } // on abort request call abort and return false. catch (AbortRequest) { context.Context.Response.Abort(); return(false); } // on stop processing just return false. catch (StopProcessingRequest) { return(false); } // if we got here we return true to continue handling request. return(true); }
/// <summary> /// Start listening to incoming requests. /// </summary> public void Start() { // start listener _listener.Start(); // do an endless loop that listen to requests while (_listener.IsListening) { // hold the context we are currently processing ServeritoContext context = null; // handle incoming requests try { // get context (block until a connection comes in) context = new ServeritoContext() { Context = _listener.GetContext() }; // set response default status code and if sending in chunks context.Context.Response.StatusCode = (int)HttpStatusCode.OK; context.Context.Response.SendChunked = UseChunks; // call new requests callbacks if (!InvokeCallbacks(OnNewRawRequest, context)) { continue; } // call the handler function using threads if (UseThreads) { var thisContext = context; ThreadPool.QueueUserWorkItem(o => HandleRequest(thisContext)); } // call the handler function without using threads else { HandleRequest(context); } // lose context pointer context = null; } // Client disconnected or some other error catch (Exception e) { HandleException(context, e); } } }
/// <summary> /// Handle an exception while handling a request. /// </summary> /// <param name="context">Request context, or null if the error happened while trying to get context.</param> /// <param name="exp">Exception object.</param> private void HandleException(ServeritoContext context, Exception exp) { // set status code to error by default. // note: the try-catch is in case the context was disposed. try { context.Context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } catch { } // call the errors handler callback try { OnException?.Invoke(context, exp); } catch (BreakCallbacks) { } // if need to close requests, try to close it if (CloseRequests) { Utils.TryCloseResponse(context.Context); } }
/// <summary> /// Serve an HTML file as a webpage. /// Note: this works using the static files mechanism, eg you must set 'StaticFilesPath' path for it to work. /// </summary> /// <param name="context">Context containing the request to serve file for.</param> /// <param name="path">Path of file to serve, under 'StaticFilesPath'.</param> public void ServeHtmlPage(ServeritoContext context, string path) { ServeStaticFile(context, path, true); }
/// <summary> /// Serve a static file. /// Note: will close request even if 'CloseRequests' is set to false. /// </summary> /// <param name="context">Context containing the request to serve file for.</param> /// <param name="path">Path of file to serve, under 'StaticFilesPath'.</param> /// <param name="serveHtml">If true, instead of serving file as a file it will render it as an HTML page.</param> public void ServeStaticFile(ServeritoContext context, string path, bool serveHtml = false) { // static files path not defined? error if (StaticFilesPath == null) { throw new NullReferenceException("To serve static files you must set the 'StaticFilesPath' property."); } // get file full path path = System.IO.Path.Combine(StaticFilesPath, path); // file not found? return 404 if (!System.IO.File.Exists(path)) { // call missing file callback if (!InvokeCallbacks(OnMissingFile, context)) { return; } // set status code to not found and try closing request context.Context.Response.StatusCode = (int)HttpStatusCode.NotFound; Utils.TryCloseResponse(context.Context); return; } // set default content type context.Context.Response.ContentType = serveHtml ? "text/html" : "application/octet-stream"; // get filename var filename = System.IO.Path.GetFileName(path); // set and content disposition for serving html if (serveHtml) { context.Context.Response.Headers.Add("Content-Disposition", "inline;filename=\"" + filename + "\""); } // set and content disposition for file else { context.Context.Response.Headers.Add("Content-Disposition", "attachment;filename=\"" + filename + "\""); } // set content type based on known mime types if (!serveHtml && SetMimeContentType) { context.Context.Response.ContentType = Utils.ExtensionToMimeType(System.IO.Path.GetExtension(path)) ?? context.Context.Response.ContentType; } // set encoding type if (StaticFilesEncodingType != EncodingType.Default) { context.Context.Response.ContentType += _encodingToCharsetString[(int)StaticFilesEncodingType]; } // read file into response var fileContent = StaticFilesReader(path); context.Context.Response.OutputStream.Write(fileContent, 0, fileContent.Length); // call serving files callback if (!InvokeCallbacks(OnServingFile, context)) { return; } // close response Utils.TryCloseResponse(context.Context); }
/// <summary> /// Handle a single incoming request. /// Note: this function can run inside or outside a thread, depends on config. /// </summary> /// <param name="context"></param> public void HandleRequest(ServeritoContext context) { try { // handle static files if (IsStaticFileRequest(context.Context.Request)) { ServeStaticFile(context, context.Context.Request.RawUrl.Substring(StaticFilesRootUrl.Length)); return; } // call the before URL matching callback if (!InvokeCallbacks(OnUrlMatching, context)) { return; } // find view to handle this request bool missingUrl = true; foreach (var view in _views) { // check if view match if (view.UrlPattern.IsMatch(context.Context.Request.RawUrl, context.Context.Request.HttpMethod)) { // do before-processing requests callbacks if (!InvokeCallbacks(OnPassingRequestToView, context)) { return; } // call view and set no longer missing url view.ViewFunc(context); missingUrl = false; } } // didn't match any URL? set status code to 404 and call the missed URLs handler if (missingUrl) { context.Context.Response.StatusCode = (int)HttpStatusCode.NotFound; if (!InvokeCallbacks(OnUndefinedURL, context)) { return; } } // found and handled via view? invoke callbacks else { // invoke end handling request callbacks if (!InvokeCallbacks(OnFinishedProcessingView, context)) { return; } } // invoke end handling request callbacks if (!InvokeCallbacks(OnFinishHandlingRequest, context)) { return; } // if need to close requests, try to close it if (CloseRequests) { Utils.TryCloseResponse(context.Context); } } // handle exceptions catch (Exception exp) { HandleException(context, exp); } }