mirror of
https://github.com/denoland/deno.git
synced 2025-01-05 13:59:01 -05:00
Basic http demo working.
This commit is contained in:
parent
9329cd76bd
commit
805efdb750
6 changed files with 216 additions and 54 deletions
24
bufio.ts
24
bufio.ts
|
@ -12,7 +12,13 @@ const MAX_CONSECUTIVE_EMPTY_READS = 100;
|
||||||
const CR = charCode("\r");
|
const CR = charCode("\r");
|
||||||
const LF = charCode("\n");
|
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. */
|
/** BufReader implements buffering for a Reader object. */
|
||||||
export class BufReader implements Reader {
|
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.
|
* At EOF, the count will be zero and err will be io.EOF.
|
||||||
* To read exactly len(p) bytes, use io.ReadFull(b, p).
|
* 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 };
|
let rr: ReadResult = { nread: p.byteLength, eof: false };
|
||||||
if (rr.nread === 0) {
|
if (rr.nread === 0) {
|
||||||
if (this.err) {
|
if (this.err) {
|
||||||
|
@ -334,7 +340,7 @@ export class BufReader implements Reader {
|
||||||
export class BufWriter implements Writer {
|
export class BufWriter implements Writer {
|
||||||
buf: Uint8Array;
|
buf: Uint8Array;
|
||||||
n: number = 0;
|
n: number = 0;
|
||||||
err: null | Error = null;
|
err: null | BufState = null;
|
||||||
|
|
||||||
constructor(private wr: Writer, size = DEFAULT_BUF_SIZE) {
|
constructor(private wr: Writer, size = DEFAULT_BUF_SIZE) {
|
||||||
if (size <= 0) {
|
if (size <= 0) {
|
||||||
|
@ -358,16 +364,16 @@ export class BufWriter implements Writer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Flush writes any buffered data to the underlying io.Writer. */
|
/** Flush writes any buffered data to the underlying io.Writer. */
|
||||||
async flush(): Promise<void> {
|
async flush(): Promise<BufState> {
|
||||||
if (this.err != null) {
|
if (this.err != null) {
|
||||||
throw this.err;
|
return this.err;
|
||||||
}
|
}
|
||||||
if (this.n == 0) {
|
if (this.n == 0) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let n: number;
|
let n: number;
|
||||||
let err: Error = null;
|
let err: BufState = null;
|
||||||
try {
|
try {
|
||||||
n = await this.wr.write(this.buf.subarray(0, this.n));
|
n = await this.wr.write(this.buf.subarray(0, this.n));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -375,7 +381,7 @@ export class BufWriter implements Writer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (n < this.n && err == null) {
|
if (n < this.n && err == null) {
|
||||||
err = new Error("ShortWrite");
|
err = "ShortWrite";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
|
@ -384,7 +390,7 @@ export class BufWriter implements Writer {
|
||||||
}
|
}
|
||||||
this.n -= n;
|
this.n -= n;
|
||||||
this.err = err;
|
this.err = err;
|
||||||
return;
|
return err;
|
||||||
}
|
}
|
||||||
this.n = 0;
|
this.n = 0;
|
||||||
}
|
}
|
||||||
|
|
34
headers.ts
34
headers.ts
|
@ -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]();
|
|
||||||
}
|
|
||||||
}
|
|
72
http.ts
72
http.ts
|
@ -1,7 +1,8 @@
|
||||||
import { listen, Conn } from "deno";
|
import { listen, Conn } from "deno";
|
||||||
import { BufReader, BufState } from "./bufio.ts";
|
import { BufReader, BufState, BufWriter } from "./bufio.ts";
|
||||||
import { TextProtoReader } from "./textproto.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) {
|
export async function* serve(addr: string) {
|
||||||
const listener = listen("tcp", addr);
|
const listener = listen("tcp", addr);
|
||||||
|
@ -14,9 +15,21 @@ export async function* serve(addr: string) {
|
||||||
|
|
||||||
export async function* serveConn(c: Conn) {
|
export async function* serveConn(c: Conn) {
|
||||||
let bufr = new BufReader(c);
|
let bufr = new BufReader(c);
|
||||||
|
let bufw = new BufWriter(c);
|
||||||
try {
|
try {
|
||||||
while (true) {
|
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;
|
yield req;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -26,7 +39,19 @@ export async function* serveConn(c: Conn) {
|
||||||
|
|
||||||
interface Response {
|
interface Response {
|
||||||
status?: number;
|
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 {
|
class ServerRequest {
|
||||||
|
@ -34,13 +59,41 @@ class ServerRequest {
|
||||||
method: string;
|
method: string;
|
||||||
proto: string;
|
proto: string;
|
||||||
headers: Headers;
|
headers: Headers;
|
||||||
|
w: BufWriter;
|
||||||
|
|
||||||
respond(r: Response): Promise<void> {
|
async respond(r: Response): Promise<void> {
|
||||||
throw Error("not implemented");
|
const protoMajor = 1;
|
||||||
|
const protoMinor = 1;
|
||||||
|
const statusCode = r.status || 200;
|
||||||
|
const statusText = STATUS_TEXT.get(statusCode);
|
||||||
|
if (!statusText) {
|
||||||
|
throw Error("bad status code");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 tp = new TextProtoReader(b);
|
||||||
const req = new ServerRequest();
|
const req = new ServerRequest();
|
||||||
|
|
||||||
|
@ -49,9 +102,12 @@ async function readRequest(b: BufReader): Promise<ServerRequest> {
|
||||||
|
|
||||||
// First line: GET /index.html HTTP/1.0
|
// First line: GET /index.html HTTP/1.0
|
||||||
[s, err] = await tp.readLine();
|
[s, err] = await tp.readLine();
|
||||||
|
if (err) {
|
||||||
|
return [null, err];
|
||||||
|
}
|
||||||
[req.method, req.url, req.proto] = s.split(" ", 3);
|
[req.method, req.url, req.proto] = s.split(" ", 3);
|
||||||
|
|
||||||
[req.headers, err] = await tp.readMIMEHeader();
|
[req.headers, err] = await tp.readMIMEHeader();
|
||||||
|
|
||||||
return req;
|
return [req, err];
|
||||||
}
|
}
|
||||||
|
|
134
http_status.ts
Normal file
134
http_status.ts
Normal file
|
@ -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"]
|
||||||
|
]);
|
|
@ -5,10 +5,11 @@ const addr = "0.0.0.0:8000";
|
||||||
const s = serve(addr);
|
const s = serve(addr);
|
||||||
console.log(`listening on http://${addr}/`);
|
console.log(`listening on http://${addr}/`);
|
||||||
|
|
||||||
|
const body = new TextEncoder().encode("Hello World\n");
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
for await (const req of s) {
|
for await (const req of s) {
|
||||||
console.log("Req", req);
|
await req.respond({ status: 200, body });
|
||||||
req.respond({ body: "Hello World\n" });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
import { BufReader, BufState } from "./bufio.ts";
|
import { BufReader, BufState } from "./bufio.ts";
|
||||||
import { charCode } from "./util.ts";
|
import { charCode } from "./util.ts";
|
||||||
import { Headers } from "./headers.ts";
|
|
||||||
|
|
||||||
const asciiDecoder = new TextDecoder("ascii");
|
const asciiDecoder = new TextDecoder("ascii");
|
||||||
function str(buf: Uint8Array): string {
|
function str(buf: Uint8Array): string {
|
||||||
|
|
Loading…
Reference in a new issue