/// <summary> /// Creates a JSON string of the response and data including fields indicating success and data MD5. /// </summary> /// <returns>String containing JSON representation of the HTTP response.</returns> public string ToJson() { Dictionary <string, object> ret = new Dictionary <string, object>(); ret.Add("success", Success); if (Data != null) { ret.Add("md5", WatsonCommon.CalculateMd5(Data.ToString())); ret.Add("data", Data); } return(WatsonCommon.SerializeJson(ret)); }
/// <summary> /// Creates a byte array containing the HTTP response (useful for sockets apps what want to transmit the response directly). /// </summary> /// <returns></returns> public byte[] ToHttpBytes() { byte[] ret = null; string statusLine = ProtocolVersion + " " + StatusCode + " " + StatusDescription + "\r\n"; ret = AppendBytes(ret, Encoding.UTF8.GetBytes(statusLine)); if (!String.IsNullOrEmpty(ContentType)) { string contentTypeLine = "Content-Type: " + ContentType + "\r\n"; ret = AppendBytes(ret, Encoding.UTF8.GetBytes(contentTypeLine)); } if (Headers != null && Headers.Count > 0) { foreach (KeyValuePair <string, string> currHeader in Headers) { if (String.IsNullOrEmpty(currHeader.Key)) { continue; } if (currHeader.Key.ToLower().Trim().Equals("content-type")) { continue; } string headerLine = currHeader.Key + ": " + currHeader.Value + "\r\n"; ret = AppendBytes(ret, Encoding.UTF8.GetBytes(headerLine)); } } ret = AppendBytes(ret, Encoding.UTF8.GetBytes("\r\n")); if (Data != null) { if (Data is byte[]) { ret = AppendBytes(ret, (byte[])Data); } else if (Data is string) { ret = AppendBytes(ret, Encoding.UTF8.GetBytes((string)Data)); } else { ret = AppendBytes(ret, Encoding.UTF8.GetBytes(WatsonCommon.SerializeJson(Data))); } } return(ret); }
/// <summary> /// Retrieve a string-formatted, human-readable copy of the HttpResponse instance. /// </summary> /// <returns>String-formatted, human-readable copy of the HttpResponse instance.</returns> public override string ToString() { string ret = ""; ret += "--- HTTP Response ---" + Environment.NewLine; ret += TimestampUtc.ToString("MM/dd/yyyy HH:mm:ss") + " " + SourceIp + ":" + SourcePort + " to " + DestIp + ":" + DestPort + " " + Method + " " + RawUrlWithoutQuery + Environment.NewLine; ret += " Success : " + Success + Environment.NewLine; ret += " Content : " + ContentType + " (" + ContentLength + " bytes)" + Environment.NewLine; if (Headers != null && Headers.Count > 0) { ret += " Headers : " + Environment.NewLine; foreach (KeyValuePair <string, string> curr in Headers) { ret += " " + curr.Key + ": " + curr.Value + Environment.NewLine; } } else { ret += " Headers : none" + Environment.NewLine; } if (Data != null) { ret += " Data : " + Environment.NewLine; if (Data is byte[]) { ret += Encoding.UTF8.GetString(((byte[])Data)) + Environment.NewLine; } else if (Data is string) { ret += Data + Environment.NewLine; } else { ret += WatsonCommon.SerializeJson(Data) + Environment.NewLine; } } else { ret += " Data : [null]" + Environment.NewLine; } return(ret); }
private void SendResponse( HttpListenerContext context, HttpRequest req, object data, Dictionary <string, string> headers, int status) { int responseLen = 0; HttpListenerResponse response = null; try { #region Set-Variables if (data != null) { if (data is string) { if (!String.IsNullOrEmpty(data.ToString())) { responseLen = data.ToString().Length; } } else if (data is byte[]) { if ((byte[])data != null) { if (((byte[])data).Length > 0) { responseLen = ((byte[])data).Length; } } } else { responseLen = (WatsonCommon.SerializeJson(data)).Length; } } #endregion #region Status-Code-and-Description response = context.Response; response.StatusCode = status; switch (status) { case 200: response.StatusDescription = "OK"; break; case 201: response.StatusDescription = "Created"; break; case 301: response.StatusDescription = "Moved Permanently"; break; case 302: response.StatusDescription = "Moved Temporarily"; break; case 304: response.StatusDescription = "Not Modified"; break; case 400: response.StatusDescription = "Bad Request"; break; case 401: response.StatusDescription = "Unauthorized"; break; case 403: response.StatusDescription = "Forbidden"; break; case 404: response.StatusDescription = "Not Found"; break; case 405: response.StatusDescription = "Method Not Allowed"; break; case 429: response.StatusDescription = "Too Many Requests"; break; case 500: response.StatusDescription = "Internal Server Error"; break; case 501: response.StatusDescription = "Not Implemented"; break; case 503: response.StatusDescription = "Service Unavailable"; break; default: response.StatusDescription = "Unknown Status"; break; } #endregion #region Response-Headers response.AddHeader("Access-Control-Allow-Origin", "*"); response.ContentType = req.ContentType; int headerCount = 0; if (headers != null) { if (headers.Count > 0) { headerCount = headers.Count; } } if (headerCount > 0) { foreach (KeyValuePair <string, string> curr in headers) { response.AddHeader(curr.Key, curr.Value); } } #endregion #region Handle-HEAD-Request if (String.Compare(req.Method.ToLower(), "head") == 0) { data = null; } #endregion #region Send-Response Stream output = response.OutputStream; try { if (data != null) { #region Response-Body-Attached if (data is string) { #region string if (!String.IsNullOrEmpty(data.ToString())) { if (data.ToString().Length > 0) { byte[] buffer = System.Text.Encoding.UTF8.GetBytes(data.ToString()); response.ContentLength64 = buffer.Length; output.Write(buffer, 0, buffer.Length); output.Close(); } } #endregion } else if (data is byte[]) { #region byte-array response.ContentLength64 = responseLen; output.Write((byte[])data, 0, responseLen); output.Close(); #endregion } else { #region other response.ContentLength64 = responseLen; output.Write(Encoding.UTF8.GetBytes(WatsonCommon.SerializeJson(data)), 0, responseLen); output.Close(); #endregion } #endregion } else { #region No-Response-Body response.ContentLength64 = 0; output.Flush(); output.Close(); #endregion } } catch (HttpListenerException) { Logging.Log("Remote endpoint " + req.SourceIp + ":" + req.SourcePort + " appears to have disconnected"); } finally { if (response != null) { response.Close(); } } #endregion return; } catch (IOException) { Logging.Log("Remote endpoint " + req.SourceIp + ":" + req.SourcePort + " appears to have terminated connection prematurely (outer IOException)"); return; } catch (HttpListenerException) { Logging.Log("Remote endpoint " + req.SourceIp + ":" + req.SourcePort + " appears to have terminated connection prematurely (outer HttpListenerException)"); return; } catch (Exception e) { Logging.LogException("SendResponse", e); return; } finally { if (req != null) { if (req.TimestampUtc != null) { Logging.Log("Thread " + req.ThreadId + " sending " + responseLen + "B status " + status + " " + req.SourceIp + ":" + req.SourcePort + " for " + req.Method + " " + req.RawUrlWithoutQuery + " (" + WatsonCommon.TotalMsFrom(req.TimestampUtc) + "ms)"); } } if (response != null) { response.Close(); } } }
private byte[] BuildSuccessResponse( int status, object data) { Dictionary <string, object> ret = new Dictionary <string, object>(); ret.Add("data", data); ret.Add("success", true); ret.Add("http_status", status); switch (status) { // see https://en.wikipedia.org/wiki/List_of_HTTP_status_codes #region Official case 100: ret.Add("http_text", "Continue"); break; case 101: ret.Add("http_text", "Switching Protocols"); break; case 102: ret.Add("http_text", "Processing"); break; case 200: ret.Add("http_text", "OK"); break; case 201: ret.Add("http_text", "Created"); break; case 202: ret.Add("http_text", "Accepted"); break; case 203: ret.Add("http_text", "Non-Authoritative Information"); break; case 204: ret.Add("http_text", "No Content"); break; case 205: ret.Add("http_text", "Reset Content"); break; case 206: ret.Add("http_text", "Partial Content"); break; case 207: ret.Add("http_text", "Multi-Status"); break; case 208: ret.Add("http_text", "Already Reported"); break; case 226: ret.Add("http_text", "IM Used"); break; case 300: ret.Add("http_text", "Multiple Choices"); break; case 301: ret.Add("http_text", "Moved Permanently"); break; case 302: ret.Add("http_text", "Moved Temporarily"); break; case 303: ret.Add("http_text", "See Other"); break; case 304: ret.Add("http_text", "Not Modified"); break; case 305: ret.Add("http_text", "Use Proxy"); break; case 306: ret.Add("http_text", "Switch Proxy"); break; case 307: ret.Add("http_text", "Temporary Redirect"); break; case 308: ret.Add("http_text", "Permanent Redirect"); break; case 400: ret.Add("http_text", "Bad Request"); break; case 401: ret.Add("http_text", "Unauthorized"); break; case 402: ret.Add("http_text", "Payment Required"); break; case 403: ret.Add("http_text", "Forbidden"); break; case 404: ret.Add("http_text", "Not Found"); break; case 405: ret.Add("http_text", "Method Not Allowed"); break; case 406: ret.Add("http_text", "Not Acceptable"); break; case 407: ret.Add("http_text", "Proxy Authentication Required"); break; case 408: ret.Add("http_text", "Request Timeout"); break; case 409: ret.Add("http_text", "Conflict"); break; case 410: ret.Add("http_text", "Gone"); break; case 411: ret.Add("http_text", "Length Required"); break; case 412: ret.Add("http_text", "Precondition Failed"); break; case 413: ret.Add("http_text", "Payload Too Large"); break; case 414: ret.Add("http_text", "URI Too Long"); break; case 415: ret.Add("http_text", "Unsupported Media Type"); break; case 416: ret.Add("http_text", "Range Not Satisfiable"); break; case 417: ret.Add("http_text", "Expectation Failed"); break; case 418: ret.Add("http_text", "I'm a Teapot :)"); break; case 421: ret.Add("http_text", "Misdirected Request"); break; case 422: ret.Add("http_text", "Unprocessable Entity"); break; case 423: ret.Add("http_text", "Locked"); break; case 424: ret.Add("http_text", "Failed Dependency"); break; case 426: ret.Add("http_text", "Upgrade Required"); break; case 428: ret.Add("http_text", "Precondition Required"); break; case 429: ret.Add("http_text", "Too Many Requests"); break; case 431: ret.Add("http_text", "Request Header Fields Too Large"); break; case 451: ret.Add("http_text", "Unavailable for Legal Reasons"); break; case 500: ret.Add("http_text", "Internal Server Error"); break; case 501: ret.Add("http_text", "Not Implemented"); break; case 502: ret.Add("http_text", "Bad Gateway"); break; case 503: ret.Add("http_text", "Service Unavailable"); break; case 504: ret.Add("http_text", "Gateway Timeout"); break; case 505: ret.Add("http_text", "HTTP Version Not Supported"); break; case 506: ret.Add("http_text", "Variant Also Negotiates"); break; case 507: ret.Add("http_text", "Insufficient Storage"); break; case 508: ret.Add("http_text", "Loop Detected"); break; case 510: ret.Add("http_text", "Not Extended"); break; case 511: ret.Add("http_text", "Network Authentication Required"); break; #endregion #region Unofficial case 103: ret.Add("http_text", "Checkpoint or Early Hints"); break; case 420: ret.Add("http_text", "Method Failure or Enhance Your Calm"); break; case 450: ret.Add("http_text", "Blocked By Parental Controls"); break; case 498: ret.Add("http_text", "Invalid Token"); break; case 499: ret.Add("http_text", "Token Required or Client Closed Request"); break; case 509: ret.Add("http_text", "Bandwidth Limit Exceeded"); break; case 530: ret.Add("http_text", "Site Is Frozen"); break; case 598: ret.Add("http_text", "Network Read Timeout Error"); break; case 599: ret.Add("http_text", "Network Connect Timeout Error"); break; #endregion #region IIS case 440: ret.Add("http_text", "Login Timeout"); break; case 449: ret.Add("http_text", "Retry With"); break; #endregion #region nginx case 444: ret.Add("http_text", "No Response"); break; case 495: ret.Add("http_text", "SSL Certificate Error"); break; case 496: ret.Add("http_text", "SSL Certificate Required"); break; case 497: ret.Add("http_text", "HTTP Request Sent To HTTPS Port"); break; #endregion #region Cloudflare case 520: ret.Add("http_text", "Unknown Error"); break; case 521: ret.Add("http_text", "Web Server Is Down"); break; case 522: ret.Add("http_text", "Connection Timed Out"); break; case 523: ret.Add("http_text", "Origin Is Unreachable"); break; case 524: ret.Add("http_text", "A Timeout Occurred"); break; case 525: ret.Add("http_text", "SSL Handshake Failed"); break; case 526: ret.Add("http_text", "Invalid SSL Certificate"); break; case 527: ret.Add("http_text", "Railgun Error"); break; #endregion default: ret.Add("http_text", "Unknown Status"); break; } string json = WatsonCommon.SerializeJson(ret); return(Encoding.UTF8.GetBytes(json)); }
private byte[] BuildErrorResponse( int status, string text, byte[] data) { Dictionary <string, object> ret = new Dictionary <string, object>(); ret.Add("data", data); ret.Add("success", false); ret.Add("http_status", status); switch (status) { case 200: ret.Add("http_text", "OK"); break; case 201: ret.Add("http_text", "Created"); break; case 301: ret.Add("http_text", "Moved Permanently"); break; case 302: ret.Add("http_text", "Moved Temporarily"); break; case 304: ret.Add("http_text", "Not Modified"); break; case 400: ret.Add("http_text", "Bad Request"); break; case 401: ret.Add("http_text", "Unauthorized"); break; case 403: ret.Add("http_text", "Forbidden"); break; case 404: ret.Add("http_text", "Not Found"); break; case 405: ret.Add("http_text", "Method Not Allowed"); break; case 429: ret.Add("http_text", "Too Many Requests"); break; case 500: ret.Add("http_text", "Internal Server Error"); break; case 501: ret.Add("http_text", "Not Implemented"); break; case 503: ret.Add("http_text", "Service Unavailable"); break; default: ret.Add("http_text", "Unknown Status"); break; } ret.Add("text", text); string json = WatsonCommon.SerializeJson(ret); return(Encoding.UTF8.GetBytes(json)); }
private void AcceptConnections(CancellationToken token) { try { if (ListenerSsl) { ListenerPrefix = "https://" + ListenerIp + ":" + ListenerPort + "/"; } else { ListenerPrefix = "http://" + ListenerIp + ":" + ListenerPort + "/"; } Http.Prefixes.Add(ListenerPrefix); Http.Start(); while (Http.IsListening) { ThreadPool.QueueUserWorkItem((c) => { if (token.IsCancellationRequested) { throw new OperationCanceledException(); } var context = c as HttpListenerContext; try { #region Populate-Http-Request-Object HttpRequest currRequest = new HttpRequest(context); if (currRequest == null) { Logging.Log("Unable to populate HTTP request object on thread ID " + Thread.CurrentThread.ManagedThreadId + ", returning 400"); SendResponse( context, currRequest, BuildErrorResponse(500, "Unable to parse your request.", null), WatsonCommon.AddToDict("content-type", "application/json", null), 400); return; } Logging.Log("Thread " + currRequest.ThreadId + " " + currRequest.SourceIp + ":" + currRequest.SourcePort + " " + currRequest.Method + " " + currRequest.RawUrlWithoutQuery); #endregion #region Process-OPTIONS-Request if (currRequest.Method.ToLower().Trim().Contains("option") && OptionsRoute != null) { Logging.Log("Thread " + Thread.CurrentThread.ManagedThreadId + " OPTIONS request received"); OptionsProcessor(context, currRequest); return; } #endregion #region Send-to-Handler if (DebugRestRequests) { Logging.Log(currRequest.ToString()); } Task.Run(() => { HttpResponse currResponse = null; Func <HttpRequest, HttpResponse> handler = null; #region Find-Route if (currRequest.Method.ToLower().Equals("get") || currRequest.Method.ToLower().Equals("head")) { if (ContentRoutes.Exists(currRequest.RawUrlWithoutQuery)) { // content route found currResponse = ContentProcessor.Process(currRequest); } } if (currResponse == null) { handler = StaticRoutes.Match(currRequest.Method, currRequest.RawUrlWithoutQuery); if (handler != null) { // static route found currResponse = handler(currRequest); } else { // no static route, check for dynamic route handler = DynamicRoutes.Match(currRequest.Method, currRequest.RawUrlWithoutQuery); if (handler != null) { // dynamic route found currResponse = handler(currRequest); } else { // process using default route currResponse = DefaultRouteProcessor(context, currRequest); } } } #endregion #region Return if (currResponse == null) { Logging.Log("Null response from handler for " + currRequest.SourceIp + ":" + currRequest.SourcePort + " " + currRequest.Method + " " + currRequest.RawUrlWithoutQuery); SendResponse( context, currRequest, BuildErrorResponse(500, "Unable to generate response", null), WatsonCommon.AddToDict("content-type", "application/json", null), 500); return; } else { if (DebugRestResponses) { Logging.Log(currResponse.ToString()); } Dictionary <string, string> headers = new Dictionary <string, string>(); if (!String.IsNullOrEmpty(currResponse.ContentType)) { headers.Add("content-type", currResponse.ContentType); } if (currResponse.Headers != null && currResponse.Headers.Count > 0) { foreach (KeyValuePair <string, string> curr in currResponse.Headers) { headers = WatsonCommon.AddToDict(curr.Key, curr.Value, headers); } } if (currResponse.RawResponse) { SendResponse( context, currRequest, currResponse.Data, headers, currResponse.StatusCode); return; } else { SendResponse( context, currRequest, currResponse.ToJsonBytes(), headers, currResponse.StatusCode); return; } } #endregion }); #endregion } catch (Exception) { } finally { } }, Http.GetContext()); } } catch (Exception eOuter) { Logging.LogException("AcceptConnections", eOuter); throw; } finally { Logging.Log("Exiting"); } }
private void AcceptConnections(CancellationToken token) { try { foreach (string curr in _ListenerHostnames) { string prefix = null; if (_ListenerSsl) { prefix = "https://" + curr + ":" + _ListenerPort + "/"; } else { prefix = "http://" + curr + ":" + _ListenerPort + "/"; } _HttpListener.Prefixes.Add(prefix); } _HttpListener.Start(); while (_HttpListener.IsListening) { ThreadPool.QueueUserWorkItem((c) => { if (token.IsCancellationRequested) { throw new OperationCanceledException(); } var context = c as HttpListenerContext; try { #region Populate-Http-Request-Object HttpRequest req = new HttpRequest(context, ReadInputStream); if (req == null) { HttpResponse resp = new HttpResponse( req, 500, null, "text/plain", Encoding.UTF8.GetBytes("Unable to parse your HTTP request")); SendResponse( context, req, resp); return; } #endregion #region Access-Control if (!AccessControl.Permit(req.SourceIp)) { context.Response.Close(); return; } #endregion #region Process-OPTIONS-Request if (req.Method == HttpMethod.OPTIONS && OptionsRoute != null) { OptionsProcessor(context, req); return; } #endregion #region Send-to-Handler Task.Run(() => { HttpResponse resp = null; Func <HttpRequest, HttpResponse> handler = null; #region Find-Route if (req.Method == HttpMethod.GET || req.Method == HttpMethod.HEAD) { if (ContentRoutes.Exists(req.RawUrlWithoutQuery)) { // content route found resp = _ContentRouteProcessor.Process(req); } } if (resp == null) { handler = StaticRoutes.Match(req.Method, req.RawUrlWithoutQuery); if (handler != null) { // static route found resp = handler(req); } else { // no static route, check for dynamic route handler = DynamicRoutes.Match(req.Method, req.RawUrlWithoutQuery); if (handler != null) { // dynamic route found resp = handler(req); } else { // process using default route resp = DefaultRouteProcessor(context, req); } } } #endregion #region Return if (resp == null) { resp = new HttpResponse( req, 500, null, "text/plain", Encoding.UTF8.GetBytes("Unable to generate response")); SendResponse( context, req, resp); return; } else { Dictionary <string, string> headers = new Dictionary <string, string>(); if (!String.IsNullOrEmpty(resp.ContentType)) { headers.Add("content-type", resp.ContentType); } if (resp.Headers != null && resp.Headers.Count > 0) { foreach (KeyValuePair <string, string> curr in resp.Headers) { headers = WatsonCommon.AddToDict(curr.Key, curr.Value, headers); } } SendResponse( context, req, resp); return; } #endregion }); #endregion } catch (Exception) { } finally { } }, _HttpListener.GetContext()); } } catch (Exception) { throw; } finally { } }
/// <summary> /// Create a new HttpResponse object. /// </summary> /// <param name="req">The HttpRequest object for which this request is being created.</param> /// <param name="success">Indicates whether or not the request was successful.</param> /// <param name="status">The HTTP status code to return to the requestor (client).</param> /// <param name="headers">User-supplied headers to include in the response.</param> /// <param name="contentType">User-supplied content-type to include in the response.</param> /// <param name="data">The data to return to the requestor in the response body. This must be either a byte[] or string.</param> /// <param name="rawResponse">Indicates whether or not the response Data should be enapsulated in a JSON object containing standard fields including 'success'.</param> public HttpResponse(HttpRequest req, bool success, int status, Dictionary <string, string> headers, string contentType, object data, bool rawResponse) { if (req == null) { throw new ArgumentNullException(nameof(req)); } #region Set-Base-Variables TimestampUtc = req.TimestampUtc; SourceIp = req.SourceIp; SourcePort = req.SourcePort; DestIp = req.DestIp; DestPort = req.DestPort; Method = req.Method; RawUrlWithoutQuery = req.RawUrlWithoutQuery; Success = success; Headers = headers; ContentType = contentType; if (String.IsNullOrEmpty(ContentType)) { ContentType = "application/json"; } StatusCode = status; RawResponse = rawResponse; Data = data; #endregion #region Set-Status switch (status) { case 200: StatusDescription = "OK"; break; case 201: StatusDescription = "Created"; break; case 204: StatusDescription = "No Content"; break; case 301: StatusDescription = "Moved Permanently"; break; case 302: StatusDescription = "Moved Temporarily"; break; case 304: StatusDescription = "Not Modified"; break; case 400: StatusDescription = "Bad Request"; break; case 401: StatusDescription = "Unauthorized"; break; case 403: StatusDescription = "Forbidden"; break; case 404: StatusDescription = "Not Found"; break; case 405: StatusDescription = "Method Not Allowed"; break; case 429: StatusDescription = "Too Many Requests"; break; case 500: StatusDescription = "Internal Server Error"; break; case 501: StatusDescription = "Not Implemented"; break; case 503: StatusDescription = "Service Unavailable"; break; default: StatusDescription = "Unknown"; return; } #endregion #region Check-Data if (Data != null) { if (Data is byte[]) { ContentLength = ((byte[])Data).Length; } else if (Data is string) { ContentLength = ((string)Data).Length; } else { ContentLength = (WatsonCommon.SerializeJson(Data)).Length; Data = WatsonCommon.SerializeJson(Data); } } else { ContentLength = 0; } #endregion }