Skip to content

Commit c466b5c

Browse files
committed
Adds HTTP server
Does not yet support streaming bodies.
1 parent 801107b commit c466b5c

13 files changed

+686
-34
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",
@@ -97,6 +98,7 @@ ts_sources = [
9798
"js/form_data.ts",
9899
"js/global_eval.ts",
99100
"js/globals.ts",
101+
"js/http.ts",
100102
"js/io.ts",
101103
"js/libdeno.ts",
102104
"js/main.ts",

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ flatbuffers = { path = "third_party/flatbuffers/rust/flatbuffers/" }
1515
futures = "0.1.25"
1616
getopts = "0.2.18"
1717
hyper = "0.12.13"
18+
http = "0.1.13"
1819
# The current version of hyper-rustls, 0.14.0, depends on tokio-core, which is
1920
# deprecated.
2021
hyper-rustls = { git = "https://github.com/ctz/hyper-rustls.git" }

js/deno.ts

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

js/http.ts

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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); // rid should be after stdout/stderr
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+
80+
msg.HttpAccept.startHttpAccept(builder);
81+
msg.HttpAccept.addListenerRid(builder, rid);
82+
const inner = msg.HttpAccept.endHttpAccept(builder);
83+
84+
const baseRes = await dispatch.sendAsync(builder, msg.Any.HttpAccept, inner);
85+
assert(baseRes != null);
86+
assert(msg.Any.HttpAcceptRes === baseRes!.innerType());
87+
const acceptResMsg = new msg.HttpAcceptRes();
88+
assert(baseRes!.inner(acceptResMsg) != null);
89+
90+
const transactionRid = acceptResMsg.transactionRid();
91+
const header = acceptResMsg.header()!;
92+
const fields = deserializeHeaderFields(header);
93+
const url = header.url()!;
94+
const method = header.method()!;
95+
log("http accept:", method, url, fields);
96+
97+
const req = new ServerRequest(transactionRid, method, url, fields);
98+
const res = new ServerResponse(transactionRid, url);
99+
return [req, res];
100+
}
101+
102+
export function httpListen(address: string): HttpServer {
103+
const builder = flatbuffers.createBuilder();
104+
const address_ = builder.createString(address);
105+
106+
msg.HttpListen.startHttpListen(builder);
107+
msg.HttpListen.addAddress(builder, address_);
108+
const inner = msg.HttpListen.endHttpListen(builder);
109+
110+
const baseRes = dispatch.sendSync(builder, msg.Any.HttpListen, inner);
111+
assert(baseRes != null);
112+
assert(msg.Any.HttpListenRes === baseRes!.innerType());
113+
const res = new msg.HttpListenRes();
114+
assert(baseRes!.inner(res) != null);
115+
return new HttpServer(res.rid());
116+
}
117+
118+
export function httpWriteResponse(
119+
res: ServerResponse,
120+
body?: ArrayBufferView
121+
): void {
122+
const builder = flatbuffers.createBuilder();
123+
const fields = msg.HttpHeader.createFieldsVector(
124+
builder,
125+
res.headers.map(([key, val]) => {
126+
const key_ = builder.createString(key);
127+
const val_ = builder.createString(val);
128+
msg.KeyValue.startKeyValue(builder);
129+
msg.KeyValue.addKey(builder, key_);
130+
msg.KeyValue.addValue(builder, val_);
131+
return msg.KeyValue.endKeyValue(builder);
132+
})
133+
);
134+
msg.HttpHeader.startHttpHeader(builder);
135+
msg.HttpHeader.addFields(builder, fields);
136+
msg.HttpHeader.addStatus(builder, res.status);
137+
msg.HttpHeader.addIsRequest(builder, false);
138+
139+
const header = msg.HttpHeader.endHttpHeader(builder);
140+
msg.HttpWriteResponse.startHttpWriteResponse(builder);
141+
msg.HttpWriteResponse.addTransactionRid(builder, res.rid);
142+
msg.HttpWriteResponse.addHeader(builder, header);
143+
const inner = msg.HttpWriteResponse.endHttpWriteResponse(builder);
144+
const r = dispatch.sendSync(builder, msg.Any.HttpWriteResponse, inner, body);
145+
assert(r == null);
146+
}

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/unit_tests.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import "./file_test.ts";
1515
import "./files_test.ts";
1616
import "./form_data_test.ts";
1717
import "./headers_test.ts";
18+
import "./http_test.ts";
1819
import "./make_temp_dir_test.ts";
1920
import "./metrics_test.ts";
2021
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)