require('source-map-support').install() require('tsconfig-paths').register() import {request_process, parse_post_param} from './init' const _ = require('lodash') // // 临时调试线上日志打印 // //framework/index.ts(8,13): error TS2339: Property 'setLogLevel' does not exist on type 'Console' // exports.handler = async function (event, context, callback) { // // console.setLogLevel('error') 不能写在此会导致编译错误阿里云自己扩展的私有方法 // console.log('xxxxx', 'yyyyy', 'zzzzzz') // console.error('xxxxx1', 'yyyyy1', 'zzzzzz1') // let out = 'zzzzzz' // let htmlResponse = { // isBase64Encoded: true, // statusCode: 200, // headers: { // "Content-type": "text/html; charset=utf-8", // }, // // base64 encode body so it can be safely returned as JSON value // body: new Buffer(out as string).toString('base64') // } // callback(null, htmlResponse) // } // 基于阿里云的函数计算统一入口的路由处理 exports.handler = async function (event, context, __callback__) { // 重置请求上下文环境变量 xreset() // xlog('请求的参数event',event) // xlog('请求的参数context',context) // TODO 增加对于定时器的功能模拟实现 // 调试打印请求的输入和输出方便准确的定位调试线上的响应信息 xlog('$$$$1111=>' + event.toString()) const callback = (context, out) => { xlog('$$$$=>', out.body ? Object.assign({}, out, {body: xbase64decode(out.body)}) : out) __callback__(context, out) } let evt = JSON.parse(event.toString()) console.log('请求的参数evt',evt) // FIXME yg 定制 ----- 暂时 if(/MP_verify_KEtrfArJjFUvBw93\.txt$/.test(evt.path)){ let jsonResponse = { isBase64Encoded: false, statusCode: 200, headers: { "Content-type": "text/plain", }, body: 'KEtrfArJjFUvBw93' } return callback(null, jsonResponse) } // FIXME 蜜豆游学 定制 ----- 暂时 if(/MP_verify_fQngODKLLpay3tw5\.txt$/.test(evt.path)){ let jsonResponse = { isBase64Encoded: false, statusCode: 200, headers: { "Content-type": "text/plain", }, body: 'MP_verify_fQngODKLLpay3tw5' } return callback(null, jsonResponse) } console.log('特殊路径匹配:',evt.path) // FIXME 蜜豆游学 定制 ----- 暂时 if(/va6V9xUN8j\.txt$/.test(evt.path)){ console.log('匹配成功') let jsonResponse = { isBase64Encoded: false, statusCode: 200, headers: { "Content-type": "text/plain", }, body: '8de0c0c23097c6e90710cb48908a9738' } console.log('返回结果',JSON.stringify(jsonResponse)) return callback(null, jsonResponse) } // console.log('header中的参数:',evt['headers']) // console.log('header中的数据:',JSON.stringify(evt['headers'])) // FIXME yg ====> 特殊路径处理 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)) { let urlBase = evt.path.split("/share/") // console.log('查看分解分解之后的地址',urlBase) let url = xbase64decode(urlBase[urlBase.length - 1]) // console.log('查看解码之后的路径:',url) callback(null, { statusCode: 301, // 永久重定向 headers: { "Location": url, // 特殊路径处理 }, }) return } // TODO 需要对于事件定时器进行统一的约定在应用层可扩展自定义相关定时器等事件类型 if (evt['triggerName']) { // 对定时触发器的统一拦截处理 switch (evt['triggerName']) { // FC线上容器的预热定时器预置到开发框架上统一集成支持 case '__axjs-preheat-timer__': // // 获取请求参数的配置数据 // try { // let payload = JSON.parse(evt['payload']) // xassert(payload['url'] && payload['timeout']) // await xpost(payload['url'], payload['param'], undefined, payload['timeout']) // } catch (err) { // // ignore error // xlog(JSON.stringify(xerror(err)).replace(/\r/g, '').replace(/\n/g, '')) // } break } callback(null, { statusCode: 200, }) return } // else if (evt['path'] === '/__axjs-preheat-timer__') { // // 预热api网关的空请求心跳接口实现 // return // } // FIXME 全站HTTPS开启将HTTP请求做重定向跳转以及主域名跳转(例如youngget.com跳转到www.youngget.com) // 由于api网关暂不支持在nginx上配置重定向跳转,需要在框架层面上做跳转配置支持此特性,解决部分老浏览器无法自动跳转问题。 // 仅仅对根请求开放支持HTTP和HTTPS双重请求,以便于支持自动重定向跳转功能实现。 if (evt.httpMethod == 'GET') { let bIsNeedRedirect = false let domain = evt['headers']['CA-Host'] if (/^[0-9a-zA-Z_-]+\.[0-9a-zA-Z_-]+$/.test(domain)) { domain = 'www.' + domain // 将一级域名强制跳转到www二级主域名上符合国际惯例约定俗成的规范标准化 bIsNeedRedirect = true } if (evt['headers']['X-Forwarded-Proto'] == 'http') { bIsNeedRedirect = true } if (bIsNeedRedirect) { callback(null, { statusCode: 301, // 永久重定向 headers: { "Location": `https://${domain}/`, // 全部重定向到首页不支持非法网址 }, }) return } } // 根据API网关设置的环境常量参数正确配置线上版本的运行环境,取代AONE的本地、日常、预发、灰度等环境部署支持开发。 // 全局变量仅仅用于放在整个应用生命周期变量 global['__env__'] = _.get(evt, 'headers.__env__', 'prod') const __cors__ = {} // FIXME yg定制需要生产环境下支付宝回调请求是跨域的需要支持options的跨域请求(临时放开限制,需要配置允许跨域的白名单列表在API网关上设置) // if (global['__env__'] !== 'prod' && global['__env__'] !== 'gray') { // // 非生产环境或灰度环境,需要禁用CORS功能禁止跨域访问增加安全性。 // if (evt.headers.origin) { // // 获取源站动态允许请求跨域 (FIXME 需要进行安全限制对来源服务器网址合法性进行安全限制,本地开发调试全部放开请求) // __cors__['Access-Control-Allow-Origin'] = evt.headers.origin // // __cors__['Access-Control-Allow-Origin'] = '*' // 不能设置为任意值浏览器有安全限制 // } // __cors__['Access-Control-Allow-Credentials'] = 'true' // __cors__['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept' // __cors__['Access-Control-Allow-Methods'] = 'POST, OPTIONS' // } if (evt.headers.origin) { // 获取源站动态允许请求跨域 (FIXME 需要进行安全限制对来源服务器网址合法性进行安全限制,本地开发调试全部放开请求) __cors__['Access-Control-Allow-Origin'] = evt.headers.origin // __cors__['Access-Control-Allow-Origin'] = '*' // 不能设置为任意值浏览器有安全限制 } __cors__['Access-Control-Allow-Credentials'] = 'true' __cors__['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept' __cors__['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' // 支持协议约定格式 // 支持跨域的验证请求方法的合法性规避掉过滤掉不安全的请求。 switch (evt.httpMethod) { case 'GET': case 'POST': break case 'OPTIONS': // if (global['__env__'] == 'prod' || global['__env__'] == 'gray') { // // 生产和灰度环境下禁止跨域OPTIONS请求以便于增强安全性 // callback(null, { // statusCode: 501, // }) // } else { // // 非生产和灰度环境下才允许支持跨域OPTIONS请求 // callback(null, { // statusCode: 200, // headers: { // ...__cors__, // }, // }) // } // FIXME yg定制需要生产环境下支付宝回调请求是跨域的需要支持options的跨域请求(临时放开限制,需要配置允许跨域的白名单列表在API网关上设置) callback(null, { statusCode: 200, headers: { ...__cors__, }, }) return default: // 禁止任何的非法请求严格进行约定限制 callback(null, { statusCode: 502, }) return } global['__evt__'] = event.toString() // FIXME 需要设计上下文定义请求实例生命周期变量 // xlog('获取全局变量global["__evt__"]的值:',global['__evt__']) // 先以HEAER中的__api__字段进行识别,如果HEADER中没有定义再使用URL路径对应的PATH识别,以此支持SEO等前端路径重写问题。 let __api__ = _.get(evt, 'headers.__api__', evt.path) // 最终控制器与app子目录下的ts文件保持完全的一一映射关系。 // 改进为根据API网关的相关参数自动拼接出来正确的URL网址请求路径 let __url__ = evt['headers']['X-Forwarded-Proto'] + '://' + evt['headers']['CA-Host'] + evt['path'] // 将全部的HEADER信息透传到应用中 // console.log(838383838383838) // console.log(JSON.stringify(evt['headers'])) let __header__ = evt['headers'] let param = Object.assign( {__api__, __url__, __header__}, // header请求中的两个框架层面上的预定义参数 TODO 需要移除掉并非应用关心的内容 evt.pathParameters || {}, // 路由重写参数 domain/[a]/[b]?xxx (可被GET参数覆盖) evt.queryParameters || {} // GET请求参数 domain/path?a=x&b=x ) let out = {} as any // // 条件日志打印线上临时问题排查处理(TODO 增加业务逻辑扩展注入的钩子实现) // let xdebug = async (...args) => { // if (!_.includes(__url__, 'y.alibaba-inc.com')) { // return // } // await xwarn(...args) // } // global['xdebug'] = xdebug // 全局可用 // await xdebug({param, evt}) switch (evt.httpMethod) { case 'GET': break case 'POST': try { // 阿里云API网关的POST请求参数是JSON字符串的BASE64编码所以需要进行转义处理 let post = {} if (_.isString(evt.body)) { let body = evt.body if (evt.isBase64Encoded) { body = new Buffer(evt.body, 'base64').toString() } post = parse_post_param(evt['headers'], body) } param = Object.assign(param, post) // 用post参数覆盖掉get参数,用于灵活的设置请求参数兼容get和post两种方法方便开发调试。 } catch (err) { // xlog(err, evt.httpMethod, evt.body) await xwarn(err) // 400 Bad Request 客户端请求的语法错误,服务器无法理解 callback(null, { statusCode: 503, }) return } break default: // xlog(evt.httpMethod, evt.body) // 400 Bad Request 客户端请求的语法错误,服务器无法理解 callback(null, { statusCode: 504, }) return } try { console.log('进入try') // 解析请求中的cookies数据并转换为JSON对象存储到全局变量中便于后续应用xcookie接口使用 let cookie = _.get(evt['headers'], 'Cookie', undefined) console.log('获取cookie信息',JSON.stringify(cookie)) if (!cookie) { cookie = _.get(evt['headers'], 'cookie', '') } global['__request_cookies__'] = require('cookie').parse(cookie) console.log('赋值给__request_cookies__',global['__request_cookies__']) // 获取use-agent请求头部信息 global['__user_agent__'] = evt['headers']['User-Agent'] console.log('赋值给__user_agent__',global['__user_agent__']) // 获取客户端的IP地址信息 global['__client_ip__'] = evt['headers']['X-Real-IP'] console.log('赋值给__client_ip__',global['__client_ip__']) // TODO WEB请求需要对404页面以及ERROR页面进行兼容处理(在framework内部进行细节的错误判断在out中透传status的http的状态吗正确错误返回) // 404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 // 403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求(权限验证处理错误) // console.log('查看框架是否又错') console.log('__api__是否出错',__api__) out = await request_process(__api__, param) console.log('返回的out值',JSON.stringify(out)) if (_.isEmpty(out)) { // 对于nowrap的情况做特殊容错处理 out = undefined } // console.log('准备处理cookies') // 处理response的cookies设置 let setCookie = {'Set-Cookie': undefined} if (!_.isEmpty(global['__respond_cookies__'])) { setCookie['Set-Cookie'] = _.values(global['__respond_cookies__']) } // console.log('查看redirect_url的值',global['__redirect_url__']) if (global['__redirect_url__']) { // console.log('进入第一个选项') callback(null, { statusCode: 302, headers: { "Location": global['__redirect_url__'], ...setCookie }, }) global['__redirect_url__'] = undefined } else if (_.isString(out)) { // console.log('进入第二个选项') let htmlResponse = { isBase64Encoded: true, statusCode: 200, headers: { "Content-type": "text/html; charset=utf-8", ...setCookie, ...__cors__, }, // base64 encode body so it can be safely returned as JSON value body: new Buffer(out as string).toString('base64') } callback(null, htmlResponse) } else { // console.log('进入第三个选项') let jsonResponse = { isBase64Encoded: true, statusCode: 200, headers: { "Content-type": "application/json", ...__cors__, }, // base64 encode body so it can be safely returned as JSON value body: new Buffer(out ? JSON.stringify(out) : '').toString('base64') } callback(null, jsonResponse) } } catch (err) { // 通知框架自身实现逻辑的意外报错(框架自身不论何种情况都应该正常工作,一旦出现此问题大多数情况是框架自身问题或者流量引发的运维问题) await xwarn({ __api__, __url__, param, message: err.message, stack: xstack(err) }) // 500 Internal Server Error 服务器内部错误,无法完成请求 callback(null, {statusCode: 505}) } } // API参数的识别逻辑,为普通字符串路径定义与app下的ts文件路由完全一一对应,优先取HEADER中的定义,再取GET中的形参定义或者POST形参定义进行参数请求的覆盖处理。 // PARAM请求的参数处理要求为BASE64编码化的JSON字符串。 // "event": { // "body": "ewoJImFwaSI6ICIvYS9iL2MiLAoJInBhcmFtIjogImFzZGZhc2RmYXNkZiIKfQ==", => 对应于POST请求数据 // "headers": { // "X-Ca-Api-Gateway": "B25CD51B-5815-4775-9EAB-6D481BC1AE17", // "__api__": "/web/mobile/test", =》 自定义转义PATH的路径信息对于SEO优化兼容处理(也可以在GET或POST请求中传递对应参数) // "X-Forwarded-For": "42.120.74.88", // "Content-Type": "application/json" // }, // "httpMethod": "POST", // "isBase64Encoded": true, // "path": "/", =》 没有意义用于前端根据需要自己进行扩展别名映射处理,后端的控制器不以此为标准,可以对同一个控制器定义N个不同的路径标识。 // "pathParameters": {}, // "queryParameters": {} => 对应于GET请求数据 // }, // "query": {}, // "context": { // "requestId": "B25CD51B-5815-4775-9EAB-6D481BC1AE17", // "credentials": { // "accessKeyId": "", // "accessKeySecret": "", // "securityToken": "" // }, // "function": { // "name": "test", // "handler": "index.handler", // "memory": 128, // "timeout": 300 // }, // "services": { // "name": "alilang", // "logProject": "", // "logStore": "" // }, // "region": "cn-shanghai", // "accountId": "1734066057689528" // } // } // 最新版本event的API网关的FC返回数据格式: // CA-Host请求域名、 // X-Forwarded-Proto // "path": "/test", ==》》基本上可以拼接出__url__参数可以省略掉此冗余参数配置了。 // ==》》借助route.map.ts文件的映射关系定义省略掉__api__配置进一步简化网关应用。 // "httpMethod": "GET", // "isBase64Encoded": true, // "X-Forwarded-For": "42.120.74.103", // 客户端请求IP地址用户判定所属区域信息海外访问问题优化依赖点 // "X-Real-IP": "42.120.74.103", // Cookie // User-Agent =>> PC站和M站自适应问题 // Accept-Language =>> 浏览器客户端的语言类型自动适配多语言架构设计问题 // let x = { // "body": "", // "headers": { // "X-Ca-Api-Gateway": "B276F77B-334E-4857-AE64-65BAFD419E2A", // "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", // "X-Forwarded-Proto": "https", // "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", // "__url__": "https://toufang.alibaba-inc.com/test", // "CA-Host": "toufang.alibaba-inc.com", // "Cache-Control": "max-age=0", // "upgrade-insecure-requests": "1", // "Accept-Language": "zh-CN,zh;q=0.9", // "__api__": "/web/test/test", // "Accept-Encoding": "gzip, deflate, br", // "X-Forwarded-For": "42.120.74.103", // "X-Real-IP": "42.120.74.103", // "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8" // }, // "httpMethod": "GET", // "isBase64Encoded": true, // "path": "/test", // "pathParameters": {}, // "queryParameters": {} // }