using System.Text; using Uwaa.HTTP.Websockets; namespace Uwaa.HTTP; public class SwitchingProtocols : HttpResponse { /// /// Called once a HTTP request is upgrade to a websocket. /// public readonly WebsocketHandler Callback; internal SwitchingProtocols(string acceptKey, string? chosenProtocol, WebsocketHandler callback) : base(101, "Switching Protocols", new HttpFields()) { Fields.WebSocketAccept = acceptKey; Fields.WebSocketProtocol = chosenProtocol; Fields.Upgrade = "websocket"; Fields.Connection = ConnectionType.Upgrade; Callback = callback; } } /// /// Contains the status code, status message, fields, and content of a HTTP response. /// public class HttpResponse { public static HttpResponse OK(HttpContent? content = null) => new HttpResponse(200, "OK", content); public static HttpResponse Redirect(string location) => new HttpResponse(301, "Redirect", new HttpFields() { Location = location }); public static HttpResponse BadRequest(HttpContent? content = null) => new HttpResponse(400, "Bad request", content); public static HttpResponse Forbidden(HttpContent? content = null) => new HttpResponse(403, "Forbidden", content); public static HttpResponse NotFound(HttpContent? content = null) => new HttpResponse(404, "Not found", content); public static HttpResponse MethodNotAllowed(HttpContent? content = null) => new HttpResponse(405, "Method not allowed", content); public static HttpResponse NotAcceptable(HttpContent? content = null) => new HttpResponse(406, "Not acceptable", content); public static HttpResponse InternalServerError(HttpContent? content = null) => new HttpResponse(500, "Internal server error", content); public static implicit operator HttpResponse(HttpContent value) => OK(value); public static implicit operator HttpResponse(HttpContent? value) => value == null ? NotFound(value) : OK(value); public readonly int StatusCode; public readonly string StatusMessage; /// /// HTTP header fields included in the response. /// public readonly HttpFields Fields; /// /// The body of the HTTP body, if any. /// public readonly HttpContent? Content; public HttpResponse(int statusCode, string statusMessage, HttpContent? body = null) : this(statusCode, statusMessage, new HttpFields(), body) { } public HttpResponse(int statusCode, string statusMessage, HttpFields fields, HttpContent? body = null) { StatusCode = statusCode; StatusMessage = statusMessage; Fields = fields; Content = body; } internal async Task WriteTo(HttpStream stream) { if (StatusCode == 0) return; StringBuilder sb = new StringBuilder(); sb.Append("HTTP/1.1 "); sb.Append(StatusCode); sb.Append(' '); sb.Append(StatusMessage); sb.Append("\r\n"); if (Content.HasValue) { Fields.ContentLength = Content.Value.Content.Length; Fields.ContentType = Content.Value.Type.ToString(); } else { Fields.ContentLength = 0; Fields.ContentType = null; } Fields.EmitAll((string name, string value) => { sb.Append(name); sb.Append(": "); sb.Append(value); sb.Append("\r\n"); }); sb.Append("\r\n"); await stream.Write(Encoding.ASCII.GetBytes(sb.ToString())); if (Content.HasValue) await stream.Write(Content.Value.Content); await stream.Flush(); } }