/// <summary> /// Listens to incoming connections and calls the spawner method for each new connection /// </summary> /// <returns>Awaitable task.</returns> /// <param name="addr">The address to listen to.</param> /// <param name="usessl">A flag indicating if the socket listens for SSL requests</param> /// <param name="stoptoken">The stoptoken.</param> /// <param name="config">The server configuration</param> /// <param name="spawner">The method handling the new connection.</param> private static async Task ListenToSocketInternalAsync(IPEndPoint addr, bool usessl, CancellationToken stoptoken, ServerConfig config, Action <TcpClient, EndPoint, string, RunnerControl> spawner) { var rc = new RunnerControl(stoptoken, usessl, config); var listener = new TcpListener(addr); listener.Start(config.SocketBacklog); var taskid = SetLoggingSocketHandlerID(); while (!stoptoken.IsCancellationRequested) { // Wait if there are too many active config.DebugLogHandler?.Invoke("Waiting for throttle", taskid, null); await rc.ThrottleTask; config.DebugLogHandler?.Invoke("Waiting for socket", taskid, null); var ls = listener.AcceptTcpClientAsync(); if (await Task.WhenAny(rc.StopTask, ls) == ls) { config.DebugLogHandler?.Invoke("Re-waiting for socket", taskid, null); var client = await ls; var newtaskid = SetLoggingTaskHandlerID(); try { int wt, cpt; ThreadPool.GetAvailableThreads(out wt, out cpt); config.DebugLogHandler?.Invoke(string.Format("Threadpool says {0}, {1}", wt, cpt), taskid, newtaskid); config.DebugLogHandler?.Invoke(string.Format("Spawning runner with id: {0}", newtaskid), taskid, newtaskid); // Read the endpoint here to avoid crashes when invoking the spawner var ep = client.Client.RemoteEndPoint; ThreadPool.QueueUserWorkItem(x => spawner(client, ep, newtaskid, rc)); } catch (Exception ex) { config.DebugLogHandler?.Invoke("Failed to listen to socket", taskid, ex); } } } config.DebugLogHandler?.Invoke("Stopping", taskid, null); listener.Stop(); rc.Stop(taskid); config.DebugLogHandler?.Invoke("Socket stopped, waiting for workers ...", taskid, null); await rc.FinishedTask; config.DebugLogHandler?.Invoke("Stopped", taskid, null); }
/// <summary> /// Listens to incoming connections and calls the spawner method for each new connection /// </summary> /// <returns>Awaitable task.</returns> /// <param name="acceptAsync">Method that returns an accepted socket.</param> /// <param name="usessl">A flag indicating if the socket listens for SSL requests</param> /// <param name="stoptoken">The stoptoken.</param> /// <param name="config">The server configuration</param> /// <param name="spawner">The method handling the new connection.</param> public static Task ListenToSocketAsync(Func <CancellationToken, Task <KeyValuePair <long, EndPoint> > > acceptAsync, bool usessl, CancellationToken stoptoken, ServerConfig config, Action <long, EndPoint, string> spawner) { return(ListenToSocketInternalAsync(acceptAsync, usessl, stoptoken, config, (client, remoteendpoint, logid, controller) => spawner(client, remoteendpoint, logid))); }
/// <summary> /// Listens to incoming connections and calls the spawner method for each new connection /// </summary> /// <returns>Awaitable task.</returns> /// <param name="acceptAsync">Method that returns an accepted socket.</param> /// <param name="usessl">A flag indicating if the socket listens for SSL requests</param> /// <param name="stoptoken">The stoptoken.</param> /// <param name="config">The server configuration</param> /// <param name="spawner">The method handling the new connection.</param> private static async Task ListenToSocketInternalAsync(Func <CancellationToken, Task <KeyValuePair <long, EndPoint> > > acceptAsync, bool usessl, CancellationToken stoptoken, ServerConfig config, Action <long, EndPoint, string, RunnerControl> spawner) { if (acceptAsync == null) { throw new ArgumentNullException(nameof(acceptAsync)); } var rc = new RunnerControl(stoptoken, usessl, config); var taskid = SetLoggingSocketHandlerID(); while (!stoptoken.IsCancellationRequested) { // Wait if there are too many active config.DebugLogHandler?.Invoke("Waiting for throttle", taskid, null); await rc.ThrottleTask; config.DebugLogHandler?.Invoke("Waiting for socket", taskid, null); var ls = acceptAsync(stoptoken); if (await Task.WhenAny(rc.StopTask, ls) == ls) { config.DebugLogHandler?.Invoke("Re-waiting for socket", taskid, null); var client = await ls; var newtaskid = SetLoggingTaskHandlerID(); try { int wt, cpt; ThreadPool.GetAvailableThreads(out wt, out cpt); config.DebugLogHandler?.Invoke(string.Format("Threadpool says {0}, {1}", wt, cpt), taskid, newtaskid); config.DebugLogHandler?.Invoke(string.Format("Spawning runner with id: {0}", newtaskid), taskid, newtaskid); ThreadPool.QueueUserWorkItem(x => spawner(client.Key, client.Value, newtaskid, rc)); } catch (Exception ex) { config.DebugLogHandler?.Invoke("Failed to listen to socket", taskid, ex); } } } config.DebugLogHandler?.Invoke("Stopping", taskid, null); rc.Stop(taskid); config.DebugLogHandler?.Invoke("Socket stopped, waiting for workers ...", taskid, null); await rc.FinishedTask; config.DebugLogHandler?.Invoke("Stopped", taskid, null); }
/// <summary> /// Creates and initializes a new InterProcess bridge /// </summary> /// <returns>The app domain bridge.</returns> /// <param name="usessl">If set to <c>true</c> use ssl.</param> /// <param name="config">The server config.</param> public static InterProcessBridge CreateInterProcessBridge(bool usessl, ServerConfig config) { return(new InterProcessBridge(usessl, config)); }
/// <summary> /// Listens to incoming connections and calls the spawner method for each new connection /// </summary> /// <returns>Awaitable task.</returns> /// <param name="addr">The address to listen to.</param> /// <param name="usessl">A flag indicating if the socket listens for SSL requests</param> /// <param name="stoptoken">The stoptoken.</param> /// <param name="config">The server configuration</param> /// <param name="spawner">The method handling the new connection.</param> public static Task ListenToSocketAsync(IPEndPoint addr, bool usessl, CancellationToken stoptoken, ServerConfig config, Action <TcpClient, EndPoint, string> spawner) { return(ListenToSocketInternalAsync(addr, usessl, stoptoken, config, (client, remoteendpoint, logid, controller) => spawner(client, remoteendpoint, logid))); }
/// <summary> /// Creates and initializes a new AppDomain bridge /// </summary> /// <returns>The app domain bridge.</returns> /// <param name="usessl">If set to <c>true</c> use ssl.</param> /// <param name="config">The server config.</param> public static AppDomainBridge CreateAppDomainBridge(bool usessl, ServerConfig config) { return(new AppDomainBridge(usessl, config)); }
/// <summary> /// Setup this instance /// </summary> /// <param name="usessl">If set to <c>true</c> use ssl.</param> /// <param name="config">The configuration.</param> public InterProcessBridge(bool usessl, ServerConfig config) { Setup(usessl, config); }
/// <summary> /// Initializes a new instance of the <see cref="T:Ceen.Httpd.HttpServer.AppDomainBridge"/> class. /// </summary> /// <param name="usessl">If set to <c>true</c> use ssl.</param> /// <param name="config">The server config.</param> public AppDomainBridge(bool usessl, ServerConfig config) { Setup(usessl, config); }
/// <summary> /// Parses url encoded form data /// </summary> /// <returns>An awaitable task.</returns> /// <param name="reader">The stream to read from.</param> /// <param name="config">The server configuration.</param> /// <param name="idletime">The maximum idle time.</param> /// <param name="timeouttask">A task that signals request timeout.</param> /// <param name="stoptask">A task that signals server stop.</param> internal async Task ParseFormData(BufferedStreamReader reader, ServerConfig config, TimeSpan idletime, Task timeouttask, Task stoptask) { if (this.IsContentType("application/x-www-form-urlencoded")) { if (this.ContentLength != 0) { if (this.ContentLength > config.MaxUrlEncodedFormSize) { throw new HttpException(HttpStatusCode.PayloadTooLarge); } var enc = this.GetEncodingForContentType(); ParseQueryString( enc.GetString( this.ContentLength > 0 ? await reader.RepeatReadAsync(this.ContentLength, idletime, timeouttask, stoptask) : await reader.ReadUntilCrlfAsync(config.MaxRequestLineSize, config.MaxUrlEncodedFormSize, idletime, timeouttask, stoptask) ), this.Form); } this.Body = new LimitedBodyStream(reader, 0, idletime, timeouttask, stoptask); } else if (RequestUtility.IsMultipartRequest(this.ContentType) && this.ContentLength > 0 && this.ContentLength < config.MaxUrlEncodedFormSize && config.AutoParseMultipartFormData) { await ParseMultiPart( async (headers, stream) => { var dispositionItems = RequestUtility.SplitHeaderLine(headers["Content-Disposition"]); if (!string.Equals(dispositionItems.FirstOrDefault().Key, "form-data", StringComparison.OrdinalIgnoreCase)) { throw new HttpException(HttpStatusCode.BadRequest); } var name = RequestUtility.GetHeaderComponent(headers["Content-Disposition"], "name"); if (string.IsNullOrWhiteSpace("name")) { throw new HttpException(HttpStatusCode.BadRequest); } var filename = RequestUtility.GetHeaderComponent(headers["Content-Disposition"], "filename"); var charset = RequestUtility.GetHeaderComponent(headers["Content-Type"], "charset") ?? "ascii"; if (string.IsNullOrWhiteSpace(filename)) { using (var sr = new StreamReader(stream, RequestUtility.GetEncodingForCharset(charset))) { var rtask = sr.ReadToEndAsync(); var rt = await Task.WhenAny(timeouttask, stoptask, rtask); if (rt != rtask) { if (rt == stoptask) { throw new TaskCanceledException(); } else { throw new HttpException(HttpStatusCode.RequestTimeout); } } this.Form[name] = rtask.Result; } } else { var me = new MultipartItem(headers) { Name = name, Filename = filename, Data = new MemoryStream() }; Task rtask; Task rt; using (var cs = new CancellationTokenSource(idletime)) { rtask = stream.CopyToAsync(me.Data, 8 * 1024, cs.Token); rt = await Task.WhenAny(timeouttask, stoptask, rtask); } if (rt != rtask) { if (rt == stoptask) { throw new TaskCanceledException(); } else { throw new HttpException(HttpStatusCode.RequestTimeout); } } rtask.GetAwaiter().GetResult(); me.Data.Position = 0; this.Files.Add(me); } }, reader, config, idletime, timeouttask, stoptask ); this.Body = new LimitedBodyStream(reader, 0, idletime, timeouttask, stoptask); } else { this.Body = new LimitedBodyStream(reader, this.ContentLength, idletime, timeouttask, stoptask); } }
/// <summary> /// Parses the stream items, if sent as multipart encoded /// </summary> /// <param name="itemparser">The parser method</param> /// <param name="reader">The stream to read from.</param> /// <param name="config">The server configuration.</param> /// <param name="idletime">The maximum idle time.</param> /// <param name="timeouttask">A task that signals request timeout.</param> /// <param name="stoptask">A task that signals server stop.</param> /// <returns>An awaitable task</returns> private async Task ParseMultiPart(Func <IDictionary <string, string>, Stream, Task> itemparser, BufferedStreamReader reader, ServerConfig config, TimeSpan idletime, Task timeouttask, Task stoptask) { if ((this.ContentType ?? "").StartsWith("multipart/form-data", StringComparison.OrdinalIgnoreCase)) { if (this.ContentLength > config.MaxPostSize) { throw new HttpException(HttpStatusCode.PayloadTooLarge); } var startpos = reader.Position; var trail = new byte[2]; var parts = this.ContentType.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); var bndpart = parts.FirstOrDefault(x => x.Trim().StartsWith("boundary", StringComparison.OrdinalIgnoreCase)) ?? string.Empty; var boundary = bndpart.Split(new char[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries).LastOrDefault(); if (string.IsNullOrWhiteSpace(boundary)) { throw new HttpException(HttpStatusCode.BadRequest); } // Since we have read the headers, we have consumed the initial CRLF // so we adjust the initial boundary reading to skip the CRLF var itemboundary = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary); var tmp = await reader.RepeatReadAsync(itemboundary.Length - 2, idletime, timeouttask, stoptask); if (!Enumerable.SequenceEqual(itemboundary.Skip(2), tmp)) { throw new HttpException(HttpStatusCode.BadRequest); } await reader.RepeatReadAsync(trail, 0, 2, idletime, timeouttask, stoptask); if (trail[0] != '\r' || trail[1] != '\n') { throw new HttpException(HttpStatusCode.BadRequest); } do { var headers = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase).WithDefaultValue(null); await reader.ReadHeaders( config.MaxRequestLineSize, config.MaxRequestHeaderSize, idletime, line => { var components = line.Split(new char[] { ':' }, 2); if (components.Length != 2 || string.IsNullOrWhiteSpace(components[0])) { throw new HttpException(HttpStatusCode.BadRequest); } headers[components[0].Trim()] = (components[1] ?? string.Empty).Trim(); }, timeouttask, stoptask ); await itemparser(headers, reader.GetDelimitedSubStream(itemboundary, idletime, timeouttask, stoptask)); await reader.RepeatReadAsync(trail, 0, 2, idletime, timeouttask, stoptask); }while (trail[0] == '\r' && trail[1] == '\n'); if (trail[0] != '-' || trail[1] != '-') { throw new HttpException(HttpStatusCode.BadRequest); } await reader.RepeatReadAsync(trail, 0, 2, idletime, timeouttask, stoptask); if (trail[0] != '\r' || trail[1] != '\n') { throw new HttpException(HttpStatusCode.BadRequest); } if (this.ContentLength > 0 && this.ContentLength != (reader.Position - startpos)) { throw new HttpException(HttpStatusCode.BadRequest); } } }