Durian is a generic purpose web server, like apache,nginx and caddy(Durian is based on caddy).
And, Durian is also a modular and pluggable framework.
See examples to have a quick start.
Durian itself is a powerful and high performance web server. You can use it like nginx:
:8051 {
# if url matches pattern, then response directly
response {
pattern /foo/\d+/.*
body "{\"name\": \"caibirdme\", \"age\": 25, \"some\":[12,3,4]}"
content_type Application/json
}
# reverse proxy
proxy {
pattern /bar/.*
upstream {
10.10.10.1
10.10.10.2
}
}
# configure log
log {
access_path ./access.log
err_path ./error.log
# if no more words, this means using default log format
# for more infomartion about the log format, see doc for log module
format
}
}
Config file above can sets up a server listening on port 8051 and does response and reverse proxy as you wish. each entry in access.log is a json, such as:
{"remote_addr":"127.0.0.1:50926","host":"localhost:8051","method":"GET","request_uri":"/user/deen","status":200,"start_time":"2019-04-27T19:56:51.811+0800","process_time":"19.337µs","bytes_sent":85,"user_agent":"curl/7.58.0","response_body":"Hello deen\n"}
{"remote_addr":"127.0.0.1:50928","host":"localhost:8051","method":"GET","request_uri":"/foo/123/bar","status":200,"start_time":"2019-04-27T19:57:06.733+0800","process_time":"12.34µs","bytes_sent":155,"user_agent":"curl/7.58.0","response_body":"{\"name\": \"caibirdme\", \"age\": 25, \"some\":[12,3,4]}"}
- easy to configure
- high performance(at least 5 times faster than Go's net/http)
- zero-downtime
- graceful shutdown
- graceful restart
- graceful upgrade
- cross platform
- could work with supervisor
- highly extensible(really!!)
Like OpenResty, you can use Lua to extend the function of Nginx. You can extend Durian with pure go due to Durian's modular design, with only no more than 10 lines, which is really very easy.
Below is a full and runnable example:
package main
import (
"fmt"
"github.com/buaazp/fasthttprouter"
"github.com/caibirdme/durian"
"github.com/caibirdme/durian/router"
"github.com/mholt/caddy"
"github.com/valyala/fasthttp"
"log"
)
func init() {
// trap signals and enable graceful restart
caddy.TrapSignals()
}
func main() {
caddy.AppName = "haha"
caddy.AppVersion = "0.0.1"
// Register your own logic here
router.RegisterPlugin(handler)
// read config
input, err := durian.ReadConfig("./Caddyfile")
// start the server
instance, err := caddy.Start(input)
if err != nil {
log.Fatal(err)
}
// wait for all servers to stop
instance.Wait()
}
// your logic entry, the main function for your plugin
func handler(cfg router.RouterConfig) (fasthttp.RequestHandler, error) {
/*
# in your Caddyfile
router {
config /home/my/app.toml
}
*/
// cfg.CfgPath == "/home/my/app.toml"
_ = cfg.CfgPath
// whatever router or framework(based on fasthttp)
r := fasthttprouter.New()
// configure route rules
r.GET("/user/:name", getUserName)
return r.Handler, nil
}
func getUserName(ctx *fasthttp.RequestCtx) {
ctx.WriteString(fmt.Sprintf("Hello %v\n", ctx.UserValue("name")))
}
See, It's really easy, and now you have all the abilities Durian provides.
This project is still in progress, blow are the current supported directives:
:8051 {
# set timeout for server
timeout {
keep_alive 5m
read 10s
write 10s
}
gzip {
level 6
}
# /api/asd/hello_123 -> /foo/hello_123/other/asd
rewrite /api/(\w+)/(.*) {
to /foo/{2}/other/{1}
}
# reverse proxy
proxy {
pattern /foo/(\w)/(.*)
upstream {
localhost:8776
localhost:8775
}
timeout 300ms
header_upstream X-Foo ffoo
header_upstream X-Bar barbar
header_downstream X-Baz bazbaz
}
response {
pattern /hello/\w+
body "{\"name\": \"caibirdme\", \"age\": 25, \"some\":[12,3,4]}"
content_type Application/json
}
log {
access_path ./access.log
err_path ./error.log
format {
remote_addr
host
method
request_uri
status
start_time
process_time
bytes_sent
user_agent
response_body
}
}
}
:8012 {
concurrency 1000 # The maximum number of concurrent connections the server may serve
root / /home/my/sitedir # root simply specifies the root of the site
}
if path matched, return directly. This is always helpful when you want to set up a mock server
response {
subdirectives
#...
}
path string
: path prefix to matchpattern string
: path pattern to matchcode int
: status code, default 200content_type string
: Content-Type header, default "text/html; charset=utf-8"body string
: body to return(must add"
if there're spaces in body)header string string
: headers added to response
Note: path and pattern is exclusively required
:8080 {
response {
pattern /foo/\d+/.*
body "{\"name\": \"caibirdme\", \"age\":25}"
content_type Application/json
}
}
match /foo/123/whatever /foo/0/
:8080 {
response {
path /foo
body "{\"name\": \"caibirdme\", \"age\":25}"
content_type Application/json
}
}
match all requests prefixed with /foo, such as /foo/1 /foo/bar/baz ...
proxy requests to upstreams
proxy {
subdirecitves
#...
}
pattern string
: url pattern to matchpath string
: url prefix to matchupstream block
: specify upstream address, one address per line. The address is the form ofip:port
timeout duration
: timeout for waiting upstream responseheader_upstream string string
: header added to upstreamheader_downstream string string
: header added to downstreammax_conn int
: max connections to keep for upstream
note: pattern and path is exclusively required
proxy {
pattern /foo/bar/.*
upstream {
10.10.18.3:8000
10.10.19.4:7000
}
timeout 300ms # 1s 2min...
header_upstream X-Foo foo
header_upstream X-Other ttt
header_downstream X-Bar "hello client"
header_downstream Access-Control-Allow-Origin *
max_conn 1000
}
Randomly reverse proxy request /foo/bar/xxx to 10.10.18.3:8000 or 10.10.19.4:7000
set a directory as the root of a static file server
root url_prefix directory_path
root url_prefix {
subdirectives
#...
}
dir string
: root directory pathcompress
: cache compressed file to reduce usage of CPU(need write access to dir)index string...
: specify index file
root /download {
dir /var/www/static
compress
index index.html index.htm index.json
}
rewrite url
rewrite pattern {
to string
}
to string
: target pattern to rewrite, use {num} as placeholder
rewrite /foo/(\w+)/(\d+)/(.*) {
to /{2}/bar/{1}/tee/{3}
}
This will rewrite /foo/some/123/hello/world
to /123/bar/some/tee/hello/world
set timeout for all requests
timeout {
subdirectives
#...
}
keep_alive duration
: set keepalive durationread duration
: set readTimeout, the time spend to read data from the connection.write duration
: set writeTimeout, writeTimeout should largeEqual thanreadTimeout+processTime
note: For detailed explanations about timeout, see: here
timeout {
keep_alive 30s
read 1s
write 2s
}
set extra header to request
header path key val
header path {
key1 val1
key2 val2
#...
}
header / {
X-Server caddy_fast
}
enable gzip if request contains gzip related header
gzip #default level 6
gzip {
subdirectives
#...
}
level int
: set gzip level
gzip {
level 7
}
not_found is used to specify the action when url mismatch
not_found {
subdirectives
#...
}
file string
: specify a file to send to clientbody string
: specify a string to send to client, default "not found"code int
: specify the response status code, default 404content_type string
: set content type, default "text/html; charset=utf-8"
note: can't set file and body at the same time
not_found {
file /var/www/site/404.html
}
log related config, each entry is in json format
log {
subdirective
#...
}
access_path string
: specify the path of access logaccess_path /foo/bar
: access log will be stored in /foo/bar/access.logaccess_path /foo/bar/customize.log
: access log will be stored in customize.log
err_path string
: specify the path of error logformat {entries...}
: specify access log content- now
- bytes_sent
- body_bytes_sent
- connection_requests
- request_time
- request_length
- status
- user_agent
- remote_addr
- request_uri
- query_string
- request_body
- request_header
- method
- response_body
- response_header
- referer
log {
access_path /var/site/test.log
err_path /var/site/err.log
format {
now
remote_addr
bytes_sent
method
request_uri
status
user_agent
}
}
- rewrite
- circuit breaker for reverse proxy
- dynamic upstream for reverse proxy(watch specified file)
- request_id
- rate limit
- many other directives caddy supported yet...
CPU: i7-8700 12cores 3.2GHz
MEM: 16G
OS: Linux caibirdme-MS-7B53 4.15.0-47-generic #50-Ubuntu SMP Wed Mar 13 10:44:52 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
:8051
proxy {
path /foo
upstream {
localhost:9001
localhost:9002
}
max_conn 1000
}
wrk -c950 -t2 -d20s http://localhost:8051/foo/bar
Running 20s test @ http://localhost:8051/foo/bar
2 threads and 950 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 50.59ms 635.91us 70.82ms 95.06%
Req/Sec 9.41k 355.56 9.60k 97.50%
374795 requests in 20.09s, 63.62MB read
Requests/sec: 18651.61
Transfer/sec: 3.17MB
:8051
proxy /foo localhost:9001 localhost:9002
wrk -c950 -t2 -d20s http://localhost:8051/foo/bar
Running 20s test @ http://localhost:8051/foo/bar
2 threads and 950 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 270.50ms 364.35ms 2.00s 86.32%
Req/Sec 2.34k 2.72k 9.60k 84.78%
89122 requests in 20.07s, 14.89MB read
Socket errors: connect 0, read 0, write 0, timeout 1369
Non-2xx or 3xx responses: 1178
Requests/sec: 4440.60
Transfer/sec: 759.75KB
From the benchmark above, durian is more than 4 times faster than caddy. More benchmarks are on the way...