paddy是一款单进程的独立运行的web server,基于golang的标准库net/http实现。
paddy提供以下功能:
- 直接配置http响应
- 目录文件服务器(小文件采用LRU缓存,大文件采用sendfile零内存拷贝)
- proxy_pass代理
- http反向代理与负载均衡
- 支持HTTP请求和响应插件
- TCP流量代理与负载均衡(支持零内存拷贝)
$ go build ./main/paddy.go
$ ./paddy -configFile default.config
若不附带-configFile参数,则paddy默认从paddy执行程序文件所在目录查找并加载default.config
$ ./paddy -t default.config
热重启一般发生在修改配置之后需要使参数生效。
$ kill -USR2 `cat paddy.pid`
paddy配置文件基于json语法,支持双斜线开头的单行注释。配置格式请参考默认配置文件
paddy配置文件支持强大的"json表达式"语法。
paddy的location配置支持"正则表达式"和"jsonexp"两种方式。 通过在request_filter和response_filter中对请求和响应进行灵活的处理
location配置中,优先级从高到低次序: 直接配置响应 > file_root > proxy_pass > backend
可在location_regexp的request_filter和rewponse_filter,或location_jsonexp中直接写入http响应,json表达式变量$set_response=1表示直接响应。如:
...
"location_regexp": [
{
"exp": "^\\/response_direct\\?.*",
"response_filter": [
[
[
// 表示直接响应
["$set_response","=",1],
// 设置响应的http status code = 200
["$resp.status","=",200],
// 设置响应的http body
["$resp.body","=","response from {{$req.path}},{{$req_param.echo}}"]
]
]
]
}
...
paddy通过goutil.LRUFileCache以LRU策略执行文件缓存,并提供目录文件服务。配置目录文件的方式通过file_root参数进行,如下:
...
"location_regexp": [
{
"exp": "^/paddy/default\\.config$",
// 返回本地目录 /tmp/paddy/default.config文件的内容
"file_root": "/tmp"
}
...
与nginx类似,proxy_pass指示一个url,服务器向该url请求获取响应,并响应给客户端。如下:
...
"location_regexp": [
{
"exp": "^\\/proxy_pass.*",
"proxy_pass": "http://192.168.0.1:80/real_path"
}
...
backend主要用来支持paddy作为http反向代理。paddy预先定义后端服务器或服务器组,一个backend包含一组后端服务器地址,paddy支持对backend的多种负载策略:
- roundrobin 轮询
- minpending 最低负载+轮询
- iphash 按客户端ip地址进行哈希分布
- uri_param 根据uri参数值进行哈希分布
- random 随机选择
配置举例:
...
"location_regexp": [
{
"exp": "^\\/backend.*",
"backend": "login_server",
// 负载策略
"method": "roundrobin"
},
...
除了这上述负载策略以外, 可以通过json表达式(jsonexp),以语义控制的方式选择后端服务器,达到非常灵活的负载能力。这也是"json表达式"的强大之处,举例:
{
"request_filter": [
[
// 如果url参数user_name是“bob,tom,franky”之一,则选择backend_engineer作为后端服务器
["$req_param.user_name", "in", "bob,tom,franky"],
[
["$backend","=","backend_engineer"],
["$break","=",1]
]
],
[
// 如果url参数user_age > 18, 则选择向http://192.168.0.1/adult?{{params}}获取响应
["$req_param.user_age", ">", 18],
["$proxy_pass","=","http://192.168.0.1/adult?{{params}}"]
]
]
}
paddy除了可以支持上述配置功能以外。如果需要非常个性化的处理,或希望减少流量转发而是直接处理请求,处理web socket...其他功能,则可以通过编写插件,然后将包含插件代码的整个代码完整编译部署。paddy插件提供最高的可控性。编写插件的方式: 编写支持插件接口的组件,并通过Paddy.RegisterPlugin注册即可。
// 插件接口
type Plugin interface {
// 唯一身份ID
ID() string
// 在http请求接收完成后介入
// hijacked 是否劫持:true则必须实现respWriter写响应;false时不准向respWriter写响应,可以返回backend(此时框架直接去请求backend而不再走location匹配流程,否则框架执行location匹配)
RequestHeaderCompleted(req *http.Request, respWriter http.ResponseWriter, context goutil.Context) (hijacked bool, proxyPass, backend string, err goutil.Error)
// 框架在得到响应后,给客户端发送响应之前介入
// hijacked 是否劫持:true则必须实现respWriter写响应;false时,不准向respWriter写响应,可以返回newResponse(此时框架以newResponse写响应,否则以originResponse写响应)
ResponseHeaderCompleted(originResponse *http.Response, respWriter http.ResponseWriter, context goutil.Context) (hijacked bool, newResponse *http.Response, err goutil.Error)
}
宏将在运行时被实际值替换,location的配置的proxy_pass参数支持如下宏:
- {{backend}} 当前的backend,backend由一个或多个“服务器地址:端口”组成
- {{domain}} 当前请求url的host(domain:port)的domain部分
- {{port}} 当前请求url的host(domain:port)的port部分
- {{host}} 当前请求url的host(domain:port)
- {{uri}} 当前请求url(http://domain:port/path?param1=xxx,...\)的/path?param1=xxx,...
- {{path}} 当前请求url(http://domain:port/path?param1=xxx,...\)的/path
- {{params}} 当前请求url(http://domain:port/path?param1=xxx,...\)的param1=xxx,...
paddy的json表达式支持以下paddy专有jsonexp变量:
- $proxy_pass 设置proxy_pass
- $backend 设置backend
- $file_root 设置file_root
- $set_response 设置set_response
以及paddy专有jsonexp对象:
- $req 当前http请求对象,支持属性: ver,host,method,path,uri
- $req_param 当前http请求对象的url参数对象,可以通过.操作符读取或设置参数值,如 $req_param.arg1
- $req_header 当前http请求对象header对象,可以通过.操作符读取或设置header信息, 如 $req_header.content_type
- $resp 当前http响应对象,支持属性:status,body
- $resp_header 当前http响应对象的header对象,可用通过.操作符读取或设置header信息, 如["$resp_header.Content_Encoding","=","gzip"]
paddy站在go-runtime这个巨人的肩膀上,支持内存零拷贝技术,尽可能减少流量转发带来的延迟以及降低负载。
该功能通过upstream和tcp_server两个配置参数进行配置,点击这里查看配置参考