UNPKG

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