UNPKG

15 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 // 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// }