This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+39
View File
@@ -0,0 +1,39 @@
/// <reference path="../../../adonis-typings/index.d.ts" />
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption';
import { CookieClientContract } from '@ioc:Adonis/Core/CookieClient';
/**
* Cookie client exposes the API to parse/set AdonisJS
* cookies as a client.
*/
export declare class CookieClient implements CookieClientContract {
private encryption;
constructor(encryption: EncryptionContract);
/**
* Encrypt a key value pair to be sent in the cookie header
*/
encrypt(key: string, value: any): string | null;
/**
* Sign a key value pair to be sent in the cookie header
*/
sign(key: string, value: any): string | null;
/**
* Encode a key value pair to be sent in the cookie header
*/
encode(_: string, value: any): string | null;
/**
* Unsign a signed cookie value
*/
unsign(key: string, value: string): any;
/**
* Decrypt an encrypted cookie value
*/
decrypt(key: string, value: string): any;
/**
* Decode an encoded cookie value
*/
decode(_: string, value: string): any;
/**
* Parse response cookie
*/
parse(key: string, value: any): any;
}
+108
View File
@@ -0,0 +1,108 @@
"use strict";
/*
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CookieClient = void 0;
const PlainCookie = __importStar(require("../Drivers/Plain"));
const SignedCookie = __importStar(require("../Drivers/Signed"));
const EncryptedCookie = __importStar(require("../Drivers/Encrypted"));
/**
* Cookie client exposes the API to parse/set AdonisJS
* cookies as a client.
*/
class CookieClient {
constructor(encryption) {
this.encryption = encryption;
}
/**
* Encrypt a key value pair to be sent in the cookie header
*/
encrypt(key, value) {
return EncryptedCookie.pack(key, value, this.encryption);
}
/**
* Sign a key value pair to be sent in the cookie header
*/
sign(key, value) {
return SignedCookie.pack(key, value, this.encryption);
}
/**
* Encode a key value pair to be sent in the cookie header
*/
encode(_, value) {
return PlainCookie.pack(value);
}
/**
* Unsign a signed cookie value
*/
unsign(key, value) {
return SignedCookie.canUnpack(value) ? SignedCookie.unpack(key, value, this.encryption) : null;
}
/**
* Decrypt an encrypted cookie value
*/
decrypt(key, value) {
return EncryptedCookie.canUnpack(value)
? EncryptedCookie.unpack(key, value, this.encryption)
: null;
}
/**
* Decode an encoded cookie value
*/
decode(_, value) {
return PlainCookie.canUnpack(value) ? PlainCookie.unpack(value) : null;
}
/**
* Parse response cookie
*/
parse(key, value) {
/**
* Unsign signed cookie
*/
if (SignedCookie.canUnpack(value)) {
return SignedCookie.unpack(key, value, this.encryption);
}
/**
* Decrypted encrypted cookie
*/
if (EncryptedCookie.canUnpack(value)) {
return EncryptedCookie.unpack(key, value, this.encryption);
}
/**
* Decode encoded cookie
*/
if (PlainCookie.canUnpack(value)) {
return PlainCookie.unpack(value);
}
}
}
exports.CookieClient = CookieClient;
@@ -0,0 +1,24 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption';
/**
* Encrypt a value to be set as cookie
*/
export declare function pack(key: string, value: any, encryption: EncryptionContract): null | string;
/**
* Returns a boolean, if the unpack method from this module can attempt
* to unpack encrypted value.
*/
export declare function canUnpack(encryptedValue: string): boolean;
/**
* Attempts to unpack the encrypted cookie value. Returns null, when fails to do so.
* Only call this method, when `canUnpack` returns true, otherwise runtime
* exceptions can be raised.
*/
export declare function unpack(key: string, encryptedValue: string, encryption: EncryptionContract): null | any;
@@ -0,0 +1,42 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.unpack = exports.canUnpack = exports.pack = void 0;
/**
* Encrypt a value to be set as cookie
*/
function pack(key, value, encryption) {
if (value === undefined || value === null) {
return null;
}
return `e:${encryption.encrypt(value, undefined, key)}`;
}
exports.pack = pack;
/**
* Returns a boolean, if the unpack method from this module can attempt
* to unpack encrypted value.
*/
function canUnpack(encryptedValue) {
return typeof encryptedValue === 'string' && encryptedValue.substr(0, 2) === 'e:';
}
exports.canUnpack = canUnpack;
/**
* Attempts to unpack the encrypted cookie value. Returns null, when fails to do so.
* Only call this method, when `canUnpack` returns true, otherwise runtime
* exceptions can be raised.
*/
function unpack(key, encryptedValue, encryption) {
const value = encryptedValue.slice(2);
if (!value) {
return null;
}
return encryption.decrypt(value, key);
}
exports.unpack = unpack;
+23
View File
@@ -0,0 +1,23 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Encodes a value into a base64 url encoded string to
* be set as cookie
*/
export declare function pack(value: any): null | string;
/**
* Returns true when this `unpack` method of this module can attempt
* to unpack the encode value.
*/
export declare function canUnpack(encodedValue: string): boolean;
/**
* Attempts to unpack the value by decoding it. Make sure to call, `canUnpack`
* before calling this method
*/
export declare function unpack(encodedValue: string): null | any;
+40
View File
@@ -0,0 +1,40 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.unpack = exports.canUnpack = exports.pack = void 0;
const helpers_1 = require("@poppinss/utils/build/helpers");
/**
* Encodes a value into a base64 url encoded string to
* be set as cookie
*/
function pack(value) {
if (value === undefined || value === null) {
return null;
}
return helpers_1.base64.urlEncode(new helpers_1.MessageBuilder().build(value));
}
exports.pack = pack;
/**
* Returns true when this `unpack` method of this module can attempt
* to unpack the encode value.
*/
function canUnpack(encodedValue) {
return typeof encodedValue === 'string';
}
exports.canUnpack = canUnpack;
/**
* Attempts to unpack the value by decoding it. Make sure to call, `canUnpack`
* before calling this method
*/
function unpack(encodedValue) {
const verified = new helpers_1.MessageBuilder().verify(helpers_1.base64.urlDecode(encodedValue, 'utf-8', true));
return verified === undefined ? null : verified;
}
exports.unpack = unpack;
@@ -0,0 +1,24 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption';
/**
* Signs a value to be shared as a cookie. The signed output has a
* hash to verify tampering with the original value
*/
export declare function pack(key: string, value: any, encryption: EncryptionContract): null | string;
/**
* Returns a boolean, if the unpack method from this module can attempt
* to unpack the signed value.
*/
export declare function canUnpack(signedValue: string): boolean;
/**
* Attempts to unpack the signed value. Make sure to call `canUnpack` before
* calling this method.
*/
export declare function unpack(key: string, signedValue: string, encryption: EncryptionContract): null | any;
+42
View File
@@ -0,0 +1,42 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.unpack = exports.canUnpack = exports.pack = void 0;
/**
* Signs a value to be shared as a cookie. The signed output has a
* hash to verify tampering with the original value
*/
function pack(key, value, encryption) {
if (value === undefined || value === null) {
return null;
}
return `s:${encryption.verifier.sign(value, undefined, key)}`;
}
exports.pack = pack;
/**
* Returns a boolean, if the unpack method from this module can attempt
* to unpack the signed value.
*/
function canUnpack(signedValue) {
return typeof signedValue === 'string' && signedValue.substr(0, 2) === 's:';
}
exports.canUnpack = canUnpack;
/**
* Attempts to unpack the signed value. Make sure to call `canUnpack` before
* calling this method.
*/
function unpack(key, signedValue, encryption) {
const value = signedValue.slice(2);
if (!value) {
return null;
}
return encryption.verifier.unsign(value, key);
}
exports.unpack = unpack;
+61
View File
@@ -0,0 +1,61 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption';
/**
* Cookie parser parses the HTTP `cookie` header and collects all cookies
* inside an object of `key-value` pair, but doesn't attempt to decrypt
* or unsign or decode the individual values.
*
* The cookie values are lazily decrypted, or unsigned to avoid unncessary
* processing, which infact can be used as a means to burden the server
* by sending too many cookies which even doesn't belongs to the
* server.
*/
export declare class CookieParser {
private cookieHeader;
private encryption;
private client;
/**
* A copy of cached cookies, they are cached during a request after
* initial decoding, unsigning or decrypting.
*/
private cachedCookies;
/**
* An object of key-value pair collected by parsing
* the request cookie header.
*/
private cookies;
constructor(cookieHeader: string, encryption: EncryptionContract);
/**
* Parses the request `cookie` header
*/
private parse;
/**
* Attempts to decode a cookie by the name. When calling this method,
* you are assuming that the cookie was just encoded at the first
* place and not signed or encrypted.
*/
decode(key: string, encoded?: boolean): any | null;
/**
* Attempts to unsign a cookie by the name. When calling this method,
* you are assuming that the cookie was signed at the first place.
*/
unsign(key: string): null | any;
/**
* Attempts to decrypt a cookie by the name. When calling this method,
* you are assuming that the cookie was encrypted at the first place.
*/
decrypt(key: string): null | any;
/**
* Returns an object of cookies key-value pair. Do note, the
* cookies are not decoded, unsigned or decrypted inside this
* list.
*/
list(): Record<string, any>;
}
+174
View File
@@ -0,0 +1,174 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CookieParser = void 0;
const cookie_1 = __importDefault(require("cookie"));
const Client_1 = require("../Client");
/**
* Cookie parser parses the HTTP `cookie` header and collects all cookies
* inside an object of `key-value` pair, but doesn't attempt to decrypt
* or unsign or decode the individual values.
*
* The cookie values are lazily decrypted, or unsigned to avoid unncessary
* processing, which infact can be used as a means to burden the server
* by sending too many cookies which even doesn't belongs to the
* server.
*/
class CookieParser {
constructor(cookieHeader, encryption) {
this.cookieHeader = cookieHeader;
this.encryption = encryption;
this.client = new Client_1.CookieClient(this.encryption);
/**
* A copy of cached cookies, they are cached during a request after
* initial decoding, unsigning or decrypting.
*/
this.cachedCookies = {
signedCookies: {},
plainCookies: {},
encryptedCookies: {},
};
/**
* An object of key-value pair collected by parsing
* the request cookie header.
*/
this.cookies = this.parse();
}
/**
* Parses the request `cookie` header
*/
parse() {
/*
* Set to empty object when cookie header is empty string
*/
if (!this.cookieHeader) {
return {};
}
/*
* Parse and store reference
*/
return cookie_1.default.parse(this.cookieHeader);
}
/**
* Attempts to decode a cookie by the name. When calling this method,
* you are assuming that the cookie was just encoded at the first
* place and not signed or encrypted.
*/
decode(key, encoded = true) {
/*
* Ignore when initial value is not defined or null
*/
const value = this.cookies[key];
if (value === null || value === undefined) {
return null;
}
/*
* Reference to the cache object. Mainly done to avoid typos,
* since this object is referenced a handful of times inside
* this method.
*/
const cacheObject = this.cachedCookies.plainCookies;
/*
* Return from cache, when already parsed
*/
if (cacheObject[key] !== undefined) {
return cacheObject[key];
}
/*
* Attempt to unpack and cache it for future. The value is only
* when value it is not null.
*/
const parsed = encoded ? this.client.decode(key, value) : value;
if (parsed !== null) {
cacheObject[key] = parsed;
}
return parsed;
}
/**
* Attempts to unsign a cookie by the name. When calling this method,
* you are assuming that the cookie was signed at the first place.
*/
unsign(key) {
/*
* Ignore when initial value is not defined or null
*/
const value = this.cookies[key];
if (value === null || value === undefined) {
return null;
}
/*
* Reference to the cache object. Mainly done to avoid typos,
* since this object is referenced a handful of times inside
* this method.
*/
const cacheObject = this.cachedCookies.signedCookies;
/*
* Return from cache, when already parsed
*/
if (cacheObject[key] !== undefined) {
return cacheObject[key];
}
/*
* Attempt to unpack and cache it for future. The value is only
* when value it is not null.
*/
const parsed = this.client.unsign(key, value);
if (parsed !== null) {
cacheObject[key] = parsed;
}
return parsed;
}
/**
* Attempts to decrypt a cookie by the name. When calling this method,
* you are assuming that the cookie was encrypted at the first place.
*/
decrypt(key) {
/*
* Ignore when initial value is not defined or null
*/
const value = this.cookies[key];
if (value === null || value === undefined) {
return null;
}
/*
* Reference to the cache object. Mainly done to avoid typos,
* since this object is referenced a handful of times inside
* this method.
*/
const cacheObject = this.cachedCookies.encryptedCookies;
/*
* Return from cache, when already parsed
*/
if (cacheObject[key] !== undefined) {
return cacheObject[key];
}
/*
* Attempt to unpack and cache it for future. The value is only
* when value it is not null.
*/
const parsed = this.client.decrypt(key, value);
if (parsed !== null) {
cacheObject[key] = parsed;
}
return parsed;
}
/**
* Returns an object of cookies key-value pair. Do note, the
* cookies are not decoded, unsigned or decrypted inside this
* list.
*/
list() {
return this.cookies;
}
}
exports.CookieParser = CookieParser;
@@ -0,0 +1,46 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { CookieOptions } from '@ioc:Adonis/Core/Response';
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption';
/**
* Cookies serializer is used to serialize a value to be set on the `Set-Cookie`
* header. You can `encode`, `sign` on `encrypt` cookies using the serializer
* and then set them individually using the `set-cookie` header.
*/
export declare class CookieSerializer {
private encryption;
private client;
constructor(encryption: EncryptionContract);
/**
* Serializes the key-value pair to a string, that can be set on the
* `Set-Cookie` header.
*/
private serializeAsCookie;
/**
* Encodes value as a plain cookie. Do note, the value is still JSON.stringified
* and converted to base64 encoded string to avoid encoding issues.
*
* @example
* ```ts
* serializer.encode('name', 'virk')
* ```
*/
encode(key: string, value: any, options?: Partial<CookieOptions & {
encode: boolean;
}>): string | null;
/**
* Signs the value and returns it back as a url safe string. The signed value
* has a verification hash attached to it to detect data tampering.
*/
sign(key: string, value: any, options?: Partial<CookieOptions>): string | null;
/**
* Encrypts the value and returns it back as a url safe string.
*/
encrypt(key: string, value: any, options?: Partial<CookieOptions>): string | null;
}
@@ -0,0 +1,88 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CookieSerializer = void 0;
const ms_1 = __importDefault(require("ms"));
const cookie_1 = __importDefault(require("cookie"));
const Client_1 = require("../Client");
/**
* Cookies serializer is used to serialize a value to be set on the `Set-Cookie`
* header. You can `encode`, `sign` on `encrypt` cookies using the serializer
* and then set them individually using the `set-cookie` header.
*/
class CookieSerializer {
constructor(encryption) {
this.encryption = encryption;
this.client = new Client_1.CookieClient(this.encryption);
}
/**
* Serializes the key-value pair to a string, that can be set on the
* `Set-Cookie` header.
*/
serializeAsCookie(key, value, options) {
/**
* Invoked expires method to get the date
*/
let expires = options?.expires;
if (typeof expires === 'function') {
expires = expires();
}
/**
* Parse string based max age to number
*/
let maxAge = options?.maxAge;
if (typeof maxAge === 'string') {
maxAge = (0, ms_1.default)(maxAge) / 1000;
}
const parsedOptions = Object.assign({}, options, { maxAge, expires });
return cookie_1.default.serialize(key, value, parsedOptions);
}
/**
* Encodes value as a plain cookie. Do note, the value is still JSON.stringified
* and converted to base64 encoded string to avoid encoding issues.
*
* @example
* ```ts
* serializer.encode('name', 'virk')
* ```
*/
encode(key, value, options) {
const packedValue = !(options?.encode ?? true) ? value : this.client.encode(key, value);
if (packedValue === null) {
return null;
}
return this.serializeAsCookie(key, packedValue, options);
}
/**
* Signs the value and returns it back as a url safe string. The signed value
* has a verification hash attached to it to detect data tampering.
*/
sign(key, value, options) {
const packedValue = this.client.sign(key, value);
if (packedValue === null) {
return null;
}
return this.serializeAsCookie(key, packedValue, options);
}
/**
* Encrypts the value and returns it back as a url safe string.
*/
encrypt(key, value, options) {
const packedValue = this.client.encrypt(key, value);
if (packedValue === null) {
return null;
}
return this.serializeAsCookie(key, packedValue, options);
}
}
exports.CookieSerializer = CookieSerializer;
@@ -0,0 +1,20 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
import { Exception } from '@poppinss/utils';
/**
* Custom exception to abort requests as one liners
*/
export declare class HttpException extends Exception {
body: any;
/**
* This method returns an instance of the exception class
*/
static invoke(body: any, status: number, code?: string): HttpException;
}
@@ -0,0 +1,36 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpException = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const utils_1 = require("@poppinss/utils");
const helpers_1 = require("@poppinss/utils/build/helpers");
const exceptions_json_1 = require("../../exceptions.json");
/**
* Custom exception to abort requests as one liners
*/
class HttpException extends utils_1.Exception {
/**
* This method returns an instance of the exception class
*/
static invoke(body, status, code) {
const message = exceptions_json_1.E_HTTP_EXCEPTION.message;
code = code || exceptions_json_1.E_HTTP_EXCEPTION.code;
if (body !== null && typeof body === 'object') {
const error = new this(body.message || (0, helpers_1.interpolate)(message, { status }), status, code);
error.body = body;
return error;
}
const error = new this(body || (0, helpers_1.interpolate)(message, { status }), status, code);
error.body = error.message;
return error;
}
}
exports.HttpException = HttpException;
@@ -0,0 +1,36 @@
import { Exception } from '@poppinss/utils';
/**
* Exceptions related to the HTTP router
*/
export declare class RouterException extends Exception {
/**
* Raised when one of the routes inside the group doesn't have a name
* but an attempt is made to name the group
*/
static cannotDefineGroupName(): void;
/**
* Raised when a duplicate route pattern is find for the same HTTP method
*/
static duplicateRoute(method: string, pattern: string): void;
/**
* Raised when a route has duplicate params
*/
static duplicateRouteParam(param: string, pattern: string): RouterException;
/**
* Raised when route name is not unique
*/
static duplicateRouteName(name: string): void;
/**
* Raised when unable to make url for a given route, because one of the
* params value is not defined
*/
static cannotMakeRoute(param: string, pattern: string): void;
/**
* Raised when unable to lookup a route using its identifier
*/
static cannotLookupRoute(identifier: string): void;
/**
* Raised when unable to lookup domain
*/
static cannotLookupDomain(domain: string): void;
}
@@ -0,0 +1,76 @@
"use strict";
/*
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouterException = void 0;
const utils_1 = require("@poppinss/utils");
const helpers_1 = require("@poppinss/utils/build/helpers");
const exceptions_json_1 = require("../../exceptions.json");
/**
* Exceptions related to the HTTP router
*/
class RouterException extends utils_1.Exception {
/**
* Raised when one of the routes inside the group doesn't have a name
* but an attempt is made to name the group
*/
static cannotDefineGroupName() {
const error = new this(exceptions_json_1.E_CANNOT_DEFINE_GROUP_NAME.message, exceptions_json_1.E_CANNOT_DEFINE_GROUP_NAME.status, exceptions_json_1.E_CANNOT_DEFINE_GROUP_NAME.code);
error.help = exceptions_json_1.E_CANNOT_DEFINE_GROUP_NAME.help.join('\n');
throw error;
}
/**
* Raised when a duplicate route pattern is find for the same HTTP method
*/
static duplicateRoute(method, pattern) {
const error = new this((0, helpers_1.interpolate)(exceptions_json_1.E_DUPLICATE_ROUTE.message, { method, pattern }), exceptions_json_1.E_DUPLICATE_ROUTE.status, exceptions_json_1.E_DUPLICATE_ROUTE.code);
error.help = exceptions_json_1.E_DUPLICATE_ROUTE.help.join('\n');
throw error;
}
/**
* Raised when a route has duplicate params
*/
static duplicateRouteParam(param, pattern) {
return new this((0, helpers_1.interpolate)(exceptions_json_1.E_DUPLICATE_ROUTE_PARAM.message, { param, pattern }), exceptions_json_1.E_DUPLICATE_ROUTE_PARAM.status, exceptions_json_1.E_DUPLICATE_ROUTE_PARAM.code);
}
/**
* Raised when route name is not unique
*/
static duplicateRouteName(name) {
const error = new this((0, helpers_1.interpolate)(exceptions_json_1.E_DUPLICATE_ROUTE_NAME.message, { name }), exceptions_json_1.E_DUPLICATE_ROUTE_NAME.status, exceptions_json_1.E_DUPLICATE_ROUTE_NAME.code);
error.help = exceptions_json_1.E_DUPLICATE_ROUTE_NAME.help.join('\n');
throw error;
}
/**
* Raised when unable to make url for a given route, because one of the
* params value is not defined
*/
static cannotMakeRoute(param, pattern) {
const error = new this((0, helpers_1.interpolate)(exceptions_json_1.E_CANNOT_MAKE_ROUTE_URL.message, { pattern, param }), exceptions_json_1.E_CANNOT_MAKE_ROUTE_URL.status, exceptions_json_1.E_CANNOT_MAKE_ROUTE_URL.code);
error.help = exceptions_json_1.E_CANNOT_MAKE_ROUTE_URL.help.join('\n');
throw error;
}
/**
* Raised when unable to lookup a route using its identifier
*/
static cannotLookupRoute(identifier) {
const error = new this((0, helpers_1.interpolate)(exceptions_json_1.E_CANNOT_LOOKUP_ROUTE.message, { identifier }), exceptions_json_1.E_CANNOT_LOOKUP_ROUTE.status, exceptions_json_1.E_CANNOT_LOOKUP_ROUTE.code);
error.help = exceptions_json_1.E_CANNOT_LOOKUP_ROUTE.help.join('\n');
throw error;
}
/**
* Raised when unable to lookup domain
*/
static cannotLookupDomain(domain) {
const error = new this((0, helpers_1.interpolate)(exceptions_json_1.E_CANNOT_LOOKUP_DOMAIN.message, { domain }), exceptions_json_1.E_CANNOT_LOOKUP_DOMAIN.status, exceptions_json_1.E_CANNOT_LOOKUP_DOMAIN.code);
error.help = exceptions_json_1.E_CANNOT_LOOKUP_DOMAIN.help.join('\n');
throw error;
}
}
exports.RouterException = RouterException;
@@ -0,0 +1,24 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../../adonis-typings/index.d.ts" />
/// <reference types="node" />
import { AsyncLocalStorage } from 'async_hooks';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
/**
* Find if the async localstorage is enabled or not
*/
export declare let usingAsyncLocalStorage: boolean;
/**
* Toggle the async local storage
*/
export declare function useAsyncLocalStorage(enabled: boolean): void;
/**
* Async local storage for the HTTP context
*/
export declare const httpContextLocalStorage: AsyncLocalStorage<HttpContextContract>;
@@ -0,0 +1,28 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.httpContextLocalStorage = exports.useAsyncLocalStorage = exports.usingAsyncLocalStorage = void 0;
/// <reference path="../../../adonis-typings/index.ts" />
const async_hooks_1 = require("async_hooks");
/**
* Find if the async localstorage is enabled or not
*/
exports.usingAsyncLocalStorage = false;
/**
* Toggle the async local storage
*/
function useAsyncLocalStorage(enabled) {
exports.usingAsyncLocalStorage = enabled;
}
exports.useAsyncLocalStorage = useAsyncLocalStorage;
/**
* Async local storage for the HTTP context
*/
exports.httpContextLocalStorage = new async_hooks_1.AsyncLocalStorage();
+90
View File
@@ -0,0 +1,90 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="@adonisjs/logger/build/adonis-typings/logger" />
/// <reference types="@adonisjs/profiler/build/adonis-typings/profiler" />
/// <reference types="node" />
import { Macroable } from 'macroable';
import { RouteNode } from '@ioc:Adonis/Core/Route';
import { IncomingMessage, ServerResponse } from 'http';
import { LoggerContract } from '@ioc:Adonis/Core/Logger';
import { RequestContract } from '@ioc:Adonis/Core/Request';
import { ResponseContract } from '@ioc:Adonis/Core/Response';
import { ProfilerRowContract } from '@ioc:Adonis/Core/Profiler';
import { ApplicationContract } from '@ioc:Adonis/Core/Application';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
/**
* Http context is passed to all route handlers, middleware,
* error handler and server hooks.
*/
export declare class HttpContext extends Macroable implements HttpContextContract {
request: RequestContract;
response: ResponseContract;
logger: LoggerContract;
profiler: ProfilerRowContract;
/**
* Set inside the provider
*/
static app: ApplicationContract;
/**
* Find if async localstorage is enabled for HTTP requests
* or not
*/
static get usingAsyncLocalStorage(): boolean;
/**
* Get access to the HTTP context. Available only when
* "usingAsyncLocalStorage" is true
*/
static get(): HttpContextContract | null;
/**
* Get the HttpContext instance or raise an exception if not
* available
*/
static getOrFail(): HttpContextContract;
/**
* Run a method that doesn't have access to HTTP context from
* the async local storage.
*/
static runOutsideContext<T>(callback: (...args: any[]) => T, ...args: any[]): T;
/**
* A unique key for the current route
*/
routeKey: string;
/**
* Route params
*/
params: Record<string, any>;
/**
* Route subdomains
*/
subdomains: Record<string, any>;
/**
* Reference to the current route. Not available inside
* server hooks
*/
route?: RouteNode & {
params: string[];
};
/**
* Required by macroable
*/
protected static macros: {};
protected static getters: {};
constructor(request: RequestContract, response: ResponseContract, logger: LoggerContract, profiler: ProfilerRowContract);
/**
* A helper to see top level properties on the context object
*/
inspect(): string;
/**
* Creates a new fake context instance for a given route. The method is
* meant to be used inside an AdonisJS application since it relies
* directly on the IoC container.
*/
static create(routePattern: string, routeParams: Record<string, any>, req?: IncomingMessage, res?: ServerResponse): HttpContext;
}
+181
View File
@@ -0,0 +1,181 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpContext = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const net_1 = require("net");
const util_1 = require("util");
const macroable_1 = require("macroable");
const matchit_1 = __importDefault(require("@poppinss/matchit"));
const utils_1 = require("@poppinss/utils");
const http_1 = require("http");
const Request_1 = require("../Request");
const Response_1 = require("../Response");
const helpers_1 = require("../helpers");
const LocalStorage_1 = require("./LocalStorage");
const exceptions_json_1 = require("../../exceptions.json");
/**
* Http context is passed to all route handlers, middleware,
* error handler and server hooks.
*/
class HttpContext extends macroable_1.Macroable {
constructor(request, response, logger, profiler) {
super();
this.request = request;
this.response = response;
this.logger = logger;
this.profiler = profiler;
/**
* Route params
*/
this.params = {};
/**
* Route subdomains
*/
this.subdomains = {};
/*
* Creating the circular reference. We do this, since request and response
* are meant to be extended and at times people would want to access
* other ctx properties like `logger`, `profiler` inside those
* extended methods.
*/
this.request.ctx = this;
this.response.ctx = this;
}
/**
* Find if async localstorage is enabled for HTTP requests
* or not
*/
static get usingAsyncLocalStorage() {
return LocalStorage_1.usingAsyncLocalStorage;
}
/**
* Get access to the HTTP context. Available only when
* "usingAsyncLocalStorage" is true
*/
static get() {
if (!LocalStorage_1.usingAsyncLocalStorage) {
return null;
}
return LocalStorage_1.httpContextLocalStorage.getStore() || null;
}
/**
* Get the HttpContext instance or raise an exception if not
* available
*/
static getOrFail() {
/**
* Localstorage is not enabled
*/
if (!LocalStorage_1.usingAsyncLocalStorage) {
const error = new utils_1.Exception(exceptions_json_1.E_INVALID_ALS_ACCESS.message, exceptions_json_1.E_INVALID_ALS_ACCESS.status, exceptions_json_1.E_INVALID_ALS_ACCESS.code);
error.help = exceptions_json_1.E_INVALID_ALS_ACCESS.help.join('\n');
throw error;
}
const store = this.get();
/**
* Store is not accessible
*/
if (!store) {
const error = new utils_1.Exception(exceptions_json_1.E_INVALID_ALS_SCOPE.message, exceptions_json_1.E_INVALID_ALS_SCOPE.status, exceptions_json_1.E_INVALID_ALS_SCOPE.code);
error.help = exceptions_json_1.E_INVALID_ALS_SCOPE.help.join('\n');
throw error;
}
return store;
}
/**
* Run a method that doesn't have access to HTTP context from
* the async local storage.
*/
static runOutsideContext(callback, ...args) {
return LocalStorage_1.httpContextLocalStorage.exit(callback, ...args);
}
/**
* A helper to see top level properties on the context object
*/
inspect() {
return (0, util_1.inspect)(this, false, 1, true);
}
/**
* Creates a new fake context instance for a given route. The method is
* meant to be used inside an AdonisJS application since it relies
* directly on the IoC container.
*/
static create(routePattern, routeParams, req, res) {
const Router = HttpContext.app.container.resolveBinding('Adonis/Core/Route');
const Encryption = HttpContext.app.container.resolveBinding('Adonis/Core/Encryption');
const serverConfig = HttpContext.app.container
.resolveBinding('Adonis/Core/Config')
.get('app.http', {});
req = req || new http_1.IncomingMessage(new net_1.Socket());
res = res || new http_1.ServerResponse(req);
/*
* Creating the url from the router pattern and params. Only
* when actual URL isn't defined.
*/
req.url = req.url || (0, helpers_1.processPattern)(routePattern, routeParams);
/*
* Creating new request instance
*/
const request = new Request_1.Request(req, res, Encryption, {
allowMethodSpoofing: serverConfig.allowMethodSpoofing,
subdomainOffset: serverConfig.subdomainOffset,
trustProxy: serverConfig.trustProxy,
generateRequestId: serverConfig.generateRequestId,
});
/*
* Creating new response instance
*/
const response = new Response_1.Response(req, res, Encryption, {
etag: serverConfig.etag,
cookie: serverConfig.cookie,
jsonpCallbackName: serverConfig.jsonpCallbackName,
}, Router);
/*
* Creating new ctx instance
*/
const ctx = new HttpContext(request, response, this.app.logger.child({}), this.app.profiler.create('http:context'));
/*
* Attaching route to the ctx
*/
ctx.route = {
pattern: routePattern,
middleware: [],
handler: async () => 'handled',
meta: {},
params: matchit_1.default
.parse(routePattern, {})
.filter((token) => [1, 3].includes(token.type))
.map((token) => token.val),
};
/*
* Defining route key
*/
ctx.routeKey = `${request.method() || 'GET'}-${ctx.route.pattern}`;
/*
* Attaching params to the ctx
*/
ctx.params = routeParams;
/**
* Set params on the request
*/
ctx.request.updateParams(routeParams);
return ctx;
}
}
exports.HttpContext = HttpContext;
/**
* Required by macroable
*/
HttpContext.macros = {};
HttpContext.getters = {};
@@ -0,0 +1,92 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
import { IocContract } from '@adonisjs/fold';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { MiddlewareHandler, MiddlewareStoreContract, ResolvedMiddlewareHandler } from '@ioc:Adonis/Core/Middleware';
/**
* Middleware store register and keep all the application middleware at one
* place. Also middleware are resolved during the registration and any
* part of the application can read them without extra overhead.
*
* @example
* ```ts
* const store = new MiddlewareStore()
*
* store.register([
* 'App/Middleware/Auth'
* ])
*
* store.registerNamed({
* auth: 'App/Middleware/Auth'
* })
*
* store.get()
* ```
*/
export declare class MiddlewareStore implements MiddlewareStoreContract {
private container;
/**
* A list of global middleware
*/
private list;
/**
* A map of named middleware. Named middleware are used as reference
* on the routes
*/
private named;
/**
* The resolver to resolve middleware from the IoC container
*/
private resolver;
constructor(container: IocContract);
/**
* Resolves the middleware node based upon it's type. If value is a string, then
* we pre-fetch it from the IoC container upfront. On every request, we just
* create a new instance of the class and avoid re-fetching it from the IoC
* container for performance reasons.
*
* The annoying part is that one has to create the middleware before registering
* it, otherwise an exception will be raised.
*/
private resolveMiddleware;
/**
* Register an array of global middleware. These middleware are read
* by HTTP server and executed on every request
*/
register(middleware: MiddlewareHandler[]): this;
/**
* Register named middleware that can be referenced later on routes
*/
registerNamed(middleware: {
[alias: string]: MiddlewareHandler;
}): this;
/**
* Return all middleware registered using [[MiddlewareStore.register]]
* method
*/
get(): ResolvedMiddlewareHandler[];
/**
* Returns a single middleware by it's name registered
* using [[MiddlewareStore.registerNamed]] method.
*/
getNamed(name: string): null | ResolvedMiddlewareHandler;
/**
* Clears all the global middleware
*/
clear(): void;
/**
* Clears all/selected named middleware
*/
clearNamed(names?: string[]): void;
/**
* Invokes a resolved middleware.
*/
invokeMiddleware(middleware: ResolvedMiddlewareHandler, params: [HttpContextContract, () => Promise<void>]): Promise<void>;
}
+133
View File
@@ -0,0 +1,133 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MiddlewareStore = void 0;
/**
* Middleware store register and keep all the application middleware at one
* place. Also middleware are resolved during the registration and any
* part of the application can read them without extra overhead.
*
* @example
* ```ts
* const store = new MiddlewareStore()
*
* store.register([
* 'App/Middleware/Auth'
* ])
*
* store.registerNamed({
* auth: 'App/Middleware/Auth'
* })
*
* store.get()
* ```
*/
class MiddlewareStore {
constructor(container) {
this.container = container;
/**
* A list of global middleware
*/
this.list = [];
/**
* A map of named middleware. Named middleware are used as reference
* on the routes
*/
this.named = {};
this.resolver = container.getResolver();
}
/**
* Resolves the middleware node based upon it's type. If value is a string, then
* we pre-fetch it from the IoC container upfront. On every request, we just
* create a new instance of the class and avoid re-fetching it from the IoC
* container for performance reasons.
*
* The annoying part is that one has to create the middleware before registering
* it, otherwise an exception will be raised.
*/
resolveMiddleware(middleware) {
return typeof middleware === 'function'
? {
type: 'lazy-import',
value: middleware,
args: [],
}
: Object.assign(this.resolver.resolve(`${middleware}.handle`), { args: [] });
}
/**
* Register an array of global middleware. These middleware are read
* by HTTP server and executed on every request
*/
register(middleware) {
this.list = middleware.map(this.resolveMiddleware.bind(this));
return this;
}
/**
* Register named middleware that can be referenced later on routes
*/
registerNamed(middleware) {
this.named = Object.keys(middleware).reduce((result, alias) => {
result[alias] = this.resolveMiddleware(middleware[alias]);
return result;
}, {});
return this;
}
/**
* Return all middleware registered using [[MiddlewareStore.register]]
* method
*/
get() {
return this.list;
}
/**
* Returns a single middleware by it's name registered
* using [[MiddlewareStore.registerNamed]] method.
*/
getNamed(name) {
return this.named[name] || null;
}
/**
* Clears all the global middleware
*/
clear() {
this.list = [];
}
/**
* Clears all/selected named middleware
*/
clearNamed(names) {
if (names) {
names.forEach((name) => delete this.named[name]);
}
else {
this.named = {};
}
}
/**
* Invokes a resolved middleware.
*/
async invokeMiddleware(middleware, params) {
if (middleware.type === 'function') {
return middleware.value(params[0], params[1], middleware.args);
}
const args = [params[0], params[1]];
args.push(middleware.args);
/**
* Handling the lazily imported middleware
*/
if (middleware.type === 'lazy-import') {
const middlewareClass = await middleware.value();
const middlewareInstance = await this.container.makeAsync(middlewareClass.default);
return this.container.callAsync(middlewareInstance, 'handle', args);
}
return this.resolver.call(middleware, undefined, args);
}
}
exports.MiddlewareStore = MiddlewareStore;
+71
View File
@@ -0,0 +1,71 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="node" />
import { IncomingMessage } from 'http';
import { RouterContract, MakeUrlOptions } from '@ioc:Adonis/Core/Route';
import { RedirectContract, ResponseContract } from '@ioc:Adonis/Core/Response';
/**
* Exposes the API to construct redirect routes
*/
export declare class Redirect implements RedirectContract {
private request;
private response;
private router;
/**
* A boolean to forward the existing query string
*/
private forwardQueryString;
/**
* The status code for the redirect
*/
private statusCode;
/**
* A custom query string to forward
*/
private queryString;
constructor(request: IncomingMessage, response: ResponseContract, router: RouterContract);
/**
* Sends response by setting require headers
*/
private sendResponse;
/**
* Returns the referrer url
*/
private getReferrerUrl;
/**
* Set a custom status code.
*/
status(statusCode: number): this;
/**
* Clearing query string values added using the
* "withQs" method
*/
clearQs(): this;
/**
* Define query string for the redirect. Not passing
* any value will forward the current request query
* string.
*/
withQs(): this;
withQs(values: Record<string, any>): this;
withQs(name: string, value: any): this;
/**
* Redirect to the previous path.
*/
back(): void;
/**
* Redirect the request using a route identifier.
*/
toRoute(routeIdentifier: string, params?: any[] | MakeUrlOptions, options?: MakeUrlOptions): void;
/**
* Redirect the request using a path.
*/
toPath(url: string): void;
}
+139
View File
@@ -0,0 +1,139 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Redirect = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const qs_1 = __importDefault(require("qs"));
const url_1 = require("url");
const encodeurl_1 = __importDefault(require("encodeurl"));
/**
* Exposes the API to construct redirect routes
*/
class Redirect {
constructor(request, response, router) {
this.request = request;
this.response = response;
this.router = router;
/**
* A boolean to forward the existing query string
*/
this.forwardQueryString = false;
/**
* The status code for the redirect
*/
this.statusCode = 302;
/**
* A custom query string to forward
*/
this.queryString = {};
}
/**
* Sends response by setting require headers
*/
sendResponse(url, query) {
const stringified = qs_1.default.stringify(query);
url = stringified ? `${url}?${stringified}` : url;
this.response.location((0, encodeurl_1.default)(url));
this.response.safeStatus(this.statusCode);
this.response.type('text/plain; charset=utf-8');
this.response.send(`Redirecting to ${url}`);
}
/**
* Returns the referrer url
*/
getReferrerUrl() {
let url = this.request.headers['referer'] || this.request.headers['referrer'] || '/';
return Array.isArray(url) ? url[0] : url;
}
/**
* Set a custom status code.
*/
status(statusCode) {
this.statusCode = statusCode;
return this;
}
/**
* Clearing query string values added using the
* "withQs" method
*/
clearQs() {
this.forwardQueryString = false;
this.queryString = {};
return this;
}
withQs(name, value) {
if (typeof name === 'undefined') {
this.forwardQueryString = true;
return this;
}
if (typeof name === 'string') {
this.queryString[name] = value;
return this;
}
Object.assign(this.queryString, name);
return this;
}
/**
* Redirect to the previous path.
*/
back() {
let query = {};
const url = (0, url_1.parse)(this.getReferrerUrl());
/**
* Parse query string from the referrer url
*/
if (this.forwardQueryString) {
query = qs_1.default.parse(url.query || '');
}
/**
* Append custom query string
*/
Object.assign(query, this.queryString);
/**
* Redirect
*/
this.sendResponse(url.pathname || '', query);
}
/**
* Redirect the request using a route identifier.
*/
toRoute(routeIdentifier, params, options) {
if (options && options.qs) {
this.withQs(options.qs);
options.qs = undefined;
}
const url = this.router.makeUrl(routeIdentifier, params, options);
return this.toPath(url);
}
/**
* Redirect the request using a path.
*/
toPath(url) {
let query = {};
/**
* Extract query string from the current URL
*/
if (this.forwardQueryString) {
query = qs_1.default.parse((0, url_1.parse)(this.request.url).query || '');
}
/**
* Assign custom query string
*/
Object.assign(query, this.queryString);
/**
* Redirect
*/
this.sendResponse(url, query);
}
}
exports.Redirect = Redirect;
+619
View File
@@ -0,0 +1,619 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="node" />
/// <reference types="node" />
import { Macroable } from 'macroable';
import { UrlWithStringQuery } from 'url';
import { ServerResponse, IncomingMessage, IncomingHttpHeaders } from 'http';
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { RequestContract, RequestConfig } from '@ioc:Adonis/Core/Request';
/**
* HTTP Request class exposes the interface to consistently read values
* related to a given HTTP request. The class is wrapper over
* [IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
* and has extended API.
*
* You can access the original [IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
* using `request.request` property.
*/
export declare class Request extends Macroable implements RequestContract {
request: IncomingMessage;
response: ServerResponse;
private encryption;
private config;
/**
* Request body set using `setBody` method
*/
private requestBody;
/**
* Route params
*/
private routeParams;
/**
* A merged copy of `request body` and `querystring`
*/
private requestData;
/**
* Original merged copy of `request body` and `querystring`.
* Further mutation to this object are not allowed
*/
private originalRequestData;
/**
* Parsed query string
*/
private requestQs;
/**
* Raw request body as text
*/
private rawRequestBody;
/**
* Cached copy of `accepts` fn to do content
* negotiation.
*/
private lazyAccepts;
/**
* Copy of lazily parsed signed and plain cookies.
*/
private cookieParser;
/**
* Required by Macroable
*/
protected static macros: {};
protected static getters: {};
/**
* Parses copy of the URL with query string as a string and not
* object. This is done to build URL's with query string without
* stringifying the object
*/
parsedUrl: UrlWithStringQuery;
/**
* The ctx will be set by the context itself. It creates a circular
* reference
*/
ctx?: HttpContextContract;
constructor(request: IncomingMessage, response: ServerResponse, encryption: EncryptionContract, config: RequestConfig);
/**
* Parses the query string
*/
private parseQueryString;
/**
* Initiates the cookie parser lazily
*/
private initiateCookieParser;
/**
* Lazily initiates the `accepts` module to make sure to parse
* the request headers only when one of the content-negotiation
* methods are used.
*/
private initiateAccepts;
/**
* Returns the request id from the `x-request-id` header. The
* header is untoched, if it already exists.
*/
id(): string | undefined;
/**
* Set initial request body. A copy of the input will be maintained as the original
* request body. Since the request body and query string is subject to mutations, we
* keep one original reference to flash old data (whenever required).
*
* This method is supposed to be invoked by the body parser and must be called only
* once. For further mutations make use of `updateBody` method.
*/
setInitialBody(body: Record<string, any>): void;
/**
* Update the request body with new data object. The `all` property
* will be re-computed by merging the query string and request
* body.
*/
updateBody(body: Record<string, any>): void;
/**
* Update the request raw body. Bodyparser sets this when unable to parse
* the request body or when request is multipart/form-data.
*/
updateRawBody(rawBody: string): void;
/**
* Update the query string with the new data object. The `all` property
* will be re-computed by merging the query and the request body.
*/
updateQs(data: Record<string, any>): void;
/**
* Update route params
*/
updateParams(data: Record<string, any>): void;
/**
* Returns route params
*/
params(): Record<string, any>;
/**
* Returns reference to the query string object
*/
get(): Record<string, any>;
/**
* Returns the query string object by reference
*/
qs(): Record<string, any>;
/**
* Returns reference to the request body
*/
post(): Record<string, any>;
/**
* Returns reference to the request body
*/
body(): Record<string, any>;
/**
* Returns reference to the merged copy of request body
* and query string
*/
all(): Record<string, any>;
/**
* Returns reference to the merged copy of original request
* query string and body
*/
original(): Record<string, any>;
/**
* Returns the request raw body (if exists), or returns `null`.
*
* Ideally you must be dealing with the parsed body accessed using [[input]], [[all]] or
* [[post]] methods. The `raw` body is always a string.
*/
raw(): string | null;
/**
* Returns value for a given key from the request body or query string.
* The `defaultValue` is used when original value is `undefined`.
*
* @example
* ```js
* request.input('username')
*
* // with default value
* request.input('username', 'virk')
* ```
*/
input(key: string, defaultValue?: any): any;
/**
* Returns value for a given key from route params
*
* @example
* ```js
* request.param('id')
*
* // with default value
* request.param('id', 1)
* ```
*/
param(key: string, defaultValue?: any): any;
/**
* Get everything from the request body except the given keys.
*
* @example
* ```js
* request.except(['_csrf'])
* ```
*/
except(keys: string[]): Record<string, any>;
/**
* Get value for specified keys.
*
* @example
* ```js
* request.only(['username', 'age'])
* ```
*/
only<T extends string>(keys: T[]): {
[K in T]: any;
};
/**
* Returns the HTTP request method. This is the original
* request method. For spoofed request method, make
* use of [[method]].
*
* @example
* ```js
* request.intended()
* ```
*/
intended(): string;
/**
* Returns the request HTTP method by taking method spoofing into account.
*
* Method spoofing works when all of the following are true.
*
* 1. `app.http.allowMethodSpoofing` config value is true.
* 2. request query string has `_method`.
* 3. The [[intended]] request method is `POST`.
*
* @example
* ```js
* request.method()
* ```
*/
method(): string;
/**
* Returns a copy of headers as an object
*/
headers(): IncomingHttpHeaders;
/**
* Returns value for a given header key. The default value is
* used when original value is `undefined`.
*/
header(key: string, defaultValue?: any): string | undefined;
/**
* Returns the ip address of the user. This method is optimize to fetch
* ip address even when running your AdonisJs app behind a proxy.
*
* You can also define your own custom function to compute the ip address by
* defining `app.http.getIp` as a function inside the config file.
*
* ```js
* {
* http: {
* getIp (request) {
* // I am using nginx as a proxy server and want to trust 'x-real-ip'
* return request.header('x-real-ip')
* }
* }
* }
* ```
*
* You can control the behavior of trusting the proxy values by defining it
* inside the `config/app.js` file.
*
* ```js
* {
* http: {
* trustProxy: '127.0.0.1'
* }
* }
* ```
*
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
*/
ip(): string;
/**
* Returns an array of ip addresses from most to least trusted one.
* This method is optimize to fetch ip address even when running
* your AdonisJs app behind a proxy.
*
* You can control the behavior of trusting the proxy values by defining it
* inside the `config/app.js` file.
*
* ```js
* {
* http: {
* trustProxy: '127.0.0.1'
* }
* }
* ```
*
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
*/
ips(): string[];
/**
* Returns the request protocol by checking for the URL protocol or
* `X-Forwarded-Proto` header.
*
* If the `trust` is evaluated to `false`, then URL protocol is returned,
* otherwise `X-Forwarded-Proto` header is used (if exists).
*
* You can control the behavior of trusting the proxy values by defining it
* inside the `config/app.js` file.
*
* ```js
* {
* http: {
* trustProxy: '127.0.0.1'
* }
* }
* ```
*
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
*/
protocol(): string;
/**
* Returns a boolean telling if request is served over `https`
* or not. Check [[protocol]] method to know how protocol is
* fetched.
*/
secure(): boolean;
/**
* Returns the request host. If proxy headers are trusted, then
* `X-Forwarded-Host` is given priority over the `Host` header.
*
* You can control the behavior of trusting the proxy values by defining it
* inside the `config/app.js` file.
*
* ```js
* {
* http: {
* trustProxy: '127.0.0.1'
* }
* }
* ```
*
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
*/
host(): string | null;
/**
* Returns the request hostname. If proxy headers are trusted, then
* `X-Forwarded-Host` is given priority over the `Host` header.
*
* You can control the behavior of trusting the proxy values by defining it
* inside the `config/app.js` file.
*
* ```js
* {
* http: {
* trustProxy: '127.0.0.1'
* }
* }
* ```
*
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
*/
hostname(): string | null;
/**
* Returns an array of subdomains for the given host. An empty array is
* returned if [[hostname]] is `null` or is an IP address.
*
* Also `www` is not considered as a subdomain
*/
subdomains(): string[];
/**
* Returns a boolean telling, if request `X-Requested-With === 'xmlhttprequest'`
* or not.
*/
ajax(): boolean;
/**
* Returns a boolean telling, if request has `X-Pjax` header
* set or not
*/
pjax(): boolean;
/**
* Returns the request relative URL.
*
* @example
* ```js
* request.url()
*
* // include query string
* request.url(true)
* ```
*/
url(includeQueryString?: boolean): string;
/**
* Returns the complete HTTP url by combining
* [[protocol]]://[[hostname]]/[[url]]
*
* @example
* ```js
* request.completeUrl()
*
* // include query string
* request.completeUrl(true)
* ```
*/
completeUrl(includeQueryString?: boolean): string;
/**
* Find if the current HTTP request is for the given route or the routes
*/
matchesRoute(routeIdentifier: string | string[]): boolean;
/**
* Returns the best matching content type of the request by
* matching against the given types.
*
* The content type is picked from the `content-type` header and request
* must have body.
*
* The method response highly depends upon the types array values. Described below:
*
* | Type(s) | Return value |
* |----------|---------------|
* | ['json'] | json |
* | ['application/*'] | application/json |
* | ['vnd+json'] | application/json |
*
* @example
* ```js
* const bodyType = request.is(['json', 'xml'])
*
* if (bodyType === 'json') {
* // process JSON
* }
*
* if (bodyType === 'xml') {
* // process XML
* }
* ```
*/
is(types: string[]): string | null;
/**
* Returns the best type using `Accept` header and
* by matching it against the given types.
*
* If nothing is matched, then `null` will be returned
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*
* @example
* ```js
* switch (request.accepts(['json', 'html'])) {
* case 'json':
* return response.json(user)
* case 'html':
* return view.render('user', { user })
* default:
* // decide yourself
* }
* ```
*/
accepts<T extends string>(types: T[]): T | null;
/**
* Return the types that the request accepts, in the order of the
* client's preference (most preferred first).
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*/
types(): string[];
/**
* Returns the best language using `Accept-language` header
* and by matching it against the given languages.
*
* If nothing is matched, then `null` will be returned
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*
* @example
* ```js
* switch (request.language(['fr', 'de'])) {
* case 'fr':
* return view.render('about', { lang: 'fr' })
* case 'de':
* return view.render('about', { lang: 'de' })
* default:
* return view.render('about', { lang: 'en' })
* }
* ```
*/
language<T extends string>(languages: T[]): T | null;
/**
* Return the languages that the request accepts, in the order of the
* client's preference (most preferred first).
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*/
languages(): string[];
/**
* Returns the best charset using `Accept-charset` header
* and by matching it against the given charsets.
*
* If nothing is matched, then `null` will be returned
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*
* @example
* ```js
* switch (request.charset(['utf-8', 'ISO-8859-1'])) {
* case 'utf-8':
* // make utf-8 friendly response
* case 'ISO-8859-1':
* // make ISO-8859-1 friendly response
* }
* ```
*/
charset<T extends string>(charsets: T[]): T | null;
/**
* Return the charsets that the request accepts, in the order of the
* client's preference (most preferred first).
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*/
charsets(): string[];
/**
* Returns the best encoding using `Accept-encoding` header
* and by matching it against the given encodings.
*
* If nothing is matched, then `null` will be returned
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*/
encoding<T extends string>(encodings: T[]): T | null;
/**
* Return the charsets that the request accepts, in the order of the
* client's preference (most preferred first).
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*/
encodings(): string[];
/**
* Returns a boolean telling if request has body
*/
hasBody(): boolean;
/**
* Returns a boolean telling if the new response etag evaluates same
* as the request header `if-none-match`. In case of `true`, the
* server must return `304` response, telling the browser to
* use the client cache.
*
* You won't have to deal with this method directly, since AdonisJs will
* handle this for you when `http.etag = true` inside `config/app.js` file.
*
* However, this is how you can use it manually.
*
* @example
* ```js
* const responseBody = view.render('some-view')
*
* // sets the HTTP etag header for response
* response.setEtag(responseBody)
*
* if (request.fresh()) {
* response.sendStatus(304)
* } else {
* response.send(responseBody)
* }
* ```
*/
fresh(): boolean;
/**
* Opposite of [[fresh]]
*/
stale(): boolean;
/**
* Returns all parsed and signed cookies. Signed cookies ensures
* that their value isn't tampered.
*/
cookiesList(): Record<string, any>;
/**
* Returns value for a given key from signed cookies. Optional
* defaultValue is returned when actual value is undefined.
*/
cookie(key: string, defaultValue?: string): any;
/**
* Returns value for a given key from signed cookies. Optional
* defaultValue is returned when actual value is undefined.
*/
encryptedCookie(key: string, defaultValue?: string): any;
/**
* Returns value for a given key from unsigned cookies. Optional
* defaultValue is returned when actual value is undefined.
*/
plainCookie(key: string, defaultValue?: string, encoded?: boolean): any;
/**
* Returns a boolean telling if a signed url as a valid signature
* or not.
*/
hasValidSignature(purpose?: string): boolean;
/**
* toJSON copy of the request
*/
toJSON(): {
id: string | undefined;
url: string;
query: string | null;
body: Record<string, any>;
params: Record<string, any>;
headers: IncomingHttpHeaders;
method: string;
protocol: string;
cookies: Record<string, any>;
hostname: string | null;
ip: string;
subdomains: Record<string, any>;
};
}
+862
View File
@@ -0,0 +1,862 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Request = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const qs_1 = __importDefault(require("qs"));
const fresh_1 = __importDefault(require("fresh"));
const net_1 = require("net");
const type_is_1 = __importDefault(require("type-is"));
const accepts_1 = __importDefault(require("accepts"));
const proxy_addr_1 = __importDefault(require("proxy-addr"));
const macroable_1 = require("macroable");
const utils_1 = require("@poppinss/utils");
const url_1 = require("url");
const helpers_1 = require("@poppinss/utils/build/helpers");
const helpers_2 = require("../helpers");
const Parser_1 = require("../Cookie/Parser");
/**
* HTTP Request class exposes the interface to consistently read values
* related to a given HTTP request. The class is wrapper over
* [IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
* and has extended API.
*
* You can access the original [IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
* using `request.request` property.
*/
class Request extends macroable_1.Macroable {
constructor(request, response, encryption, config) {
super();
this.request = request;
this.response = response;
this.encryption = encryption;
this.config = config;
/**
* Request body set using `setBody` method
*/
this.requestBody = {};
/**
* Route params
*/
this.routeParams = {};
/**
* A merged copy of `request body` and `querystring`
*/
this.requestData = {};
/**
* Original merged copy of `request body` and `querystring`.
* Further mutation to this object are not allowed
*/
this.originalRequestData = {};
/**
* Parsed query string
*/
this.requestQs = {};
/**
* Raw request body as text
*/
this.rawRequestBody = null;
/**
* Cached copy of `accepts` fn to do content
* negotiation.
*/
this.lazyAccepts = null;
/**
* Parses copy of the URL with query string as a string and not
* object. This is done to build URL's with query string without
* stringifying the object
*/
this.parsedUrl = (0, url_1.parse)(this.request.url, false);
this.parseQueryString();
}
/**
* Parses the query string
*/
parseQueryString() {
if (this.parsedUrl.query) {
this.updateQs(qs_1.default.parse(this.parsedUrl.query));
this.originalRequestData = { ...this.requestData };
}
}
/**
* Initiates the cookie parser lazily
*/
initiateCookieParser() {
if (!this.cookieParser) {
this.cookieParser = new Parser_1.CookieParser(this.header('cookie'), this.encryption);
}
}
/**
* Lazily initiates the `accepts` module to make sure to parse
* the request headers only when one of the content-negotiation
* methods are used.
*/
initiateAccepts() {
this.lazyAccepts = this.lazyAccepts || (0, accepts_1.default)(this.request);
}
/**
* Returns the request id from the `x-request-id` header. The
* header is untoched, if it already exists.
*/
id() {
let requestId = this.header('x-request-id');
if (!requestId && this.config.generateRequestId) {
requestId = (0, helpers_1.cuid)();
this.request.headers['x-request-id'] = requestId;
}
return requestId;
}
/**
* Set initial request body. A copy of the input will be maintained as the original
* request body. Since the request body and query string is subject to mutations, we
* keep one original reference to flash old data (whenever required).
*
* This method is supposed to be invoked by the body parser and must be called only
* once. For further mutations make use of `updateBody` method.
*/
setInitialBody(body) {
if (this.originalRequestData && Object.isFrozen(this.originalRequestData)) {
throw new Error('Cannot re-set initial body. Use "request.updateBody" instead');
}
this.updateBody(body);
/*
* Freeze the original object
*/
this.originalRequestData = Object.freeze({ ...this.requestData });
}
/**
* Update the request body with new data object. The `all` property
* will be re-computed by merging the query string and request
* body.
*/
updateBody(body) {
this.requestBody = body;
this.requestData = { ...this.requestBody, ...this.requestQs };
}
/**
* Update the request raw body. Bodyparser sets this when unable to parse
* the request body or when request is multipart/form-data.
*/
updateRawBody(rawBody) {
this.rawRequestBody = rawBody;
}
/**
* Update the query string with the new data object. The `all` property
* will be re-computed by merging the query and the request body.
*/
updateQs(data) {
this.requestQs = data;
this.requestData = { ...this.requestBody, ...this.requestQs };
}
/**
* Update route params
*/
updateParams(data) {
this.routeParams = data;
}
/**
* Returns route params
*/
params() {
return this.routeParams;
}
/**
* Returns reference to the query string object
*/
get() {
process.emitWarning('DeprecationWarning', 'request.get() is deprecated. Use request.qs() instead');
return this.qs();
}
/**
* Returns the query string object by reference
*/
qs() {
return this.requestQs;
}
/**
* Returns reference to the request body
*/
post() {
process.emitWarning('DeprecationWarning', 'request.post() is deprecated. Use request.body() instead');
return this.body();
}
/**
* Returns reference to the request body
*/
body() {
return this.requestBody;
}
/**
* Returns reference to the merged copy of request body
* and query string
*/
all() {
return this.requestData;
}
/**
* Returns reference to the merged copy of original request
* query string and body
*/
original() {
return this.originalRequestData;
}
/**
* Returns the request raw body (if exists), or returns `null`.
*
* Ideally you must be dealing with the parsed body accessed using [[input]], [[all]] or
* [[post]] methods. The `raw` body is always a string.
*/
raw() {
return this.rawRequestBody;
}
/**
* Returns value for a given key from the request body or query string.
* The `defaultValue` is used when original value is `undefined`.
*
* @example
* ```js
* request.input('username')
*
* // with default value
* request.input('username', 'virk')
* ```
*/
input(key, defaultValue) {
return utils_1.lodash.get(this.requestData, key, defaultValue);
}
/**
* Returns value for a given key from route params
*
* @example
* ```js
* request.param('id')
*
* // with default value
* request.param('id', 1)
* ```
*/
param(key, defaultValue) {
return utils_1.lodash.get(this.routeParams, key, defaultValue);
}
/**
* Get everything from the request body except the given keys.
*
* @example
* ```js
* request.except(['_csrf'])
* ```
*/
except(keys) {
return utils_1.lodash.omit(this.requestData, keys);
}
/**
* Get value for specified keys.
*
* @example
* ```js
* request.only(['username', 'age'])
* ```
*/
only(keys) {
return utils_1.lodash.pick(this.requestData, keys);
}
/**
* Returns the HTTP request method. This is the original
* request method. For spoofed request method, make
* use of [[method]].
*
* @example
* ```js
* request.intended()
* ```
*/
intended() {
return this.request.method;
}
/**
* Returns the request HTTP method by taking method spoofing into account.
*
* Method spoofing works when all of the following are true.
*
* 1. `app.http.allowMethodSpoofing` config value is true.
* 2. request query string has `_method`.
* 3. The [[intended]] request method is `POST`.
*
* @example
* ```js
* request.method()
* ```
*/
method() {
if (this.config.allowMethodSpoofing && this.intended() === 'POST') {
return this.input('_method', this.intended()).toUpperCase();
}
return this.intended();
}
/**
* Returns a copy of headers as an object
*/
headers() {
return this.request.headers;
}
/**
* Returns value for a given header key. The default value is
* used when original value is `undefined`.
*/
header(key, defaultValue) {
key = key.toLowerCase();
const headers = this.headers();
switch (key) {
case 'referer':
case 'referrer':
return headers.referrer || headers.referer || defaultValue;
default:
return headers[key] || defaultValue;
}
}
/**
* Returns the ip address of the user. This method is optimize to fetch
* ip address even when running your AdonisJs app behind a proxy.
*
* You can also define your own custom function to compute the ip address by
* defining `app.http.getIp` as a function inside the config file.
*
* ```js
* {
* http: {
* getIp (request) {
* // I am using nginx as a proxy server and want to trust 'x-real-ip'
* return request.header('x-real-ip')
* }
* }
* }
* ```
*
* You can control the behavior of trusting the proxy values by defining it
* inside the `config/app.js` file.
*
* ```js
* {
* http: {
* trustProxy: '127.0.0.1'
* }
* }
* ```
*
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
*/
ip() {
const ipFn = this.config.getIp;
if (typeof ipFn === 'function') {
return ipFn(this);
}
return (0, proxy_addr_1.default)(this.request, this.config.trustProxy);
}
/**
* Returns an array of ip addresses from most to least trusted one.
* This method is optimize to fetch ip address even when running
* your AdonisJs app behind a proxy.
*
* You can control the behavior of trusting the proxy values by defining it
* inside the `config/app.js` file.
*
* ```js
* {
* http: {
* trustProxy: '127.0.0.1'
* }
* }
* ```
*
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
*/
ips() {
return proxy_addr_1.default.all(this.request, this.config.trustProxy);
}
/**
* Returns the request protocol by checking for the URL protocol or
* `X-Forwarded-Proto` header.
*
* If the `trust` is evaluated to `false`, then URL protocol is returned,
* otherwise `X-Forwarded-Proto` header is used (if exists).
*
* You can control the behavior of trusting the proxy values by defining it
* inside the `config/app.js` file.
*
* ```js
* {
* http: {
* trustProxy: '127.0.0.1'
* }
* }
* ```
*
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
*/
protocol() {
if (this.request.connection['encrypted']) {
return 'https';
}
if (!(0, helpers_2.trustProxy)(this.request.connection.remoteAddress, this.config.trustProxy)) {
return this.parsedUrl.protocol || 'http';
}
const forwardedProtocol = this.header('X-Forwarded-Proto');
return forwardedProtocol ? forwardedProtocol.split(/\s*,\s*/)[0] : 'http';
}
/**
* Returns a boolean telling if request is served over `https`
* or not. Check [[protocol]] method to know how protocol is
* fetched.
*/
secure() {
return this.protocol() === 'https';
}
/**
* Returns the request host. If proxy headers are trusted, then
* `X-Forwarded-Host` is given priority over the `Host` header.
*
* You can control the behavior of trusting the proxy values by defining it
* inside the `config/app.js` file.
*
* ```js
* {
* http: {
* trustProxy: '127.0.0.1'
* }
* }
* ```
*
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
*/
host() {
let host = this.header('host');
/*
* Use X-Fowarded-Host when we trust the proxy header and it
* exists
*/
if (!(0, helpers_2.trustProxy)(this.request.connection.remoteAddress, this.config.trustProxy)) {
host = this.header('X-Forwarded-Host') || host;
}
if (!host) {
return null;
}
return host;
}
/**
* Returns the request hostname. If proxy headers are trusted, then
* `X-Forwarded-Host` is given priority over the `Host` header.
*
* You can control the behavior of trusting the proxy values by defining it
* inside the `config/app.js` file.
*
* ```js
* {
* http: {
* trustProxy: '127.0.0.1'
* }
* }
* ```
*
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
*/
hostname() {
const host = this.host();
if (!host) {
return null;
}
/*
* Support for IPv6
* https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2
* https://github.com/nodejs/node/pull/5314
*/
const offset = host[0] === '[' ? host.indexOf(']') + 1 : 0;
const index = host.indexOf(':', offset);
return index !== -1 ? host.substring(0, index) : host;
}
/**
* Returns an array of subdomains for the given host. An empty array is
* returned if [[hostname]] is `null` or is an IP address.
*
* Also `www` is not considered as a subdomain
*/
subdomains() {
const hostname = this.hostname();
/*
* Return empty array when hostname is missing or it's
* an IP address
*/
if (!hostname || (0, net_1.isIP)(hostname)) {
return [];
}
const offset = this.config.subdomainOffset;
const subdomains = hostname.split('.').reverse().slice(offset);
/*
* Remove www from the subdomains list
*/
if (subdomains[subdomains.length - 1] === 'www') {
subdomains.splice(subdomains.length - 1, 1);
}
return subdomains;
}
/**
* Returns a boolean telling, if request `X-Requested-With === 'xmlhttprequest'`
* or not.
*/
ajax() {
const xRequestedWith = this.header('X-Requested-With', '');
return xRequestedWith.toLowerCase() === 'xmlhttprequest';
}
/**
* Returns a boolean telling, if request has `X-Pjax` header
* set or not
*/
pjax() {
return !!this.header('X-Pjax');
}
/**
* Returns the request relative URL.
*
* @example
* ```js
* request.url()
*
* // include query string
* request.url(true)
* ```
*/
url(includeQueryString) {
const pathname = this.parsedUrl.pathname;
return includeQueryString && this.parsedUrl.query
? `${pathname}?${this.parsedUrl.query}`
: pathname;
}
/**
* Returns the complete HTTP url by combining
* [[protocol]]://[[hostname]]/[[url]]
*
* @example
* ```js
* request.completeUrl()
*
* // include query string
* request.completeUrl(true)
* ```
*/
completeUrl(includeQueryString) {
const protocol = this.protocol();
const hostname = this.host();
return `${protocol}://${hostname}${this.url(includeQueryString)}`;
}
/**
* Find if the current HTTP request is for the given route or the routes
*/
matchesRoute(routeIdentifier) {
/**
* The context is missing inside the HTTP server hooks.
*/
if (!this.ctx || !this.ctx.route) {
return false;
}
const route = this.ctx.route;
/**
* Search the identifier(s) against the route "pattern", "name" and the route handler
*/
return !!(Array.isArray(routeIdentifier) ? routeIdentifier : [routeIdentifier]).find((identifier) => {
return (route.pattern === identifier || route.name === identifier || route.handler === identifier);
});
}
/**
* Returns the best matching content type of the request by
* matching against the given types.
*
* The content type is picked from the `content-type` header and request
* must have body.
*
* The method response highly depends upon the types array values. Described below:
*
* | Type(s) | Return value |
* |----------|---------------|
* | ['json'] | json |
* | ['application/*'] | application/json |
* | ['vnd+json'] | application/json |
*
* @example
* ```js
* const bodyType = request.is(['json', 'xml'])
*
* if (bodyType === 'json') {
* // process JSON
* }
*
* if (bodyType === 'xml') {
* // process XML
* }
* ```
*/
is(types) {
return (0, type_is_1.default)(this.request, types) || null;
}
/**
* Returns the best type using `Accept` header and
* by matching it against the given types.
*
* If nothing is matched, then `null` will be returned
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*
* @example
* ```js
* switch (request.accepts(['json', 'html'])) {
* case 'json':
* return response.json(user)
* case 'html':
* return view.render('user', { user })
* default:
* // decide yourself
* }
* ```
*/
accepts(types) {
this.initiateAccepts();
return this.lazyAccepts.type(types) || null;
}
/**
* Return the types that the request accepts, in the order of the
* client's preference (most preferred first).
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*/
types() {
this.initiateAccepts();
return this.lazyAccepts.types();
}
/**
* Returns the best language using `Accept-language` header
* and by matching it against the given languages.
*
* If nothing is matched, then `null` will be returned
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*
* @example
* ```js
* switch (request.language(['fr', 'de'])) {
* case 'fr':
* return view.render('about', { lang: 'fr' })
* case 'de':
* return view.render('about', { lang: 'de' })
* default:
* return view.render('about', { lang: 'en' })
* }
* ```
*/
language(languages) {
this.initiateAccepts();
return this.lazyAccepts.language(languages) || null;
}
/**
* Return the languages that the request accepts, in the order of the
* client's preference (most preferred first).
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*/
languages() {
this.initiateAccepts();
return this.lazyAccepts.languages();
}
/**
* Returns the best charset using `Accept-charset` header
* and by matching it against the given charsets.
*
* If nothing is matched, then `null` will be returned
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*
* @example
* ```js
* switch (request.charset(['utf-8', 'ISO-8859-1'])) {
* case 'utf-8':
* // make utf-8 friendly response
* case 'ISO-8859-1':
* // make ISO-8859-1 friendly response
* }
* ```
*/
charset(charsets) {
this.initiateAccepts();
return this.lazyAccepts.charset(charsets) || null;
}
/**
* Return the charsets that the request accepts, in the order of the
* client's preference (most preferred first).
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*/
charsets() {
this.initiateAccepts();
return this.lazyAccepts.charsets();
}
/**
* Returns the best encoding using `Accept-encoding` header
* and by matching it against the given encodings.
*
* If nothing is matched, then `null` will be returned
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*/
encoding(encodings) {
this.initiateAccepts();
return this.lazyAccepts.encoding(encodings) || null;
}
/**
* Return the charsets that the request accepts, in the order of the
* client's preference (most preferred first).
*
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
* docs too.
*/
encodings() {
this.initiateAccepts();
return this.lazyAccepts.encodings();
}
/**
* Returns a boolean telling if request has body
*/
hasBody() {
return type_is_1.default.hasBody(this.request);
}
/**
* Returns a boolean telling if the new response etag evaluates same
* as the request header `if-none-match`. In case of `true`, the
* server must return `304` response, telling the browser to
* use the client cache.
*
* You won't have to deal with this method directly, since AdonisJs will
* handle this for you when `http.etag = true` inside `config/app.js` file.
*
* However, this is how you can use it manually.
*
* @example
* ```js
* const responseBody = view.render('some-view')
*
* // sets the HTTP etag header for response
* response.setEtag(responseBody)
*
* if (request.fresh()) {
* response.sendStatus(304)
* } else {
* response.send(responseBody)
* }
* ```
*/
fresh() {
if (['GET', 'HEAD'].indexOf(this.intended()) === -1) {
return false;
}
const status = this.response.statusCode;
if ((status >= 200 && status < 300) || status === 304) {
return (0, fresh_1.default)(this.headers(), this.response.getHeaders());
}
return false;
}
/**
* Opposite of [[fresh]]
*/
stale() {
return !this.fresh();
}
/**
* Returns all parsed and signed cookies. Signed cookies ensures
* that their value isn't tampered.
*/
cookiesList() {
this.initiateCookieParser();
return this.cookieParser.list();
}
/**
* Returns value for a given key from signed cookies. Optional
* defaultValue is returned when actual value is undefined.
*/
cookie(key, defaultValue) {
this.initiateCookieParser();
return this.cookieParser.unsign(key) || defaultValue;
}
/**
* Returns value for a given key from signed cookies. Optional
* defaultValue is returned when actual value is undefined.
*/
encryptedCookie(key, defaultValue) {
this.initiateCookieParser();
return this.cookieParser.decrypt(key) || defaultValue;
}
/**
* Returns value for a given key from unsigned cookies. Optional
* defaultValue is returned when actual value is undefined.
*/
plainCookie(key, defaultValue, encoded) {
this.initiateCookieParser();
return this.cookieParser.decode(key, encoded) || defaultValue;
}
/**
* Returns a boolean telling if a signed url as a valid signature
* or not.
*/
hasValidSignature(purpose) {
const { signature, ...rest } = this.qs();
if (!signature) {
return false;
}
/*
* Return false when signature fails
*/
const signedUrl = this.encryption.verifier.unsign(signature, purpose);
if (!signedUrl) {
return false;
}
const queryString = qs_1.default.stringify(rest);
return queryString ? `${this.url()}?${queryString}` === signedUrl : this.url() === signedUrl;
}
/**
* toJSON copy of the request
*/
toJSON() {
return {
id: this.id(),
url: this.url(),
query: this.parsedUrl.query,
body: this.all(),
params: this.params(),
headers: this.headers(),
method: this.method(),
protocol: this.protocol(),
cookies: this.cookiesList(),
hostname: this.hostname(),
ip: this.ip(),
subdomains: this.ctx.subdomains,
};
}
}
exports.Request = Request;
/**
* Required by Macroable
*/
Request.macros = {};
Request.getters = {};
+414
View File
@@ -0,0 +1,414 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="node" />
/// <reference types="node" />
import { Macroable } from 'macroable';
import { ServerResponse, IncomingMessage } from 'http';
import { CookieOptions, CastableHeader, ResponseConfig, ResponseStream, ResponseContract, RedirectContract } from '@ioc:Adonis/Core/Response';
import { RouterContract } from '@ioc:Adonis/Core/Route';
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
/**
* The response is a wrapper over [ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse)
* streamlining the process of writing response body and automatically setting up appropriate headers.
*/
export declare class Response extends Macroable implements ResponseContract {
request: IncomingMessage;
response: ServerResponse;
private encryption;
private config;
private router;
protected static macros: {};
protected static getters: {};
private headers;
private explicitStatus;
private writerMethod;
/**
* Cookies serializer
*/
private cookieSerializer;
/**
* Returns a boolean telling if lazy body is already set or not
*/
hasLazyBody: boolean;
/**
* Returns true when response body is set using "response.download"
* and "response.attachment" methods
*/
get isStreamResponse(): boolean;
/**
* Lazy body is used to set the response body. However, do not
* write it on the socket immediately unless `response.finish`
* is called.
*/
lazyBody: any[];
/**
* The ctx will be set by the context itself. It creates a circular
* reference
*/
ctx?: HttpContextContract;
constructor(request: IncomingMessage, response: ServerResponse, encryption: EncryptionContract, config: ResponseConfig, router: RouterContract);
/**
* Returns a boolean telling if response is finished or not.
* Any more attempts to update headers or body will result
* in raised exceptions.
*/
get finished(): boolean;
/**
* Returns a boolean telling if response headers has been sent or not.
* Any more attempts to update headers will result in raised
* exceptions.
*/
get headersSent(): boolean;
/**
* Returns a boolean telling if response headers and body is written
* or not. When value is `true`, you can feel free to write headers
* and body.
*/
get isPending(): boolean;
/**
* Normalizes header value to a string or an array of string
*/
private castHeaderValue;
/**
* Ends the response by flushing headers and writing body
*/
private endResponse;
/**
* Returns type for the content body. Only following types are allowed
*
* - Dates
* - Arrays
* - Booleans
* - Objects
* - Strings
* - Buffer
*/
private getDataType;
/**
* Writes the body with appropriate response headers. Etag header is set
* when `generateEtag` is set to `true`.
*
* Empty body results in `204`.
*/
protected writeBody(content: any, generateEtag: boolean, jsonpCallbackName?: string): void;
/**
* Stream the body to the response and handles cleaning up the stream
*/
protected streamBody(body: ResponseStream, errorCallback?: (error: NodeJS.ErrnoException) => [string, number?]): Promise<void>;
/**
* Downloads a file by streaming it to the response
*/
protected streamFileForDownload(filePath: string, generateEtag: boolean, errorCallback?: (error: NodeJS.ErrnoException) => [string, number?]): Promise<void>;
/**
* Writes headers to the response.
*/
flushHeaders(statusCode?: number): this;
/**
* Returns the existing value for a given HTTP response
* header.
*/
getHeader(key: string): any;
/**
* Get response headers
*/
getHeaders(): {
[key: string]: string | string[] | number;
};
/**
* Set header on the response. To `append` values to the existing header, we suggest
* using [[append]] method.
*
* If `value` is non existy, then header won't be set.
*
* @example
* ```js
* response.header('content-type', 'application/json')
* ```
*/
header(key: string, value: CastableHeader): this;
/**
* Append value to an existing header. To replace the value, we suggest using
* [[header]] method.
*
* If `value` is not existy, then header won't be set.
*
* @example
* ```js
* response.append('set-cookie', 'username=virk')
* ```
*/
append(key: string, value: CastableHeader): this;
/**
* Adds HTTP response header, when it doesn't exists already.
*/
safeHeader(key: string, value: CastableHeader): this;
/**
* Removes the existing response header from being sent.
*/
removeHeader(key: string): this;
/**
* Returns the status code for the response
*/
getStatus(): number;
/**
* Set HTTP status code
*/
status(code: number): this;
/**
* Set's status code only when it's not explictly
* set
*/
safeStatus(code: number): this;
/**
* Set response type by looking up for the mime-type using
* partial types like file extensions.
*
* Make sure to read [mime-types](https://www.npmjs.com/package/mime-types) docs
* too.
*
* @example
* ```js
* response.type('.json') // Content-type: application/json
* ```
*/
type(type: string, charset?: string): this;
/**
* Set the Vary HTTP header
*/
vary(field: string | string[]): this;
/**
* Set etag by computing hash from the body. This class will set the etag automatically
* when `etag = true` in the defined config object.
*
* Use this function, when you want to compute etag manually for some other resons.
*/
setEtag(body: any, weak?: boolean): this;
/**
* Returns a boolean telling if the new response etag evaluates same
* as the request header `if-none-match`. In case of `true`, the
* server must return `304` response, telling the browser to
* use the client cache.
*
* You won't have to deal with this method directly, since AdonisJs will
* handle this for you when `http.etag = true` inside `config/app.js` file.
*
* However, this is how you can use it manually.
*
* @example
* ```js
* const responseBody = view.render('some-view')
*
* // sets the HTTP etag header for response
* response.setEtag(responseBody)
*
* if (response.fresh()) {
* response.sendStatus(304)
* } else {
* response.send(responseBody)
* }
* ```
*/
fresh(): boolean;
/**
* Returns the response body. Returns null when response
* body is a stream
*/
getBody(): any;
/**
* Send the body as response and optionally generate etag. The default value
* is read from `config/app.js` file, using `http.etag` property.
*
* This method buffers the body if `explicitEnd = true`, which is the default
* behavior and do not change, unless you know what you are doing.
*/
send(body: any, generateEtag?: boolean): void;
/**
* Alias of [[send]]
*/
json(body: any, generateEtag?: boolean): void;
/**
* Writes response as JSONP. The callback name is resolved as follows, with priority
* from top to bottom.
*
* 1. Explicitly defined as 2nd Param.
* 2. Fetch from request query string.
* 3. Use the config value `http.jsonpCallbackName` from `config/app.js`.
* 4. Fallback to `callback`.
*
* This method buffers the body if `explicitEnd = true`, which is the default
* behavior and do not change, unless you know what you are doing.
*/
jsonp(body: any, callbackName?: string, generateEtag?: boolean): void;
/**
* Pipe stream to the response. This method will gracefully destroy
* the stream, avoiding memory leaks.
*
* If `raiseErrors=false`, then this method will self handle all the exceptions by
* writing a generic HTTP response. To have more control over the error, it is
* recommended to set `raiseErrors=true` and wrap this function inside a
* `try/catch` statement.
*
* Streaming a file from the disk and showing 404 when file is missing.
*
* @example
* ```js
* // Errors handled automatically with generic HTTP response
* response.stream(fs.createReadStream('file.txt'))
*
* // Manually handle (note the await call)
* try {
* await response.stream(fs.createReadStream('file.txt'))
* } catch () {
* response.status(404).send('File not found')
* }
* ```
*/
stream(body: ResponseStream, errorCallback?: (error: NodeJS.ErrnoException) => [string, number?]): void;
/**
* Download file by streaming it from the file path. This method will setup
* appropriate `Content-type`, `Content-type` and `Last-modified` headers.
*
* Unexpected stream errors are handled gracefully to avoid memory leaks.
*
* If `raiseErrors=false`, then this method will self handle all the exceptions by
* writing a generic HTTP response. To have more control over the error, it is
* recommended to set `raiseErrors=true` and wrap this function inside a
* `try/catch` statement.
*
* @example
* ```js
* // Errors handled automatically with generic HTTP response
* response.download('somefile.jpg')
*
* // Manually handle (note the await call)
* try {
* await response.download('somefile.jpg')
* } catch (error) {
* response.status(error.code === 'ENOENT' ? 404 : 500)
* response.send('Cannot process file')
* }
* ```
*/
download(filePath: string, generateEtag?: boolean, errorCallback?: (error: NodeJS.ErrnoException) => [string, number?]): void;
/**
* Download the file by forcing the user to save the file vs displaying it
* within the browser.
*
* Internally calls [[download]]
*/
attachment(filePath: string, name?: string, disposition?: string, generateEtag?: boolean, errorCallback?: (error: NodeJS.ErrnoException) => [string, number?]): void;
/**
* Set the location header.
*
* @example
* ```js
* response.location('/login')
* ```
*/
location(url: string): this;
/**
* Redirect the request.
*
* @example
* ```js
* response.redirect('/foo')
* response.redirect().toRoute('foo.bar')
* response.redirect().back()
* ```
*/
redirect(): RedirectContract;
redirect(path: string, forwardQueryString?: boolean, statusCode?: number): void;
/**
* Abort the request with custom body and a status code. 400 is
* used when status is not defined
*/
abort(body: any, status?: number): never;
/**
* Abort the request with custom body and a status code when
* passed condition returns `true`
*/
abortIf(condition: any, body: any, status?: number): void;
/**
* Abort the request with custom body and a status code when
* passed condition returns `false`
*/
abortUnless(condition: any, body: any, status?: number): void;
/**
* Set signed cookie as the response header. The inline options overrides
* all options from the config (means they are not merged).
*/
cookie(key: string, value: any, options?: Partial<CookieOptions>): this;
/**
* Set unsigned cookie as the response header. The inline options overrides
* all options from the config (means they are not merged)
*/
plainCookie(key: string, value: any, options?: Partial<CookieOptions & {
encode: boolean;
}>): this;
/**
* Set unsigned cookie as the response header. The inline options overrides
* all options from the config (means they are not merged)
*/
encryptedCookie(key: string, value: any, options?: Partial<CookieOptions>): this;
/**
* Clear existing cookie.
*/
clearCookie(key: string, options?: Partial<CookieOptions>): this;
/**
* Finishes the response by writing the lazy body, when `explicitEnd = true`
* and response is already pending.
*
* Calling this method twice or when `explicitEnd = false` is noop.
*/
finish(): void;
continue(): void;
switchingProtocols(): void;
ok(body: any, generateEtag?: boolean): void;
created(body?: any, generateEtag?: boolean): void;
accepted(body: any, generateEtag?: boolean): void;
nonAuthoritativeInformation(body: any, generateEtag?: boolean): void;
noContent(): void;
resetContent(): void;
partialContent(body: any, generateEtag?: boolean): void;
multipleChoices(body?: any, generateEtag?: boolean): void;
movedPermanently(body?: any, generateEtag?: boolean): void;
movedTemporarily(body?: any, generateEtag?: boolean): void;
seeOther(body?: any, generateEtag?: boolean): void;
notModified(body?: any, generateEtag?: boolean): void;
useProxy(body?: any, generateEtag?: boolean): void;
temporaryRedirect(body?: any, generateEtag?: boolean): void;
badRequest(body?: any, generateEtag?: boolean): void;
unauthorized(body?: any, generateEtag?: boolean): void;
paymentRequired(body?: any, generateEtag?: boolean): void;
forbidden(body?: any, generateEtag?: boolean): void;
notFound(body?: any, generateEtag?: boolean): void;
methodNotAllowed(body?: any, generateEtag?: boolean): void;
notAcceptable(body?: any, generateEtag?: boolean): void;
proxyAuthenticationRequired(body?: any, generateEtag?: boolean): void;
requestTimeout(body?: any, generateEtag?: boolean): void;
conflict(body?: any, generateEtag?: boolean): void;
gone(body?: any, generateEtag?: boolean): void;
lengthRequired(body?: any, generateEtag?: boolean): void;
preconditionFailed(body?: any, generateEtag?: boolean): void;
requestEntityTooLarge(body?: any, generateEtag?: boolean): void;
requestUriTooLong(body?: any, generateEtag?: boolean): void;
unsupportedMediaType(body?: any, generateEtag?: boolean): void;
requestedRangeNotSatisfiable(body?: any, generateEtag?: boolean): void;
expectationFailed(body?: any, generateEtag?: boolean): void;
unprocessableEntity(body?: any, generateEtag?: boolean): void;
tooManyRequests(body?: any, generateEtag?: boolean): void;
internalServerError(body?: any, generateEtag?: boolean): void;
notImplemented(body?: any, generateEtag?: boolean): void;
badGateway(body?: any, generateEtag?: boolean): void;
serviceUnavailable(body?: any, generateEtag?: boolean): void;
gatewayTimeout(body?: any, generateEtag?: boolean): void;
httpVersionNotSupported(body?: any, generateEtag?: boolean): void;
}
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
import { Macroable } from 'macroable';
import { Route } from './Route';
import { BriskRouteContract, RouteMatchersNode, RouteHandler, MakeUrlOptions } from '@ioc:Adonis/Core/Route';
/**
* Brisk route enables you to expose expressive API for
* defining route handler.
*
* For example: AdonisJs uses [[BriskRoute]] `Route.on().render()`
* to render a view without defining a controller method or
* closure.
*/
export declare class BriskRoute extends Macroable implements BriskRouteContract {
private pattern;
private globalMatchers;
protected static macros: {};
protected static getters: {};
/**
* Invoked by is reference to the parent method that calls `setHandler` on
* this class. We keep a reference to the parent method name for raising
* meaningful exception
*/
private invokedBy;
/**
* Reference to route instance. Set after `setHandler` is called
*/
route: null | Route;
constructor(pattern: string, globalMatchers: RouteMatchersNode);
/**
* Set handler for the brisk route. The `invokedBy` string is the reference
* to the method that calls this method. It is required to create human
* readable error message when `setHandler` is called for multiple
* times.
*/
setHandler(handler: RouteHandler, invokedBy: string, methods?: string[]): Route;
/**
* Redirect to a given route. Params from the original request will be used when no
* custom params are defined
*/
redirect(identifier: string, params?: any[] | Record<string, any>, options?: MakeUrlOptions): Route;
/**
* Redirect request to a fixed path
*/
redirectToPath(url: string): Route;
}
+74
View File
@@ -0,0 +1,74 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BriskRoute = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const macroable_1 = require("macroable");
const utils_1 = require("@poppinss/utils");
const Route_1 = require("./Route");
/**
* Brisk route enables you to expose expressive API for
* defining route handler.
*
* For example: AdonisJs uses [[BriskRoute]] `Route.on().render()`
* to render a view without defining a controller method or
* closure.
*/
class BriskRoute extends macroable_1.Macroable {
constructor(pattern, globalMatchers) {
super();
this.pattern = pattern;
this.globalMatchers = globalMatchers;
/**
* Invoked by is reference to the parent method that calls `setHandler` on
* this class. We keep a reference to the parent method name for raising
* meaningful exception
*/
this.invokedBy = '';
/**
* Reference to route instance. Set after `setHandler` is called
*/
this.route = null;
}
/**
* Set handler for the brisk route. The `invokedBy` string is the reference
* to the method that calls this method. It is required to create human
* readable error message when `setHandler` is called for multiple
* times.
*/
setHandler(handler, invokedBy, methods) {
if (this.route) {
throw new utils_1.Exception(`\`Route.${invokedBy}\` and \`${this.invokedBy}\` cannot be called together`, 500, 'E_MULTIPLE_BRISK_HANDLERS');
}
this.route = new Route_1.Route(this.pattern, methods || ['GET', 'HEAD'], handler, this.globalMatchers);
this.invokedBy = invokedBy;
return this.route;
}
/**
* Redirect to a given route. Params from the original request will be used when no
* custom params are defined
*/
redirect(identifier, params, options) {
return this.setHandler(async (ctx) => {
return ctx.response.redirect().toRoute(identifier, params || ctx.params, options);
}, 'redirect');
}
/**
* Redirect request to a fixed path
*/
redirectToPath(url) {
return this.setHandler(async (ctx) => {
return ctx.response.redirect().toPath(url);
}, 'redirect');
}
}
exports.BriskRoute = BriskRoute;
BriskRoute.macros = {};
BriskRoute.getters = {};
+101
View File
@@ -0,0 +1,101 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
import { Macroable } from 'macroable';
import { RouteGroupContract, RouteParamMatcher, RouteMiddlewareHandler } from '@ioc:Adonis/Core/Route';
import { Route } from './Route';
import { BriskRoute } from './BriskRoute';
import { RouteResource } from './Resource';
/**
* Group class exposes the API to take action on a group of routes.
* The group routes must be pre-defined using the constructor.
*/
export declare class RouteGroup extends Macroable implements RouteGroupContract {
routes: (Route | RouteResource | BriskRoute | RouteGroup)[];
protected static macros: {};
protected static getters: {};
/**
* Array of middleware registered on the group
*/
private groupMiddleware;
/**
* We register the group middleware only once with the route
* and then mutate the internal stack. This ensures that
* group own middleware are pushed to the last, but the
* entire group of middleware is added to the front
* in the routes
*/
private registeredMiddlewareWithRoute;
constructor(routes: (Route | RouteResource | BriskRoute | RouteGroup)[]);
/**
* Invokes a given method with params on the route instance or route
* resource instance
*/
private invoke;
/**
* Define Regex matchers for a given param for all the routes.
*
* @example
* ```ts
* Route.group(() => {
* }).where('id', /^[0-9]+/)
* ```
*/
where(param: string, matcher: RouteParamMatcher): this;
/**
* Define prefix all the routes in the group.
*
* @example
* ```ts
* Route.group(() => {
* }).prefix('v1')
* ```
*/
prefix(prefix: string): this;
/**
* Define domain for all the routes.
*
* @example
* ```ts
* Route.group(() => {
* }).domain(':name.adonisjs.com')
* ```
*/
domain(domain: string): this;
/**
* Prepend name to the routes name.
*
* @example
* ```ts
* Route.group(() => {
* }).as('version1')
* ```
*/
as(name: string): this;
/**
* Prepend an array of middleware to all routes middleware.
*
* @example
* ```ts
* Route.group(() => {
* }).middleware(['auth'])
* ```
*/
middleware(middleware: RouteMiddlewareHandler | RouteMiddlewareHandler[], prepend?: boolean): this;
/**
* Define namespace for all the routes inside the group.
*
* @example
* ```ts
* Route.group(() => {
* }).namespace('App/Admin/Controllers')
* ```
*/
namespace(namespace: string): this;
}
+165
View File
@@ -0,0 +1,165 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouteGroup = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const macroable_1 = require("macroable");
const BriskRoute_1 = require("./BriskRoute");
const Resource_1 = require("./Resource");
const RouterException_1 = require("../Exceptions/RouterException");
/**
* Group class exposes the API to take action on a group of routes.
* The group routes must be pre-defined using the constructor.
*/
class RouteGroup extends macroable_1.Macroable {
constructor(routes) {
super();
this.routes = routes;
/**
* Array of middleware registered on the group
*/
this.groupMiddleware = [];
/**
* We register the group middleware only once with the route
* and then mutate the internal stack. This ensures that
* group own middleware are pushed to the last, but the
* entire group of middleware is added to the front
* in the routes
*/
this.registeredMiddlewareWithRoute = false;
}
/**
* Invokes a given method with params on the route instance or route
* resource instance
*/
invoke(route, method, params) {
if (route instanceof Resource_1.RouteResource) {
route.routes.forEach((child) => this.invoke(child, method, params));
return;
}
if (route instanceof RouteGroup) {
route.routes.forEach((child) => this.invoke(child, method, params));
return;
}
if (route instanceof BriskRoute_1.BriskRoute) {
/* istanbul ignore else */
if (route.route) {
/*
* Raise error when trying to prefix route name but route doesn't have
* a name
*/
if (method === 'as' && !route.route.name) {
throw RouterException_1.RouterException.cannotDefineGroupName();
}
route.route[method](...params);
}
return;
}
/*
* Raise error when trying to prefix route name but route doesn't have
* a name
*/
if (method === 'as' && !route.name) {
throw RouterException_1.RouterException.cannotDefineGroupName();
}
route[method](...params);
}
/**
* Define Regex matchers for a given param for all the routes.
*
* @example
* ```ts
* Route.group(() => {
* }).where('id', /^[0-9]+/)
* ```
*/
where(param, matcher) {
this.routes.forEach((route) => this.invoke(route, 'where', [param, matcher]));
return this;
}
/**
* Define prefix all the routes in the group.
*
* @example
* ```ts
* Route.group(() => {
* }).prefix('v1')
* ```
*/
prefix(prefix) {
this.routes.forEach((route) => this.invoke(route, 'prefix', [prefix]));
return this;
}
/**
* Define domain for all the routes.
*
* @example
* ```ts
* Route.group(() => {
* }).domain(':name.adonisjs.com')
* ```
*/
domain(domain) {
this.routes.forEach((route) => this.invoke(route, 'domain', [domain]));
return this;
}
/**
* Prepend name to the routes name.
*
* @example
* ```ts
* Route.group(() => {
* }).as('version1')
* ```
*/
as(name) {
this.routes.forEach((route) => this.invoke(route, 'as', [name, true]));
return this;
}
/**
* Prepend an array of middleware to all routes middleware.
*
* @example
* ```ts
* Route.group(() => {
* }).middleware(['auth'])
* ```
*/
middleware(middleware, prepend = false) {
middleware = Array.isArray(middleware) ? middleware : [middleware];
if (prepend) {
middleware.forEach((one) => this.groupMiddleware.unshift(one));
}
else {
middleware.forEach((one) => this.groupMiddleware.push(one));
}
if (!this.registeredMiddlewareWithRoute) {
this.registeredMiddlewareWithRoute = true;
this.routes.forEach((route) => this.invoke(route, 'middleware', [this.groupMiddleware, true]));
}
return this;
}
/**
* Define namespace for all the routes inside the group.
*
* @example
* ```ts
* Route.group(() => {
* }).namespace('App/Admin/Controllers')
* ```
*/
namespace(namespace) {
this.routes.forEach((route) => this.invoke(route, 'namespace', [namespace]));
return this;
}
}
exports.RouteGroup = RouteGroup;
RouteGroup.macros = {};
RouteGroup.getters = {};
+122
View File
@@ -0,0 +1,122 @@
import { RouteJSON, LookupStoreTree, UrlBuilderContract, LookupStoreContract } from '@ioc:Adonis/Core/Route';
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption';
/**
* A class to encapsulate finding routes
*/
declare class Routes {
private routes;
constructor(routes: RouteJSON[]);
/**
* Find a route by indentifier
*/
find(routeIdentifier: string): RouteJSON | null;
/**
* Find a route by indentifier or fail
*/
findOrFail(routeIdentifier: string): RouteJSON;
/**
* Find if a route exists
*/
has(routeIdentifier: string): boolean;
}
/**
* Url builder is responsible for building the URLs
*/
export declare class UrlBuilder implements UrlBuilderContract {
private encryption;
private routes;
/**
* Params to be used for building the URL
*/
private routeParams;
/**
* A custom query string to append to the URL
*/
private queryString;
/**
* A boolean to know if the route should be looked
* up inside the route store or not
*/
private lookupRoute;
/**
* A baseUrl to prefix to the endpoint
*/
private baseUrl;
constructor(encryption: EncryptionContract, routes: Routes);
/**
* Processes the pattern against the params
*/
private processPattern;
/**
* Suffix the query string to the URL
*/
private suffixQueryString;
/**
* Prefix a custom url to the final URI
*/
prefixUrl(url: string): this;
/**
* Disable route lookup. Calling this method considers
* the "identifier" as the route pattern
*/
disableRouteLookup(): this;
/**
* Append query string to the final URI
*/
qs(queryString?: Record<string, any>): this;
/**
* Define required params to resolve the route
*/
params(params?: any[] | Record<string, any>): this;
/**
* Generate url for the given route identifier
*/
make(identifier: string): string;
/**
* Generate url for the given route identifier
*/
makeSigned(identifier: string, options?: {
expiresIn?: string | number;
purpose?: string;
}): string;
}
/**
* The look up store to make URLs for a given route by looking
* it by its name, route handler or the pattern directly.
*/
export declare class LookupStore implements LookupStoreContract {
private encryption;
/**
* Shape of the registered routes. Optimized for lookups
*/
tree: LookupStoreTree;
constructor(encryption: EncryptionContract);
/**
* Register a route for lookups
*/
register(route: RouteJSON): void;
/**
* Returns the route builder for the root domain
*/
builder(): UrlBuilder;
/**
* Returns the route builder a given domain.
*/
builderForDomain(domainPattern: string): UrlBuilder;
/**
* Find a route by indentifier. Optionally one can find routes inside
* a given domain
*/
find(routeIdentifier: string, domainPattern?: string): RouteJSON | null;
/**
* Find a route by indentifier or fail. Optionally one can find routes inside
* a given domain
*/
findOrFail(routeIdentifier: string, domainPattern?: string): RouteJSON;
/**
* Find if a route for given identifier exists. Optionally one can find routes inside
* a given domain
*/
has(routeIdentifier: string, domainPattern?: string): boolean;
}
export {};
+264
View File
@@ -0,0 +1,264 @@
"use strict";
/*
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LookupStore = exports.UrlBuilder = void 0;
const qs_1 = __importDefault(require("qs"));
const encodeurl_1 = __importDefault(require("encodeurl"));
const RouterException_1 = require("../Exceptions/RouterException");
/**
* A class to encapsulate finding routes
*/
class Routes {
constructor(routes) {
this.routes = routes;
}
/**
* Find a route by indentifier
*/
find(routeIdentifier) {
return (this.routes.find(({ name, pattern, handler }) => {
return (name === routeIdentifier || pattern === routeIdentifier || handler === routeIdentifier);
}) || null);
}
/**
* Find a route by indentifier or fail
*/
findOrFail(routeIdentifier) {
const route = this.find(routeIdentifier);
if (!route) {
throw RouterException_1.RouterException.cannotLookupRoute(routeIdentifier);
}
return route;
}
/**
* Find if a route exists
*/
has(routeIdentifier) {
return !!this.find(routeIdentifier);
}
}
/**
* Url builder is responsible for building the URLs
*/
class UrlBuilder {
constructor(encryption, routes) {
this.encryption = encryption;
this.routes = routes;
/**
* A custom query string to append to the URL
*/
this.queryString = {};
/**
* A boolean to know if the route should be looked
* up inside the route store or not
*/
this.lookupRoute = true;
}
/**
* Processes the pattern against the params
*/
processPattern(pattern) {
let url = [];
const isParamsAnArray = Array.isArray(this.routeParams);
/*
* Split pattern when route has dynamic segments
*/
const tokens = pattern.split('/');
let paramsIndex = 0;
for (const token of tokens) {
/**
* Expected wildcard param to be at the end always and hence
* we must break out from the loop
*/
if (token === '*') {
const wildcardParams = isParamsAnArray
? this.routeParams.slice(paramsIndex)
: this.routeParams['*'];
if (!wildcardParams || !Array.isArray(wildcardParams) || !wildcardParams.length) {
throw RouterException_1.RouterException.cannotMakeRoute('*', pattern);
}
url = url.concat(wildcardParams);
break;
}
/**
* Token is a static value
*/
if (!token.startsWith(':')) {
url.push(token);
}
else {
const isOptional = token.endsWith('?');
const paramName = token.replace(/^:/, '').replace(/\?$/, '');
const param = isParamsAnArray ? this.routeParams[paramsIndex] : this.routeParams[paramName];
paramsIndex++;
/*
* A required param is always required to make the complete URL
*/
if (!param && !isOptional) {
throw RouterException_1.RouterException.cannotMakeRoute(paramName, pattern);
}
url.push(param);
}
}
return url.join('/');
}
/**
* Suffix the query string to the URL
*/
suffixQueryString(url) {
if (this.queryString) {
const encoded = qs_1.default.stringify(this.queryString);
url = encoded ? `${url}?${(0, encodeurl_1.default)(encoded)}` : url;
}
return url;
}
/**
* Prefix a custom url to the final URI
*/
prefixUrl(url) {
this.baseUrl = url;
return this;
}
/**
* Disable route lookup. Calling this method considers
* the "identifier" as the route pattern
*/
disableRouteLookup() {
this.lookupRoute = false;
return this;
}
/**
* Append query string to the final URI
*/
qs(queryString) {
if (!queryString) {
return this;
}
this.queryString = queryString;
return this;
}
/**
* Define required params to resolve the route
*/
params(params) {
if (!params) {
return this;
}
this.routeParams = params;
return this;
}
/**
* Generate url for the given route identifier
*/
make(identifier) {
let url;
if (this.lookupRoute) {
const route = this.routes.findOrFail(identifier);
url = this.processPattern(route.pattern);
}
else {
url = this.processPattern(identifier);
}
return this.suffixQueryString(this.baseUrl ? `${this.baseUrl}${url}` : url);
}
/**
* Generate url for the given route identifier
*/
makeSigned(identifier, options) {
let url;
if (this.lookupRoute) {
const route = this.routes.findOrFail(identifier);
url = this.processPattern(route.pattern);
}
else {
url = this.processPattern(identifier);
}
/*
* Making the signature from the qualified url. We do not prefix the domain when
* making signature, since it just makes the signature big.
*
* There might be a case, when someone wants to generate signature for the same route
* on their 2 different domains, but we ignore that case for now and can consider
* it later (when someone asks for it)
*/
const signature = this.encryption.verifier.sign(this.suffixQueryString(url), options?.expiresIn, options?.purpose);
/*
* Adding signature to the query string and re-making the url again
*/
Object.assign(this.queryString, { signature });
return this.suffixQueryString(this.baseUrl ? `${this.baseUrl}${url}` : url);
}
}
exports.UrlBuilder = UrlBuilder;
/**
* The look up store to make URLs for a given route by looking
* it by its name, route handler or the pattern directly.
*/
class LookupStore {
constructor(encryption) {
this.encryption = encryption;
/**
* Shape of the registered routes. Optimized for lookups
*/
this.tree = {};
}
/**
* Register a route for lookups
*/
register(route) {
const domain = route.domain || 'root';
this.tree[domain] = this.tree[domain] || [];
this.tree[domain].push(route);
}
/**
* Returns the route builder for the root domain
*/
builder() {
return this.builderForDomain('root');
}
/**
* Returns the route builder a given domain.
*/
builderForDomain(domainPattern) {
const domainRoutes = this.tree[domainPattern];
if (!domainRoutes && domainPattern !== 'root') {
throw RouterException_1.RouterException.cannotLookupDomain(domainPattern);
}
return new UrlBuilder(this.encryption, new Routes(domainRoutes || []));
}
/**
* Find a route by indentifier. Optionally one can find routes inside
* a given domain
*/
find(routeIdentifier, domainPattern) {
const routes = this.tree[domainPattern || 'root'] || [];
return new Routes(routes || []).find(routeIdentifier);
}
/**
* Find a route by indentifier or fail. Optionally one can find routes inside
* a given domain
*/
findOrFail(routeIdentifier, domainPattern) {
const routes = this.tree[domainPattern || 'root'] || [];
return new Routes(routes || []).findOrFail(routeIdentifier);
}
/**
* Find if a route for given identifier exists. Optionally one can find routes inside
* a given domain
*/
has(routeIdentifier, domainPattern) {
const routes = this.tree[domainPattern || 'root'] || [];
return new Routes(routes || []).has(routeIdentifier);
}
}
exports.LookupStore = LookupStore;
+31
View File
@@ -0,0 +1,31 @@
/// <reference path="../../adonis-typings/index.d.ts" />
import { Macroable } from 'macroable';
import { RouteMatchersContract } from '@ioc:Adonis/Core/Route';
/**
* Shortcut methods for commonly used route matchers
*/
export declare class RouteMatchers extends Macroable implements RouteMatchersContract {
protected static macros: {};
protected static getters: {};
/**
* Enforce value to be a number and also casts it to number data
* type
*/
number(): {
match: RegExp;
cast: (value: string) => number;
};
/**
* Enforce value to be formatted as uuid
*/
uuid(): {
match: RegExp;
cast: (value: string) => string;
};
/**
* Enforce value to be formatted as slug
*/
slug(): {
match: RegExp;
};
}
+43
View File
@@ -0,0 +1,43 @@
"use strict";
/*
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouteMatchers = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const macroable_1 = require("macroable");
/**
* Shortcut methods for commonly used route matchers
*/
class RouteMatchers extends macroable_1.Macroable {
/**
* Enforce value to be a number and also casts it to number data
* type
*/
number() {
return { match: /^[0-9]+$/, cast: (value) => Number(value) };
}
/**
* Enforce value to be formatted as uuid
*/
uuid() {
return {
match: /^[0-9a-zA-F]{8}-[0-9a-zA-F]{4}-[0-9a-zA-F]{4}-[0-9a-zA-F]{4}-[0-9a-zA-F]{12}$/,
cast: (value) => value.toLowerCase(),
};
}
/**
* Enforce value to be formatted as slug
*/
slug() {
return { match: /^[^\s-_](?!.*?[-_]{2,})([a-z0-9-\\]{1,})[^\s]*[^-_\s]$/ };
}
}
exports.RouteMatchers = RouteMatchers;
RouteMatchers.macros = {};
RouteMatchers.getters = {};
+95
View File
@@ -0,0 +1,95 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
import { Macroable } from 'macroable';
import { RouteParamMatcher, ResourceRouteNames, RouteMatchersNode, RouteResourceContract, RouteMiddlewareHandler } from '@ioc:Adonis/Core/Route';
import { Route } from './Route';
/**
* Resource route helps in defining multiple conventional routes. The support
* for shallow routes makes it super easy to avoid deeply nested routes.
* Learn more http://weblog.jamisbuck.org/2007/2/5/nesting-resources.
*
* @example
* ```ts
* const resource = new RouteResource('articles', 'ArticlesController')
* ```
*/
export declare class RouteResource extends Macroable implements RouteResourceContract {
private resource;
private controller;
private globalMatchers;
private shallow;
protected static macros: {};
protected static getters: {};
/**
* The param names used to create the resource URLs.
*
* We need these later when someone explicitly wants to remap
* param name for a given resource using the "paramFor" method.
*/
private resourceParamNames;
/**
* A copy of routes that belongs to this resource
*/
routes: Route[];
/**
* Resource name
*/
private resourceName;
constructor(resource: string, controller: string, globalMatchers: RouteMatchersNode, shallow?: boolean);
/**
* Add a new route for the given pattern, methods and controller action
*/
private makeRoute;
/**
* Build routes for the given resource
*/
private buildRoutes;
/**
* Filter the routes based on their partial names
*/
private filter;
/**
* Register only given routes and remove others
*/
only(names: ResourceRouteNames[]): this;
/**
* Register all routes, except the one's defined
*/
except(names: ResourceRouteNames[]): this;
/**
* Register api only routes. The `create` and `edit` routes, which
* are meant to show forms will not be registered
*/
apiOnly(): this;
/**
* Add middleware to routes inside the resource
*/
middleware(middleware: {
[P in ResourceRouteNames]?: RouteMiddlewareHandler | RouteMiddlewareHandler[];
} & {
'*'?: RouteMiddlewareHandler | RouteMiddlewareHandler[];
}): this;
/**
* Define matcher for params inside the resource
*/
where(key: string, matcher: RouteParamMatcher): this;
/**
* Define namespace for all the routes inside a given resource
*/
namespace(namespace: string): this;
/**
* Set the param name for a given resource
*/
paramFor(resource: string, param: string): this;
/**
* Prepend name to the routes names
*/
as(name: string): this;
}
+182
View File
@@ -0,0 +1,182 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouteResource = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const pluralize_1 = require("pluralize");
const macroable_1 = require("macroable");
const helpers_1 = require("@poppinss/utils/build/helpers");
const Route_1 = require("./Route");
/**
* Resource route helps in defining multiple conventional routes. The support
* for shallow routes makes it super easy to avoid deeply nested routes.
* Learn more http://weblog.jamisbuck.org/2007/2/5/nesting-resources.
*
* @example
* ```ts
* const resource = new RouteResource('articles', 'ArticlesController')
* ```
*/
class RouteResource extends macroable_1.Macroable {
constructor(resource, controller, globalMatchers, shallow = false) {
super();
this.resource = resource;
this.controller = controller;
this.globalMatchers = globalMatchers;
this.shallow = shallow;
/**
* The param names used to create the resource URLs.
*
* We need these later when someone explicitly wants to remap
* param name for a given resource using the "paramFor" method.
*/
this.resourceParamNames = {};
/**
* A copy of routes that belongs to this resource
*/
this.routes = [];
/**
* Resource name
*/
this.resourceName = this.resource
.split('.')
.map((token) => helpers_1.string.snakeCase(token))
.join('.');
this.buildRoutes();
}
/**
* Add a new route for the given pattern, methods and controller action
*/
makeRoute(pattern, methods, action) {
const route = new Route_1.Route(pattern, methods, `${this.controller}.${action}`, this.globalMatchers);
route.as(`${this.resourceName}.${action}`);
this.routes.push(route);
}
/**
* Build routes for the given resource
*/
buildRoutes() {
this.resource = this.resource.replace(/^\//, '').replace(/\/$/, '');
const resourceTokens = this.resource.split('.');
const mainResource = resourceTokens.pop();
/**
* The main resource always uses ids
*/
this.resourceParamNames[mainResource] = ':id';
const fullUrl = `${resourceTokens
.map((token) => {
const paramName = `:${helpers_1.string.snakeCase((0, pluralize_1.singular)(token))}_id`;
this.resourceParamNames[token] = paramName;
return `${token}/${paramName}`;
})
.join('/')}/${mainResource}`;
this.makeRoute(fullUrl, ['GET', 'HEAD'], 'index');
this.makeRoute(`${fullUrl}/create`, ['GET', 'HEAD'], 'create');
this.makeRoute(fullUrl, ['POST'], 'store');
this.makeRoute(`${this.shallow ? mainResource : fullUrl}/:id`, ['GET', 'HEAD'], 'show');
this.makeRoute(`${this.shallow ? mainResource : fullUrl}/:id/edit`, ['GET', 'HEAD'], 'edit');
this.makeRoute(`${this.shallow ? mainResource : fullUrl}/:id`, ['PUT', 'PATCH'], 'update');
this.makeRoute(`${this.shallow ? mainResource : fullUrl}/:id`, ['DELETE'], 'destroy');
}
/**
* Filter the routes based on their partial names
*/
filter(names, inverse) {
return this.routes.filter((route) => {
const match = names.find((name) => route.name.endsWith(name));
return inverse ? !match : match;
});
}
/**
* Register only given routes and remove others
*/
only(names) {
this.filter(names, true).forEach((route) => (route.deleted = true));
return this;
}
/**
* Register all routes, except the one's defined
*/
except(names) {
this.filter(names, false).forEach((route) => (route.deleted = true));
return this;
}
/**
* Register api only routes. The `create` and `edit` routes, which
* are meant to show forms will not be registered
*/
apiOnly() {
return this.except(['create', 'edit']);
}
/**
* Add middleware to routes inside the resource
*/
middleware(middleware) {
for (let name in middleware) {
if (name === '*') {
this.routes.forEach((one) => one.middleware(middleware[name]));
}
else {
const route = this.routes.find((one) => one.name.endsWith(name));
/* istanbul ignore else */
if (route) {
route.middleware(middleware[name]);
}
}
}
return this;
}
/**
* Define matcher for params inside the resource
*/
where(key, matcher) {
this.routes.forEach((route) => {
route.where(key, matcher);
});
return this;
}
/**
* Define namespace for all the routes inside a given resource
*/
namespace(namespace) {
this.routes.forEach((route) => {
route.namespace(namespace);
});
return this;
}
/**
* Set the param name for a given resource
*/
paramFor(resource, param) {
const existingParam = this.resourceParamNames[resource];
this.resourceParamNames[resource] = `:${param}`;
this.routes.forEach((route) => {
/**
* Update the pattern for the route with the new param name
*/
route.setPattern(route.getPattern().replace(`${resource}/${existingParam}`, `${resource}/:${param}`));
});
return this;
}
/**
* Prepend name to the routes names
*/
as(name) {
name = helpers_1.string.snakeCase(name);
this.routes.forEach((route) => {
route.as(route.name.replace(this.resourceName, name), false);
});
this.resourceName = name;
return this;
}
}
exports.RouteResource = RouteResource;
RouteResource.macros = {};
RouteResource.getters = {};
+138
View File
@@ -0,0 +1,138 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
import { Macroable } from 'macroable';
import { RouteJSON, RouteContract, RouteHandler, RouteMatchersNode, RouteParamMatcher, RouteMiddlewareHandler } from '@ioc:Adonis/Core/Route';
/**
* Route class is used to construct consistent [[RouteDefinition]] using
* fluent API. An instance of route is usually obtained using the
* [[Router]] class helper methods.
*
* @example
* ```ts
* const route = new Route('posts/:id', ['GET'], async function () {
* })
*
* route
* .where('id', /^[0-9]+$/)
* .middleware(async function () {
* })
* ```
*/
export declare class Route extends Macroable implements RouteContract {
private pattern;
private methods;
private handler;
private globalMatchers;
protected static macros: {};
protected static getters: {};
/**
* By default the route is part of `root` domain. Root
* domain is used when no domain is defined
*/
private routeDomain;
/**
* An object of matchers to be forwarded to the
* store. The matchers list is populated by
* calling `where` method
*/
private matchers;
/**
* Custom prefixes. Usually added to a group of routes. We keep an array of them
* since nested groups will want all of them ot concat.
*/
private prefixes;
/**
* An array of middleware. Added using `middleware` function
*/
private routeMiddleware;
/**
* Storing the namespace explicitly set using `route.namespace` method
*/
private routeNamespace;
/**
* A boolean to prevent route from getting registered within
* the [[Store]].
*
* This flag must be set before [[Router.commit]] method
*/
deleted: boolean;
/**
* A unique name to lookup the route
*/
name: string;
constructor(pattern: string, methods: string[], handler: RouteHandler, globalMatchers: RouteMatchersNode);
/**
* Returns an object of param matchers by merging global and local
* matchers. The local copy is given preference over the global
* one's
*/
private getMatchers;
/**
* Returns a normalized pattern string by prefixing the `prefix` (if defined).
*/
private computePattern;
/**
* Define Regex matcher for a given param. If a matcher exists, then we do not
* override that, since the routes inside a group will set matchers before
* the group, so they should have priority over the route matchers.
*
* ```
* Route.group(() => {
* Route.get('/:id', 'handler').where('id', /^[0-9]$/)
* }).where('id', /[^a-z$]/)
* ```
*
* The `/^[0-9]$/` should win over the matcher defined by the group
*/
where(param: string, matcher: RouteParamMatcher): this;
/**
* Define prefix for the route. Prefixes will be concated
* This method is mainly exposed for the [[RouteGroup]]
*/
prefix(prefix: string): this;
/**
* Define a custom domain for the route. Again we do not overwrite the domain
* unless `overwrite` flag is set to true.
*
* This is again done to make route.domain win over route.group.domain
*/
domain(domain: string, overwrite?: boolean): this;
/**
* Define an array of middleware to be executed on the route. If `prepend`
* is true, then middleware will be added to start of the existing
* middleware. The option is exposed for [[RouteGroup]]
*/
middleware(middleware: RouteMiddlewareHandler | RouteMiddlewareHandler[], prepend?: boolean): this;
/**
* Give memorizable name to the route. This is helpful, when you
* want to lookup route defination by it's name.
*
* If `prepend` is true, then it will keep on prepending to the existing
* name. This option is exposed for [[RouteGroup]]
*/
as(name: string, prepend?: boolean): this;
/**
* Define controller namespace for a given route
*/
namespace(namespace: string, overwrite?: boolean): this;
/**
* Get the route pattern
*/
getPattern(): string;
/**
* Set the route pattern
*/
setPattern(pattern: string): this;
/**
* Returns [[RouteDefinition]] that can be passed to the [[Store]] for
* registering the route
*/
toJSON(): RouteJSON;
}
+204
View File
@@ -0,0 +1,204 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Route = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const macroable_1 = require("macroable");
const helpers_1 = require("@poppinss/utils/build/helpers");
const helpers_2 = require("../helpers");
/**
* Route class is used to construct consistent [[RouteDefinition]] using
* fluent API. An instance of route is usually obtained using the
* [[Router]] class helper methods.
*
* @example
* ```ts
* const route = new Route('posts/:id', ['GET'], async function () {
* })
*
* route
* .where('id', /^[0-9]+$/)
* .middleware(async function () {
* })
* ```
*/
class Route extends macroable_1.Macroable {
constructor(pattern, methods, handler, globalMatchers) {
super();
this.pattern = pattern;
this.methods = methods;
this.handler = handler;
this.globalMatchers = globalMatchers;
/**
* By default the route is part of `root` domain. Root
* domain is used when no domain is defined
*/
this.routeDomain = 'root';
/**
* An object of matchers to be forwarded to the
* store. The matchers list is populated by
* calling `where` method
*/
this.matchers = {};
/**
* Custom prefixes. Usually added to a group of routes. We keep an array of them
* since nested groups will want all of them ot concat.
*/
this.prefixes = [];
/**
* An array of middleware. Added using `middleware` function
*/
this.routeMiddleware = [];
/**
* A boolean to prevent route from getting registered within
* the [[Store]].
*
* This flag must be set before [[Router.commit]] method
*/
this.deleted = false;
}
/**
* Returns an object of param matchers by merging global and local
* matchers. The local copy is given preference over the global
* one's
*/
getMatchers() {
return Object.assign({}, this.globalMatchers, this.matchers);
}
/**
* Returns a normalized pattern string by prefixing the `prefix` (if defined).
*/
computePattern() {
const pattern = (0, helpers_2.dropSlash)(this.pattern);
const prefix = this.prefixes
.slice()
.reverse()
.map((one) => (0, helpers_2.dropSlash)(one))
.join('');
return prefix ? `${prefix}${pattern === '/' ? '' : pattern}` : pattern;
}
/**
* Define Regex matcher for a given param. If a matcher exists, then we do not
* override that, since the routes inside a group will set matchers before
* the group, so they should have priority over the route matchers.
*
* ```
* Route.group(() => {
* Route.get('/:id', 'handler').where('id', /^[0-9]$/)
* }).where('id', /[^a-z$]/)
* ```
*
* The `/^[0-9]$/` should win over the matcher defined by the group
*/
where(param, matcher) {
if (this.matchers[param]) {
return this;
}
if (typeof matcher === 'string') {
this.matchers[param] = { match: new RegExp(matcher) };
}
else if (helpers_1.types.isRegexp(matcher)) {
this.matchers[param] = { match: matcher };
}
else {
this.matchers[param] = matcher;
}
return this;
}
/**
* Define prefix for the route. Prefixes will be concated
* This method is mainly exposed for the [[RouteGroup]]
*/
prefix(prefix) {
this.prefixes.push(prefix);
return this;
}
/**
* Define a custom domain for the route. Again we do not overwrite the domain
* unless `overwrite` flag is set to true.
*
* This is again done to make route.domain win over route.group.domain
*/
domain(domain, overwrite = false) {
if (this.routeDomain === 'root' || overwrite) {
this.routeDomain = domain;
}
return this;
}
/**
* Define an array of middleware to be executed on the route. If `prepend`
* is true, then middleware will be added to start of the existing
* middleware. The option is exposed for [[RouteGroup]]
*/
middleware(middleware, prepend = false) {
middleware = Array.isArray(middleware) ? middleware : [middleware];
if (prepend) {
this.routeMiddleware.unshift(middleware);
}
else {
this.routeMiddleware.push(middleware);
}
return this;
}
/**
* Give memorizable name to the route. This is helpful, when you
* want to lookup route defination by it's name.
*
* If `prepend` is true, then it will keep on prepending to the existing
* name. This option is exposed for [[RouteGroup]]
*/
as(name, prepend = false) {
this.name = prepend ? `${name}.${this.name}` : name;
return this;
}
/**
* Define controller namespace for a given route
*/
namespace(namespace, overwrite = false) {
if (!this.routeNamespace || overwrite) {
this.routeNamespace = namespace;
}
return this;
}
/**
* Get the route pattern
*/
getPattern() {
return this.pattern;
}
/**
* Set the route pattern
*/
setPattern(pattern) {
this.pattern = pattern;
return this;
}
/**
* Returns [[RouteDefinition]] that can be passed to the [[Store]] for
* registering the route
*/
toJSON() {
return {
domain: this.routeDomain,
pattern: this.computePattern(),
matchers: this.getMatchers(),
meta: {
namespace: this.routeNamespace,
},
name: this.name,
handler: this.handler,
methods: this.methods,
middleware: this.routeMiddleware.flat(),
};
}
}
exports.Route = Route;
Route.macros = {};
Route.getters = {};
+93
View File
@@ -0,0 +1,93 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
import { RouteJSON, RoutesTree, MatchedRoute, RouteStoreMatch } from '@ioc:Adonis/Core/Route';
/**
* Store class is used to store a list of routes, along side with their tokens
* to match the URL's. The used data structures to store information is tailored
* for quick lookups.
*
* @example
* ```ts
* const store = new Store()
*
* store.add({
* pattern: 'posts/:id',
* handler: function onRoute () {},
* middleware: [],
* matchers: {
* id: '^[0-9]$+'
* },
* meta: {},
* methods: ['GET']
* })
*
* store.match('posts/1', 'GET')
* ```
*/
export declare class Store {
tree: RoutesTree;
/**
* The [[matchDomainReal]] and [[matchDomainNoop]] functions are two
* implementation of matching a domain. We use noop implementation
* by default and once an explicit domain is registered, we
* pivot to [[matchDomainReal]].
*
* This all is done for performance, since we have noticed around 8-10%
* improvement.
*/
private matchDomainReal;
private matchDomainNoop;
/**
* The implementation used for matching domain. Will pivot to `matchDomainReal`
* when one or more domains will be defined
*/
matchDomain: any;
/**
* Returns the domain node for a given domain. If domain node is missing,
* it will added to the routes object and tokens are also generated
*/
private getDomainNode;
/**
* Returns the method node for a given domain and method. If method is
* missing, it will be added to the domain node
*/
private getMethodRoutes;
/**
* Adds a route to the store for all the given HTTP methods. Also an array
* of tokens is generated for the route pattern. The tokens are then
* matched against the URL to find the appropriate route.
*
* @example
* ```ts
* store.add({
* pattern: 'post/:id',
* methods: ['GET'],
* matchers: {},
* meta: {},
* handler: function handler () {
* }
* })
* ```
*/
add(route: RouteJSON): this;
/**
* Matches the url, method and optionally domain to pull the matching
* route. `null` is returned when unable to match the URL against
* registered routes.
*
* The domain parameter has to be a registered pattern and not the fully
* qualified runtime domain. You must call `matchDomain` first to fetch
* the pattern for qualified domain
*/
match(url: string, method: string, domain?: {
storeMatch: RouteStoreMatch[];
value: string;
}): null | MatchedRoute;
}
+211
View File
@@ -0,0 +1,211 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Store = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const matchit_1 = __importDefault(require("@poppinss/matchit"));
const utils_1 = require("@poppinss/utils");
const RouterException_1 = require("../Exceptions/RouterException");
/**
* Store class is used to store a list of routes, along side with their tokens
* to match the URL's. The used data structures to store information is tailored
* for quick lookups.
*
* @example
* ```ts
* const store = new Store()
*
* store.add({
* pattern: 'posts/:id',
* handler: function onRoute () {},
* middleware: [],
* matchers: {
* id: '^[0-9]$+'
* },
* meta: {},
* methods: ['GET']
* })
*
* store.match('posts/1', 'GET')
* ```
*/
class Store {
constructor() {
this.tree = { tokens: [], domains: {} };
/**
* The [[matchDomainReal]] and [[matchDomainNoop]] functions are two
* implementation of matching a domain. We use noop implementation
* by default and once an explicit domain is registered, we
* pivot to [[matchDomainReal]].
*
* This all is done for performance, since we have noticed around 8-10%
* improvement.
*/
this.matchDomainReal = function (domain) {
return matchit_1.default.match(domain || 'root', this.tree.tokens);
}.bind(this);
this.matchDomainNoop = function (_) {
return [];
}.bind(this);
/**
* The implementation used for matching domain. Will pivot to `matchDomainReal`
* when one or more domains will be defined
*/
this.matchDomain = this.matchDomainNoop;
}
/**
* Returns the domain node for a given domain. If domain node is missing,
* it will added to the routes object and tokens are also generated
*/
getDomainNode(domain) {
if (!this.tree.domains[domain]) {
/**
* The tokens are required to match dynamic domains
*/
this.tree.tokens.push(matchit_1.default.parse(domain));
this.tree.domains[domain] = {};
}
return this.tree.domains[domain];
}
/**
* Returns the method node for a given domain and method. If method is
* missing, it will be added to the domain node
*/
getMethodRoutes(domain, method) {
const domainNode = this.getDomainNode(domain);
if (!domainNode[method]) {
domainNode[method] = { tokens: [], routes: {} };
}
return domainNode[method];
}
/**
* Adds a route to the store for all the given HTTP methods. Also an array
* of tokens is generated for the route pattern. The tokens are then
* matched against the URL to find the appropriate route.
*
* @example
* ```ts
* store.add({
* pattern: 'post/:id',
* methods: ['GET'],
* matchers: {},
* meta: {},
* handler: function handler () {
* }
* })
* ```
*/
add(route) {
/*
* Create a copy of route properties by cherry picking
* fields. We create the copy outside the forEach
* loop, so that the same object is shared across
* all the methods (saving memory).
*
* Also sharing a single route note among all the methods is fine,
* since we create sub-trees for each method to make the lookups
* fast.
*/
const routeJSON = {};
utils_1.lodash.merge(routeJSON, utils_1.lodash.pick(route, ['pattern', 'handler', 'meta', 'middleware', 'name']));
/*
* An explicit domain is defined
*/
if (route.domain && route.domain !== 'root' && this.matchDomain !== this.matchDomainReal) {
this.matchDomain = this.matchDomainReal;
}
/*
* Generate tokens for the given route and push to the list
* of tokens
*/
const tokens = matchit_1.default.parse(route.pattern, route.matchers);
const collectedParams = new Set();
/**
* Avoiding duplicate route params
*/
for (let token of tokens) {
if ([1, 3].includes(token.type)) {
if (collectedParams.has(token.val)) {
throw RouterException_1.RouterException.duplicateRouteParam(token.val, route.pattern);
}
else {
collectedParams.add(token.val);
}
}
}
routeJSON.params = new Array(...collectedParams);
collectedParams.clear();
route.methods.forEach((method) => {
const methodRoutes = this.getMethodRoutes(route.domain || 'root', method);
/*
* Ensure that route doesn't pre-exists. In that case, we need to throw
* the exception, since it's a programmer error to create multiple
* routes with the same pattern on the same method.
*/
if (methodRoutes.routes[route.pattern]) {
throw RouterException_1.RouterException.duplicateRoute(method, route.pattern);
}
methodRoutes.tokens.push(tokens);
/*
* Store reference to the route, so that we can return it to the user, when
* they call `match`.
*/
methodRoutes.routes[route.pattern] = routeJSON;
});
return this;
}
/**
* Matches the url, method and optionally domain to pull the matching
* route. `null` is returned when unable to match the URL against
* registered routes.
*
* The domain parameter has to be a registered pattern and not the fully
* qualified runtime domain. You must call `matchDomain` first to fetch
* the pattern for qualified domain
*/
match(url, method, domain) {
const matchingDomain = domain && domain.storeMatch[0] && domain.storeMatch[0].old;
const domainName = matchingDomain || 'root';
const matchedDomain = this.tree.domains[domainName];
if (!matchedDomain) {
return null;
}
/*
* Next get the method node for the given method inside the domain. If
* method node is missing, means no routes ever got registered for that
* method
*/
const matchedMethod = this.tree.domains[domainName][method];
if (!matchedMethod) {
return null;
}
/*
* Next, match route for the given url inside the tokens list for the
* matchedMethod
*/
const matchedRoute = matchit_1.default.match(url, matchedMethod.tokens);
if (!matchedRoute.length) {
return null;
}
const route = matchedMethod.routes[matchedRoute[0].old];
return {
route: route,
routeKey: matchingDomain
? `${matchingDomain}-${method}-${route.pattern}`
: `${method}-${route.pattern}`,
params: matchit_1.default.exec(url, matchedRoute),
subdomains: domain?.value ? matchit_1.default.exec(domain.value, domain.storeMatch) : {},
};
}
}
exports.Store = Store;
+142
View File
@@ -0,0 +1,142 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption';
import { RouteNode, RouteHandler, MatchedRoute, RouterContract, MakeUrlOptions, MakeSignedUrlOptions } from '@ioc:Adonis/Core/Route';
import { Route } from './Route';
import { RouteGroup } from './Group';
import { BriskRoute } from './BriskRoute';
import { RouteResource } from './Resource';
import { RouteMatchers } from './Matchers';
import { LookupStore } from './LookupStore';
/**
* Router class exposes unified API to create new routes, group them or
* create route resources.
*
* @example
* ```ts
* const router = new Router()
*
* router.get('/', async function () {
* // handle request
* })
* ```
*/
export declare class Router extends LookupStore implements RouterContract {
private routeProcessor?;
/**
* Collection of routes, including route resource and route
* group. To get a flat list of routes, call `router.toJSON()`
*/
routes: (Route | RouteResource | RouteGroup | BriskRoute)[];
/**
* Exposing BriskRoute, RouteGroup and RouteResource constructors
* to be extended from outside
*/
BriskRoute: typeof BriskRoute;
RouteGroup: typeof RouteGroup;
RouteResource: typeof RouteResource;
Route: typeof Route;
RouteMatchers: typeof RouteMatchers;
/**
* Shortcut methods for commonly used route matchers
*/
matchers: RouteMatchers;
/**
* Global matchers to test route params against regular expressions.
*/
private paramMatchers;
/**
* Store with tokenized routes
*/
private store;
/**
* A boolean to tell the router that a group is in
* open state right now
*/
private openedGroups;
private getRecentGroup;
constructor(encryption: EncryptionContract, routeProcessor?: ((route: RouteNode) => void) | undefined);
/**
* Add route for a given pattern and methods
*/
route(pattern: string, methods: string[], handler: RouteHandler): Route;
/**
* Define a route that handles all common HTTP methods
*/
any(pattern: string, handler: RouteHandler): Route;
/**
* Define `GET` route
*/
get(pattern: string, handler: RouteHandler): Route;
/**
* Define `POST` route
*/
post(pattern: string, handler: RouteHandler): Route;
/**
* Define `PUT` route
*/
put(pattern: string, handler: RouteHandler): Route;
/**
* Define `PATCH` route
*/
patch(pattern: string, handler: RouteHandler): Route;
/**
* Define `DELETE` route
*/
delete(pattern: string, handler: RouteHandler): Route;
/**
* Creates a group of routes. A route group can apply transforms
* to routes in bulk
*/
group(callback: () => void): RouteGroup;
/**
* Registers a route resource with conventional set of routes
*/
resource(resource: string, controller: string): RouteResource;
/**
* Register a route resource with shallow nested routes.
*/
shallowResource(resource: string, controller: string): RouteResource;
/**
* Returns a brisk route instance for a given URL pattern
*/
on(pattern: string): BriskRoute;
/**
* Define global route matcher
*/
where(param: string, matcher: string | RegExp): this;
/**
* Returns a flat list of routes JSON
*/
toJSON(): {
[domain: string]: (RouteNode & {
methods: string[];
})[];
};
/**
* Commit routes to the store. After this, no more
* routes can be registered.
*/
commit(): void;
/**
* Find route for a given URL, method and optionally domain
*/
match(url: string, method: string, domain?: string): null | MatchedRoute;
/**
* Makes url to a registered route by looking it up with the route pattern,
* name or the controller.method
*/
makeUrl(routeIdentifier: string, params?: any[] | MakeUrlOptions, options?: MakeUrlOptions): string;
/**
* Makes a signed url, which can be confirmed for it's integrity without
* relying on any sort of backend storage.
*/
makeSignedUrl(routeIdentifier: string, params?: any[] | MakeSignedUrlOptions, options?: MakeSignedUrlOptions): string;
}
+333
View File
@@ -0,0 +1,333 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Router = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const helpers_1 = require("@poppinss/utils/build/helpers");
const Route_1 = require("./Route");
const Store_1 = require("./Store");
const Group_1 = require("./Group");
const BriskRoute_1 = require("./BriskRoute");
const Resource_1 = require("./Resource");
const RouterException_1 = require("../Exceptions/RouterException");
const helpers_2 = require("../helpers");
const Matchers_1 = require("./Matchers");
const LookupStore_1 = require("./LookupStore");
/**
* Router class exposes unified API to create new routes, group them or
* create route resources.
*
* @example
* ```ts
* const router = new Router()
*
* router.get('/', async function () {
* // handle request
* })
* ```
*/
class Router extends LookupStore_1.LookupStore {
constructor(encryption, routeProcessor) {
super(encryption);
this.routeProcessor = routeProcessor;
/**
* Collection of routes, including route resource and route
* group. To get a flat list of routes, call `router.toJSON()`
*/
this.routes = [];
/**
* Exposing BriskRoute, RouteGroup and RouteResource constructors
* to be extended from outside
*/
this.BriskRoute = BriskRoute_1.BriskRoute;
this.RouteGroup = Group_1.RouteGroup;
this.RouteResource = Resource_1.RouteResource;
this.Route = Route_1.Route;
this.RouteMatchers = Matchers_1.RouteMatchers;
/**
* Shortcut methods for commonly used route matchers
*/
this.matchers = new Matchers_1.RouteMatchers();
/**
* Global matchers to test route params against regular expressions.
*/
this.paramMatchers = {};
/**
* Store with tokenized routes
*/
this.store = new Store_1.Store();
/**
* A boolean to tell the router that a group is in
* open state right now
*/
this.openedGroups = [];
}
getRecentGroup() {
return this.openedGroups[this.openedGroups.length - 1];
}
/**
* Add route for a given pattern and methods
*/
route(pattern, methods, handler) {
const route = new Route_1.Route(pattern, methods, handler, this.paramMatchers);
const openedGroup = this.getRecentGroup();
if (openedGroup) {
openedGroup.routes.push(route);
}
else {
this.routes.push(route);
}
return route;
}
/**
* Define a route that handles all common HTTP methods
*/
any(pattern, handler) {
return this.route(pattern, ['HEAD', 'OPTIONS', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE'], handler);
}
/**
* Define `GET` route
*/
get(pattern, handler) {
return this.route(pattern, ['GET', 'HEAD'], handler);
}
/**
* Define `POST` route
*/
post(pattern, handler) {
return this.route(pattern, ['POST'], handler);
}
/**
* Define `PUT` route
*/
put(pattern, handler) {
return this.route(pattern, ['PUT'], handler);
}
/**
* Define `PATCH` route
*/
patch(pattern, handler) {
return this.route(pattern, ['PATCH'], handler);
}
/**
* Define `DELETE` route
*/
delete(pattern, handler) {
return this.route(pattern, ['DELETE'], handler);
}
/**
* Creates a group of routes. A route group can apply transforms
* to routes in bulk
*/
group(callback) {
/*
* Create a new group with empty set of routes
*/
const group = new Group_1.RouteGroup([]);
/*
* See if there is any opened existing route groups. If yes, then we
* push this new group to the old group, otherwise we push it to
* the list of routes.
*/
const openedGroup = this.getRecentGroup();
if (openedGroup) {
openedGroup.routes.push(group);
}
else {
this.routes.push(group);
}
/*
* Track the group, so that the upcoming calls inside the callback
* can use this group
*/
this.openedGroups.push(group);
/*
* Execute the callback. Now all registered routes will be
* collected seperately from the `routes` array
*/
callback();
/*
* Now the callback is over, get rid of the opened group
*/
this.openedGroups.pop();
return group;
}
/**
* Registers a route resource with conventional set of routes
*/
resource(resource, controller) {
const resourceInstance = new Resource_1.RouteResource(resource, controller, this.paramMatchers);
const openedGroup = this.getRecentGroup();
if (openedGroup) {
openedGroup.routes.push(resourceInstance);
}
else {
this.routes.push(resourceInstance);
}
return resourceInstance;
}
/**
* Register a route resource with shallow nested routes.
*/
shallowResource(resource, controller) {
const resourceInstance = new Resource_1.RouteResource(resource, controller, this.paramMatchers, true);
const openedGroup = this.getRecentGroup();
if (openedGroup) {
openedGroup.routes.push(resourceInstance);
}
else {
this.routes.push(resourceInstance);
}
return resourceInstance;
}
/**
* Returns a brisk route instance for a given URL pattern
*/
on(pattern) {
const briskRoute = new BriskRoute_1.BriskRoute(pattern, this.paramMatchers);
const openedGroup = this.getRecentGroup();
if (openedGroup) {
openedGroup.routes.push(briskRoute);
}
else {
this.routes.push(briskRoute);
}
return briskRoute;
}
/**
* Define global route matcher
*/
where(param, matcher) {
if (typeof matcher === 'string') {
this.paramMatchers[param] = { match: new RegExp(matcher) };
}
else if (helpers_1.types.isRegexp(matcher)) {
this.paramMatchers[param] = { match: matcher };
}
else {
this.paramMatchers[param] = matcher;
}
return this;
}
/**
* Returns a flat list of routes JSON
*/
toJSON() {
const lookupStoreRoutes = this.tree;
const domains = Object.keys(lookupStoreRoutes);
return domains.reduce((result, domain) => {
result[domain] = lookupStoreRoutes[domain].map((route) => {
const routeJSON = this.store.tree.domains[domain][route.methods[0]].routes[route.pattern];
return Object.assign({ methods: route.methods }, routeJSON);
});
return result;
}, {});
}
/**
* Commit routes to the store. After this, no more
* routes can be registered.
*/
commit() {
const names = [];
(0, helpers_2.toRoutesJSON)(this.routes).forEach((route) => {
/*
* Raise error when route name is already in use. Route names have to be unique
* to ensure that only one route is returned during lookup.
*/
if (route.name && names.indexOf(route.name) > -1) {
throw RouterException_1.RouterException.duplicateRouteName(route.name);
}
/*
* If route has a unique, then track the name for checking duplicates
*/
if (route.name) {
names.push(route.name);
}
/*
* If a pre-processor is defined then pass the [[RouteNode]]
* to it.
*/
if (this.routeProcessor) {
this.routeProcessor(route);
}
/**
* Register the route with the lookup store
*/
this.register(route);
this.store.add(route);
});
this.routes = [];
this.paramMatchers = {};
}
/**
* Find route for a given URL, method and optionally domain
*/
match(url, method, domain) {
/*
* 1. If domain is not mentioned, then we lookup for root level defined
* routes.
*
* 2. If domain is mentioned, then we check the store to see if user has registered
* one or more routes for that domain or not.
*
* - If they have not registered any routes for the mentioned domain, it means,
* they don't want any special treatment for this domain, hence we search
* with the the root level routes. (Same as 1)
*
* - Else we search within the routes of the mentioned domain.
*/
let response = null;
const matchingDomain = domain ? this.store.matchDomain(domain) : [];
if (!matchingDomain.length) {
response = this.store.match(url, method);
}
else {
/*
* Search within the domain
*/
response = this.store.match(url, method, {
storeMatch: matchingDomain,
value: domain,
});
}
return response;
}
/**
* Makes url to a registered route by looking it up with the route pattern,
* name or the controller.method
*/
makeUrl(routeIdentifier, params, options) {
const normalizedOptions = (0, helpers_2.normalizeMakeUrlOptions)(params, options);
const builder = normalizedOptions.domain
? this.builderForDomain(normalizedOptions.domain)
: this.builder();
normalizedOptions.params && builder.params(normalizedOptions.params);
normalizedOptions.qs && builder.qs(normalizedOptions.qs);
normalizedOptions.prefixUrl && builder.prefixUrl(normalizedOptions.prefixUrl);
normalizedOptions.disableRouteLookup && builder.disableRouteLookup();
return builder.make(routeIdentifier);
}
/**
* Makes a signed url, which can be confirmed for it's integrity without
* relying on any sort of backend storage.
*/
makeSignedUrl(routeIdentifier, params, options) {
const normalizedOptions = (0, helpers_2.normalizeMakeSignedUrlOptions)(params, options);
const builder = normalizedOptions.domain
? this.builderForDomain(normalizedOptions.domain)
: this.builder();
normalizedOptions.params && builder.params(normalizedOptions.params);
normalizedOptions.qs && builder.qs(normalizedOptions.qs);
normalizedOptions.prefixUrl && builder.prefixUrl(normalizedOptions.prefixUrl);
normalizedOptions.disableRouteLookup && builder.disableRouteLookup();
return builder.makeSigned(routeIdentifier, normalizedOptions);
}
}
exports.Router = Router;
@@ -0,0 +1,49 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../../adonis-typings/index.d.ts" />
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { ErrorHandler } from '@ioc:Adonis/Core/Server';
import { IocContract } from '@adonisjs/fold';
/**
* Exception manager exposes the API to register custom error handler
* and invoke it when exceptions are raised during the HTTP
* lifecycle.
*/
export declare class ExceptionManager {
/**
* Resolved copy of error handler
*/
private resolvedErrorHandler?;
/**
* Resolved copy of error reporter
*/
private resolvedErrorReporter?;
/**
* A reference to ioc resolver to resolve the error handler from
* the IoC container
*/
private resolver;
constructor(container: IocContract);
/**
* Register a custom error handler
*/
registerHandler(handler: ErrorHandler): void;
/**
* Handle error
*/
private handleError;
/**
* Report error when report method exists
*/
private reportError;
/**
* Handle the error
*/
handle(error: any, ctx: HttpContextContract): Promise<void>;
}
@@ -0,0 +1,96 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExceptionManager = void 0;
const helpers_1 = require("../../helpers");
/**
* Exception manager exposes the API to register custom error handler
* and invoke it when exceptions are raised during the HTTP
* lifecycle.
*/
class ExceptionManager {
constructor(container) {
this.resolver = container.getResolver();
}
/**
* Register a custom error handler
*/
registerHandler(handler) {
if (typeof handler === 'string') {
this.resolvedErrorHandler = this.resolver.resolve(`${handler}.handle`);
this.resolvedErrorReporter = this.resolver.resolve(`${handler}.report`);
}
else {
this.resolvedErrorHandler = {
type: 'function',
value: handler,
};
}
}
/**
* Handle error
*/
async handleError(error, ctx) {
ctx.response.safeStatus(error.status || 500);
/*
* Make response when no error handler has been registered
*/
if (!this.resolvedErrorHandler) {
ctx.response.send(error.message);
return;
}
/*
* Invoke the error handler and catch any errors raised by the error
* handler itself. We don't expect error handlers to raise exceptions.
* However, during development a broken error handler may raise
* exceptions.
*/
try {
let value = null;
if (this.resolvedErrorHandler.type === 'function') {
value = await this.resolvedErrorHandler.value(error, ctx);
}
else {
value = await this.resolver.call(this.resolvedErrorHandler, undefined, [error, ctx]);
}
if ((0, helpers_1.useReturnValue)(value, ctx)) {
ctx.response.send(value);
}
}
catch (finalError) {
/*
* Unexpected block
*/
ctx.response.status(error.status || 500).send(error.message);
ctx.logger.fatal(finalError, 'Unexpected exception raised from HTTP ExceptionHandler "handle" method');
}
}
/**
* Report error when report method exists
*/
async reportError(error, ctx) {
if (this.resolvedErrorReporter) {
try {
await this.resolver.call(this.resolvedErrorReporter, undefined, [error, ctx]);
}
catch (finalError) {
ctx.logger.fatal(finalError, 'Unexpected exception raised from HTTP ExceptionHandler "report" method');
}
}
}
/**
* Handle the error
*/
async handle(error, ctx) {
await this.handleError(error, ctx);
this.reportError(error, ctx);
}
}
exports.ExceptionManager = ExceptionManager;
+43
View File
@@ -0,0 +1,43 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../../adonis-typings/index.d.ts" />
import { HookHandler, HooksContract } from '@ioc:Adonis/Core/Server';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
/**
* Exposes to API to register and execute before and after hooks
*/
export declare class Hooks implements HooksContract {
/**
* Registered before and after hooks
*/
private hooks;
/**
* Register before hook
*/
before(cb: HookHandler): this;
/**
* Register after hook
*/
after(cb: HookHandler): this;
/**
* Executing before hooks in series. If this method returns `true`,
* it means that one of the before hooks wants to end the request
* without further processing it.
*/
executeBefore(ctx: HttpContextContract): Promise<boolean>;
/**
* Executes after hooks in series.
*/
executeAfter(ctx: HttpContextContract): Promise<void>;
/**
* The commit action enables us to optimize the hook handlers
* for runtime peformance
*/
commit(): void;
}
+77
View File
@@ -0,0 +1,77 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Hooks = void 0;
/**
* Exposes to API to register and execute before and after hooks
*/
class Hooks {
constructor() {
/**
* Registered before and after hooks
*/
this.hooks = {
before: [],
after: [],
};
}
/**
* Register before hook
*/
before(cb) {
this.hooks.before.push(cb);
return this;
}
/**
* Register after hook
*/
after(cb) {
this.hooks.after.push(cb);
return this;
}
/**
* Executing before hooks in series. If this method returns `true`,
* it means that one of the before hooks wants to end the request
* without further processing it.
*/
async executeBefore(ctx) {
for (let hook of this.hooks.before) {
await hook(ctx);
/*
* We must break the loop when one of the hooks set the response
*/
if (ctx.response.hasLazyBody || !ctx.response.isPending) {
return true;
}
}
return false;
}
/**
* Executes after hooks in series.
*/
async executeAfter(ctx) {
for (let hook of this.hooks.after) {
await hook(ctx);
}
}
/**
* The commit action enables us to optimize the hook handlers
* for runtime peformance
*/
commit() {
if (this.hooks.before.length === 0) {
this.executeBefore = async () => false;
}
if (this.hooks.after.length === 0) {
this.executeAfter = async () => { };
}
}
}
exports.Hooks = Hooks;
@@ -0,0 +1,60 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../../adonis-typings/index.d.ts" />
import { RouteNode } from '@ioc:Adonis/Core/Route';
import { IocContract } from '@adonisjs/fold';
import { MiddlewareStoreContract } from '@ioc:Adonis/Core/Middleware';
/**
* Precompiler is used to pre compiler the route handler and middleware. We
* lookup the middleware and controllers upfront in the IoC container
* and cache the lookup to boost the runtime performance.
*
* Also each route gets a `finalHandler` property, which is used to invoke the
* route middleware and the route actual handler
*/
export declare class PreCompiler {
private middlewareStore;
/**
* This function is used by reference to execute the route handler
*/
private runRouteHandler;
/**
* Method to execute middleware using the middleware store
*/
private executeMiddleware;
/**
* This function is used by reference to execute the route middleware + route handler
*/
private runRouteMiddleware;
/**
* The resolver used to resolve the controllers from IoC container
*/
private resolver;
constructor(container: IocContract, middlewareStore: MiddlewareStoreContract);
/**
* Pre-compiling the handler to boost the runtime performance
*/
private compileHandler;
/**
* Pre-compile the route middleware to boost runtime performance
*/
private compileMiddleware;
/**
* Sets `finalHandler` property on the `route.meta`. This method
* can be invoked to execute route middleware stack + route
* controller/closure.
*/
private setFinalHandler;
/**
* Pre-compile route handler and it's middleware to boost runtime performance. Since
* most of this work is repetitive, we pre-compile and avoid doing it on every
* request
*/
compileRoute(route: RouteNode): void;
}
@@ -0,0 +1,143 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PreCompiler = void 0;
/// <reference path="../../../adonis-typings/index.ts" />
const haye_1 = __importDefault(require("haye"));
const co_compose_1 = require("co-compose");
const utils_1 = require("@poppinss/utils");
const helpers_1 = require("@poppinss/utils/build/helpers");
const helpers_2 = require("../../helpers");
const exceptions_json_1 = require("../../../exceptions.json");
/**
* Precompiler is used to pre compiler the route handler and middleware. We
* lookup the middleware and controllers upfront in the IoC container
* and cache the lookup to boost the runtime performance.
*
* Also each route gets a `finalHandler` property, which is used to invoke the
* route middleware and the route actual handler
*/
class PreCompiler {
constructor(container, middlewareStore) {
this.middlewareStore = middlewareStore;
/**
* This function is used by reference to execute the route handler
*/
this.runRouteHandler = async (ctx) => {
const routeHandler = ctx.route.meta.resolvedHandler;
/*
* Passing a child to the route handler, so that all internal
* actions can have their own child row
*/
let returnValue;
if (routeHandler.type === 'function') {
returnValue = await routeHandler.handler(ctx);
}
else {
returnValue = await this.resolver.call(routeHandler, undefined, (controller) => {
/**
* Allowing controller to provide the controller method argument
*/
if (typeof controller['getHandlerArguments'] === 'function') {
return controller['getHandlerArguments'](ctx);
}
return [ctx];
});
}
if ((0, helpers_2.useReturnValue)(returnValue, ctx)) {
ctx.response.send(returnValue);
}
};
/**
* Method to execute middleware using the middleware store
*/
this.executeMiddleware = (middleware, params) => {
return this.middlewareStore.invokeMiddleware(middleware, params);
};
/**
* This function is used by reference to execute the route middleware + route handler
*/
this.runRouteMiddleware = (ctx) => {
return new co_compose_1.Middleware()
.register(ctx.route.meta.resolvedMiddleware)
.runner()
.executor(this.executeMiddleware)
.finalHandler(this.runRouteHandler, [ctx])
.run([ctx]);
};
this.resolver = container.getResolver(undefined, 'httpControllers', 'App/Controllers/Http');
}
/**
* Pre-compiling the handler to boost the runtime performance
*/
compileHandler(route) {
if (typeof route.handler === 'string') {
route.meta.resolvedHandler = this.resolver.resolve(route.handler, route.meta.namespace);
}
else {
route.meta.resolvedHandler = { type: 'function', handler: route.handler };
}
}
/**
* Pre-compile the route middleware to boost runtime performance
*/
compileMiddleware(route) {
route.meta.resolvedMiddleware = route.middleware.map((item) => {
if (typeof item === 'function') {
return { type: 'function', value: item, args: [] };
}
/*
* Extract middleware name and args from the string
*/
const [{ name, args }] = haye_1.default.fromPipe(item).toArray();
/*
* Get resolved node for the given name and raise exception when that
* name is missing
*/
const resolvedMiddleware = this.middlewareStore.getNamed(name);
if (!resolvedMiddleware) {
const error = new utils_1.Exception((0, helpers_1.interpolate)(exceptions_json_1.E_MISSING_NAMED_MIDDLEWARE.message, { name }), exceptions_json_1.E_MISSING_NAMED_MIDDLEWARE.status, exceptions_json_1.E_MISSING_NAMED_MIDDLEWARE.code);
error.help = exceptions_json_1.E_MISSING_NAMED_MIDDLEWARE.help.join('\n');
throw error;
}
return {
...resolvedMiddleware,
args,
};
});
}
/**
* Sets `finalHandler` property on the `route.meta`. This method
* can be invoked to execute route middleware stack + route
* controller/closure.
*/
setFinalHandler(route) {
if (route.meta.resolvedMiddleware && route.meta.resolvedMiddleware.length) {
route.meta.finalHandler = this.runRouteMiddleware;
}
else {
route.meta.finalHandler = this.runRouteHandler;
}
}
/**
* Pre-compile route handler and it's middleware to boost runtime performance. Since
* most of this work is repetitive, we pre-compile and avoid doing it on every
* request
*/
compileRoute(route) {
this.compileHandler(route);
this.compileMiddleware(route);
this.setFinalHandler(route);
}
}
exports.PreCompiler = PreCompiler;
@@ -0,0 +1,39 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../../adonis-typings/index.d.ts" />
import { RouterContract } from '@ioc:Adonis/Core/Route';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { MiddlewareStoreContract } from '@ioc:Adonis/Core/Middleware';
/**
* Handles the request by invoking it's middleware chain, along with the
* route finalHandler
*/
export declare class RequestHandler {
private middlewareStore;
private router;
private globalMiddleware;
private handleRequest;
constructor(middlewareStore: MiddlewareStoreContract, router: RouterContract);
/**
* Function to invoke global middleware
*/
private executeMiddleware;
/**
* Finds the route for the request
*/
private findRoute;
/**
* Handles the request and invokes required middleware/handlers
*/
handle(ctx: HttpContextContract): Promise<void>;
/**
* Computing certain methods to optimize for runtime performance
*/
commit(): void;
}
@@ -0,0 +1,87 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RequestHandler = void 0;
/// <reference path="../../../adonis-typings/index.ts" />
const co_compose_1 = require("co-compose");
const helpers_1 = require("@poppinss/utils/build/helpers");
const HttpException_1 = require("../../Exceptions/HttpException");
const exceptions_json_1 = require("../../../exceptions.json");
/**
* Handles the request by invoking it's middleware chain, along with the
* route finalHandler
*/
class RequestHandler {
constructor(middlewareStore, router) {
this.middlewareStore = middlewareStore;
this.router = router;
/**
* Function to invoke global middleware
*/
this.executeMiddleware = (middleware, params) => {
return this.middlewareStore.invokeMiddleware(middleware, params);
};
}
/**
* Finds the route for the request
*/
findRoute(ctx) {
const url = ctx.request.url();
const method = ctx.request.method();
const hostname = ctx.request.hostname();
/*
* Profiling `route.match` method
*/
const matchRoute = ctx.profiler.profile('http:route:match');
const route = this.router.match(url, method, hostname || undefined);
matchRoute.end();
/*
* Raise error when route is missing
*/
if (!route) {
throw HttpException_1.HttpException.invoke((0, helpers_1.interpolate)(exceptions_json_1.E_ROUTE_NOT_FOUND.message, { method, url }), exceptions_json_1.E_ROUTE_NOT_FOUND.status, exceptions_json_1.E_ROUTE_NOT_FOUND.code);
}
/*
* Attach `params`, `subdomains` and `route` when route is found. This
* information only exists on a given route
*/
ctx.params = route.params;
ctx.subdomains = route.subdomains;
ctx.route = route.route;
ctx.routeKey = route.routeKey;
ctx.request.updateParams(ctx.params);
}
/**
* Handles the request and invokes required middleware/handlers
*/
async handle(ctx) {
this.findRoute(ctx);
return this.handleRequest(ctx);
}
/**
* Computing certain methods to optimize for runtime performance
*/
commit() {
const middleware = this.middlewareStore.get();
if (!middleware.length) {
this.handleRequest = (ctx) => ctx.route.meta.finalHandler(ctx);
return;
}
this.globalMiddleware = new co_compose_1.Middleware().register(middleware);
this.handleRequest = (ctx) => {
return this.globalMiddleware
.runner()
.executor(this.executeMiddleware)
.finalHandler(ctx.route.meta.finalHandler, [ctx])
.run([ctx]);
};
}
}
exports.RequestHandler = RequestHandler;
+90
View File
@@ -0,0 +1,90 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="node" />
/// <reference types="node" />
import { Server as HttpsServer } from 'https';
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption';
import { ApplicationContract } from '@ioc:Adonis/Core/Application';
import { IncomingMessage, ServerResponse, Server as HttpServer } from 'http';
import { ServerContract, ServerConfig, ErrorHandler } from '@ioc:Adonis/Core/Server';
import { Hooks } from './Hooks';
import { Router } from '../Router';
import { MiddlewareStore } from '../MiddlewareStore';
/**
* Server class handles the HTTP requests by using all Adonis micro modules.
*/
export declare class Server implements ServerContract {
private application;
private encryption;
private httpConfig;
/**
* The server itself doesn't create the http server instance. However, the consumer
* of this class can create one and set the instance for further reference. This
* is what ignitor does.
*/
instance?: HttpServer | HttpsServer;
/**
* The middleware store to register global and named middleware
*/
middleware: MiddlewareStore;
/**
* The route to register routes
*/
router: Router;
/**
* Server before/after hooks
*/
hooks: Hooks;
/**
* Precompiler to set the finalHandler for the route
*/
private precompiler;
/**
* Exception manager to handle exceptions
*/
private exception;
/**
* Request handler to handle request after route is found
*/
private requestHandler;
constructor(application: ApplicationContract, encryption: EncryptionContract, httpConfig: ServerConfig);
/**
* Handles HTTP request
*/
private runBeforeHooksAndHandler;
/**
* Returns the profiler row
*/
private getProfilerRow;
/**
* Returns the context for the request
*/
private getContext;
/**
* Handle the request
*/
private handleRequest;
/**
* Define custom error handler to handler all errors
* occurred during HTTP request
*/
errorHandler(handler: ErrorHandler): this;
/**
* Optimizes internal handlers, based upon the existence of
* before handlers and global middleware. This helps in
* increasing throughput by 10%
*/
optimize(): void;
/**
* Handles a given HTTP request. This method can be attached to any HTTP
* server
*/
handle(req: IncomingMessage, res: ServerResponse): Promise<void>;
}
+175
View File
@@ -0,0 +1,175 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Server = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const ms_1 = __importDefault(require("ms"));
const Hooks_1 = require("./Hooks");
const Router_1 = require("../Router");
const Request_1 = require("../Request");
const Response_1 = require("../Response");
const PreCompiler_1 = require("./PreCompiler");
const HttpContext_1 = require("../HttpContext");
const RequestHandler_1 = require("./RequestHandler");
const MiddlewareStore_1 = require("../MiddlewareStore");
const ExceptionManager_1 = require("./ExceptionManager");
const LocalStorage_1 = require("../HttpContext/LocalStorage");
/**
* Server class handles the HTTP requests by using all Adonis micro modules.
*/
class Server {
constructor(application, encryption, httpConfig) {
this.application = application;
this.encryption = encryption;
this.httpConfig = httpConfig;
/**
* The middleware store to register global and named middleware
*/
this.middleware = new MiddlewareStore_1.MiddlewareStore(this.application.container);
/**
* The route to register routes
*/
this.router = new Router_1.Router(this.encryption, (route) => this.precompiler.compileRoute(route));
/**
* Server before/after hooks
*/
this.hooks = new Hooks_1.Hooks();
/**
* Precompiler to set the finalHandler for the route
*/
this.precompiler = new PreCompiler_1.PreCompiler(this.application.container, this.middleware);
/**
* Exception manager to handle exceptions
*/
this.exception = new ExceptionManager_1.ExceptionManager(this.application.container);
/**
* Request handler to handle request after route is found
*/
this.requestHandler = new RequestHandler_1.RequestHandler(this.middleware, this.router);
/*
* Pre process config to convert max age string to seconds.
*/
if (httpConfig.cookie.maxAge && typeof httpConfig.cookie.maxAge === 'string') {
httpConfig.cookie.maxAge = (0, ms_1.default)(httpConfig.cookie.maxAge) / 1000;
}
(0, LocalStorage_1.useAsyncLocalStorage)(httpConfig.useAsyncLocalStorage || false);
}
/**
* Handles HTTP request
*/
async runBeforeHooksAndHandler(ctx) {
/*
* Start with before hooks upfront. If they raise error
* then execute error handler.
*/
return this.hooks.executeBefore(ctx).then((shortcircuit) => {
if (!shortcircuit) {
return this.requestHandler.handle(ctx);
}
});
}
/**
* Returns the profiler row
*/
getProfilerRow(request) {
return this.application.profiler.create('http:request', {
request_id: request.id(),
url: request.url(),
method: request.method(),
});
}
/**
* Returns the context for the request
*/
getContext(request, response, profilerRow) {
return new HttpContext_1.HttpContext(request, response, this.application.logger.child({
request_id: request.id(),
}), profilerRow);
}
/**
* Handle the request
*/
async handleRequest(ctx, requestAction, res) {
/*
* Handle request by executing hooks, request middleware stack
* and route handler
*/
try {
await this.runBeforeHooksAndHandler(ctx);
}
catch (error) {
await this.exception.handle(error, ctx);
}
/*
* Excute hooks when there are one or more hooks. The `ctx.response.finish`
* is intentionally inside both the `try` and `catch` blocks as a defensive
* measure.
*
* When we call `response.finish`, it will serialize the response body and may
* encouter errors while doing so and hence will be catched by the catch
* block.
*/
try {
await this.hooks.executeAfter(ctx);
requestAction.end({ status_code: res.statusCode });
ctx.response.finish();
}
catch (error) {
await this.exception.handle(error, ctx);
requestAction.end({ status_code: res.statusCode, error });
ctx.response.finish();
}
}
/**
* Define custom error handler to handler all errors
* occurred during HTTP request
*/
errorHandler(handler) {
this.exception.registerHandler(handler);
return this;
}
/**
* Optimizes internal handlers, based upon the existence of
* before handlers and global middleware. This helps in
* increasing throughput by 10%
*/
optimize() {
this.router.commit();
this.hooks.commit();
this.requestHandler.commit();
}
/**
* Handles a given HTTP request. This method can be attached to any HTTP
* server
*/
async handle(req, res) {
const request = new Request_1.Request(req, res, this.encryption, this.httpConfig);
const response = new Response_1.Response(req, res, this.encryption, this.httpConfig, this.router);
const requestAction = this.getProfilerRow(request);
const ctx = this.getContext(request, response, requestAction);
/*
* Reset accept header when `forceContentNegotiationTo` is defined
*/
const accept = this.httpConfig.forceContentNegotiationTo;
if (accept) {
req.headers['accept'] = typeof accept === 'function' ? accept(ctx) : accept;
}
if (LocalStorage_1.usingAsyncLocalStorage) {
return LocalStorage_1.httpContextLocalStorage.run(ctx, () => this.handleRequest(ctx, requestAction, res));
}
else {
return this.handleRequest(ctx, requestAction, res);
}
}
}
exports.Server = Server;
+69
View File
@@ -0,0 +1,69 @@
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../adonis-typings/index.d.ts" />
/// <reference types="node" />
import { Stats } from 'fs';
import { Route } from './Router/Route';
import { RouteGroup } from './Router/Group';
import { BriskRoute } from './Router/BriskRoute';
import { RouteResource } from './Router/Resource';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { RouteJSON, MakeUrlOptions, MakeSignedUrlOptions } from '@ioc:Adonis/Core/Route';
/**
* Makes input string consistent by having only the starting
* slash
*/
export declare function dropSlash(input: string): string;
/**
* Converts and array of routes or route groups or route resource to a flat
* list of route defination.
*/
export declare function toRoutesJSON(routes: (RouteGroup | RouteResource | Route | BriskRoute)[]): RouteJSON[];
/**
* Makes url for a route pattern and params and querystring.
*/
export declare function processPattern(pattern: string, data: any): string;
/**
* Returns a boolean telling if the return value must be used as
* the response body or not
*/
export declare function useReturnValue(returnValue: any, ctx: HttpContextContract): boolean;
/**
* Since finding the trusted proxy based upon the remote address
* is an expensive function, we cache its result
*/
export declare function trustProxy(remoteAddress: string, proxyFn: (addr: string, distance: number) => boolean): boolean;
/**
* Normalizes the makeURL options to work with the new API and the old
* one as well
*/
export declare function normalizeMakeUrlOptions(params?: any[] | MakeUrlOptions, options?: MakeUrlOptions): {
params: any;
qs: any;
domain: string | undefined;
prefixUrl: string | undefined;
disableRouteLookup: boolean;
};
/**
* Normalizes the make signed url options by allowing params to appear on
* top level object with option to nest inside `params` property.
*/
export declare function normalizeMakeSignedUrlOptions(params?: any[] | MakeSignedUrlOptions, options?: MakeSignedUrlOptions): {
params: any;
qs: any;
domain: string | undefined;
prefixUrl: string | undefined;
expiresIn: any;
purpose: any;
disableRouteLookup: boolean;
};
/**
* Wraps `fs.stat` to promise interface.
*/
export declare function statFn(filePath: string): Promise<Stats>;
+196
View File
@@ -0,0 +1,196 @@
"use strict";
/**
* @adonisjs/http-server
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.statFn = exports.normalizeMakeSignedUrlOptions = exports.normalizeMakeUrlOptions = exports.trustProxy = exports.useReturnValue = exports.processPattern = exports.toRoutesJSON = exports.dropSlash = void 0;
/// <reference path="../adonis-typings/index.ts" />
const tmp_cache_1 = __importDefault(require("tmp-cache"));
const fs_1 = require("fs");
const Group_1 = require("./Router/Group");
const BriskRoute_1 = require("./Router/BriskRoute");
const Resource_1 = require("./Router/Resource");
const RouterException_1 = require("./Exceptions/RouterException");
const proxyCache = new tmp_cache_1.default({ max: 200 });
/**
* Makes input string consistent by having only the starting
* slash
*/
function dropSlash(input) {
if (input === '/') {
return '/';
}
return `/${input.replace(/^\//, '').replace(/\/$/, '')}`;
}
exports.dropSlash = dropSlash;
/**
* Converts and array of routes or route groups or route resource to a flat
* list of route defination.
*/
function toRoutesJSON(routes) {
return routes.reduce((list, route) => {
if (route instanceof Group_1.RouteGroup) {
list = list.concat(toRoutesJSON(route.routes));
return list;
}
if (route instanceof Resource_1.RouteResource) {
list = list.concat(toRoutesJSON(route.routes));
return list;
}
if (route instanceof BriskRoute_1.BriskRoute) {
if (route.route) {
list.push(route.route.toJSON());
}
return list;
}
if (!route.deleted) {
list.push(route.toJSON());
}
return list;
}, []);
}
exports.toRoutesJSON = toRoutesJSON;
/**
* Makes url for a route pattern and params and querystring.
*/
function processPattern(pattern, data) {
let url = pattern;
if (url.indexOf(':') > -1) {
/*
* Split pattern when route has dynamic segments
*/
const tokens = url.split('/');
/*
* Lookup over the route tokens and replace them the params values
*/
url = tokens
.map((token) => {
if (!token.startsWith(':')) {
return token;
}
const isOptional = token.endsWith('?');
const paramName = token.replace(/^:/, '').replace(/\?$/, '');
const param = data[paramName];
/*
* A required param is always required to make the complete URL
*/
if (!param && !isOptional) {
throw RouterException_1.RouterException.cannotMakeRoute(paramName, pattern);
}
return param;
})
.join('/');
}
return url;
}
exports.processPattern = processPattern;
/**
* Returns a boolean telling if the return value must be used as
* the response body or not
*/
function useReturnValue(returnValue, ctx) {
return (returnValue !== undefined && // Return value is explicitly defined
returnValue !== ctx.response && // Return value is not the instance of response object
!ctx.response.hasLazyBody // Lazy body is not set
);
}
exports.useReturnValue = useReturnValue;
/**
* Since finding the trusted proxy based upon the remote address
* is an expensive function, we cache its result
*/
function trustProxy(remoteAddress, proxyFn) {
if (proxyCache.has(remoteAddress)) {
return proxyCache.get(remoteAddress);
}
const result = proxyFn(remoteAddress, 0);
proxyCache.set(remoteAddress, result);
return result;
}
exports.trustProxy = trustProxy;
/**
* Invokes a callback if any of the defined keys is part of the values object
*/
function onIntersect(values, keys, callback) {
if (Array.isArray(values)) {
return;
}
const valueKeys = Object.keys(values);
const matchingKey = keys.find((key) => valueKeys.includes(key));
if (matchingKey) {
callback(matchingKey);
}
}
/**
* Normalizes the makeURL options to work with the new API and the old
* one as well
*/
function normalizeMakeUrlOptions(params, options) {
/**
* Params used to be options earlier. So we are checking a few properties of it
*/
params = params || {};
options = options || {};
const normalizedParams = params['params'] ? params['params'] : params;
const qs = options.qs || params['qs'];
const domain = options.domain;
const prefixUrl = options.prefixUrl;
const disableRouteLookup = options.disableRouteLookup || false;
/**
* Using legacy options
*/
onIntersect(params, ['prefixDomain', 'domainParams', 'qs', 'params'], () => {
process.emitWarning('DeprecationWarning', 'You are using legacy the API of the "Route.makeUrl". We recommend reading the docs and use the latest API');
});
return { params: normalizedParams, qs, domain, prefixUrl, disableRouteLookup };
}
exports.normalizeMakeUrlOptions = normalizeMakeUrlOptions;
/**
* Normalizes the make signed url options by allowing params to appear on
* top level object with option to nest inside `params` property.
*/
function normalizeMakeSignedUrlOptions(params, options) {
/**
* Params used to be options earlier. So we are checking a few properties of it
*/
params = params || {};
options = options || {};
const normalizedParams = params['params'] ? params['params'] : params;
const qs = options.qs || params['qs'];
const expiresIn = options.expiresIn || params['expiresIn'];
const purpose = options.purpose || params['purpose'];
const domain = options.domain;
const prefixUrl = options.prefixUrl;
const disableRouteLookup = options.disableRouteLookup || false;
/**
* Using legacy options
*/
onIntersect(params, ['prefixDomain', 'domainParams', 'qs', 'params', 'purpose', 'expiresIn'], () => {
process.emitWarning('DeprecationWarning', 'You are using legacy the API of the "Route.makeSignedUrl". We recommend reading the docs and use the latest API');
});
return { params: normalizedParams, qs, domain, prefixUrl, expiresIn, purpose, disableRouteLookup };
}
exports.normalizeMakeSignedUrlOptions = normalizeMakeSignedUrlOptions;
/**
* Wraps `fs.stat` to promise interface.
*/
function statFn(filePath) {
return new Promise((resolve, reject) => {
(0, fs_1.stat)(filePath, (error, stats) => {
if (error) {
reject(error);
return;
}
resolve(stats);
});
});
}
exports.statFn = statFn;