-
Notifications
You must be signed in to change notification settings - Fork 230
/
http_server_native.ts
162 lines (138 loc) · 4.61 KB
/
http_server_native.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Copyright 2018-2023 the oak authors. All rights reserved. MIT license.
import type { Application, State } from "./application.ts";
import { NativeRequest } from "./http_server_native_request.ts";
import type { HttpConn, Listener, Server } from "./types.d.ts";
import { assert, isListenTlsOptions } from "./util.ts";
// this is included so when down-emitting to npm/Node.js, ReadableStream has
// async iterators
declare global {
// deno-lint-ignore no-explicit-any
interface ReadableStream<R = any> {
[Symbol.asyncIterator](options?: {
preventCancel?: boolean;
}): AsyncIterableIterator<R>;
}
}
export type Respond = (r: Response | Promise<Response>) => void;
// This type is part of Deno, but not part of lib.dom.d.ts, therefore add it here
// so that type checking can occur properly under `lib.dom.d.ts`.
interface ReadableStreamDefaultControllerCallback<R> {
(controller: ReadableStreamDefaultController<R>): void | PromiseLike<void>;
}
const serveHttp: (conn: Deno.Conn) => HttpConn = "serveHttp" in Deno
// deno-lint-ignore no-explicit-any
? (Deno as any).serveHttp.bind(Deno)
: undefined;
/** The oak abstraction of the Deno native HTTP server which is used internally
* for handling native HTTP requests. Generally users of oak do not need to
* worry about this class. */
// deno-lint-ignore no-explicit-any
export class HttpServer<AS extends State = Record<string, any>>
implements Server<NativeRequest> {
#app: Application<AS>;
#closed = false;
#listener?: Deno.Listener;
#httpConnections: Set<HttpConn> = new Set();
#options: Deno.ListenOptions | Deno.ListenTlsOptions;
constructor(
app: Application<AS>,
options: Deno.ListenOptions | Deno.ListenTlsOptions,
) {
if (!("serveHttp" in Deno)) {
throw new Error(
"The native bindings for serving HTTP are not available.",
);
}
this.#app = app;
this.#options = options;
}
get app(): Application<AS> {
return this.#app;
}
get closed(): boolean {
return this.#closed;
}
close(): void {
this.#closed = true;
if (this.#listener) {
this.#listener.close();
this.#listener = undefined;
}
for (const httpConn of this.#httpConnections) {
try {
httpConn.close();
} catch (error) {
if (!(error instanceof Deno.errors.BadResource)) {
throw error;
}
}
}
this.#httpConnections.clear();
}
listen(): Listener {
return (this.#listener = isListenTlsOptions(this.#options)
? Deno.listenTls(this.#options)
: Deno.listen(this.#options)) as Listener;
}
#trackHttpConnection(httpConn: HttpConn): void {
this.#httpConnections.add(httpConn);
}
#untrackHttpConnection(httpConn: HttpConn): void {
this.#httpConnections.delete(httpConn);
}
[Symbol.asyncIterator](): AsyncIterableIterator<NativeRequest> {
const start: ReadableStreamDefaultControllerCallback<NativeRequest> = (
controller,
) => {
// deno-lint-ignore no-this-alias
const server = this;
async function serve(conn: Deno.Conn) {
const httpConn = serveHttp(conn);
server.#trackHttpConnection(httpConn);
while (true) {
try {
const requestEvent = await httpConn.nextRequest();
if (requestEvent === null) {
return;
}
const nativeRequest = new NativeRequest(requestEvent, { conn });
controller.enqueue(nativeRequest);
// if we await here, this becomes blocking, and really all we want
// it to dispatch any errors that occur on the promise
nativeRequest.donePromise.catch((error) => {
server.app.dispatchEvent(new ErrorEvent("error", { error }));
});
} catch (error) {
server.app.dispatchEvent(new ErrorEvent("error", { error }));
}
if (server.closed) {
server.#untrackHttpConnection(httpConn);
httpConn.close();
controller.close();
}
}
}
const listener = this.#listener;
assert(listener);
async function accept() {
while (true) {
try {
const conn = await listener!.accept();
serve(conn);
} catch (error) {
if (!server.closed) {
server.app.dispatchEvent(new ErrorEvent("error", { error }));
}
}
if (server.closed) {
controller.close();
return;
}
}
}
accept();
};
const stream = new ReadableStream<NativeRequest>({ start });
return stream[Symbol.asyncIterator]();
}
}