Skip to content

Commit

Permalink
Decoupled compatibility methods into separate source files with proto…
Browse files Browse the repository at this point in the history
…type inheritance at runtime
  • Loading branch information
kartikk221 committed Aug 3, 2022
1 parent a74b44e commit d9cdb7d
Show file tree
Hide file tree
Showing 4 changed files with 373 additions and 321 deletions.
174 changes: 174 additions & 0 deletions src/components/compatibility/ExpressRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
'use strict';
const accepts = require('accepts');
const parse_range = require('range-parser');
const type_is = require('type-is');
const is_ip = require('net').isIP;

class ExpressRequest {
/* Methods */
get(name) {
let lowercase = name.toLowerCase();
switch (lowercase) {
case 'referer':
// Continue execution to below case for catching of both spelling variations
case 'referrer':
return this.headers['referer'] || this.headers['referrer'];
default:
return this.headers[lowercase];
}
}

header(name) {
return this.get(name);
}

accepts() {
let instance = accepts(this);
return instance.types.apply(instance, arguments);
}

acceptsCharsets() {
let instance = accepts(this);
return instance.charsets.apply(instance, arguments);
}

acceptsEncodings() {
let instance = accepts(this);
return instance.encodings.apply(instance, arguments);
}

acceptsLanguages() {
let instance = accepts(this);
return instance.languages.apply(instance, arguments);
}

range(size, options) {
let range = this.get('Range');
if (!range) return;
return parse_range(size, range, options);
}

param(name, default_value) {
// Parse three dataset candidates
let body = this.body;
let path_parameters = this.path_parameters;
let query_parameters = this.query_parameters;

// First check path parameters, body, and finally query_parameters
if (null != path_parameters[name] && path_parameters.hasOwnProperty(name)) return path_parameters[name];
if (null != body[name]) return body[name];
if (null != query_parameters[name]) return query_parameters[name];

return default_value;
}

is(types) {
// support flattened arguments
let arr = types;
if (!Array.isArray(types)) {
arr = new Array(arguments.length);
for (let i = 0; i < arr.length; i++) arr[i] = arguments[i];
}
return type_is(this, arr);
}

/* Properties */
get baseUrl() {
return this.path;
}

get originalUrl() {
return this.url;
}

get fresh() {
this._throw_unsupported('fresh');
}

get params() {
return this.path_parameters;
}

get hostname() {
// Retrieve the host header and determine if we can trust intermediary proxy servers
let host = this.get('X-Forwarded-Host');
const trust_proxy = this.route.app._options.trust_proxy;
if (!host || !trust_proxy) {
// Use the 'Host' header as fallback
host = this.get('Host');
} else {
// Note: X-Forwarded-Host is normally only ever a single value, but this is to be safe.
host = host.split(',')[0];
}

// If we don't have a host, return undefined
if (!host) return;

// IPv6 literal support
let offset = host[0] === '[' ? host.indexOf(']') + 1 : 0;
let index = host.indexOf(':', offset);
return index !== -1 ? host.substring(0, index) : host;
}

get ips() {
// Retrieve the client and proxy IP addresses
const client_ip = this.ip;
const proxy_ip = this.proxy_ip;

// Determine if we can trust intermediary proxy servers and have a x-forwarded-for header
const trust_proxy = this.route.app._options.trust_proxy;
const x_forwarded_for = this.get('X-Forwarded-For');
if (trust_proxy && x_forwarded_for) {
// Will split and return all possible IP addresses in the x-forwarded-for header (e.g. "client, proxy1, proxy2")
return x_forwarded_for.split(',');
} else {
// Returns all valid IP addresses available from uWS
return [client_ip, proxy_ip].filter((ip) => ip);
}
}

get protocol() {
// Resolves x-forwarded-proto header if trust proxy is enabled
const trust_proxy = this.route.app._options.trust_proxy;
const x_forwarded_proto = this.get('X-Forwarded-Proto');
if (trust_proxy && x_forwarded_proto) {
// Return the first protocol in the x-forwarded-proto header
// If the header contains a single value, the split will contain that value in the first index element anyways
return x_forwarded_proto.split(',')[0];
} else {
// Use HyperExpress/uWS initially defined protocol as fallback
return this.route.app.is_ssl ? 'https' : 'http';
}
}

get query() {
return this.query_parameters;
}

get secure() {
return this.protocol === 'https';
}

get signedCookies() {
this._throw_unsupported('signedCookies');
}

get stale() {
this._throw_unsupported('stale');
}

get subdomains() {
let hostname = this.hostname;
if (!hostname) return [];

let offset = 2;
let subdomains = !is_ip(hostname) ? hostname.split('.').reverse() : [hostname];
return subdomains.slice(offset);
}

get xhr() {
return (this.get('X-Requested-With') || '').toLowerCase() === 'xmlhttprequest';
}
}

module.exports = ExpressRequest;
120 changes: 120 additions & 0 deletions src/components/compatibility/ExpressResponse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use strict';

class ExpressResponse {
/* Methods */
append(name, values) {
return this.header(name, values);
}

setHeader(name, values) {
return this.append(name, values);
}

writeHeaders(headers) {
Object.keys(headers).forEach((name) => this.header(name, headers[name]));
}

setHeaders(headers) {
this.writeHeaders(headers);
}

writeHeaderValues(name, values) {
values.forEach((value) => this.header(name, value));
}

getHeader(name) {
return this._headers[name];
}

removeHeader(name) {
delete this._headers[name];
}

setCookie(name, value, options) {
return this.cookie(name, value, null, options);
}

hasCookie(name) {
return this._cookies && this._cookies[name] !== undefined;
}

removeCookie(name) {
return this.cookie(name, null);
}

clearCookie(name) {
return this.cookie(name, null);
}

end(data) {
return this.send(data);
}

format() {
this._throw_unsupported('format()');
}

get(name) {
let values = this._headers[name];
if (values) return values.length == 0 ? values[0] : values;
}

links(links) {
// Build chunks of links and combine into header spec
let chunks = [];
Object.keys(links).forEach((rel) => {
let url = links[rel];
chunks.push(`<${url}>; rel="${rel}"`);
});

// Write the link header
this.header('link', chunks.join(', '));
}

location(path) {
return this.header('location', path);
}

render() {
this._throw_unsupported('render()');
}

sendFile(path) {
return this.file(path);
}

sendStatus(status_code) {
return this.status(status_code);
}

set(field, value) {
if (typeof field == 'object') {
const reference = this;
Object.keys(field).forEach((name) => {
let value = field[name];
reference.header(field, value);
});
} else {
this.header(field, value);
}
}

vary(name) {
return this.header('vary', name);
}

/* Properties */
get headersSent() {
return this.initiated;
}

get statusCode() {
return this._status_code;
}

set statusCode(value) {
this._status_code = value;
}
}

module.exports = ExpressResponse;
Loading

0 comments on commit d9cdb7d

Please sign in to comment.