2021-01-10 21:59:07 -05:00
|
|
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
2020-04-07 06:34:18 -04:00
|
|
|
import { encode } from "../encoding/utf8.ts";
|
2020-02-26 10:48:35 -05:00
|
|
|
import { BufReader, BufWriter } from "../io/bufio.ts";
|
2020-06-07 09:20:33 -04:00
|
|
|
import { assert } from "../_util/assert.ts";
|
2020-09-27 06:22:32 -04:00
|
|
|
import { Deferred, deferred, MuxAsyncIterator } from "../async/mod.ts";
|
2020-02-24 22:49:39 -05:00
|
|
|
import {
|
|
|
|
bodyReader,
|
|
|
|
chunkedBodyReader,
|
2020-02-26 10:48:35 -05:00
|
|
|
emptyReader,
|
2020-03-28 13:03:49 -04:00
|
|
|
readRequest,
|
2020-09-27 06:22:32 -04:00
|
|
|
writeResponse,
|
2020-05-09 08:34:47 -04:00
|
|
|
} from "./_io.ts";
|
2019-05-20 09:17:26 -04:00
|
|
|
|
2019-02-19 12:38:19 -05:00
|
|
|
export class ServerRequest {
|
2019-05-30 08:59:30 -04:00
|
|
|
url!: string;
|
|
|
|
method!: string;
|
|
|
|
proto!: string;
|
|
|
|
protoMinor!: number;
|
|
|
|
protoMajor!: number;
|
|
|
|
headers!: Headers;
|
2020-06-12 15:23:38 -04:00
|
|
|
conn!: Deno.Conn;
|
2019-05-30 08:59:30 -04:00
|
|
|
r!: BufReader;
|
|
|
|
w!: BufWriter;
|
2019-02-19 12:38:19 -05:00
|
|
|
|
2021-01-06 08:55:08 -05:00
|
|
|
#done: Deferred<Error | undefined> = deferred();
|
|
|
|
#contentLength?: number | null = undefined;
|
|
|
|
#body?: Deno.Reader = undefined;
|
|
|
|
#finalized = false;
|
2020-12-29 20:22:09 -05:00
|
|
|
|
|
|
|
get done(): Promise<Error | undefined> {
|
|
|
|
return this.#done.then((e) => e);
|
|
|
|
}
|
|
|
|
|
2020-01-02 12:34:33 -05:00
|
|
|
/**
|
|
|
|
* Value of Content-Length header.
|
|
|
|
* If null, then content length is invalid or not given (e.g. chunked encoding).
|
|
|
|
*/
|
|
|
|
get contentLength(): number | null {
|
|
|
|
// undefined means not cached.
|
|
|
|
// null means invalid or not provided.
|
2021-01-06 08:55:08 -05:00
|
|
|
if (this.#contentLength === undefined) {
|
2020-02-07 02:23:38 -05:00
|
|
|
const cl = this.headers.get("content-length");
|
|
|
|
if (cl) {
|
2021-01-06 08:55:08 -05:00
|
|
|
this.#contentLength = parseInt(cl);
|
2020-01-02 12:34:33 -05:00
|
|
|
// Convert NaN to null (as NaN harder to test)
|
2021-01-06 08:55:08 -05:00
|
|
|
if (Number.isNaN(this.#contentLength)) {
|
|
|
|
this.#contentLength = null;
|
2020-01-02 12:34:33 -05:00
|
|
|
}
|
|
|
|
} else {
|
2021-01-06 08:55:08 -05:00
|
|
|
this.#contentLength = null;
|
2020-01-02 12:34:33 -05:00
|
|
|
}
|
|
|
|
}
|
2021-01-06 08:55:08 -05:00
|
|
|
return this.#contentLength;
|
2020-01-02 12:34:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-05-28 13:36:18 -04:00
|
|
|
* Body of the request. The easiest way to consume the body is:
|
2020-01-02 12:34:33 -05:00
|
|
|
*
|
2020-05-28 13:36:18 -04:00
|
|
|
* const buf: Uint8Array = await Deno.readAll(req.body);
|
2020-01-02 12:34:33 -05:00
|
|
|
*/
|
2020-02-24 22:49:39 -05:00
|
|
|
get body(): Deno.Reader {
|
2021-01-06 08:55:08 -05:00
|
|
|
if (!this.#body) {
|
2020-02-24 22:49:39 -05:00
|
|
|
if (this.contentLength != null) {
|
2021-01-06 08:55:08 -05:00
|
|
|
this.#body = bodyReader(this.contentLength, this.r);
|
2020-02-24 22:49:39 -05:00
|
|
|
} else {
|
|
|
|
const transferEncoding = this.headers.get("transfer-encoding");
|
|
|
|
if (transferEncoding != null) {
|
|
|
|
const parts = transferEncoding
|
|
|
|
.split(",")
|
|
|
|
.map((e): string => e.trim().toLowerCase());
|
|
|
|
assert(
|
|
|
|
parts.includes("chunked"),
|
2020-07-14 15:24:17 -04:00
|
|
|
'transfer-encoding must include "chunked" if content-length is not set',
|
2020-02-24 22:49:39 -05:00
|
|
|
);
|
2021-01-06 08:55:08 -05:00
|
|
|
this.#body = chunkedBodyReader(this.headers, this.r);
|
2020-02-24 22:49:39 -05:00
|
|
|
} else {
|
|
|
|
// Neither content-length nor transfer-encoding: chunked
|
2021-01-06 08:55:08 -05:00
|
|
|
this.#body = emptyReader();
|
2019-02-19 12:38:19 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-06 08:55:08 -05:00
|
|
|
return this.#body;
|
2019-02-19 12:38:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
async respond(r: Response): Promise<void> {
|
2019-12-11 19:46:03 -05:00
|
|
|
let err: Error | undefined;
|
|
|
|
try {
|
|
|
|
// Write our response!
|
|
|
|
await writeResponse(this.w, r);
|
|
|
|
} catch (e) {
|
|
|
|
try {
|
|
|
|
// Eagerly close on error.
|
|
|
|
this.conn.close();
|
2020-06-12 09:19:29 -04:00
|
|
|
} catch {
|
|
|
|
// Pass
|
|
|
|
}
|
2019-12-11 19:46:03 -05:00
|
|
|
err = e;
|
|
|
|
}
|
2019-05-20 09:17:26 -04:00
|
|
|
// Signal that this request has been processed and the next pipelined
|
|
|
|
// request on the same connection can be accepted.
|
2020-12-29 20:22:09 -05:00
|
|
|
this.#done.resolve(err);
|
2019-12-11 19:46:03 -05:00
|
|
|
if (err) {
|
|
|
|
// Error during responding, rethrow.
|
|
|
|
throw err;
|
|
|
|
}
|
2019-02-19 12:38:19 -05:00
|
|
|
}
|
2020-02-24 22:49:39 -05:00
|
|
|
|
|
|
|
async finalize(): Promise<void> {
|
2021-01-06 08:55:08 -05:00
|
|
|
if (this.#finalized) return;
|
2020-02-24 22:49:39 -05:00
|
|
|
// Consume unread body
|
|
|
|
const body = this.body;
|
|
|
|
const buf = new Uint8Array(1024);
|
2020-06-12 09:19:29 -04:00
|
|
|
while ((await body.read(buf)) !== null) {
|
|
|
|
// Pass
|
|
|
|
}
|
2021-01-06 08:55:08 -05:00
|
|
|
this.#finalized = true;
|
2020-02-24 22:49:39 -05:00
|
|
|
}
|
2019-02-19 12:38:19 -05:00
|
|
|
}
|
|
|
|
|
2019-05-20 09:17:26 -04:00
|
|
|
export class Server implements AsyncIterable<ServerRequest> {
|
2021-01-06 08:55:08 -05:00
|
|
|
#closing = false;
|
|
|
|
#connections: Deno.Conn[] = [];
|
2019-03-09 11:46:53 -05:00
|
|
|
|
2020-06-12 15:23:38 -04:00
|
|
|
constructor(public listener: Deno.Listener) {}
|
2019-03-09 11:46:53 -05:00
|
|
|
|
2019-05-20 09:17:26 -04:00
|
|
|
close(): void {
|
2021-01-06 08:55:08 -05:00
|
|
|
this.#closing = true;
|
2019-05-20 09:17:26 -04:00
|
|
|
this.listener.close();
|
2021-01-06 08:55:08 -05:00
|
|
|
for (const conn of this.#connections) {
|
2020-03-19 11:04:26 -04:00
|
|
|
try {
|
|
|
|
conn.close();
|
|
|
|
} catch (e) {
|
|
|
|
// Connection might have been already closed
|
|
|
|
if (!(e instanceof Deno.errors.BadResource)) {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-20 09:17:26 -04:00
|
|
|
}
|
2019-03-09 11:46:53 -05:00
|
|
|
|
2019-05-20 09:17:26 -04:00
|
|
|
// Yields all HTTP requests on a single TCP connection.
|
|
|
|
private async *iterateHttpRequests(
|
2020-07-14 15:24:17 -04:00
|
|
|
conn: Deno.Conn,
|
2019-05-20 09:17:26 -04:00
|
|
|
): AsyncIterableIterator<ServerRequest> {
|
2020-04-07 06:34:18 -04:00
|
|
|
const reader = new BufReader(conn);
|
|
|
|
const writer = new BufWriter(conn);
|
2019-05-20 09:17:26 -04:00
|
|
|
|
2021-01-06 08:55:08 -05:00
|
|
|
while (!this.#closing) {
|
2020-04-28 12:40:43 -04:00
|
|
|
let request: ServerRequest | null;
|
2019-05-22 19:28:03 -04:00
|
|
|
try {
|
2020-04-07 06:34:18 -04:00
|
|
|
request = await readRequest(conn, reader);
|
|
|
|
} catch (error) {
|
|
|
|
if (
|
|
|
|
error instanceof Deno.errors.InvalidData ||
|
|
|
|
error instanceof Deno.errors.UnexpectedEof
|
|
|
|
) {
|
|
|
|
// An error was thrown while parsing request headers.
|
2020-11-18 11:47:47 -05:00
|
|
|
// Try to send the "400 Bad Request" before closing the connection.
|
|
|
|
try {
|
|
|
|
await writeResponse(writer, {
|
|
|
|
status: 400,
|
|
|
|
body: encode(`${error.message}\r\n\r\n`),
|
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
// The connection is broken.
|
|
|
|
}
|
2020-04-07 06:34:18 -04:00
|
|
|
}
|
|
|
|
break;
|
2019-05-23 22:04:06 -04:00
|
|
|
}
|
2020-04-28 12:40:43 -04:00
|
|
|
if (request === null) {
|
2019-05-23 22:04:06 -04:00
|
|
|
break;
|
2019-05-22 19:28:03 -04:00
|
|
|
}
|
2019-05-23 22:04:06 -04:00
|
|
|
|
2020-04-07 06:34:18 -04:00
|
|
|
request.w = writer;
|
|
|
|
yield request;
|
2019-05-23 22:04:06 -04:00
|
|
|
|
2019-05-20 09:17:26 -04:00
|
|
|
// Wait for the request to be processed before we accept a new request on
|
|
|
|
// this connection.
|
2020-04-07 06:34:18 -04:00
|
|
|
const responseError = await request.done;
|
|
|
|
if (responseError) {
|
2019-12-11 19:46:03 -05:00
|
|
|
// Something bad happened during response.
|
|
|
|
// (likely other side closed during pipelined req)
|
|
|
|
// req.done implies this connection already closed, so we can just return.
|
2020-04-07 06:34:18 -04:00
|
|
|
this.untrackConnection(request.conn);
|
2019-12-11 19:46:03 -05:00
|
|
|
return;
|
|
|
|
}
|
2020-11-18 11:47:47 -05:00
|
|
|
|
|
|
|
try {
|
|
|
|
// Consume unread body and trailers if receiver didn't consume those data
|
|
|
|
await request.finalize();
|
|
|
|
} catch (error) {
|
|
|
|
// Invalid data was received or the connection was closed.
|
|
|
|
break;
|
|
|
|
}
|
2019-05-20 09:17:26 -04:00
|
|
|
}
|
2019-03-09 11:46:53 -05:00
|
|
|
|
2020-03-19 11:04:26 -04:00
|
|
|
this.untrackConnection(conn);
|
|
|
|
try {
|
|
|
|
conn.close();
|
|
|
|
} catch (e) {
|
|
|
|
// might have been already closed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-12 15:23:38 -04:00
|
|
|
private trackConnection(conn: Deno.Conn): void {
|
2021-01-06 08:55:08 -05:00
|
|
|
this.#connections.push(conn);
|
2020-03-19 11:04:26 -04:00
|
|
|
}
|
|
|
|
|
2020-06-12 15:23:38 -04:00
|
|
|
private untrackConnection(conn: Deno.Conn): void {
|
2021-01-06 08:55:08 -05:00
|
|
|
const index = this.#connections.indexOf(conn);
|
2020-03-19 11:04:26 -04:00
|
|
|
if (index !== -1) {
|
2021-01-06 08:55:08 -05:00
|
|
|
this.#connections.splice(index, 1);
|
2020-03-19 11:04:26 -04:00
|
|
|
}
|
2019-05-20 09:17:26 -04:00
|
|
|
}
|
2019-03-09 11:46:53 -05:00
|
|
|
|
2019-05-20 09:17:26 -04:00
|
|
|
// Accepts a new TCP connection and yields all HTTP requests that arrive on
|
|
|
|
// it. When a connection is accepted, it also creates a new iterator of the
|
|
|
|
// same kind and adds it to the request multiplexer so that another TCP
|
|
|
|
// connection can be accepted.
|
|
|
|
private async *acceptConnAndIterateHttpRequests(
|
2020-07-14 15:24:17 -04:00
|
|
|
mux: MuxAsyncIterator<ServerRequest>,
|
2019-05-20 09:17:26 -04:00
|
|
|
): AsyncIterableIterator<ServerRequest> {
|
2021-01-06 08:55:08 -05:00
|
|
|
if (this.#closing) return;
|
2019-05-20 09:17:26 -04:00
|
|
|
// Wait for a new connection.
|
2020-06-12 15:23:38 -04:00
|
|
|
let conn: Deno.Conn;
|
2020-03-10 15:14:22 -04:00
|
|
|
try {
|
|
|
|
conn = await this.listener.accept();
|
|
|
|
} catch (error) {
|
2020-06-08 11:58:52 -04:00
|
|
|
if (
|
2020-11-18 11:47:47 -05:00
|
|
|
// The listener is closed:
|
2020-06-08 11:58:52 -04:00
|
|
|
error instanceof Deno.errors.BadResource ||
|
2020-11-18 11:47:47 -05:00
|
|
|
// TLS handshake errors:
|
2020-06-08 11:58:52 -04:00
|
|
|
error instanceof Deno.errors.InvalidData ||
|
2020-11-18 11:47:47 -05:00
|
|
|
error instanceof Deno.errors.UnexpectedEof ||
|
|
|
|
error instanceof Deno.errors.ConnectionReset
|
2020-06-08 11:58:52 -04:00
|
|
|
) {
|
|
|
|
return mux.add(this.acceptConnAndIterateHttpRequests(mux));
|
2020-03-10 15:14:22 -04:00
|
|
|
}
|
|
|
|
throw error;
|
|
|
|
}
|
2020-03-19 11:04:26 -04:00
|
|
|
this.trackConnection(conn);
|
2019-05-20 09:17:26 -04:00
|
|
|
// Try to accept another connection and add it to the multiplexer.
|
|
|
|
mux.add(this.acceptConnAndIterateHttpRequests(mux));
|
|
|
|
// Yield the requests that arrive on the just-accepted connection.
|
|
|
|
yield* this.iterateHttpRequests(conn);
|
2018-12-18 20:48:05 -05:00
|
|
|
}
|
2019-05-20 09:17:26 -04:00
|
|
|
|
|
|
|
[Symbol.asyncIterator](): AsyncIterableIterator<ServerRequest> {
|
|
|
|
const mux: MuxAsyncIterator<ServerRequest> = new MuxAsyncIterator();
|
|
|
|
mux.add(this.acceptConnAndIterateHttpRequests(mux));
|
|
|
|
return mux.iterate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-04 09:15:23 -05:00
|
|
|
/** Options for creating an HTTP server. */
|
|
|
|
export type HTTPOptions = Omit<Deno.ListenOptions, "transport">;
|
2019-11-06 12:18:28 -05:00
|
|
|
|
2020-06-29 10:39:17 -04:00
|
|
|
/**
|
|
|
|
* Parse addr from string
|
|
|
|
*
|
|
|
|
* const addr = "::1:8000";
|
|
|
|
* parseAddrFromString(addr);
|
|
|
|
*
|
|
|
|
* @param addr Address string
|
|
|
|
*/
|
|
|
|
export function _parseAddrFromStr(addr: string): HTTPOptions {
|
|
|
|
let url: URL;
|
|
|
|
try {
|
2020-07-13 00:56:45 -04:00
|
|
|
const host = addr.startsWith(":") ? `0.0.0.0${addr}` : addr;
|
|
|
|
url = new URL(`http://${host}`);
|
2020-06-29 10:39:17 -04:00
|
|
|
} catch {
|
|
|
|
throw new TypeError("Invalid address.");
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
url.username ||
|
|
|
|
url.password ||
|
|
|
|
url.pathname != "/" ||
|
|
|
|
url.search ||
|
|
|
|
url.hash
|
|
|
|
) {
|
|
|
|
throw new TypeError("Invalid address.");
|
|
|
|
}
|
|
|
|
|
2020-07-09 03:37:50 -04:00
|
|
|
return {
|
2020-07-13 00:56:45 -04:00
|
|
|
hostname: url.hostname,
|
2020-07-09 03:37:50 -04:00
|
|
|
port: url.port === "" ? 80 : Number(url.port),
|
|
|
|
};
|
2020-06-29 10:39:17 -04:00
|
|
|
}
|
|
|
|
|
2019-11-06 12:18:28 -05:00
|
|
|
/**
|
2020-03-14 10:17:44 -04:00
|
|
|
* Create a HTTP server
|
2019-11-06 12:18:28 -05:00
|
|
|
*
|
|
|
|
* import { serve } from "https://deno.land/std/http/server.ts";
|
2020-01-17 18:44:35 -05:00
|
|
|
* const body = "Hello World\n";
|
2020-05-26 10:09:47 -04:00
|
|
|
* const server = serve({ port: 8000 });
|
|
|
|
* for await (const req of server) {
|
2019-11-06 12:18:28 -05:00
|
|
|
* req.respond({ body });
|
|
|
|
* }
|
|
|
|
*/
|
2020-02-04 09:15:23 -05:00
|
|
|
export function serve(addr: string | HTTPOptions): Server {
|
2019-11-06 12:18:28 -05:00
|
|
|
if (typeof addr === "string") {
|
2020-06-29 10:39:17 -04:00
|
|
|
addr = _parseAddrFromStr(addr);
|
2019-11-06 12:18:28 -05:00
|
|
|
}
|
|
|
|
|
2020-06-12 15:23:38 -04:00
|
|
|
const listener = Deno.listen(addr);
|
2019-05-20 09:17:26 -04:00
|
|
|
return new Server(listener);
|
2019-03-09 11:46:53 -05:00
|
|
|
}
|
|
|
|
|
2020-03-14 10:17:44 -04:00
|
|
|
/**
|
|
|
|
* Start an HTTP server with given options and request handler
|
|
|
|
*
|
|
|
|
* const body = "Hello World\n";
|
|
|
|
* const options = { port: 8000 };
|
2020-04-28 09:05:14 -04:00
|
|
|
* listenAndServe(options, (req) => {
|
2020-03-14 10:17:44 -04:00
|
|
|
* req.respond({ body });
|
|
|
|
* });
|
|
|
|
*
|
|
|
|
* @param options Server configuration
|
|
|
|
* @param handler Request handler
|
|
|
|
*/
|
2019-03-09 11:46:53 -05:00
|
|
|
export async function listenAndServe(
|
2020-02-04 09:15:23 -05:00
|
|
|
addr: string | HTTPOptions,
|
2020-07-14 15:24:17 -04:00
|
|
|
handler: (req: ServerRequest) => void,
|
2019-03-12 01:51:51 -04:00
|
|
|
): Promise<void> {
|
2019-03-09 11:46:53 -05:00
|
|
|
const server = serve(addr);
|
|
|
|
|
|
|
|
for await (const request of server) {
|
2019-05-20 20:08:39 -04:00
|
|
|
handler(request);
|
2018-12-18 20:48:05 -05:00
|
|
|
}
|
2019-03-09 11:46:53 -05:00
|
|
|
}
|
|
|
|
|
2019-11-04 13:45:29 -05:00
|
|
|
/** Options for creating an HTTPS server. */
|
2020-04-24 17:29:14 -04:00
|
|
|
export type HTTPSOptions = Omit<Deno.ListenTlsOptions, "transport">;
|
2019-11-04 13:45:29 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an HTTPS server with given options
|
2019-11-06 12:18:28 -05:00
|
|
|
*
|
2020-01-17 18:44:35 -05:00
|
|
|
* const body = "Hello HTTPS";
|
2019-11-06 12:18:28 -05:00
|
|
|
* const options = {
|
|
|
|
* hostname: "localhost",
|
|
|
|
* port: 443,
|
|
|
|
* certFile: "./path/to/localhost.crt",
|
|
|
|
* keyFile: "./path/to/localhost.key",
|
|
|
|
* };
|
|
|
|
* for await (const req of serveTLS(options)) {
|
|
|
|
* req.respond({ body });
|
|
|
|
* }
|
|
|
|
*
|
2019-11-04 13:45:29 -05:00
|
|
|
* @param options Server configuration
|
|
|
|
* @return Async iterable server instance for incoming requests
|
|
|
|
*/
|
|
|
|
export function serveTLS(options: HTTPSOptions): Server {
|
2020-04-24 17:29:14 -04:00
|
|
|
const tlsOptions: Deno.ListenTlsOptions = {
|
2019-11-04 13:45:29 -05:00
|
|
|
...options,
|
2020-03-28 13:03:49 -04:00
|
|
|
transport: "tcp",
|
2019-11-04 13:45:29 -05:00
|
|
|
};
|
2020-06-12 15:23:38 -04:00
|
|
|
const listener = Deno.listenTls(tlsOptions);
|
2019-11-04 13:45:29 -05:00
|
|
|
return new Server(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-03-14 10:17:44 -04:00
|
|
|
* Start an HTTPS server with given options and request handler
|
2019-11-07 14:00:27 -05:00
|
|
|
*
|
2020-01-17 18:44:35 -05:00
|
|
|
* const body = "Hello HTTPS";
|
2019-11-07 14:00:27 -05:00
|
|
|
* const options = {
|
|
|
|
* hostname: "localhost",
|
|
|
|
* port: 443,
|
|
|
|
* certFile: "./path/to/localhost.crt",
|
|
|
|
* keyFile: "./path/to/localhost.key",
|
|
|
|
* };
|
|
|
|
* listenAndServeTLS(options, (req) => {
|
|
|
|
* req.respond({ body });
|
|
|
|
* });
|
|
|
|
*
|
2019-11-04 13:45:29 -05:00
|
|
|
* @param options Server configuration
|
|
|
|
* @param handler Request handler
|
|
|
|
*/
|
|
|
|
export async function listenAndServeTLS(
|
|
|
|
options: HTTPSOptions,
|
2020-07-14 15:24:17 -04:00
|
|
|
handler: (req: ServerRequest) => void,
|
2019-11-04 13:45:29 -05:00
|
|
|
): Promise<void> {
|
|
|
|
const server = serveTLS(options);
|
|
|
|
|
|
|
|
for await (const request of server) {
|
|
|
|
handler(request);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-17 18:44:35 -05:00
|
|
|
/**
|
|
|
|
* Interface of HTTP server response.
|
|
|
|
* If body is a Reader, response would be chunked.
|
|
|
|
* If body is a string, it would be UTF-8 encoded by default.
|
|
|
|
*/
|
2019-03-09 11:46:53 -05:00
|
|
|
export interface Response {
|
|
|
|
status?: number;
|
|
|
|
headers?: Headers;
|
2020-06-12 15:23:38 -04:00
|
|
|
body?: Uint8Array | Deno.Reader | string;
|
2020-02-10 11:38:48 -05:00
|
|
|
trailers?: () => Promise<Headers> | Headers;
|
2018-12-18 20:48:05 -05:00
|
|
|
}
|