mirror of
https://github.com/denoland/deno.git
synced 2025-01-03 12:58:54 -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 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;
|
||||
}
|
||||
|
|
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 { 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;
|
||||
|
||||
respond(r: Response): Promise<void> {
|
||||
throw Error("not implemented");
|
||||
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");
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
|
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);
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue