UNPKG

scf-framework

Version:
203 lines (172 loc) 5.08 kB
'use strict' const http = require('http') const url = require('url') const isType = require('type-is') const binaryMimeTypes = [ 'application/octet-stream', 'font/eot', 'font/opentype', 'font/otf', 'image/jpeg', 'image/png', 'image/svg+xml' ] function getPathWithQueryStringParams(event) { return url.format({ pathname: event.path, query: event.queryStringParameters }) } function getEventBody(event) { return Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8') } function clone(json) { return JSON.parse(JSON.stringify(json)) } function getContentType(params) { // only compare mime type; ignore encoding part return params.contentTypeHeader ? params.contentTypeHeader.split(';')[0] : '' } function isContentTypeBinaryMimeType(contentType) { return isType.is(contentType, binaryMimeTypes) } function mapEventToHttpRequest(event, context, socketPath) { const headers = Object.assign({}, event.headers) // 计算Content-Length并设置 if (event.body && !headers['Content-Length']) { const body = getEventBody(event) headers['Content-Length'] = Buffer.byteLength(body) } const clonedEventWithoutBody = clone(event) delete clonedEventWithoutBody.body headers['x-apigateway-event'] = encodeURIComponent( JSON.stringify(clonedEventWithoutBody) ) headers['x-apigateway-context'] = encodeURIComponent(JSON.stringify(context)) return { method: event.httpMethod, path: getPathWithQueryStringParams(event), headers, socketPath } } function forwardResponse(response, resolver) { let buf = [] response .on('data', chunk => buf.push(chunk)) .on('end', () => { const bodyBuffer = Buffer.concat(buf) const statusCode = response.statusCode const headers = response.headers // apigateway暂时不支持chunked transfer if (headers['transfer-encoding'] === 'chunked') { delete headers['transfer-encoding'] } //如果头部传入数组,后续要特殊处理(比如,set-cookie暂时不支持多个) Object.keys(headers).forEach(h => { if (Array.isArray(headers[h])) { headers[h] = headers[h].join(',') } }) const contentType = getContentType({ contentTypeHeader: headers['content-type'] }) const isBase64Encoded = isContentTypeBinaryMimeType(contentType) const body = bodyBuffer.toString(isBase64Encoded ? 'base64' : 'utf8') const successResponse = { statusCode, body, headers, isBase64Encoded } resolver(successResponse) }) } //连接错误 function forwardConnectionErrorResponse(error, resolver) { console.log('ERROR: connection error') console.error(error) const errorResponse = { statusCode: 502, body: '', headers: {} } resolver(errorResponse) } function forwardLibraryErrorResponse(error, resolver) { console.log('ERROR: system error') console.error(error) const errorResponse = { statusCode: 500, body: '', headers: {} } resolver(errorResponse) } function forwardRequest(server, event, context, resolve, reject) { try { const requestOptions = mapEventToHttpRequest( event, context, getSocketPath(server._socketPathSuffix) ) const req = http.request(requestOptions, response => forwardResponse(response, resolve) ) if (event.body) { const body = getEventBody(event) req.write(body) } req .on('error', error => forwardConnectionErrorResponse(error, reject)) .end() } catch (error) { forwardLibraryErrorResponse(error, reject) return server } } function startServer(server) { return server.listen(getSocketPath(server._socketPathSuffix)) } function getSocketPath(socketPathSuffix) { return `/tmp/server-${socketPathSuffix}.sock` } function getRandomString() { return Math.random() .toString(36) .substring(2, 15) } function createServer(app) { const server = http.createServer(app.callback ? app.callback() : app) server._socketPathSuffix = getRandomString() server.on('listening', () => { server._isListening = true }) server .on('close', () => { server._isListening = false }) .on('error', error => { if (error.code === 'EADDRINUSE') { console.warn( `WARNING: Attempting to listen on socket ${getSocketPath( server._socketPathSuffix )}, but it is already in use.` ) server._socketPathSuffix = getRandomString() return server.close(() => startServer(server)) } else { console.log('ERROR: server error') console.error(error) } }) return server } function proxy(server, event, context) { return new Promise((resolve, reject) => { if (server._isListening) { forwardRequest(server, event, context, resolve, reject) } else { startServer(server).on('listening', () => forwardRequest(server, event, context, resolve, reject) ) } }) } exports.createServer = createServer exports.proxy = proxy