summaryrefslogtreecommitdiff
path: root/std/http/server.ts
diff options
context:
space:
mode:
Diffstat (limited to 'std/http/server.ts')
-rw-r--r--std/http/server.ts194
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.