1 | require('source-map-support').install()
|
2 | require('tsconfig-paths').register()
|
3 | import {request_process, parse_post_param} from './init'
|
4 |
|
5 | const _ = require('lodash')
|
6 |
|
7 | // // 临时调试线上日志打印
|
8 | // //framework/index.ts(8,13): error TS2339: Property 'setLogLevel' does not exist on type 'Console'
|
9 | // exports.handler = async function (event, context, callback) {
|
10 | // // console.setLogLevel('error') 不能写在此会导致编译错误阿里云自己扩展的私有方法
|
11 | // console.log('xxxxx', 'yyyyy', 'zzzzzz')
|
12 | // console.error('xxxxx1', 'yyyyy1', 'zzzzzz1')
|
13 | // let out = '<html><body>zzzzzz</body></html>'
|
14 | // let htmlResponse = {
|
15 | // isBase64Encoded: true,
|
16 | // statusCode: 200,
|
17 | // headers: {
|
18 | // "Content-type": "text/html; charset=utf-8",
|
19 | // },
|
20 | // // base64 encode body so it can be safely returned as JSON value
|
21 | // body: new Buffer(out as string).toString('base64')
|
22 | // }
|
23 | // callback(null, htmlResponse)
|
24 | // }
|
25 |
|
26 | // 基于阿里云的函数计算统一入口的路由处理
|
27 | exports.handler = async function (event, context, callback) {
|
28 | // TODO 增加对于定时器的功能模拟实现
|
29 | // xlog('$$$$=>' + event.toString())
|
30 | // TODO 去除掉换行方便方便SLS上的日志输出排版显示
|
31 | // xlog('$$$$=>' + event.toString().replace(/\r/g, '').replace(/\n/g, ''))
|
32 |
|
33 | let evt = JSON.parse(event.toString())
|
34 |
|
35 | // 根据API网关设置的环境常量参数正确配置线上版本的运行环境,取代AONE的本地、日常、预发、灰度等环境部署支持开发。
|
36 | // 全局变量仅仅用于放在整个应用生命周期变量
|
37 | global['__env__'] = _.get(evt, 'headers.__env__', 'prod')
|
38 |
|
39 | const __cors__ = {}
|
40 | if (global['__env__'] !== 'prod' && global['__env__'] !== 'gray') {
|
41 | // 非生产环境或灰度环境,需要禁用CORS功能禁止跨域访问增加安全性。
|
42 | if (evt.headers.origin) {
|
43 | // 获取源站动态允许请求跨域 (FIXME 需要进行安全限制对来源服务器网址合法性进行安全限制,本地开发调试全部放开请求)
|
44 | __cors__['Access-Control-Allow-Origin'] = evt.headers.origin
|
45 | // __cors__['Access-Control-Allow-Origin'] = '*' // 不能设置为任意值浏览器有安全限制
|
46 | }
|
47 | __cors__['Access-Control-Allow-Credentials'] = 'true'
|
48 | __cors__['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept'
|
49 | __cors__['Access-Control-Allow-Methods'] = 'POST, OPTIONS'
|
50 | }
|
51 |
|
52 | // 支持跨域的验证请求方法的合法性规避掉过滤掉不安全的请求。
|
53 | switch (evt.httpMethod) {
|
54 | case 'GET':
|
55 | case 'POST':
|
56 | break
|
57 | case 'OPTIONS':
|
58 | if (global['__env__'] == 'prod' || global['__env__'] == 'gray') {
|
59 | // 生产和灰度环境下禁止跨域OPTIONS请求以便于增强安全性
|
60 | callback(null, {
|
61 | statusCode: 501,
|
62 | })
|
63 | } else {
|
64 | // 非生产和灰度环境下才允许支持跨域OPTIONS请求
|
65 | callback(null, {
|
66 | statusCode: 200,
|
67 | headers: {
|
68 | ...__cors__,
|
69 | },
|
70 | })
|
71 | }
|
72 | return
|
73 | default:
|
74 | // 禁止任何的非法请求严格进行约定限制
|
75 | callback(null, {
|
76 | statusCode: 502,
|
77 | })
|
78 | return
|
79 | }
|
80 |
|
81 | // TODO 需要对于事件定时器进行统一的约定在应用层可扩展自定义相关定时器等事件类型
|
82 | if (evt['triggerName']) {
|
83 | // 对定时触发器的统一拦截处理
|
84 | switch (evt['triggerName']) {
|
85 | case '__axjs-preheat-timer__':
|
86 | // 获取请求参数的配置数据
|
87 | try {
|
88 | let payload = JSON.parse(evt['payload'])
|
89 | xassert(payload['url'] && payload['timeout'])
|
90 | await xpost(payload['url'], payload['param'], undefined, payload['timeout'])
|
91 | } catch (err) {
|
92 | // ignore error
|
93 | xlog(JSON.stringify(xerror(err)).replace(/\r/g, '').replace(/\n/g, ''))
|
94 | }
|
95 | break
|
96 | }
|
97 | return
|
98 | } else if (evt['path'] === '/__axjs-preheat-timer__') {
|
99 | // 预热api网关的空请求心跳接口实现
|
100 | return
|
101 | }
|
102 | global['__evt__'] = event.toString() // FIXME 需要设计上下文定义请求实例生命周期变量
|
103 | // 先以HEAER中的__api__字段进行识别,如果HEADER中没有定义再使用URL路径对应的PATH识别,以此支持SEO等前端路径重写问题。
|
104 | let __api__ = _.get(evt, 'headers.__api__', evt.path) // 最终控制器与app子目录下的ts文件保持完全的一一映射关系。
|
105 | // 改进为根据API网关的相关参数自动拼接出来正确的URL网址请求路径
|
106 | let __url__ = evt['headers']['X-Forwarded-Proto'] + '://' + evt['headers']['CA-Host'] + evt['path']
|
107 | // 将全部的HEADER信息透传到应用中
|
108 | let __header__ = evt['headers']
|
109 |
|
110 | let param = Object.assign(
|
111 | {__api__, __url__, __header__}, // header请求中的两个框架层面上的预定义参数 TODO 需要移除掉并非应用关心的内容
|
112 | evt.pathParameters || {}, // 路由重写参数 domain/[a]/[b]?xxx (可被GET参数覆盖)
|
113 | evt.queryParameters || {} // GET请求参数 domain/path?a=x&b=x
|
114 | )
|
115 | let out = {}
|
116 |
|
117 | // // 条件日志打印线上临时问题排查处理(TODO 增加业务逻辑扩展注入的钩子实现)
|
118 | // let xdebug = async (...args) => {
|
119 | // if (!_.includes(__url__, 'y.alibaba-inc.com')) {
|
120 | // return
|
121 | // }
|
122 | // await xwarn(...args)
|
123 | // }
|
124 | // global['xdebug'] = xdebug // 全局可用
|
125 | // await xdebug({param, evt})
|
126 |
|
127 | switch (evt.httpMethod) {
|
128 | case 'GET':
|
129 | break
|
130 | case 'POST':
|
131 | try {
|
132 | // 阿里云API网关的POST请求参数是JSON字符串的BASE64编码所以需要进行转义处理
|
133 | let post = {}
|
134 | if (_.isString(evt.body)) {
|
135 | let body = evt.body
|
136 | if (evt.isBase64Encoded) {
|
137 | body = new Buffer(evt.body, 'base64').toString()
|
138 | }
|
139 | post = parse_post_param(evt['headers'], body)
|
140 | }
|
141 | param = Object.assign(param, post) // 用post参数覆盖掉get参数,用于灵活的设置请求参数兼容get和post两种方法方便开发调试。
|
142 | } catch (err) {
|
143 | xlog(err, evt.httpMethod, evt.body)
|
144 | await xwarn(err)
|
145 | // 400 Bad Request 客户端请求的语法错误,服务器无法理解
|
146 | callback(null, {
|
147 | statusCode: 503,
|
148 | })
|
149 | return
|
150 | }
|
151 | break
|
152 | default:
|
153 | xlog(evt.httpMethod, evt.body)
|
154 | // 400 Bad Request 客户端请求的语法错误,服务器无法理解
|
155 | callback(null, {
|
156 | statusCode: 504,
|
157 | })
|
158 | return
|
159 | }
|
160 |
|
161 | try {
|
162 | // 解析请求中的cookies数据并转换为JSON对象存储到全局变量中便于后续应用xcookie接口使用
|
163 | let cookie = _.get(evt['headers'], 'Cookie', undefined)
|
164 | if (!cookie) {
|
165 | cookie = _.get(evt['headers'], 'cookie', '')
|
166 | }
|
167 | global['__request_cookies__'] = require('cookie').parse(cookie)
|
168 |
|
169 | // 获取use-agent请求头部信息
|
170 | global['__user_agent__'] = evt['headers']['User-Agent']
|
171 | // 获取客户端的IP地址信息
|
172 | global['__client_ip__'] = evt['headers']['X-Real-IP']
|
173 |
|
174 | // TODO WEB请求需要对404页面以及ERROR页面进行兼容处理(在framework内部进行细节的错误判断在out中透传status的http的状态吗正确错误返回)
|
175 | // 404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
|
176 | // 403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求(权限验证处理错误)
|
177 | out = await request_process(__api__, param)
|
178 |
|
179 | // 处理response的cookies设置
|
180 | let setCookie = {'Set-Cookie': undefined}
|
181 | if (!_.isEmpty(global['__respond_cookies__'])) {
|
182 | setCookie['Set-Cookie'] = _.values(global['__respond_cookies__'])
|
183 | }
|
184 |
|
185 | if (global['__redirect_url__']) {
|
186 | callback(null, {
|
187 | statusCode: 302,
|
188 | headers: {
|
189 | "Location": global['__redirect_url__'],
|
190 | ...setCookie
|
191 | },
|
192 | })
|
193 | global['__redirect_url__'] = undefined
|
194 | }
|
195 | else if (_.isString(out)) {
|
196 | let htmlResponse = {
|
197 | isBase64Encoded: true,
|
198 | statusCode: 200,
|
199 | headers: {
|
200 | "Content-type": "text/html; charset=utf-8",
|
201 | ...setCookie,
|
202 | ...__cors__,
|
203 | },
|
204 | // base64 encode body so it can be safely returned as JSON value
|
205 | body: new Buffer(out as string).toString('base64')
|
206 | }
|
207 | callback(null, htmlResponse)
|
208 | } else {
|
209 | let jsonResponse = {
|
210 | isBase64Encoded: true,
|
211 | statusCode: 200,
|
212 | headers: {
|
213 | "Content-type": "application/json",
|
214 | ...__cors__,
|
215 | },
|
216 | // base64 encode body so it can be safely returned as JSON value
|
217 | body: new Buffer(JSON.stringify(out)).toString('base64')
|
218 | }
|
219 | callback(null, jsonResponse)
|
220 | }
|
221 | } catch (err) {
|
222 | // 通知框架自身实现逻辑的意外报错(框架自身不论何种情况都应该正常工作,一旦出现此问题大多数情况是框架自身问题或者流量引发的运维问题)
|
223 | await xwarn({
|
224 | __api__, __url__, param, message: err.message, stack: xstack(err)
|
225 | })
|
226 | // 500 Internal Server Error 服务器内部错误,无法完成请求
|
227 | callback(null, {statusCode: 505})
|
228 | }
|
229 | }
|
230 |
|
231 | // API参数的识别逻辑,为普通字符串路径定义与app下的ts文件路由完全一一对应,优先取HEADER中的定义,再取GET中的形参定义或者POST形参定义进行参数请求的覆盖处理。
|
232 | // PARAM请求的参数处理要求为BASE64编码化的JSON字符串。
|
233 | // "event": {
|
234 | // "body": "ewoJImFwaSI6ICIvYS9iL2MiLAoJInBhcmFtIjogImFzZGZhc2RmYXNkZiIKfQ==", => 对应于POST请求数据
|
235 | // "headers": {
|
236 | // "X-Ca-Api-Gateway": "B25CD51B-5815-4775-9EAB-6D481BC1AE17",
|
237 | // "__api__": "/web/mobile/test", =》 自定义转义PATH的路径信息对于SEO优化兼容处理(也可以在GET或POST请求中传递对应参数)
|
238 | // "X-Forwarded-For": "42.120.74.88",
|
239 | // "Content-Type": "application/json"
|
240 | // },
|
241 | // "httpMethod": "POST",
|
242 | // "isBase64Encoded": true,
|
243 | // "path": "/", =》 没有意义用于前端根据需要自己进行扩展别名映射处理,后端的控制器不以此为标准,可以对同一个控制器定义N个不同的路径标识。
|
244 | // "pathParameters": {},
|
245 | // "queryParameters": {} => 对应于GET请求数据
|
246 | // },
|
247 | // "query": {},
|
248 | // "context": {
|
249 | // "requestId": "B25CD51B-5815-4775-9EAB-6D481BC1AE17",
|
250 | // "credentials": {
|
251 | // "accessKeyId": "",
|
252 | // "accessKeySecret": "",
|
253 | // "securityToken": ""
|
254 | // },
|
255 | // "function": {
|
256 | // "name": "test",
|
257 | // "handler": "index.handler",
|
258 | // "memory": 128,
|
259 | // "timeout": 300
|
260 | // },
|
261 | // "services": {
|
262 | // "name": "alilang",
|
263 | // "logProject": "",
|
264 | // "logStore": ""
|
265 | // },
|
266 | // "region": "cn-shanghai",
|
267 | // "accountId": "1734066057689528"
|
268 | // }
|
269 | // }
|
270 |
|
271 | // 最新版本event的API网关的FC返回数据格式:
|
272 | // CA-Host请求域名、
|
273 | // X-Forwarded-Proto
|
274 | // "path": "/test", ==》》基本上可以拼接出__url__参数可以省略掉此冗余参数配置了。
|
275 | // ==》》借助route.map.ts文件的映射关系定义省略掉__api__配置进一步简化网关应用。
|
276 | // "httpMethod": "GET",
|
277 | // "isBase64Encoded": true,
|
278 | // "X-Forwarded-For": "42.120.74.103", // 客户端请求IP地址用户判定所属区域信息海外访问问题优化依赖点
|
279 | // "X-Real-IP": "42.120.74.103",
|
280 | // Cookie
|
281 | // User-Agent =>> PC站和M站自适应问题
|
282 | // Accept-Language =>> 浏览器客户端的语言类型自动适配多语言架构设计问题
|
283 | // let x = {
|
284 | // "body": "",
|
285 | // "headers": {
|
286 | // "X-Ca-Api-Gateway": "B276F77B-334E-4857-AE64-65BAFD419E2A",
|
287 | // "Cookie": "cna=r5snEzzOvA0CASp4Smdq+II/; UM_distinctid=162bcd61d9111cd-080e0f43e6b60d-33697b04-13c680-162bcd61d92c86; _tb_token_=H8U7BqixF3YVR3GnhMRz; NEW2_ACR_JSESSIONID=VM566F91-K5CP8QIVMQTSAH3C4H5X1-DRIHYJHJ-QE2; _new_cr_session0=1AbLByOMHeZe3G41KYd5WcPdC%2Fi8qvGHUBTK8Fbrfx8Soi%2BHELuxxA6jros7W%2FqC1YtebgB3auEF5lu1SCzUzTkt6v%2FiFeN%2FptbvBRziYEGXSEVhWnUlBR2tfpjrXMnIcfb2%2FwnGkH4vkeMIJ1Bvuw%3D%3D; emplId=149337; hrc_sidebar=open; traceId=7d4b16de-74bc-4eac-884b-54ca0354e4aa; SSO_LANG=ZH-CN; SSO_EMPID_HASH=9db1ed21402f7c36674b5e6e6de1fc68; animate_date=201864; aa=xxxxxxx; cn_1260001221_dplus=%7B%22distinct_id%22%3A%20%22162bcd61d9111cd-080e0f43e6b60d-33697b04-13c680-162bcd61d92c86%22%2C%22sp%22%3A%20%7B%22%24_sessionid%22%3A%200%2C%22%24_sessionTime%22%3A%201528086234%2C%22%24dp%22%3A%200%2C%22%24_sessionPVTime%22%3A%201528086234%7D%7D; isg=BDw8SonyS9utyn5fedYRe-uRDdzwNAD0-51BHha9_ScJ4dxrNkWw77KQxQmZqRi3",
|
288 | // "X-Forwarded-Proto": "https",
|
289 | // "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
|
290 | // "__url__": "https://toufang.alibaba-inc.com/test",
|
291 | // "CA-Host": "toufang.alibaba-inc.com",
|
292 | // "Cache-Control": "max-age=0",
|
293 | // "upgrade-insecure-requests": "1",
|
294 | // "Accept-Language": "zh-CN,zh;q=0.9",
|
295 | // "__api__": "/web/test/test",
|
296 | // "Accept-Encoding": "gzip, deflate, br",
|
297 | // "X-Forwarded-For": "42.120.74.103",
|
298 | // "X-Real-IP": "42.120.74.103",
|
299 | // "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
|
300 | // },
|
301 | // "httpMethod": "GET",
|
302 | // "isBase64Encoded": true,
|
303 | // "path": "/test",
|
304 | // "pathParameters": {},
|
305 | // "queryParameters": {}
|
306 | // }
|