UNPKG

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