@loopback/rest
Version:
Expose controllers as REST endpoints and route REST API requests to controller methods
126 lines (112 loc) • 3.98 kB
text/typescript
// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
// Node module: @loopback/rest
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
import {Context} from '@loopback/core';
import {
HandlerContext,
MiddlewareContext,
Request,
Response,
} from '@loopback/express';
import {RestBindings} from './keys';
import {RestServerResolvedConfig} from './rest.server';
/**
* A per-request Context combining an IoC container with handler context
* (request, response, etc.).
*/
export class RequestContext
extends MiddlewareContext
implements HandlerContext
{
/**
* Get the protocol used by the client to make the request.
* Please note this protocol may be different from what we are observing
* at HTTP/TCP level, because reverse proxies like nginx or sidecars like
* Envoy are switching between protocols.
*/
get requestedProtocol(): string {
return (
((this.request.get('x-forwarded-proto') ?? '').split(',')[0] ||
this.request.protocol ||
this.serverConfig.protocol) ??
'http'
);
}
/**
* Get the effective base path of the incoming request. This base path
* combines `baseUrl` provided by Express when LB4 handler is mounted on
* a non-root path, with the `basePath` value configured at LB4 side.
*/
get basePath(): string {
const request = this.request;
let basePath = this.serverConfig.basePath ?? '';
if (request.baseUrl && request.baseUrl !== '/') {
if (!basePath || request.baseUrl.endsWith(basePath)) {
// Express has already applied basePath to baseUrl
basePath = request.baseUrl;
} else {
basePath = request.baseUrl + basePath;
}
}
return basePath;
}
/**
* Get the base URL used by the client to make the request.
* This URL contains the protocol, hostname, port and base path.
* The path of the invoked route and query string is not included.
*
* Please note these values may be different from what we are observing
* at HTTP/TCP level, because reverse proxies like nginx are rewriting them.
*/
get requestedBaseUrl(): string {
const request = this.request;
const config = this.serverConfig;
const protocol = this.requestedProtocol;
// The host can be in one of the forms
// [::1]:3000
// [::1]
// 127.0.0.1:3000
// 127.0.0.1
let {host, port} = parseHostAndPort(
request.get('x-forwarded-host') ?? request.headers.host,
);
const forwardedPort = (request.get('x-forwarded-port') ?? '').split(',')[0];
port = forwardedPort || port;
if (!host) {
// No host detected from http headers
// Use the configured values or the local network address
host = config.host ?? request.socket.localAddress ?? '';
port = ((config.port || request.socket.localPort) ?? '').toString();
}
// clear default ports
port = protocol === 'https' && port === '443' ? '' : port;
port = protocol === 'http' && port === '80' ? '' : port;
// add port number of present
host += port !== '' ? ':' + port : '';
return protocol + '://' + host + this.basePath;
}
constructor(
public readonly request: Request,
public readonly response: Response,
parent: Context,
public readonly serverConfig: RestServerResolvedConfig,
name?: string,
) {
super(request, response, parent, name);
}
protected setupBindings() {
super.setupBindings();
this.bind(RestBindings.Http.REQUEST).to(this.request).lock();
this.bind(RestBindings.Http.RESPONSE).to(this.response).lock();
this.bind(RestBindings.Http.CONTEXT).to(this).lock();
}
}
function parseHostAndPort(host: string | undefined) {
host = host ?? '';
host = host.split(',')[0];
const portPattern = /:([0-9]+)$/;
const port = (host.match(portPattern) ?? [])[1] || '';
host = host.replace(portPattern, '');
return {host, port};
}