UNPKG

3.98 kBPlain TextView Raw
1// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
2// Node module: @loopback/rest
3// This file is licensed under the MIT License.
4// License text available at https://opensource.org/licenses/MIT
5
6import {Context} from '@loopback/core';
7import {
8 HandlerContext,
9 MiddlewareContext,
10 Request,
11 Response,
12} from '@loopback/express';
13import {RestBindings} from './keys';
14import {RestServerResolvedConfig} from './rest.server';
15
16/**
17 * A per-request Context combining an IoC container with handler context
18 * (request, response, etc.).
19 */
20export class RequestContext
21 extends MiddlewareContext
22 implements HandlerContext
23{
24 /**
25 * Get the protocol used by the client to make the request.
26 * Please note this protocol may be different from what we are observing
27 * at HTTP/TCP level, because reverse proxies like nginx or sidecars like
28 * Envoy are switching between protocols.
29 */
30 get requestedProtocol(): string {
31 return (
32 ((this.request.get('x-forwarded-proto') ?? '').split(',')[0] ||
33 this.request.protocol ||
34 this.serverConfig.protocol) ??
35 'http'
36 );
37 }
38
39 /**
40 * Get the effective base path of the incoming request. This base path
41 * combines `baseUrl` provided by Express when LB4 handler is mounted on
42 * a non-root path, with the `basePath` value configured at LB4 side.
43 */
44 get basePath(): string {
45 const request = this.request;
46 let basePath = this.serverConfig.basePath ?? '';
47 if (request.baseUrl && request.baseUrl !== '/') {
48 if (!basePath || request.baseUrl.endsWith(basePath)) {
49 // Express has already applied basePath to baseUrl
50 basePath = request.baseUrl;
51 } else {
52 basePath = request.baseUrl + basePath;
53 }
54 }
55 return basePath;
56 }
57
58 /**
59 * Get the base URL used by the client to make the request.
60 * This URL contains the protocol, hostname, port and base path.
61 * The path of the invoked route and query string is not included.
62 *
63 * Please note these values may be different from what we are observing
64 * at HTTP/TCP level, because reverse proxies like nginx are rewriting them.
65 */
66 get requestedBaseUrl(): string {
67 const request = this.request;
68 const config = this.serverConfig;
69
70 const protocol = this.requestedProtocol;
71 // The host can be in one of the forms
72 // [::1]:3000
73 // [::1]
74 // 127.0.0.1:3000
75 // 127.0.0.1
76 let {host, port} = parseHostAndPort(
77 request.get('x-forwarded-host') ?? request.headers.host,
78 );
79
80 const forwardedPort = (request.get('x-forwarded-port') ?? '').split(',')[0];
81 port = forwardedPort || port;
82
83 if (!host) {
84 // No host detected from http headers
85 // Use the configured values or the local network address
86 host = config.host ?? request.socket.localAddress ?? '';
87 port = ((config.port || request.socket.localPort) ?? '').toString();
88 }
89
90 // clear default ports
91 port = protocol === 'https' && port === '443' ? '' : port;
92 port = protocol === 'http' && port === '80' ? '' : port;
93
94 // add port number of present
95 host += port !== '' ? ':' + port : '';
96
97 return protocol + '://' + host + this.basePath;
98 }
99
100 constructor(
101 public readonly request: Request,
102 public readonly response: Response,
103 parent: Context,
104 public readonly serverConfig: RestServerResolvedConfig,
105 name?: string,
106 ) {
107 super(request, response, parent, name);
108 }
109
110 protected setupBindings() {
111 super.setupBindings();
112 this.bind(RestBindings.Http.REQUEST).to(this.request).lock();
113 this.bind(RestBindings.Http.RESPONSE).to(this.response).lock();
114 this.bind(RestBindings.Http.CONTEXT).to(this).lock();
115 }
116}
117
118function parseHostAndPort(host: string | undefined) {
119 host = host ?? '';
120 host = host.split(',')[0];
121 const portPattern = /:([0-9]+)$/;
122 const port = (host.match(portPattern) ?? [])[1] || '';
123 host = host.replace(portPattern, '');
124 return {host, port};
125}