UNPKG

@fanoutio/serve-grip

Version:
202 lines (201 loc) 7.67 kB
import debug from '../debug.js'; import { encodeWebSocketEvents, Channel, isNodeReqWsOverHttp, getWebSocketContextFromNodeReq, } from '@fanoutio/grip'; import { ServeGripBase } from '../ServeGripBase.js'; export class ServeGrip extends ServeGripBase { koa; constructor(config, fn = 'exec') { super(config, fn); this.koa = async (ctx, next) => { await this.run(ctx.req, ctx.res); await next(); }; } exec(req, res, fn) { debug('ServeGrip#exec - serveGrip invoked as Middleware function'); let err; this.run(req, res) .catch((ex) => (err = ex)) .then((result) => { if (err !== undefined) { fn(err); } else { if (result) { fn(); } } }); } getRequestGrip(req) { return req.grip; } setRequestGrip(req, grip) { req.grip = grip; } isRequestWsOverHttp(req) { return isNodeReqWsOverHttp(req); } getRequestWebSocketContext(req) { return getWebSocketContextFromNodeReq(req); } getRequestHeaderValue(req, key) { let value = req.headers[key]; if (Array.isArray(value)) { value = value[0]; } return value; } setResponseGrip(res, grip) { res.grip = grip; } setResponseStatus(res, code) { res.statusCode = code; } endResponse(res, chunk) { return res.end(chunk); } onAfterSetup(params) { // ## Monkey-patch res methods const { res, wsContext, gripInstructGetter } = params; if (wsContext != null) { debug('Monkey-patch res methods for WS-over-HTTP - start'); this.monkeyPatchResMethodsForWebSocket(res, wsContext); debug('Monkey-patch res methods for WS-over-HTTP - end'); } else { debug('Monkey-patch res methods for GripInstruct - start'); this.monkeyPatchResMethodsForGripInstruct(res, gripInstructGetter); debug('Monkey-patch res methods for GripInstruct - end'); } } monkeyPatchResMethodsForWebSocket(res, wsContext) { debug('res.removeHeader'); const resRemoveHeader = res.removeHeader; // @ts-ignore res.removeHeader = (name) => { debug('res.removeHeader - start'); // If we have a WsContext, then we don't want to allow removing // the following headers. let skip = false; if (name != null) { const nameLower = name.toLowerCase(); if (nameLower === 'content-type' || nameLower === 'content-length' || nameLower === 'transfer-encoding') { // turn into a no-op skip = true; } } if (!skip) { debug('not skipping removeHeader', name); resRemoveHeader.call(res, name); } else { debug('skipping removeHeader', name); } debug('res.removeHeader - end'); }; debug('res.writeHead'); const resWriteHead = res.writeHead; // @ts-ignore res.writeHead = (statusCode, reason, obj) => { debug('res.writeHead - start'); if (typeof reason === 'string') { // assume this was called like this: // writeHead(statusCode, reasonPhrase[, headers]) } else { // this was called like this: // writeHead(statusCode[, headers]) obj = reason; } debug('res.statusCode', res.statusCode); if (statusCode === 200 || statusCode === 204) { const wsContextHeaders = wsContext.toHeaders(); debug("Adding wsContext headers", wsContextHeaders); obj = Object.assign({}, obj, wsContextHeaders); // Koa will set status code 204 when the body has been set to // null. This is probably fine since the main stream // for WS-over-HTTP is supposed to have an empty // body anyway. However, we will be adding WebSocket // events into the body, so change it to a 200. statusCode = 200; reason = 'OK'; } debug('res.writeHead - end'); if (typeof reason === 'string') { // @ts-ignore resWriteHead.call(res, statusCode, reason, obj); } else { resWriteHead.call(res, statusCode, obj); } }; debug('res.end'); const resEnd = res.end; // @ts-ignore res.end = (chunk, encoding, callback) => { debug('res.end - start'); debug('res.statusCode', res.statusCode); if (res.statusCode === 200 || res.statusCode === 204) { debug('Getting outgoing events'); const events = wsContext.getOutgoingEvents(); debug('Encoding and writing events', events); res.write(encodeWebSocketEvents(events)); } debug('res.end - end'); // @ts-ignore resEnd.call(res, chunk, encoding, callback); }; } monkeyPatchResMethodsForGripInstruct(res, gripInstructGetter) { debug('res.writeHead'); const resWriteHead = res.writeHead; // @ts-ignore res.writeHead = (statusCode, reason, obj) => { debug('res.writeHead - start'); if (typeof reason === 'string') { // assume this was called like this: // writeHead(statusCode, reasonPhrase[, headers]) } else { // this was called like this: // writeHead(statusCode[, headers]) obj = reason; } debug('res.statusCode', res.statusCode); const gripInstruct = gripInstructGetter(); if (gripInstruct != null) { debug("GripInstruct present"); if (statusCode === 304) { // Code 304 only allows certain headers. // Some web servers strictly enforce this. // In that case we won't be able to use // Grip- headers to talk to the proxy. // Switch to code 200 and use Grip-Status // to specify intended status. debug("Using gripInstruct setStatus header to handle 304"); statusCode = 200; reason = 'OK'; gripInstruct.setStatus(304); } // Apply prefix to channel names gripInstruct.channels = gripInstruct.channels.map((ch) => new Channel(this.prefix + ch.name, ch.prevId)); const gripInstructHeaders = gripInstruct.toHeaders(); debug("Adding GripInstruct headers", gripInstructHeaders); obj = Object.assign({}, obj, gripInstructHeaders); } else { debug("GripInstruct not present"); } debug('res.writeHead - end'); if (typeof reason === 'string') { // @ts-ignore resWriteHead.call(res, statusCode, reason, obj); } else { resWriteHead.call(res, statusCode, obj); } }; } }