durian

package module
v0.0.0-...-4c85db1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 15, 2019 License: Apache-2.0 Imports: 16 Imported by: 0

README

Durian

中文

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.

Generic Web Server

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]}"}
Advantage of Durian
  • 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!!)
Extend Durian

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
}

Directives

response

if path matched, return directly. This is always helpful when you want to set up a mock server

syntax
response {
    subdirectives
    #...
}
Subdirectives
  • path string: path prefix to match
  • pattern string: path pattern to match
  • code int: status code, default 200
  • content_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

examples
: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

proxy requests to upstreams

syntax
proxy {
    subdirecitves
    #...
}
Subdirectives
  • pattern string: url pattern to match
  • path string: url prefix to match
  • upstream block: specify upstream address, one address per line. The address is the form of ip:port
  • timeout duration: timeout for waiting upstream response
  • header_upstream string string: header added to upstream
  • header_downstream string string: header added to downstream
  • max_conn int: max connections to keep for upstream

note: pattern and path is exclusively required

examples
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

root

set a directory as the root of a static file server

syntax
root url_prefix directory_path

root url_prefix {
    subdirectives
    #...
}
Subdirectives
  • dir string: root directory path
  • compress: cache compressed file to reduce usage of CPU(need write access to dir)
  • index string...: specify index file
examples
root /download {
    dir /var/www/static
    compress
    index index.html index.htm index.json
}
rewrite

rewrite url

syntax
rewrite pattern {
    to string
}
subdirectives
  • to string: target pattern to rewrite, use {num} as placeholder
example
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

timeout

set timeout for all requests

syntax
timeout {
    subdirectives
    #...
}
subdirectives
  • keep_alive duration: set keepalive duration
  • read duration: set readTimeout, the time spend to read data from the connection.
  • write duration: set writeTimeout, writeTimeout should largeEqual than readTimeout+processTime

note: For detailed explanations about timeout, see: here

example
timeout {
    keep_alive 30s
    read 1s
    write 2s
}
header

set extra header to request

syntax
header path key val

header path {
    key1 val1
    key2 val2
    #...
}
example
header / {
    X-Server caddy_fast
}
Gzip

enable gzip if request contains gzip related header

syntax
gzip #default level 6

gzip {
    subdirectives
    #...
}
subdirectives
  • level int: set gzip level
example
gzip {
    level 7
}
NotFound

not_found is used to specify the action when url mismatch

syntax
not_found {
    subdirectives
    #...
}
subdirectives
  • file string: specify a file to send to client
  • body string: specify a string to send to client, default "not found"
  • code int: specify the response status code, default 404
  • content_type string: set content type, default "text/html; charset=utf-8"

note: can't set file and body at the same time

example
not_found {
    file /var/www/site/404.html
}
log

log related config, each entry is in json format

syntax
log {
    subdirective
    #...
}
subdirecitve
  • access_path string: specify the path of access log
    • access_path /foo/bar: access log will be stored in /foo/bar/access.log
    • access_path /foo/bar/customize.log: access log will be stored in customize.log
  • err_path string: specify the path of error log
  • format {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
example
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
    }
}

Plan

  • rewrite
  • circuit breaker for reverse proxy
  • dynamic upstream for reverse proxy(watch specified file)
  • request_id
  • rate limit
  • many other directives caddy supported yet...

Benchmark

machine
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
upstream server sleep 50ms
durian
Caddyfile
:8051
proxy {
    path /foo
    upstream {
        localhost:9001
        localhost:9002
    }
    max_conn 1000
}
test command

wrk -c950 -t2 -d20s http://localhost:8051/foo/bar

result
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
caddy
caddyfile
:8051

proxy /foo localhost:9001 localhost:9002
test command

wrk -c950 -t2 -d20s http://localhost:8051/foo/bar

result
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
conclusion

From the benchmark above, durian is more than 4 times faster than caddy. More benchmarks are on the way...

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ReadConfig

func ReadConfig(confPath string) (caddy.Input, error)

ReadConfig reads config from given path

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL