Skip to content

Commit 7edc422

Browse files
committed
Adds HTTP server
Does not yet support streaming bodies.
1 parent 26468b4 commit 7edc422

16 files changed

+671
-39
lines changed

BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ main_extern = [
5555
"$rust_build:dirs",
5656
"$rust_build:futures",
5757
"$rust_build:getopts",
58+
"$rust_build:http",
5859
"$rust_build:hyper",
5960
"$rust_build:hyper_rustls",
6061
"$rust_build:lazy_static",
@@ -93,6 +94,7 @@ ts_sources = [
9394
"js/flatbuffers.ts",
9495
"js/global_eval.ts",
9596
"js/globals.ts",
97+
"js/http.ts",
9698
"js/io.ts",
9799
"js/libdeno.ts",
98100
"js/main.ts",

js/deno.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export { readDirSync, readDir } from "./read_dir";
3030
export { copyFileSync, copyFile } from "./copy_file";
3131
export { readlinkSync, readlink } from "./read_link";
3232
export { statSync, lstatSync, stat, lstat } from "./stat";
33+
export { httpListen, ServerRequest, ServerResponse } from "./http";
3334
export { symlinkSync, symlink } from "./symlink";
3435
export { writeFileSync, writeFile } from "./write_file";
3536
export { ErrorKind, DenoError } from "./errors";

js/dom_types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ export interface Blob {
274274
slice(start?: number, end?: number, contentType?: string): Blob;
275275
}
276276

277-
interface Body {
277+
export interface Body {
278278
/** A simple getter used to expose a `ReadableStream` of the body contents. */
279279
readonly body: ReadableStream | null;
280280
/** Stores a `Boolean` that declares whether the body has been used in a
@@ -352,7 +352,7 @@ export interface HeadersConstructor {
352352
prototype: Headers;
353353
}
354354

355-
type RequestCache =
355+
export type RequestCache =
356356
| "default"
357357
| "no-store"
358358
| "reload"
@@ -381,7 +381,7 @@ type RequestDestination =
381381
| "xslt";
382382
type RequestMode = "navigate" | "same-origin" | "no-cors" | "cors";
383383
type RequestRedirect = "follow" | "error" | "manual";
384-
type ResponseType =
384+
export type ResponseType =
385385
| "basic"
386386
| "cors"
387387
| "default"

js/headers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ function isHeaders(value: any): value is domTypes.Headers {
1010
const headerMap = Symbol("header map");
1111

1212
// ref: https://fetch.spec.whatwg.org/#dom-headers
13-
class HeadersBase {
13+
export class HeadersBase {
1414
private [headerMap]: Map<string, string>;
1515

1616
private _normalizeParams(name: string, value?: string): string[] {

js/http.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
2+
3+
import { Closer } from "./io";
4+
import * as msg from "gen/msg_generated";
5+
import { assert, log } from "./util";
6+
import * as dispatch from "./dispatch";
7+
import * as flatbuffers from "./flatbuffers";
8+
import { close } from "./files";
9+
10+
// TODO Cannot use Headers due to bug in ts_declaration_builder.
11+
// import { Headers } from "./headers";
12+
// import * as headers from "./headers";
13+
// import * as domTypes from "./dom_types";
14+
15+
type HttpHandler = (req: ServerRequest, res: ServerResponse) => void;
16+
17+
export class HttpServer implements Closer {
18+
private closing = false;
19+
20+
constructor(readonly rid: number) {
21+
assert(rid >= 2);
22+
}
23+
24+
async serve(handler: HttpHandler): Promise<void> {
25+
while (this.closing === false) {
26+
const [req, res] = await httpAccept(this.rid);
27+
handler(req, res);
28+
}
29+
}
30+
31+
close(): void {
32+
this.closing = true;
33+
close(this.rid);
34+
}
35+
}
36+
37+
function deserializeHeaderFields(m: msg.HttpHeader): Array<[string, string]> {
38+
const out: Array<[string, string]> = [];
39+
for (let i = 0; i < m.fieldsLength(); i++) {
40+
const item = m.fields(i)!;
41+
out.push([item.key()!, item.value()!]);
42+
}
43+
return out;
44+
}
45+
46+
export class ServerRequest {
47+
// TODO Cannot do this due to ts_declaration_builder bug.
48+
// headers: domTypes.Headers;
49+
readonly headers: Array<[string, string]>;
50+
51+
constructor(
52+
readonly rid: number,
53+
readonly method: string,
54+
readonly url: string,
55+
headersInit: Array<[string, string]>
56+
) {
57+
// TODO cannot use Headers due to ts_declaration_builder bug.
58+
// this.headers = new Headers(headersInit);
59+
this.headers = headersInit;
60+
}
61+
}
62+
63+
export class ServerResponse {
64+
headers = new Array<[string, string]>(); // TODO Use Headers
65+
status = 200;
66+
67+
constructor(readonly rid: number, readonly url: string) {}
68+
69+
writeResponse(body?: ArrayBufferView): void {
70+
httpWriteResponse(this, body);
71+
close(this.rid); // TODO Streaming response body.
72+
}
73+
}
74+
75+
async function httpAccept(
76+
rid: number
77+
): Promise<[ServerRequest, ServerResponse]> {
78+
const builder = flatbuffers.createBuilder();
79+
msg.HttpAccept.startHttpAccept(builder);
80+
msg.HttpAccept.addListenerRid(builder, rid);
81+
const inner = msg.HttpAccept.endHttpAccept(builder);
82+
const baseRes = await dispatch.sendAsync(builder, msg.Any.HttpAccept, inner);
83+
assert(baseRes != null);
84+
assert(msg.Any.HttpAcceptRes === baseRes!.innerType());
85+
const acceptResMsg = new msg.HttpAcceptRes();
86+
assert(baseRes!.inner(acceptResMsg) != null);
87+
88+
const transactionRid = acceptResMsg.transactionRid();
89+
const header = acceptResMsg.header()!;
90+
const fields = deserializeHeaderFields(header);
91+
const url = header.url()!;
92+
const method = header.method()!;
93+
94+
log("http accept:", method, url, fields);
95+
96+
const req = new ServerRequest(transactionRid, method, url, fields);
97+
const res = new ServerResponse(transactionRid, url);
98+
return [req, res];
99+
}
100+
101+
export function httpListen(address: string): HttpServer {
102+
const builder = flatbuffers.createBuilder();
103+
const address_ = builder.createString(address);
104+
msg.HttpListen.startHttpListen(builder);
105+
msg.HttpListen.addAddress(builder, address_);
106+
const inner = msg.HttpListen.endHttpListen(builder);
107+
const baseRes = dispatch.sendSync(builder, msg.Any.HttpListen, inner);
108+
assert(baseRes != null);
109+
assert(msg.Any.HttpListenRes === baseRes!.innerType());
110+
const res = new msg.HttpListenRes();
111+
assert(baseRes!.inner(res) != null);
112+
return new HttpServer(res.rid());
113+
}
114+
115+
export function httpWriteResponse(
116+
res: ServerResponse,
117+
body?: ArrayBufferView
118+
): void {
119+
const builder = flatbuffers.createBuilder();
120+
const fields = msg.HttpHeader.createFieldsVector(
121+
builder,
122+
res.headers.map(([key, val]) => {
123+
const key_ = builder.createString(key);
124+
const val_ = builder.createString(val);
125+
msg.KeyValue.startKeyValue(builder);
126+
msg.KeyValue.addKey(builder, key_);
127+
msg.KeyValue.addValue(builder, val_);
128+
return msg.KeyValue.endKeyValue(builder);
129+
})
130+
);
131+
msg.HttpHeader.startHttpHeader(builder);
132+
msg.HttpHeader.addFields(builder, fields);
133+
msg.HttpHeader.addStatus(builder, res.status);
134+
msg.HttpHeader.addIsRequest(builder, false);
135+
136+
const header = msg.HttpHeader.endHttpHeader(builder);
137+
msg.HttpWriteResponse.startHttpWriteResponse(builder);
138+
msg.HttpWriteResponse.addTransactionRid(builder, res.rid);
139+
msg.HttpWriteResponse.addHeader(builder, header);
140+
const inner = msg.HttpWriteResponse.endHttpWriteResponse(builder);
141+
const r = dispatch.sendSync(builder, msg.Any.HttpWriteResponse, inner, body);
142+
assert(r == null);
143+
}

js/http_test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
2+
import { test, testPerm, assert, assertEqual } from "./test_util.ts";
3+
import * as deno from "deno";
4+
5+
testPerm({ net: true }, async function httpServerBasic() {
6+
const addr = "127.0.0.1:4501";
7+
let counter = 0;
8+
const server = deno.httpListen(addr);
9+
const serverComplete = server.serve(
10+
(req: deno.ServerRequest, res: deno.ServerResponse) => {
11+
assertEqual(req.url, "/foo");
12+
assertEqual(req.method, "GET");
13+
assertEqual(req.headers, [["host", "127.0.0.1:4501"]]);
14+
15+
res.status = 404;
16+
res.headers = [["content-type", "text/plain"], ["hello", "world"]];
17+
const resBody = new TextEncoder().encode("404 Not Found\n");
18+
res.writeResponse(resBody);
19+
counter++;
20+
server.close();
21+
}
22+
);
23+
24+
const fetchRes = await fetch("http://" + addr + "/foo");
25+
// TODO
26+
// assertEqual(fetchRes.headers, [
27+
// [ "content-type", "text/plain" ],
28+
// [ "hello", "world" ],
29+
// ]);
30+
// assertEqual(fetchRes.statusText, "Not Found");
31+
assertEqual(fetchRes.status, 404);
32+
const body = await fetchRes.text();
33+
assertEqual(body, "404 Not Found\n");
34+
35+
await serverComplete;
36+
assertEqual(counter, 1);
37+
});

js/net.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as dispatch from "./dispatch";
66
import * as flatbuffers from "./flatbuffers";
77
import { read, write, close } from "./files";
88

9-
export type Network = "tcp";
9+
export type Network = "tcp" | "http";
1010
// TODO support other types:
1111
// export type Network = "tcp" | "tcp4" | "tcp6" | "unix" | "unixpacket";
1212

@@ -15,6 +15,9 @@ export type Addr = string;
1515

1616
/** A Listener is a generic network listener for stream-oriented protocols. */
1717
export interface Listener {
18+
/** Resource id for this listener. AKA file descriptor. */
19+
readonly rid: number;
20+
1821
/** Waits for and resolves to the next connection to the `Listener`. */
1922
accept(): Promise<Conn>;
2023

js/os.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ function createEnv(inner: msg.EnvironRes): { [index: string]: string } {
8686

8787
for (let i = 0; i < inner.mapLength(); i++) {
8888
const item = inner.map(i)!;
89+
8990
env[item.key()!] = item.value()!;
9091
}
9192

js/unit_tests.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import "./dir_test";
1212
import "./fetch_test.ts";
1313
import "./files_test.ts";
1414
import "./headers_test.ts";
15+
import "./http_test.ts";
1516
import "./make_temp_dir_test.ts";
1617
import "./metrics_test.ts";
1718
import "./mixins/dom_iterable_test.ts";

src/errors.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,47 @@ impl From<hyper::Error> for DenoError {
149149
}
150150
}
151151

152+
use futures::sync::mpsc::SendError;
153+
impl<T> From<SendError<T>> for DenoError {
154+
#[inline]
155+
fn from(_err: SendError<T>) -> DenoError {
156+
DenoError {
157+
repr: Repr::Simple(ErrorKind::Other, String::from("Send Error")),
158+
}
159+
}
160+
}
161+
162+
use futures::Canceled;
163+
impl From<Canceled> for DenoError {
164+
#[inline]
165+
fn from(_err: Canceled) -> DenoError {
166+
DenoError {
167+
repr: Repr::Simple(ErrorKind::Other, String::from("Future Canceled")),
168+
}
169+
}
170+
}
171+
172+
use http::status::InvalidStatusCode;
173+
use std::error::Error;
174+
impl From<InvalidStatusCode> for DenoError {
175+
#[inline]
176+
fn from(err: InvalidStatusCode) -> DenoError {
177+
DenoError {
178+
repr: Repr::Simple(ErrorKind::HttpOther, String::from(err.description())),
179+
}
180+
}
181+
}
182+
183+
use http::header::InvalidHeaderValue;
184+
impl From<InvalidHeaderValue> for DenoError {
185+
#[inline]
186+
fn from(err: InvalidHeaderValue) -> DenoError {
187+
DenoError {
188+
repr: Repr::Simple(ErrorKind::HttpOther, String::from(err.description())),
189+
}
190+
}
191+
}
192+
152193
pub fn bad_resource() -> DenoError {
153194
new(ErrorKind::BadResource, String::from("bad resource id"))
154195
}

0 commit comments

Comments
 (0)