aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Dockerfile8
-rw-r--r--README.md58
-rw-r--r--goxy.yml11
-rw-r--r--main.go110
5 files changed, 190 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f98cf82
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+goxy
+certs
+*.swp
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..28bbd11
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,8 @@
+FROM golang
+ADD . /go/src/dim13.org/goxy
+RUN go get gopkg.in/yaml.v2
+RUN go install dim13.org/goxy
+WORKDIR /go/src/dim13.org/goxy
+ENTRYPOINT /go/bin/goxy
+EXPOSE 80
+EXPOSE 443
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4e67dbd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,58 @@
+# Goxy -- a go (reverse) proxy
+
+## Goals
+
+- Listen for HTTP and HTTPS on public IP
+- Terminate SSL (SNI)
+- HTTP/2.0 out of the box
+- Proxy to backends (HTTP/1.1, wsgi, fcgi?)
+- Zero downtime (re-)configuration
+
+## Configuration unit
+
+- Hostname(s)
+- Upstream(s) (Round-robin if many)
+- Optional TLS Certificate and Key
+
+## Testing
+
+ ab -n 10000 -c 1000 -H 'Host: gowiki.moccu' http://127.0.0.1/view/Home
+
+Concurrency Level: 1000
+Time taken for tests: 1.694 seconds
+Complete requests: 10000
+Failed requests: 0
+Total transferred: 18130000 bytes
+HTML transferred: 16950000 bytes
+Requests per second: 5902.56 [#/sec] (mean)
+Time per request: 169.418 [ms] (mean)
+Time per request: 0.169 [ms] (mean, across all concurrent requests)
+Transfer rate: 10450.54 [Kbytes/sec] received
+
+Connection Times (ms)
+ min mean[+/-sd] median max
+Connect: 0 17 120.9 0 1001
+Processing: 1 94 98.1 64 1009
+Waiting: 1 93 98.1 64 1009
+Total: 1 111 152.9 67 1132
+
+ ab -n 10000 -c 1000 http://gowiki.moccu/view/Home
+
+Concurrency Level: 1000
+Time taken for tests: 1.590 seconds
+Complete requests: 10000
+Failed requests: 0
+Total transferred: 18130000 bytes
+HTML transferred: 16950000 bytes
+Requests per second: 6291.16 [#/sec] (mean)
+Time per request: 158.953 [ms] (mean)
+Time per request: 0.159 [ms] (mean, across all concurrent requests)
+Transfer rate: 11138.54 [Kbytes/sec] received
+
+Connection Times (ms)
+ min mean[+/-sd] median max
+Connect: 0 52 214.2 1 1005
+Processing: 0 48 53.8 33 427
+Waiting: 0 48 53.6 33 427
+Total: 1 100 223.2 37 1424
+
diff --git a/goxy.yml b/goxy.yml
new file mode 100644
index 0000000..a24e58d
--- /dev/null
+++ b/goxy.yml
@@ -0,0 +1,11 @@
+---
+
+wahlplan.goxy.moccu:
+ upstream: http://wahlplan:8080
+ certfile: certs/wahlplan/cert.pem
+ keyfile: certs/wahlplan/key.pem
+
+gowiki.goxy.moccu:
+ upstream: http://gowiki
+ certfile: certs/gowiki/cert.pem
+ keyfile: certs/gowiki/key.pem
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..46180fd
--- /dev/null
+++ b/main.go
@@ -0,0 +1,110 @@
+package main
+
+import (
+ "crypto/tls"
+ "errors"
+ "flag"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+ "sync"
+
+ "gopkg.in/yaml.v2"
+)
+
+var (
+ config = flag.String("conf", "goxy.yml", "configuration file")
+ listen = flag.String("listen", ":http", "HTTP")
+ listenTLS = flag.String("listentls", ":https", "TLS")
+)
+
+type Config map[string]Route
+
+type Route struct {
+ CertFile string
+ KeyFile string
+ Upstream string
+ cert *tls.Certificate
+}
+
+func (c Config) SNI(h *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ if r, ok := c[h.ServerName]; ok && r.cert != nil {
+ return r.cert, nil
+ }
+ return nil, errors.New("no cert for " + h.ServerName)
+}
+
+func (r *Route) LoadCert() error {
+ if r.CertFile == "" && r.KeyFile == "" {
+ return nil
+ }
+ cert, err := tls.LoadX509KeyPair(r.CertFile, r.KeyFile)
+ if err != nil {
+ return err
+ }
+ r.cert = &cert
+ return nil
+}
+
+func (r Route) String() string {
+ if r.cert != nil {
+ return r.Upstream + " with TLS"
+ } else {
+ return r.Upstream
+ }
+}
+
+func LoadConfig(fname string) (Config, error) {
+ conf, err := ioutil.ReadFile(fname)
+ if err != nil {
+ return Config{}, err
+ }
+ var c Config
+ return c, yaml.Unmarshal(conf, &c)
+}
+
+func main() {
+ flag.Parse()
+ c, err := LoadConfig(*config)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for k, v := range c {
+ if err := v.LoadCert(); err != nil {
+ log.Println("load", err)
+ continue
+ }
+ c[k] = v
+ up, err := url.Parse(v.Upstream)
+ if err != nil {
+ log.Println("upstream", err)
+ continue
+ }
+ log.Println("map", k, "to", v)
+ http.Handle(k+"/", httputil.NewSingleHostReverseProxy(up))
+ }
+
+ var wg sync.WaitGroup
+ wg.Add(2)
+ go func() {
+ defer wg.Done()
+ log.Println("listen", *listenTLS)
+ s := http.Server{
+ Addr: *listenTLS,
+ TLSConfig: &tls.Config{GetCertificate: c.SNI},
+ }
+ log.Fatal(s.ListenAndServeTLS("", ""))
+ }()
+ go func() {
+ defer wg.Done()
+ log.Println("listen", *listen)
+ s := http.Server{
+ Addr: *listen,
+ }
+ log.Fatal(s.ListenAndServe())
+ }()
+ wg.Wait()
+}