summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bufio.ts24
-rw-r--r--headers.ts34
-rw-r--r--http.ts72
-rw-r--r--http_status.ts134
-rw-r--r--http_test.ts5
-rw-r--r--textproto.ts1
6 files changed, 216 insertions, 54 deletions
diff --git a/bufio.ts b/bufio.ts
index 2ad9bdd09..819c610f9 100644
--- a/bufio.ts
+++ b/bufio.ts
@@ -12,7 +12,13 @@ const MAX_CONSECUTIVE_EMPTY_READS = 100;
const CR = charCode("\r");
const LF = charCode("\n");
-export type BufState = null | "EOF" | "BufferFull" | "NoProgress" | Error;
+export type BufState =
+ | null
+ | "EOF"
+ | "BufferFull"
+ | "ShortWrite"
+ | "NoProgress"
+ | Error;
/** BufReader implements buffering for a Reader object. */
export class BufReader implements Reader {
@@ -102,7 +108,7 @@ export class BufReader implements Reader {
* At EOF, the count will be zero and err will be io.EOF.
* To read exactly len(p) bytes, use io.ReadFull(b, p).
*/
- async read(p: ArrayBufferView): Promise<ReadResult> {
+ async read(p: Uint8Array): Promise<ReadResult> {
let rr: ReadResult = { nread: p.byteLength, eof: false };
if (rr.nread === 0) {
if (this.err) {
@@ -334,7 +340,7 @@ export class BufReader implements Reader {
export class BufWriter implements Writer {
buf: Uint8Array;
n: number = 0;
- err: null | Error = null;
+ err: null | BufState = null;
constructor(private wr: Writer, size = DEFAULT_BUF_SIZE) {
if (size <= 0) {
@@ -358,16 +364,16 @@ export class BufWriter implements Writer {
}
/** Flush writes any buffered data to the underlying io.Writer. */
- async flush(): Promise<void> {
+ async flush(): Promise<BufState> {
if (this.err != null) {
- throw this.err;
+ return this.err;
}
if (this.n == 0) {
- return;
+ return null;
}
let n: number;
- let err: Error = null;
+ let err: BufState = null;
try {
n = await this.wr.write(this.buf.subarray(0, this.n));
} catch (e) {
@@ -375,7 +381,7 @@ export class BufWriter implements Writer {
}
if (n < this.n && err == null) {
- err = new Error("ShortWrite");
+ err = "ShortWrite";
}
if (err != null) {
@@ -384,7 +390,7 @@ export class BufWriter implements Writer {
}
this.n -= n;
this.err = err;
- return;
+ return err;
}
this.n = 0;
}
diff --git a/headers.ts b/headers.ts
deleted file mode 100644
index 9fe218195..000000000
--- a/headers.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-// Fake headers to work around
-// https://github.com/denoland/deno/issues/1173
-
-function normalize(name: string, value?: string): [string, string] {
- name = String(name).toLowerCase();
- value = String(value).trim();
- return [name, value];
-}
-
-export class Headers {
- private map = new Map<string, string>();
-
- get(name: string): string | null {
- let [name_] = normalize(name);
- return this.map.get(name_);
- }
-
- append(name: string, value: string): void {
- [name, value] = normalize(name, value);
- this.map.set(name, value);
- }
-
- toString(): string {
- let out = "";
- this.map.forEach((v, k) => {
- out += `${k}: ${v}\n`;
- });
- return out;
- }
-
- [Symbol.iterator](): IterableIterator<[string, string]> {
- return this.map[Symbol.iterator]();
- }
-}
diff --git a/http.ts b/http.ts
index 0266501ae..4a4f0ccd9 100644
--- a/http.ts
+++ b/http.ts
@@ -1,7 +1,8 @@
import { listen, Conn } from "deno";
-import { BufReader, BufState } from "./bufio.ts";
+import { BufReader, BufState, BufWriter } from "./bufio.ts";
import { TextProtoReader } from "./textproto.ts";
-import { Headers } from "./headers.ts";
+import { STATUS_TEXT } from "./http_status";
+import { assert } from "./util";
export async function* serve(addr: string) {
const listener = listen("tcp", addr);
@@ -14,9 +15,21 @@ export async function* serve(addr: string) {
export async function* serveConn(c: Conn) {
let bufr = new BufReader(c);
+ let bufw = new BufWriter(c);
try {
while (true) {
- const req = await readRequest(bufr);
+ const [req, err] = await readRequest(bufr);
+ if (err == "EOF") {
+ break;
+ }
+ if (err == "ShortWrite") {
+ console.log("ShortWrite error");
+ break;
+ }
+ if (err) {
+ throw err;
+ }
+ req.w = bufw;
yield req;
}
} finally {
@@ -26,7 +39,19 @@ export async function* serveConn(c: Conn) {
interface Response {
status?: number;
- body: string;
+ headers?: Headers;
+ body?: Uint8Array;
+}
+
+function setContentLength(r: Response): void {
+ if (r.body) {
+ if (!r.headers) {
+ r.headers = new Headers();
+ }
+ if (!r.headers.has("content-length")) {
+ r.headers.append("Content-Length", r.body.byteLength.toString());
+ }
+ }
}
class ServerRequest {
@@ -34,13 +59,41 @@ class ServerRequest {
method: string;
proto: string;
headers: Headers;
+ w: BufWriter;
+
+ async respond(r: Response): Promise<void> {
+ const protoMajor = 1;
+ const protoMinor = 1;
+ const statusCode = r.status || 200;
+ const statusText = STATUS_TEXT.get(statusCode);
+ if (!statusText) {
+ throw Error("bad status code");
+ }
- respond(r: Response): Promise<void> {
- throw Error("not implemented");
+ let out = `HTTP/${protoMajor}.${protoMinor} ${r.status} ${statusText}\r\n`;
+
+ setContentLength(r);
+
+ if (r.headers) {
+ for (let [key, value] of r.headers) {
+ out += `${key}: ${value}\r\n`;
+ }
+ }
+ out += "\r\n";
+
+ const header = new TextEncoder().encode(out);
+ let n = await this.w.write(header);
+ assert(header.byteLength == n);
+ if (r.body) {
+ n = await this.w.write(r.body);
+ assert(r.body.byteLength == n);
+ }
+
+ await this.w.flush();
}
}
-async function readRequest(b: BufReader): Promise<ServerRequest> {
+async function readRequest(b: BufReader): Promise<[ServerRequest, BufState]> {
const tp = new TextProtoReader(b);
const req = new ServerRequest();
@@ -49,9 +102,12 @@ async function readRequest(b: BufReader): Promise<ServerRequest> {
// First line: GET /index.html HTTP/1.0
[s, err] = await tp.readLine();
+ if (err) {
+ return [null, err];
+ }
[req.method, req.url, req.proto] = s.split(" ", 3);
[req.headers, err] = await tp.readMIMEHeader();
- return req;
+ return [req, err];
}
diff --git a/http_status.ts b/http_status.ts
new file mode 100644
index 000000000..a3006d319
--- /dev/null
+++ b/http_status.ts
@@ -0,0 +1,134 @@
+export enum Status {
+ Continue = 100, // RFC 7231, 6.2.1
+ SwitchingProtocols = 101, // RFC 7231, 6.2.2
+ Processing = 102, // RFC 2518, 10.1
+
+ OK = 200, // RFC 7231, 6.3.1
+ Created = 201, // RFC 7231, 6.3.2
+ Accepted = 202, // RFC 7231, 6.3.3
+ NonAuthoritativeInfo = 203, // RFC 7231, 6.3.4
+ NoContent = 204, // RFC 7231, 6.3.5
+ ResetContent = 205, // RFC 7231, 6.3.6
+ PartialContent = 206, // RFC 7233, 4.1
+ MultiStatus = 207, // RFC 4918, 11.1
+ AlreadyReported = 208, // RFC 5842, 7.1
+ IMUsed = 226, // RFC 3229, 10.4.1
+
+ MultipleChoices = 300, // RFC 7231, 6.4.1
+ MovedPermanently = 301, // RFC 7231, 6.4.2
+ Found = 302, // RFC 7231, 6.4.3
+ SeeOther = 303, // RFC 7231, 6.4.4
+ NotModified = 304, // RFC 7232, 4.1
+ UseProxy = 305, // RFC 7231, 6.4.5
+ // _ = 306, // RFC 7231, 6.4.6 (Unused)
+ TemporaryRedirect = 307, // RFC 7231, 6.4.7
+ PermanentRedirect = 308, // RFC 7538, 3
+
+ BadRequest = 400, // RFC 7231, 6.5.1
+ Unauthorized = 401, // RFC 7235, 3.1
+ PaymentRequired = 402, // RFC 7231, 6.5.2
+ Forbidden = 403, // RFC 7231, 6.5.3
+ NotFound = 404, // RFC 7231, 6.5.4
+ MethodNotAllowed = 405, // RFC 7231, 6.5.5
+ NotAcceptable = 406, // RFC 7231, 6.5.6
+ ProxyAuthRequired = 407, // RFC 7235, 3.2
+ RequestTimeout = 408, // RFC 7231, 6.5.7
+ Conflict = 409, // RFC 7231, 6.5.8
+ Gone = 410, // RFC 7231, 6.5.9
+ LengthRequired = 411, // RFC 7231, 6.5.10
+ PreconditionFailed = 412, // RFC 7232, 4.2
+ RequestEntityTooLarge = 413, // RFC 7231, 6.5.11
+ RequestURITooLong = 414, // RFC 7231, 6.5.12
+ UnsupportedMediaType = 415, // RFC 7231, 6.5.13
+ RequestedRangeNotSatisfiable = 416, // RFC 7233, 4.4
+ ExpectationFailed = 417, // RFC 7231, 6.5.14
+ Teapot = 418, // RFC 7168, 2.3.3
+ MisdirectedRequest = 421, // RFC 7540, 9.1.2
+ UnprocessableEntity = 422, // RFC 4918, 11.2
+ Locked = 423, // RFC 4918, 11.3
+ FailedDependency = 424, // RFC 4918, 11.4
+ UpgradeRequired = 426, // RFC 7231, 6.5.15
+ PreconditionRequired = 428, // RFC 6585, 3
+ TooManyRequests = 429, // RFC 6585, 4
+ RequestHeaderFieldsTooLarge = 431, // RFC 6585, 5
+ UnavailableForLegalReasons = 451, // RFC 7725, 3
+
+ InternalServerError = 500, // RFC 7231, 6.6.1
+ NotImplemented = 501, // RFC 7231, 6.6.2
+ BadGateway = 502, // RFC 7231, 6.6.3
+ ServiceUnavailable = 503, // RFC 7231, 6.6.4
+ GatewayTimeout = 504, // RFC 7231, 6.6.5
+ HTTPVersionNotSupported = 505, // RFC 7231, 6.6.6
+ VariantAlsoNegotiates = 506, // RFC 2295, 8.1
+ InsufficientStorage = 507, // RFC 4918, 11.5
+ LoopDetected = 508, // RFC 5842, 7.2
+ NotExtended = 510, // RFC 2774, 7
+ NetworkAuthenticationRequired = 511 // RFC 6585, 6
+}
+
+export const STATUS_TEXT = new Map<Status, string>([
+ [Status.Continue, "Continue"],
+ [Status.SwitchingProtocols, "Switching Protocols"],
+ [Status.Processing, "Processing"],
+
+ [Status.OK, "OK"],
+ [Status.Created, "Created"],
+ [Status.Accepted, "Accepted"],
+ [Status.NonAuthoritativeInfo, "Non-Authoritative Information"],
+ [Status.NoContent, "No Content"],
+ [Status.ResetContent, "Reset Content"],
+ [Status.PartialContent, "Partial Content"],
+ [Status.MultiStatus, "Multi-Status"],
+ [Status.AlreadyReported, "Already Reported"],
+ [Status.IMUsed, "IM Used"],
+
+ [Status.MultipleChoices, "Multiple Choices"],
+ [Status.MovedPermanently, "Moved Permanently"],
+ [Status.Found, "Found"],
+ [Status.SeeOther, "See Other"],
+ [Status.NotModified, "Not Modified"],
+ [Status.UseProxy, "Use Proxy"],
+ [Status.TemporaryRedirect, "Temporary Redirect"],
+ [Status.PermanentRedirect, "Permanent Redirect"],
+
+ [Status.BadRequest, "Bad Request"],
+ [Status.Unauthorized, "Unauthorized"],
+ [Status.PaymentRequired, "Payment Required"],
+ [Status.Forbidden, "Forbidden"],
+ [Status.NotFound, "Not Found"],
+ [Status.MethodNotAllowed, "Method Not Allowed"],
+ [Status.NotAcceptable, "Not Acceptable"],
+ [Status.ProxyAuthRequired, "Proxy Authentication Required"],
+ [Status.RequestTimeout, "Request Timeout"],
+ [Status.Conflict, "Conflict"],
+ [Status.Gone, "Gone"],
+ [Status.LengthRequired, "Length Required"],
+ [Status.PreconditionFailed, "Precondition Failed"],
+ [Status.RequestEntityTooLarge, "Request Entity Too Large"],
+ [Status.RequestURITooLong, "Request URI Too Long"],
+ [Status.UnsupportedMediaType, "Unsupported Media Type"],
+ [Status.RequestedRangeNotSatisfiable, "Requested Range Not Satisfiable"],
+ [Status.ExpectationFailed, "Expectation Failed"],
+ [Status.Teapot, "I'm a teapot"],
+ [Status.MisdirectedRequest, "Misdirected Request"],
+ [Status.UnprocessableEntity, "Unprocessable Entity"],
+ [Status.Locked, "Locked"],
+ [Status.FailedDependency, "Failed Dependency"],
+ [Status.UpgradeRequired, "Upgrade Required"],
+ [Status.PreconditionRequired, "Precondition Required"],
+ [Status.TooManyRequests, "Too Many Requests"],
+ [Status.RequestHeaderFieldsTooLarge, "Request Header Fields Too Large"],
+ [Status.UnavailableForLegalReasons, "Unavailable For Legal Reasons"],
+
+ [Status.InternalServerError, "Internal Server Error"],
+ [Status.NotImplemented, "Not Implemented"],
+ [Status.BadGateway, "Bad Gateway"],
+ [Status.ServiceUnavailable, "Service Unavailable"],
+ [Status.GatewayTimeout, "Gateway Timeout"],
+ [Status.HTTPVersionNotSupported, "HTTP Version Not Supported"],
+ [Status.VariantAlsoNegotiates, "Variant Also Negotiates"],
+ [Status.InsufficientStorage, "Insufficient Storage"],
+ [Status.LoopDetected, "Loop Detected"],
+ [Status.NotExtended, "Not Extended"],
+ [Status.NetworkAuthenticationRequired, "Network Authentication Required"]
+]);
diff --git a/http_test.ts b/http_test.ts
index 1b16b0f0a..b0007a892 100644
--- a/http_test.ts
+++ b/http_test.ts
@@ -5,10 +5,11 @@ const addr = "0.0.0.0:8000";
const s = serve(addr);
console.log(`listening on http://${addr}/`);
+const body = new TextEncoder().encode("Hello World\n");
+
async function main() {
for await (const req of s) {
- console.log("Req", req);
- req.respond({ body: "Hello World\n" });
+ await req.respond({ status: 200, body });
}
}
diff --git a/textproto.ts b/textproto.ts
index 61ca45a8a..50c0fea9c 100644
--- a/textproto.ts
+++ b/textproto.ts
@@ -5,7 +5,6 @@
import { BufReader, BufState } from "./bufio.ts";
import { charCode } from "./util.ts";
-import { Headers } from "./headers.ts";
const asciiDecoder = new TextDecoder("ascii");
function str(buf: Uint8Array): string {