bucky-core原理及实现细节

bucky基本原理

挂载hooks文件

包含error, config, bodyparser, formparser, rewrite, log, csrf, cors, response, view, static, context, mysql, redis, api, model, service, action, socket, lift,二十个模块定义APP错误日志,监听启动错误日志

config配置

config业务配置,模块会传入APP实例,app参数配置,但这个时候 app.config 上只有appPath,获取用户config配置路径,挂载 package.json 信息
ignoreConfigFiles是否忽略配置,挂在lodash,并将_.js挂入忽略配置数组
尝试获取env.js下的配置,包含当前node环境

抓取configs/*.js文件,排除忽略的配置,获取对应文件的文件路径,必须先加载app.jsloadConfig做深度merge当前环境,再delete app路径防止重复加载,proxy设置,然后加载其他config文件,加载文件的时候有错则放入app.initErrors里。

如果存在socket文件且开启socket,启动http服务

中间件:代理请求头,重置或重写代理请求头

中间件:设置请求头uuidsequence

bodyparser配置

获取在config加载阶段挂载到app.config上的bodyparser
主要是设置解析请求body的参数,比如上传文件大小限制等等

中间件:bodyparser

formparser配置

获取在config加载阶段挂载到app.config上的formparser

中间件:处理表单文件内容和文件内容,将表单数据挂载到ctx

rewrite配置

获取在config加载阶段挂载到app.config上的rewrite

中间件:处理用户自定义rewrite规则,这里会用到两个处理规则函数,matchmakeTomatch处理匹配命中规则,from规则支持正则、函数、字符串,正则也就是整儿白净的正则,函数的话,会给函数传入请求头部信息,返回非null则认为是匹配成功,并返回匹配数组。字符串则使用minimatch规则,minimatch这里面可能有个坑,比如你想转一个路由,比如a/b=>a/b/这种,那么minimatch会认为这两个是一样的minimatch("a/b/", "a/b") // true!,makeTo处理跳转规则,to规则支持函数和字符串,函数则自行处理,字符串则替换字符串中的${index}match规则中的数据,返回处理后数据,然后解析该url,合并requestquery和替换path,也就是说路径可自定义,query参数为merge方式
获取在config加载阶段挂载到app.config上的redirect

中间件:处理用户自定义redirect规则,处理匹配规则经过matchFrommakeTo处理,然后构造目标url,最后重定向这个目标url

log配置

写日志采用第三方库,log4js

中间件:触发access日志,并处理input/output两种情况
重写console.logconsole.error,其中分别触发application、error日志

csrf配置

跨站请求伪造,简单理解攻击者盗用了你的身份,以你的名义发送恶意请求
中间件:用来生成csrf token

cors配置

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

中间件:每次请求都会重新绑定cors检测函数到本次请求的上下文上也就是·,然后在action执行的时候回检测是否有actioncors设置参数,有则检验并设置cors响应头

设置cors参数支持函数、字符串、Bool类型,启用cors后设置响应头,*/用户自定义/
如果设置并非*,则头中必须设置VaryOrigin,目地告诉客户端服务端对不同源返回不同内容

设置headers,将请求中设置headers,作为相应头的headers传回,请求的headers会作为预检请求的headers,目前告诉服务器实际请求使用的headers,同理告诉客户端实际允许的headers

设置methods,同理headers

设置expose-headers,在跨域请求中,xhr对象中getResponseHeader()只能获取基本的响应头,cache-control、content-language、content-type、expires、last-modified、pragma,访问其他头需要服务端设置响应头expose-headers白名单

设置max-age,设置改参数表示preflight(预检)请求缓存多久有效

设置credentials,表示当前浏览器的是否被允许读取response的内容,如果是预检请求中,则表示实际请求是否可以使用credentials

response配置

封装相应函数,200、302、403、404、500,同时支持自定义response

中间件:挂载上述封装方法

中间件:挂载自定义方法到当前请求上下文,自定义response中,会传入事先挂载到app.config上的response配置

view配置

获取当前用户view配置,模板类型,编译调试参数等
获取views模板匹配路由表,目前也就是让/映射到/views/
构造render函数,renderTemplate函数,函数参数当前appPath、待匹配路由表,当前模板配置
取出当前配置参数,类型、数据、母模板、本次render类型,支持ejs/pug两种,支持缓存(内存),内部实现先查看是否有使用及有无缓存,有则返回,注意这里挂载内存上的是编译后的模板函数,不包含数据
构造真正的带数据模板,函数参数为是否string、view路径,数据,viewConfig(目前仅使用layout参数),构造view路径,数据(merge 本次data到全局data),然后拿构造的view路径去待匹配的路由表中查view文件,然后构造view文件路径,然后拿着view文件路径和数据data使用事先构造的模板函数进行编译,这个时候编译后的只是文档片段(html),尚未使用,这个时候查是否有layout母模板,有则再匹配和编译一次,本次编译使用的数据为之前的data{body:html},最后将这些数据绑定到本次的请求上下文对象上,比如 Object.assign(this.response, { type: 'html', body: html }),这样也就完成了渲染

中间件:将构造构造render函数绑定到response和本次请求上下文,两种方式字符串和文档

static配置

首先还是merge用户和默认配置,生成source_buffer,生成Static对象,该对象提供几种获取文件方式参数,获取文件的md5值、获取文件的buffer类型数据(这里有缓存机制,即cache配置)、获取文本、获取base64类型

构造static文件路由表,同时如果有mix配置则走mix文件,如果有manifest配置,则存到Static.assetsassets下存的是文件路径和md5后的映射关系,最后暴露Staticglobal

中间件:在缓存和manifest同时开启情况下,存static文件的origin=>source关系,然后根据传入文件路径,返回指定类型的文件数据,这里会处理etag、lastModified,且默认是*跨域,cache-control时间默认10s,expires默认当前服务器时间+10s,在query中含nocache&requestfresh(缓存是否新鲜),走304

context配置

中间件:据说是用来追踪请求用的。。。。

MySQL、Redis就不解释了,很简单

api配置

首先还是merge用户和默认配置,这里得说明下,之前bucky是没有暴露config.api配置给用户的,三部曲中的只有action有用户配置,而且一般也不会改动,除非你的action里有共有的部分
然后拿着merge后的config创建API,参数中可设置的有queryEncodecontentTypeuserAgentrequestHandlerresponseHandlercachehostproxy等,这些参数也可以在某类API中设置。

这里首先会创建一个API类,该类为全局API对象,然后获取当前设置api根目录,其实也就是apis/,然后遍历apis/文件夹,拿到文件路径,然后解析(requireAPI)该api文件配置,里面包含解析文件的配置,配置包含默认环境配置、特殊环境配置,每一个环境中配置都是以函数形式,配置中export类型有三个环境,创建配置中会生成该APIconfigMap,存放apiNameapiconfig映射关系,然后执行该API文件中的注册API,然后生成API配置,然后深度merge两个环境的各自的基础配置和configMapAPINameAPIConfig映射关系),最后会merge每个APIconfigbaseConfig。然后会遍历这个总的config,里面包含这个API的所有配置,以及每个api的配置,生成API.APIName.apiName,这种格式,如API.Test.test,然后拿着这个和文件路径生成APIcreateAPI
createAPI里:首先解析每个api类的配置,这里的配置是某一个api配置和API基础配置的深度merge,最终返回的是一个api函数,也就是我们通常会调用的API.Test.test(data, options)这种形式,函数里面干了什么事情呢,首先从options中解析四个参数,query/headers/uriReplacer/ctx,拿着前面解析的parameters做接口数据类型检查,然后创建api请求数据对象,包含uri method timeout proxy displayname encoding headersheaders中会使用每个api中的contenttypeuserAgenthost然后merge传入的headers,所以header可以在api中设置。

