Skip to content

high performance generic web server based on caddy and fasthttp

License

Notifications You must be signed in to change notification settings

caibirdme/durian

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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...

About

high performance generic web server based on caddy and fasthttp

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages