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
里也照样可以调用api
model
等模块。