diff options
Diffstat (limited to 'std/http/server.ts')
-rw-r--r-- | std/http/server.ts | 194 |
1 files changed, 10 insertions, 184 deletions
diff --git a/std/http/server.ts b/std/http/server.ts index e7d6bd598..6e26e8456 100644 --- a/std/http/server.ts +++ b/std/http/server.ts @@ -1,91 +1,19 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -const { listen, listenTLS, copy } = Deno; -type Listener = Deno.Listener; -type Conn = Deno.Conn; -type Reader = Deno.Reader; -type Writer = Deno.Writer; -import { BufReader, BufWriter, UnexpectedEOFError } from "../io/bufio.ts"; -import { TextProtoReader } from "../textproto/mod.ts"; -import { STATUS_TEXT } from "./http_status.ts"; +import { BufReader, BufWriter } from "../io/bufio.ts"; import { assert } from "../testing/asserts.ts"; import { deferred, Deferred, MuxAsyncIterator } from "../util/async.ts"; import { bodyReader, chunkedBodyReader, - writeChunkedBody, - writeTrailers, - emptyReader + emptyReader, + writeResponse, + readRequest } from "./io.ts"; - -const encoder = new TextEncoder(); - -export function setContentLength(r: Response): void { - if (!r.headers) { - r.headers = new Headers(); - } - - if (r.body) { - if (!r.headers.has("content-length")) { - // typeof r.body === "string" handled in writeResponse. - if (r.body instanceof Uint8Array) { - const bodyLength = r.body.byteLength; - r.headers.set("content-length", bodyLength.toString()); - } else { - r.headers.set("transfer-encoding", "chunked"); - } - } - } -} - -export async function writeResponse(w: Writer, r: Response): Promise<void> { - const protoMajor = 1; - const protoMinor = 1; - const statusCode = r.status || 200; - const statusText = STATUS_TEXT.get(statusCode); - const writer = BufWriter.create(w); - if (!statusText) { - throw Error("bad status code"); - } - if (!r.body) { - r.body = new Uint8Array(); - } - if (typeof r.body === "string") { - r.body = encoder.encode(r.body); - } - - let out = `HTTP/${protoMajor}.${protoMinor} ${statusCode} ${statusText}\r\n`; - - setContentLength(r); - assert(r.headers != null); - const headers = r.headers; - - for (const [key, value] of headers) { - out += `${key}: ${value}\r\n`; - } - out += "\r\n"; - - const header = encoder.encode(out); - const n = await writer.write(header); - assert(n === header.byteLength); - - if (r.body instanceof Uint8Array) { - const n = await writer.write(r.body); - assert(n === r.body.byteLength); - } else if (headers.has("content-length")) { - const contentLength = headers.get("content-length"); - assert(contentLength != null); - const bodyLength = parseInt(contentLength); - const n = await copy(writer, r.body); - assert(n === bodyLength); - } else { - await writeChunkedBody(writer, r.body); - } - if (r.trailers) { - const t = await r.trailers(); - await writeTrailers(writer, headers, t); - } - await writer.flush(); -} +import { encode } from "../strings/mod.ts"; +import Listener = Deno.Listener; +import Conn = Deno.Conn; +import Reader = Deno.Reader; +const { listen, listenTLS } = Deno; export class ServerRequest { url!: string; @@ -194,108 +122,6 @@ export class ServerRequest { } } -function fixLength(req: ServerRequest): void { - const contentLength = req.headers.get("Content-Length"); - if (contentLength) { - const arrClen = contentLength.split(","); - if (arrClen.length > 1) { - const distinct = [...new Set(arrClen.map((e): string => e.trim()))]; - if (distinct.length > 1) { - throw Error("cannot contain multiple Content-Length headers"); - } else { - req.headers.set("Content-Length", distinct[0]); - } - } - const c = req.headers.get("Content-Length"); - if (req.method === "HEAD" && c && c !== "0") { - throw Error("http: method cannot contain a Content-Length"); - } - if (c && req.headers.has("transfer-encoding")) { - // A sender MUST NOT send a Content-Length header field in any message - // that contains a Transfer-Encoding header field. - // rfc: https://tools.ietf.org/html/rfc7230#section-3.3.2 - throw new Error( - "http: Transfer-Encoding and Content-Length cannot be send together" - ); - } - } -} - -/** - * ParseHTTPVersion parses a HTTP version string. - * "HTTP/1.0" returns (1, 0, true). - * Ported from https://github.com/golang/go/blob/f5c43b9/src/net/http/request.go#L766-L792 - */ -export function parseHTTPVersion(vers: string): [number, number] { - switch (vers) { - case "HTTP/1.1": - return [1, 1]; - - case "HTTP/1.0": - return [1, 0]; - - default: { - const Big = 1000000; // arbitrary upper bound - const digitReg = /^\d+$/; // test if string is only digit - - if (!vers.startsWith("HTTP/")) { - break; - } - - const dot = vers.indexOf("."); - if (dot < 0) { - break; - } - - const majorStr = vers.substring(vers.indexOf("/") + 1, dot); - const major = parseInt(majorStr); - if ( - !digitReg.test(majorStr) || - isNaN(major) || - major < 0 || - major > Big - ) { - break; - } - - const minorStr = vers.substring(dot + 1); - const minor = parseInt(minorStr); - if ( - !digitReg.test(minorStr) || - isNaN(minor) || - minor < 0 || - minor > Big - ) { - break; - } - - return [major, minor]; - } - } - - throw new Error(`malformed HTTP version ${vers}`); -} - -export async function readRequest( - conn: Conn, - bufr: BufReader -): Promise<ServerRequest | Deno.EOF> { - const tp = new TextProtoReader(bufr); - const firstLine = await tp.readLine(); // e.g. GET /index.html HTTP/1.0 - if (firstLine === Deno.EOF) return Deno.EOF; - const headers = await tp.readMIMEHeader(); - if (headers === Deno.EOF) throw new UnexpectedEOFError(); - - const req = new ServerRequest(); - req.conn = conn; - req.r = bufr; - [req.method, req.url, req.proto] = firstLine.split(" ", 3); - [req.protoMinor, req.protoMajor] = parseHTTPVersion(req.proto); - req.headers = headers; - fixLength(req); - return req; -} - export class Server implements AsyncIterable<ServerRequest> { private closing = false; @@ -349,7 +175,7 @@ export class Server implements AsyncIterable<ServerRequest> { try { await writeResponse(req.w, { status: 400, - body: encoder.encode(`${err.message}\r\n\r\n`) + body: encode(`${err.message}\r\n\r\n`) }); } catch (_) { // The connection is destroyed. |