1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | 'use strict'
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | const path = require('path')
|
19 |
|
20 | const handlers = require('../lib/handlers.js')
|
21 | const wrapper = require('../lib/wrapper.js')
|
22 |
|
23 |
|
24 | function normaliseLambdaRequest (
|
25 | event /* : LambdaEvent */
|
26 | ) /* : BmRequest */ {
|
27 | const headers = wrapper.keysToLowerCase(event.headers)
|
28 | let body = event.body
|
29 | try {
|
30 | body = JSON.parse(body)
|
31 | } catch (e) {
|
32 |
|
33 | }
|
34 | const host = headers['x-forwarded-host'] || headers.host
|
35 | return {
|
36 | body,
|
37 | headers,
|
38 | method: wrapper.normaliseMethod(event.httpMethod),
|
39 | route: event.path,
|
40 | url: {
|
41 | host,
|
42 | hostname: host,
|
43 | params: {},
|
44 | pathname: event.path,
|
45 | protocol: wrapper.protocolFromHeaders(headers),
|
46 | query: event.queryStringParameters || {}
|
47 | }
|
48 | }
|
49 | }
|
50 |
|
51 | function handler (
|
52 | event /* : LambdaEvent */,
|
53 | context /* : any */,
|
54 | cb /* : (error: null, response: {
|
55 | body: string,
|
56 | headers: Headers,
|
57 | statusCode: number
|
58 | }) => void */
|
59 | ) {
|
60 | const startTime = Date.now()
|
61 | const request = normaliseLambdaRequest(event)
|
62 | const internalHeaders = {}
|
63 | internalHeaders['Content-Type'] = 'application/json'
|
64 | const finish = (statusCode, body, customHeaders) => {
|
65 | const headers = Object.assign(internalHeaders, customHeaders)
|
66 | const endTime = Date.now()
|
67 | const requestTime = endTime - startTime
|
68 | console.log('BLINKM_ANALYTICS_EVENT', JSON.stringify({
|
69 | request: {
|
70 | method: request.method.toUpperCase(),
|
71 | query: request.url.query,
|
72 | port: 443,
|
73 | path: request.route,
|
74 | hostName: request.url.hostname,
|
75 | params: request.url.params,
|
76 | protocol: request.url.protocol
|
77 | },
|
78 | response: {
|
79 | statusCode: statusCode
|
80 | },
|
81 | requestTime: {
|
82 | startDateTime: new Date(startTime),
|
83 | startTimeStamp: startTime,
|
84 | endDateTime: new Date(endTime),
|
85 | endTimeStamp: endTime,
|
86 | ms: requestTime,
|
87 | s: requestTime / 1000
|
88 | }
|
89 | }, null, 2))
|
90 | cb(null, {
|
91 | body: JSON.stringify(body, null, 2),
|
92 | headers: wrapper.keysToLowerCase(headers),
|
93 | statusCode: statusCode
|
94 | })
|
95 | }
|
96 |
|
97 | return Promise.resolve()
|
98 |
|
99 | .then(() => require(path.join(__dirname, 'bm-server.json')))
|
100 | .then((config) => {
|
101 |
|
102 | let routeConfig
|
103 | try {
|
104 | routeConfig = handlers.findRouteConfig(event.path, config.routes)
|
105 | request.url.params = routeConfig.params || {}
|
106 | request.route = routeConfig.route
|
107 | } catch (error) {
|
108 | return finish(404, {
|
109 | error: 'Not Found',
|
110 | message: error.message,
|
111 | statusCode: 404
|
112 | })
|
113 | }
|
114 |
|
115 |
|
116 | if (request.headers.origin) {
|
117 | if (!config.cors) {
|
118 |
|
119 | return finish(405, {
|
120 | error: 'Method Not Allowed',
|
121 | message: 'OPTIONS method has not been implemented',
|
122 | statusCode: 405
|
123 | })
|
124 | }
|
125 | if (!config.cors.origins.some((origin) => origin === '*' || origin === request.headers.origin)) {
|
126 |
|
127 | return finish(200)
|
128 | }
|
129 |
|
130 | internalHeaders['Access-Control-Allow-Origin'] = request.headers.origin
|
131 | internalHeaders['Access-Control-Expose-Headers'] = config.cors.exposedHeaders.join(',')
|
132 |
|
133 | if (request.method === 'options' && request.headers['access-control-request-method']) {
|
134 | internalHeaders['Access-Control-Allow-Headers'] = config.cors.headers.join(',')
|
135 | internalHeaders['Access-Control-Allow-Methods'] = request.headers['access-control-request-method']
|
136 | internalHeaders['Access-Control-Max-Age'] = config.cors.maxAge
|
137 | }
|
138 |
|
139 | if (config.cors.credentials) {
|
140 | internalHeaders['Access-Control-Allow-Credentials'] = true
|
141 | }
|
142 | }
|
143 | if (request.method === 'options') {
|
144 |
|
145 |
|
146 | return finish(200)
|
147 | }
|
148 |
|
149 |
|
150 |
|
151 | const projectPath = path.join(__dirname, 'project')
|
152 | if (process.cwd() !== projectPath) {
|
153 | try {
|
154 | process.chdir(projectPath)
|
155 | } catch (err) {
|
156 | return Promise.reject(new Error(`Could not change current working directory to '${projectPath}': ${err}`))
|
157 | }
|
158 | }
|
159 |
|
160 | return handlers.getHandler(path.join(__dirname, routeConfig.module), request.method)
|
161 | .then((handler) => {
|
162 | if (typeof handler !== 'function') {
|
163 | return finish(405, {
|
164 | error: 'Method Not Allowed',
|
165 | message: `${request.method.toUpperCase()} method has not been implemented`,
|
166 | statusCode: 405
|
167 | })
|
168 | }
|
169 |
|
170 | return handlers.executeHandler(handler, request)
|
171 | .then((response) => finish(response.statusCode, response.payload, response.headers))
|
172 | })
|
173 | })
|
174 | .catch((error) => {
|
175 | if (error && error.stack) {
|
176 | console.error(error.stack)
|
177 | }
|
178 | if (error && error.isBoom && error.output && error.output.payload && error.output.statusCode) {
|
179 | if (error.data) {
|
180 | console.error('Boom Data: ', JSON.stringify(error.data, null, 2))
|
181 | }
|
182 | return finish(error.output.statusCode, error.output.payload, error.output.headers)
|
183 | }
|
184 | finish(500, {
|
185 | error: 'Internal Server Error',
|
186 | message: 'An internal server error occurred',
|
187 | statusCode: 500
|
188 | })
|
189 | })
|
190 | }
|
191 |
|
192 | module.exports = {
|
193 | handler,
|
194 | normaliseLambdaRequest
|
195 | }
|