根据方法名,合并参数,包含GET、HEAD、OPTIONS三个,剩下处理body数据,multipart/form-data文件类型的表单提交,然后将formData写到上述创建的api请求数据对象中,formData中包含data里面的数据,如果是流stream类型,则设置filenamecontentType

application/json类型则直接设置body为stringify(data)后的数据;

application/x-www-form-urlencoded类型则设置formdata,其余类型则直接data挂到body。然后拼装uri,通过base、uriReplacer规则,然后将query对象转为search类型,此时通过queryEncode判断是否转码。
调用requestHandler,参数有创建的请求数据对象、ctx,这个时候需要注意,如果你的requestHandler是自定义的,那么需要注意,函数中需要返回你处理后的请求数据对象,然后bucky拿着处理后的请求数据对象,然后处理cache缓存,这里默认使用的是Redis,如果使用缓存且缓存有效则使用缓存的结果作为返回,反之发起请求doRequest

doRequest中有递增请求sequence的操作,标识请求的唯一和连续,记录请求时间,请求使用request库,自己封装为promise方式,如果出错则抛出错误,并记录到log.api日志中,正常则调用responseHandler方法,同样报错则抛出错误,如果有cache设置则写到缓存,同时会直接返回这个responseHandler的处理结果
上述创建完成后,会将apiapis分别挂载到apis上和API 上,这里会重写get方法,避免被篡改

特别注意:上述说到的requestHandlerresponseHandler都提供有默认的,defaultRequestHandler简单的返回请求数据对象,defaultResponseHandler则会校验statusCode,解析codeKey、dataKey、messageKey、successCode,这里可以通过刚刚说到的config设置,然后会从返回的body中解析对应的字段,然后检查successCode,如果你的body中的codeKey对应的值和successCode不对应,则会抛出错误

model配置

model配置也是老套路,先解析挂载在app.config上的model配置,获取model的路径,然后加载自己的modelHdicAuth、Utils、S3、WebShot,然后挂载业务model

service配置

service是用于拦截请求用的,粗糙点说就是在action/model/api之前会执行这个,这里可以拦截并处理这个请求,service采用中间件,首先加载内部的三个serviceappwebview、logviewer、login,然后获取业务自定义的service,这里注意到有service各自的依赖service

登录service配置

目前service中包含三个service,login、appwebview、logviewer,三个模块中需要重点说明的是login模块,目前login模块中包含三种方式,分别为cas(uc最新提供的登录方式)、general(原UC登录方式)、hdic(楼盘字典登录),目前使用率最高的为general,部分用cas,hdic就一个,下面简单说明集成方式。

首先login/index.js中export一个函数,该函数管理(校验)三种登录方式。

接着看general方式,首先有部分基础配置appWebViewLoginScript、URL_BASE

appwebview配置

logviewer配置

未完待续

avatar

简单描述下:

首先bucky作为中间层,处于业务serverUI client之间,同时作为应用服务器,里面模块主要包含上面罗列的模块

我们称clientUI client,也就是通常说的传统意义的前端客户端,然后到bucky,首先到redirect,判断是否服务重定向规则,符合则重写浏览器url并发起请求,接着判断是否符合rewrite重写规则,重定向和重写区别,简单讲,比如你想去商店买个面包,但这个商店没面包,但老板说我帮去另外商店找一个给你,这叫重写;如果老师直接让你去哪个商店买面包,这叫重定向。好了,说回来,如果符合rewrite规则,这个时候,bucky会帮你重写浏览器uri,但不会重新发起请求,然后到services层,如果你没有自定义的service,那就是走默认的三个service,如果有自定义的service,则会在action之前执行,所以我们某些时候会用service来做请求的拦截或单独处理不走action及后续逻辑,然后action中可以调用model、api、redis、mysql这些,然后我们在action(action中可能有很复杂的业务或数据处理)中返回这个请求,也就是到了上面的response模块,最后到UI client。上面还有一个模块websocketwebsocket是很灵活的,可以和client双向或单向通信,比如我们的logviewer其实就是一个websocket,当然websocket里也照样可以调用api model 等模块。