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.js,loadConfig做深度merge当前环境,再delete app路径防止重复加载,proxy设置,然后加载其他config文件,加载文件的时候有错则放入app.initErrors里。
如果存在socket文件且开启socket,启动http服务
中间件:代理请求头,重置或重写代理请求头
中间件:设置请求头
uuid和sequence
bodyparser配置
获取在
config加载阶段挂载到app.config上的bodyparser
主要是设置解析请求body的参数,比如上传文件大小限制等等
中间件:
bodyparser
formparser配置
获取在config加载阶段挂载到
app.config上的formparser
中间件:处理表单文件内容和文件内容,将表单数据挂载到ctx
rewrite配置
获取在config加载阶段挂载到
app.config上的rewrite
中间件:处理用户自定义
rewrite规则,这里会用到两个处理规则函数,match和makeTo,match处理匹配命中规则,from规则支持正则、函数、字符串,正则也就是整儿白净的正则,函数的话,会给函数传入请求头部信息,返回非null则认为是匹配成功,并返回匹配数组。字符串则使用minimatch规则,minimatch这里面可能有个坑,比如你想转一个路由,比如a/b=>a/b/这种,那么minimatch会认为这两个是一样的minimatch("a/b/", "a/b")// true!,makeTo处理跳转规则,to规则支持函数和字符串,函数则自行处理,字符串则替换字符串中的${index}为match规则中的数据,返回处理后数据,然后解析该url,合并request的query和替换path,也就是说路径可自定义,query参数为merge方式
获取在config加载阶段挂载到app.config上的redirect
中间件:处理用户自定义
redirect规则,处理匹配规则经过matchFrom和makeTo处理,然后构造目标url,最后重定向这个目标url
log配置
写日志采用第三方库,
log4js
中间件:触发access日志,并处理input/output两种情况
重写console.log和console.error,其中分别触发application、error日志
csrf配置
跨站请求伪造,简单理解攻击者盗用了你的身份,以你的名义发送恶意请求
中间件:用来生成csrf token
cors配置
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
中间件:每次请求都会重新绑定
cors检测函数到本次请求的上下文上也就是·,然后在action执行的时候回检测是否有action的cors设置参数,有则检验并设置cors响应头
设置
cors参数支持函数、字符串、Bool类型,启用cors后设置响应头,*/用户自定义/
如果设置并非*,则头中必须设置Vary为Origin,目地告诉客户端服务端对不同源返回不同内容
设置
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.assets,assets下存的是文件路径和md5后的映射关系,最后暴露Static到global
中间件:在缓存和
manifest同时开启情况下,存static文件的origin=>source关系,然后根据传入文件路径,返回指定类型的文件数据,这里会处理etag、lastModified,且默认是*跨域,cache-control时间默认10s,expires默认当前服务器时间+10s,在query中含nocache&request含fresh(缓存是否新鲜),走304
context配置
中间件:据说是用来追踪请求用的。。。。
MySQL、Redis就不解释了,很简单
api配置
首先还是merge用户和默认配置,这里得说明下,之前
bucky是没有暴露config.api配置给用户的,三部曲中的只有action有用户配置,而且一般也不会改动,除非你的action里有共有的部分
然后拿着merge后的config创建API,参数中可设置的有queryEncode、contentType、userAgent、requestHandler、responseHandler、cache、host、proxy等,这些参数也可以在某类API中设置。
这里首先会创建一个
API类,该类为全局API对象,然后获取当前设置api根目录,其实也就是apis/,然后遍历apis/文件夹,拿到文件路径,然后解析(requireAPI)该api文件配置,里面包含解析文件的配置,配置包含默认环境配置、特殊环境配置,每一个环境中配置都是以函数形式,配置中export类型有三个环境,创建配置中会生成该API的configMap,存放apiName与apiconfig映射关系,然后执行该API文件中的注册API,然后生成API配置,然后深度merge两个环境的各自的基础配置和configMap(APIName与APIConfig映射关系),最后会merge每个API的config到baseConfig。然后会遍历这个总的config,里面包含这个API的所有配置,以及每个api的配置,生成API.APIName.apiName,这种格式,如API.Test.test,然后拿着这个和文件路径生成API(createAPI)createAPI里:首先解析每个api类的配置,这里的配置是某一个api配置和API基础配置的深度merge,最终返回的是一个api函数,也就是我们通常会调用的API.Test.test(data, options)这种形式,函数里面干了什么事情呢,首先从options中解析四个参数,query/headers/uriReplacer/ctx,拿着前面解析的parameters做接口数据类型检查,然后创建api请求数据对象,包含uri method timeout proxy displayname encoding headers,headers中会使用每个api中的contenttype、userAgent、host然后merge传入的headers,所以header可以在api中设置。
根据方法名,合并参数,包含
GET、HEAD、OPTIONS三个,剩下处理body数据,multipart/form-data文件类型的表单提交,然后将formData写到上述创建的api请求数据对象中,formData中包含data里面的数据,如果是流stream类型,则设置filename和contentType;
application/json类型则直接设置body为stringify(data)后的数据;
application/x-www-form-urlencoded类型则设置form为data,其余类型则直接data挂到body。然后拼装uri,通过base、uriReplacer规则,然后将query对象转为search类型,此时通过queryEncode判断是否转码。
调用requestHandler,参数有创建的请求数据对象、ctx,这个时候需要注意,如果你的requestHandler是自定义的,那么需要注意,函数中需要返回你处理后的请求数据对象,然后bucky拿着处理后的请求数据对象,然后处理cache缓存,这里默认使用的是Redis,如果使用缓存且缓存有效则使用缓存的结果作为返回,反之发起请求doRequest
doRequest中有递增请求sequence的操作,标识请求的唯一和连续,记录请求时间,请求使用request库,自己封装为promise方式,如果出错则抛出错误,并记录到log.api日志中,正常则调用responseHandler方法,同样报错则抛出错误,如果有cache设置则写到缓存,同时会直接返回这个responseHandler的处理结果
上述创建完成后,会将api和apis分别挂载到apis上和API上,这里会重写get方法,避免被篡改
特别注意:上述说到的requestHandler、responseHandler都提供有默认的,defaultRequestHandler简单的返回请求数据对象,defaultResponseHandler则会校验statusCode,解析codeKey、dataKey、messageKey、successCode,这里可以通过刚刚说到的config设置,然后会从返回的body中解析对应的字段,然后检查successCode,如果你的body中的codeKey对应的值和successCode不对应,则会抛出错误
model配置
model配置也是老套路,先解析挂载在app.config上的model配置,获取model的路径,然后加载自己的model,HdicAuth、Utils、S3、WebShot,然后挂载业务model
service配置
service是用于拦截请求用的,粗糙点说就是在action/model/api之前会执行这个,这里可以拦截并处理这个请求,service采用中间件,首先加载内部的三个service,appwebview、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配置
未完待续
简单描述下:
首先
bucky作为中间层,处于业务server和UI client之间,同时作为应用服务器,里面模块主要包含上面罗列的模块
我们称
client为UI 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。上面还有一个模块websocket,websocket是很灵活的,可以和client双向或单向通信,比如我们的logviewer其实就是一个websocket,当然websocket里也照样可以调用apimodel等模块。