Started to implement server
This commit is contained in:
1 parent
1f2ea8b7e5
commit
17a1b4bed3
177 files changed
+29894
-74
No files matched your search
+264
@@ -0,0 +1,264 @@
|
||||
// Copyright 2018-2022 the oak authors. All rights reserved. MIT license.
|
||||
|
||||
import type {
|
||||
Body,
|
||||
BodyBytes,
|
||||
BodyForm,
|
||||
BodyFormData,
|
||||
BodyJson,
|
||||
BodyOptions,
|
||||
BodyReader,
|
||||
BodyStream,
|
||||
BodyText,
|
||||
} from "./body.ts";
|
||||
import { RequestBody } from "./body.ts";
|
||||
import { accepts, acceptsEncodings, acceptsLanguages } from "./deps.ts";
|
||||
import type { HTTPMethods, ServerRequest } from "./types.d.ts";
|
||||
|
||||
export interface OakRequestOptions {
|
||||
jsonBodyReviver?: (key: string, value: unknown) => unknown;
|
||||
proxy?: boolean;
|
||||
secure?: boolean;
|
||||
}
|
||||
|
||||
/** An interface which provides information about the current request. The
|
||||
* instance related to the current request is available on the
|
||||
* {@linkcode Context}'s `.request` property.
|
||||
*
|
||||
* The interface contains several properties to get information about the
|
||||
* request as well as several methods, which include content negotiation and
|
||||
* the ability to decode a request body.
|
||||
*/
|
||||
export class Request {
|
||||
#body: RequestBody;
|
||||
#proxy: boolean;
|
||||
#secure: boolean;
|
||||
#serverRequest: ServerRequest;
|
||||
#url?: URL;
|
||||
|
||||
#getRemoteAddr(): string {
|
||||
return this.#serverRequest.remoteAddr ?? "";
|
||||
}
|
||||
|
||||
/** Is `true` if the request might have a body, otherwise `false`.
|
||||
*
|
||||
* **WARNING** this is an unreliable API. In HTTP/2 in many situations you
|
||||
* cannot determine if a request has a body or not unless you attempt to read
|
||||
* the body, due to the streaming nature of HTTP/2. As of Deno 1.16.1, for
|
||||
* HTTP/1.1, Deno also reflects that behaviour. The only reliable way to
|
||||
* determine if a request has a body or not is to attempt to read the body.
|
||||
*/
|
||||
get hasBody(): boolean {
|
||||
return this.#body.has();
|
||||
}
|
||||
|
||||
/** The `Headers` supplied in the request. */
|
||||
get headers(): Headers {
|
||||
return this.#serverRequest.headers;
|
||||
}
|
||||
|
||||
/** Request remote address. When the application's `.proxy` is true, the
|
||||
* `X-Forwarded-For` will be used to determine the requesting remote address.
|
||||
*/
|
||||
get ip(): string {
|
||||
return (this.#proxy ? this.ips[0] : this.#getRemoteAddr()) ?? "";
|
||||
}
|
||||
|
||||
/** When the application's `.proxy` is `true`, this will be set to an array of
|
||||
* IPs, ordered from upstream to downstream, based on the value of the header
|
||||
* `X-Forwarded-For`. When `false` an empty array is returned. */
|
||||
get ips(): string[] {
|
||||
return this.#proxy
|
||||
? (this.#serverRequest.headers.get("x-forwarded-for") ??
|
||||
this.#getRemoteAddr()).split(/\s*,\s*/)
|
||||
: [];
|
||||
}
|
||||
|
||||
/** The HTTP Method used by the request. */
|
||||
get method(): HTTPMethods {
|
||||
return this.#serverRequest.method as HTTPMethods;
|
||||
}
|
||||
|
||||
/** Shortcut to `request.url.protocol === "https:"`. */
|
||||
get secure(): boolean {
|
||||
return this.#secure;
|
||||
}
|
||||
|
||||
/** Set to the value of the _original_ Deno server request. */
|
||||
get originalRequest(): ServerRequest {
|
||||
return this.#serverRequest;
|
||||
}
|
||||
|
||||
/** A parsed URL for the request which complies with the browser standards.
|
||||
* When the application's `.proxy` is `true`, this value will be based off of
|
||||
* the `X-Forwarded-Proto` and `X-Forwarded-Host` header values if present in
|
||||
* the request. */
|
||||
get url(): URL {
|
||||
if (!this.#url) {
|
||||
const serverRequest = this.#serverRequest;
|
||||
if (!this.#proxy) {
|
||||
// between 1.9.0 and 1.9.1 the request.url of the native HTTP started
|
||||
// returning the full URL, where previously it only returned the path
|
||||
// so we will try to use that URL here, but default back to old logic
|
||||
// if the URL isn't valid.
|
||||
try {
|
||||
if (serverRequest.rawUrl) {
|
||||
this.#url = new URL(serverRequest.rawUrl);
|
||||
return this.#url;
|
||||
}
|
||||
} catch {
|
||||
// we don't care about errors here
|
||||
}
|
||||
}
|
||||
let proto: string;
|
||||
let host: string;
|
||||
if (this.#proxy) {
|
||||
proto = serverRequest
|
||||
.headers.get("x-forwarded-proto")?.split(/\s*,\s*/, 1)[0] ??
|
||||
"http";
|
||||
host = serverRequest.headers.get("x-forwarded-host") ??
|
||||
serverRequest.headers.get("host") ?? "";
|
||||
} else {
|
||||
proto = this.#secure ? "https" : "http";
|
||||
host = serverRequest.headers.get("host") ?? "";
|
||||
}
|
||||
try {
|
||||
this.#url = new URL(`${proto}://${host}${serverRequest.url}`);
|
||||
} catch {
|
||||
throw new TypeError(
|
||||
`The server request URL of "${proto}://${host}${serverRequest.url}" is invalid.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return this.#url;
|
||||
}
|
||||
|
||||
constructor(
|
||||
serverRequest: ServerRequest,
|
||||
{ proxy = false, secure = false, jsonBodyReviver }: OakRequestOptions = {},
|
||||
) {
|
||||
this.#proxy = proxy;
|
||||
this.#secure = secure;
|
||||
this.#serverRequest = serverRequest;
|
||||
this.#body = new RequestBody(
|
||||
serverRequest.getBody(),
|
||||
serverRequest.headers,
|
||||
jsonBodyReviver,
|
||||
);
|
||||
}
|
||||
|
||||
/** Returns an array of media types, accepted by the requestor, in order of
|
||||
* preference. If there are no encodings supplied by the requestor,
|
||||
* then accepting any is implied is returned.
|
||||
*/
|
||||
accepts(): string[] | undefined;
|
||||
/** For a given set of media types, return the best match accepted by the
|
||||
* requestor. If there are no encoding that match, then the method returns
|
||||
* `undefined`.
|
||||
*/
|
||||
accepts(...types: string[]): string | undefined;
|
||||
accepts(...types: string[]): string | string[] | undefined {
|
||||
if (!this.#serverRequest.headers.has("Accept")) {
|
||||
return types.length ? types[0] : ["*/*"];
|
||||
}
|
||||
if (types.length) {
|
||||
return accepts(this.#serverRequest, ...types);
|
||||
}
|
||||
return accepts(this.#serverRequest);
|
||||
}
|
||||
|
||||
/** Returns an array of encodings, accepted by the requestor, in order of
|
||||
* preference. If there are no encodings supplied by the requestor,
|
||||
* then `["*"]` is returned, matching any.
|
||||
*/
|
||||
acceptsEncodings(): string[] | undefined;
|
||||
/** For a given set of encodings, return the best match accepted by the
|
||||
* requestor. If there are no encodings that match, then the method returns
|
||||
* `undefined`.
|
||||
*
|
||||
* **NOTE:** You should always supply `identity` as one of the encodings
|
||||
* to ensure that there is a match when the `Accept-Encoding` header is part
|
||||
* of the request.
|
||||
*/
|
||||
acceptsEncodings(...encodings: string[]): string | undefined;
|
||||
acceptsEncodings(...encodings: string[]): string[] | string | undefined {
|
||||
if (!this.#serverRequest.headers.has("Accept-Encoding")) {
|
||||
return encodings.length ? encodings[0] : ["*"];
|
||||
}
|
||||
if (encodings.length) {
|
||||
return acceptsEncodings(this.#serverRequest, ...encodings);
|
||||
}
|
||||
return acceptsEncodings(this.#serverRequest);
|
||||
}
|
||||
|
||||
/** Returns an array of languages, accepted by the requestor, in order of
|
||||
* preference. If there are no languages supplied by the requestor,
|
||||
* `["*"]` is returned, indicating any language is accepted.
|
||||
*/
|
||||
acceptsLanguages(): string[] | undefined;
|
||||
/** For a given set of languages, return the best match accepted by the
|
||||
* requestor. If there are no languages that match, then the method returns
|
||||
* `undefined`. */
|
||||
acceptsLanguages(...langs: string[]): string | undefined;
|
||||
acceptsLanguages(...langs: string[]): string[] | string | undefined {
|
||||
if (!this.#serverRequest.headers.get("Accept-Language")) {
|
||||
return langs.length ? langs[0] : ["*"];
|
||||
}
|
||||
if (langs.length) {
|
||||
return acceptsLanguages(this.#serverRequest, ...langs);
|
||||
}
|
||||
return acceptsLanguages(this.#serverRequest);
|
||||
}
|
||||
|
||||
body(options: BodyOptions<"bytes">): BodyBytes;
|
||||
body(options: BodyOptions<"form">): BodyForm;
|
||||
body(options: BodyOptions<"form-data">): BodyFormData;
|
||||
body(options: BodyOptions<"json">): BodyJson;
|
||||
body(options: BodyOptions<"reader">): BodyReader;
|
||||
body(options: BodyOptions<"stream">): BodyStream;
|
||||
body(options: BodyOptions<"text">): BodyText;
|
||||
body(options?: BodyOptions): Body;
|
||||
/** Access the body of the request. This is a method, because there are
|
||||
* several options which can be provided which can influence how the body is
|
||||
* handled. */
|
||||
body(options: BodyOptions = {}): Body | BodyReader | BodyStream {
|
||||
return this.#body.get(options);
|
||||
}
|
||||
|
||||
[Symbol.for("Deno.customInspect")](inspect: (value: unknown) => string) {
|
||||
const { hasBody, headers, ip, ips, method, secure, url } = this;
|
||||
return `${this.constructor.name} ${
|
||||
inspect({
|
||||
hasBody,
|
||||
headers,
|
||||
ip,
|
||||
ips,
|
||||
method,
|
||||
secure,
|
||||
url: url.toString(),
|
||||
})
|
||||
}`;
|
||||
}
|
||||
|
||||
[Symbol.for("nodejs.util.inspect.custom")](
|
||||
depth: number,
|
||||
// deno-lint-ignore no-explicit-any
|
||||
options: any,
|
||||
inspect: (value: unknown, options?: unknown) => string,
|
||||
) {
|
||||
if (depth < 0) {
|
||||
return options.stylize(`[${this.constructor.name}]`, "special");
|
||||
}
|
||||
|
||||
const newOptions = Object.assign({}, options, {
|
||||
depth: options.depth === null ? null : options.depth - 1,
|
||||
});
|
||||
const { hasBody, headers, ip, ips, method, secure, url } = this;
|
||||
return `${options.stylize(this.constructor.name, "special")} ${
|
||||
inspect(
|
||||
{ hasBody, headers, ip, ips, method, secure, url },
|
||||
newOptions,
|
||||
)
|
||||
}`;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user