Compare commits

...

15 Commits
master ... new

@ -6,9 +6,6 @@ gserver is a golang http/https server
features
=======
- support UWSGI client protocol (python)
- support fastCGI client protocol (php)
- support act as resverse proxy
- support act as forward proxy
- support multiple virtual host
- support SNI (https virtual host)

@ -1,48 +1,41 @@
package main
import (
"github.com/go-yaml/yaml"
"io/ioutil"
"github.com/go-yaml/yaml"
)
type conf []server
type server struct {
Host string
Port int
Docroot string
URLRules []rule
EnableProxy bool
EnableAuth bool
PasswdFile string
Realm string
Vhost []vhost
type conf struct {
Listens []listen `yaml:"listen"`
Vhosts []vhost `yaml:"vhost"`
Proxy proxycfg `yaml:"proxy"`
}
type vhost struct {
Docroot string
Hostname string
Cert string
Key string
URLRules []rule
type proxycfg struct {
HTTP1Proxy bool `yaml:"http1-proxy"`
HTTP2Proxy bool `yaml:"http2-proxy"`
LocalDomains []string `yaml:"localdomains"`
}
type rule struct {
URLPrefix string
IsRegex bool
Docroot string
Type string
Target target
type listen struct {
Addr string `yaml:"addr"`
Port int16 `yaml:"port"`
Certificates []certificate `yaml:"certificates"`
}
type target struct {
Type string
Host string
Port int
Path string
type certificate struct {
CertFile string `yaml:"certfile"`
KeyFile string `yaml:"keyfile"`
}
type vhost struct {
Docroot string `yaml:"docroot"`
Hostname string `yaml:"hostname"`
ProxyPass string `yaml:"proxypass"`
}
func loadConfig(fn string) (conf, error) {
func loadConfig(fn string) (*conf, error) {
data, err := ioutil.ReadFile(fn)
if err != nil {
return nil, err
@ -53,5 +46,5 @@ func loadConfig(fn string) (conf, error) {
return nil, err
}
return c, nil
return &c, nil
}

@ -1,145 +1,33 @@
# vim: set ft=yaml:
# when provide certficate file, server will listen https and enable http2
# http config
-
# listen address
host: 0.0.0.0
# listen port
# when provide certficate file, server will listen https and enable http2
listen:
-
addr: 0.0.0.0
port: 9001
certificates:
-
addr: 0.0.0.0
port: 9002
certificates:
-
certfile: /etc/letsencrypt/live/ratafee.nl/fullchain.pem
keyfile: /etc/letsencrypt/live/ratafee.nl/privkey.pem
# default document root
# virtual host support
vhost:
-
hostname: www.ratafee.nl
docroot: /srv/www
enableproxy: true
enableauth: true
passwdfile: ./passwdfile
realm: example.com
# default host's url rule
# urlrules:
# -
# urlprefix: /a
# type: alias
# target:
# type: dir
# path: /home/user1/a
# -
# urlprefix: /b/a.txt
# type: alias
# target:
# type: file
# path: /home/user1/a/b/a.txt
# virtual host config
# vhost:
# - &example1_www
# hostname: www.example1.com
# docroot: /var/www/html/
# # cert:
# # key:
#
# # url rule for www.example.com
# urlrules:
# -
# # url start with /APIv1/ forward to uwsg socket
# urlprefix: /APIv1/
# type: uwsgi
# target:
# type: unix
# path: /run/uwsgi/APIv1.sock
# -
# # run php script on /phpmyadmin/ subdirectory
# urlprefix: /phpmyadmin/
# type: fastcgi
# target:
# type: unix
# path: /var/run/php-fpm/www.sock
# -
# # pass php to fastcgi socket
# urlprefix: \.php$|\.php/.*
# isregex: true
# type: fastcgi
# target:
# type: unix
# path: /var/run/php-fpm/www.sock
# -
# # run php script on other location
# urlprefix: /a/
# docroot: /home/user/php
# type: fastcgi
# target:
# type: unix
# path: /var/run/php-fpm/www.sock
# -
# # url start with /proxy/ reverse proxy for http://10.10.1.1/
# # this act as reverse proxy
# urlprefix: /proxy/
# type: reverse
# target:
# type: http
# host: 10.10.1.1
# port: 8080
# path: /
# - &example1
# <<: *example1_www
# hostname: example1.com
#
# - &example_www
# hostname: www.example.com
# docroot: /var/www/example
# urlrules:
# -
# urlprefix: /APIv2
# type: uwsgi
# target:
# type: unix
# path: /run/uwsgi/APIv2.sock
# - &example
# <<: *example_www
# hostname: example.com
#
# - &example_bbs
# hostname: bbs.example.com
# docroot: /var/www/example_bbs/
# urlrules:
# -
# #urlprefix: \.php$|\.php\/.*
# #isregex: true
#
# urlprefix: /
# type: fastcgi
# target:
# type: unix
# path: /var/run/php-fpm/www.sock
#
# https config
#-
# host: 0.0.0.0
# port: 9002
# docroot: /srv/www
# enableproxy: false
# vhost:
# -
# <<: *example1
# cert: /home/user1/cert/example1.com.crt
# key: /home/user1/cert/example1.com.key
# -
# <<: *example1_www
# cert: /home/user1/cert/example1.com.crt
# key: /home/user1/cert/example1.com.key
# -
# <<: *example_www
# cert: /etc/letsencrypt/live/example.com/fullchain.pem
# key: /etc/letsencrypt/live/example.com/privkey.pem
# -
# <<: *example
# cert: /etc/letsencrypt/live/example.com/fullchain.pem
# key: /etc/letsencrypt/live/example.com/privkey.pem
# -
# <<: *example_bbs
# cert: /etc/letsencrypt/live/bbs.example.com/fullchain.pem
# key: /etc/letsencrypt/live/bbs.example.com/privkey.pem
proxypass: http://nginx:80/
proxy:
http1-proxy: false
http2-proxy: true
# trust the follow domains as local virtual host
# when http2 proxy enabled
localdomains:
- localhost
- localdomain
- 127.0.0.1
- ratafee.nl
- 98.142.138.194

@ -1,90 +0,0 @@
package main
import (
"bufio"
"os"
"strings"
"sync"
"time"
)
type digestPwFile struct {
path string
entry []pwEntry
mtime time.Time
mu *sync.Mutex
}
type pwEntry struct {
user string
realm string
hashPw string
}
func newDigestSecret(f string) (*digestPwFile, error) {
a := &digestPwFile{path: f, mu: new(sync.Mutex)}
if err := a.loadFile(); err != nil {
return nil, err
}
go a.tryReload()
return a, nil
}
func (df *digestPwFile) tryReload() {
for {
time.Sleep(10 * time.Second)
fi, _ := os.Stat(df.path)
t1 := fi.ModTime()
if t1 != df.mtime {
df.loadFile()
}
}
}
func (df *digestPwFile) loadFile() error {
df.mu.Lock()
defer df.mu.Unlock()
fp, err := os.Open(df.path)
if err != nil {
return err
}
defer fp.Close()
entry := []pwEntry{}
r := bufio.NewReader(fp)
for {
line, err := r.ReadString('\n')
if err != nil {
break
}
line1 := strings.Trim(line, " \r\n")
if line1 == "" || line1[0] == '#' {
continue
}
fields := strings.SplitN(line1, ":", 3)
entry = append(entry, pwEntry{fields[0], fields[1], fields[2]})
}
df.entry = entry
fi, _ := os.Stat(df.path)
df.mtime = fi.ModTime()
return nil
}
func (df *digestPwFile) getPw(user, realm string) string {
df.mu.Lock()
defer df.mu.Unlock()
for i := range df.entry {
if df.entry[i].user == user && df.entry[i].realm == realm {
return df.entry[i].hashPw
}
}
return ""
}

@ -0,0 +1,15 @@
module github.com/fangdingjun/gserver
go 1.13
require (
github.com/fangdingjun/go-log v0.0.0-20190821073628-ae332053d6dc
github.com/fangdingjun/protolistener v0.0.0-20190821093313-6d5d2138f296
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/gorilla/handlers v1.4.2
github.com/gorilla/mux v1.7.3
github.com/kr/pretty v0.1.0 // indirect
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)

@ -0,0 +1,31 @@
github.com/fangdingjun/go-log v0.0.0-20190821073628-ae332053d6dc h1:Tdk7VsmsFo3d0NqHTy3SRoRnkduOxwXgR65gQsq8kXY=
github.com/fangdingjun/go-log v0.0.0-20190821073628-ae332053d6dc/go.mod h1:oi7jbIScCbha6TbVzmrJP6igIHt+jcvvEgSJ7Ww1GkI=
github.com/fangdingjun/protolistener v0.0.0-20190821093313-6d5d2138f296 h1:2c6agkdoPVSyvdJ0B+5DhOb1BQpso7a7zlBxXUnttmY=
github.com/fangdingjun/protolistener v0.0.0-20190821093313-6d5d2138f296/go.mod h1:RoT81rjdN8gQ1w/z7NiFkxV6VzkT4NZ43XIt0lu8tcc=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a h1:Igim7XhdOpBnWPuYJ70XcNpq8q3BCACtVgNfoJxOV7g=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

@ -2,23 +2,22 @@ package main
import (
"fmt"
auth "github.com/fangdingjun/go-http-auth"
"io"
"log"
"net"
"net/http"
"strings"
"time"
"github.com/fangdingjun/go-log"
"golang.org/x/net/trace"
)
// handler process the proxy request first(if enabled)
// and route the request to the registered http.Handler
type handler struct {
handler http.Handler
enableProxy bool
enableAuth bool
authMethod *auth.DigestAuth
localDomains []string
handler http.Handler
cfg *conf
events trace.EventLog
}
var defaultTransport http.RoundTripper = &http.Transport{
@ -34,6 +33,7 @@ var defaultTransport http.RoundTripper = &http.Transport{
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// http/1.1 local request
if r.ProtoMajor == 1 && r.RequestURI[0] == '/' {
h.events.Printf("http11 local request %s", r.URL.Path)
if h.handler != nil {
h.handler.ServeHTTP(w, r)
} else {
@ -44,6 +44,7 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// http/2.0 local request
if r.ProtoMajor == 2 && h.isLocalRequest(r) {
h.events.Printf("http2 local request %s", r.URL.Path)
if h.handler != nil {
h.handler.ServeHTTP(w, r)
} else {
@ -54,18 +55,16 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// proxy request
if !h.enableProxy {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "<h1>404 Not Found</h1>")
if r.ProtoMajor == 1 && !h.cfg.Proxy.HTTP1Proxy {
h.events.Errorf("http1.1 request not exists path %s", r.URL.Path)
http.Error(w, "<h1>404 Not Found</h1>", http.StatusNotFound)
return
}
if h.enableAuth {
u, _ := h.authMethod.CheckAuth(r)
if u == "" {
h.authMethod.RequireAuth(w, r)
return
}
if r.ProtoMajor == 2 && !h.cfg.Proxy.HTTP2Proxy {
h.events.Errorf("http2 request not exists path %s", r.URL.Path)
http.Error(w, "<h1>404 Not Found</h1>", http.StatusNotFound)
return
}
if r.Method == http.MethodConnect {
@ -96,12 +95,13 @@ func (h *handler) handleHTTP(w http.ResponseWriter, r *http.Request) {
}
}
h.events.Printf("%s proxy request %s", r.Proto, r.RequestURI)
resp, err = defaultTransport.RoundTrip(r)
if err != nil {
log.Printf("RoundTrip: %s", err)
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusServiceUnavailable)
fmt.Fprintf(w, "%s", err)
h.events.Errorf("roundtrip %s, error %s", r.RequestURI, err)
log.Errorf("RoundTrip: %s", err)
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
@ -137,7 +137,7 @@ func (h *handler) handleCONNECT(w http.ResponseWriter, r *http.Request) {
if r.ProtoMajor == 2 {
host = r.URL.Host
}
h.events.Printf("proxy request %s %s", r.Method, host)
if !strings.Contains(host, ":") {
host = fmt.Sprintf("%s:443", host)
}
@ -147,10 +147,10 @@ func (h *handler) handleCONNECT(w http.ResponseWriter, r *http.Request) {
conn, err = net.Dial("tcp", host)
if err != nil {
log.Printf("net.dial: %s", err)
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusServiceUnavailable)
fmt.Fprintf(w, "dial to %s failed: %s", host, err)
h.events.Errorf("dial %s, error %s", host, err)
log.Errorf("net.dial: %s", err)
msg := fmt.Sprintf("dial to %s failed: %s", host, err)
http.Error(w, msg, http.StatusServiceUnavailable)
return
}
@ -169,7 +169,8 @@ func (h *handler) handleCONNECT(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("recover %+v", err)
h.events.Errorf("http2 data pipe, panic %s", err)
log.Errorf("recover %+v", err)
}
}()
@ -178,15 +179,16 @@ func (h *handler) handleCONNECT(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.(http.Flusher).Flush()
ch := make(chan int, 2)
//h.events.Printf("data forward")
ch := make(chan struct{}, 2)
go func() {
io.Copy(conn, r.Body)
ch <- 1
ch <- struct{}{}
}()
go func() {
io.Copy(flushWriter{w}, conn)
ch <- 1
ch <- struct{}{}
}()
<-ch
@ -195,11 +197,11 @@ func (h *handler) handleCONNECT(w http.ResponseWriter, r *http.Request) {
// isLocalRequest determine the http2 request is local path request
// or the proxy request
func (h *handler) isLocalRequest(r *http.Request) bool {
if !h.enableProxy {
if !h.cfg.Proxy.HTTP2Proxy {
return true
}
if len(h.localDomains) == 0 {
if len(h.cfg.Proxy.LocalDomains) == 0 {
return true
}
@ -208,7 +210,7 @@ func (h *handler) isLocalRequest(r *http.Request) bool {
host = h1
}
for _, s := range h.localDomains {
for _, s := range h.cfg.Proxy.LocalDomains {
if strings.HasSuffix(host, s) {
return true
}
@ -218,24 +220,29 @@ func (h *handler) isLocalRequest(r *http.Request) bool {
}
func pipeAndClose(r1, r2 io.ReadWriteCloser) {
tr := trace.New("proxy", "data pipe")
defer tr.Finish()
defer func() {
if err := recover(); err != nil {
log.Printf("recover %+v", err)
log.Errorf("recover %+v", err)
tr.LazyPrintf("http 1.1 data pipe, recover %+v", err)
tr.SetError()
}
}()
defer r1.Close()
defer r2.Close()
ch := make(chan int, 2)
ch := make(chan struct{}, 2)
go func() {
io.Copy(r1, r2)
ch <- 1
ch <- struct{}{}
}()
go func() {
io.Copy(r2, r1)
ch <- 1
ch <- struct{}{}
}()
<-ch

@ -1,10 +0,0 @@
# format
# user:realm:hashed_passwd
#
# hashed_passwd = MD5(user:realm:plain_passwd)
#
# user "test", realm "example.com", password "test"
# MD5("test:example:test") = 3441b753b98a6dc702183c989e35970f
# the entry is
test:example.com:3441b753b98a6dc702183c989e35970f

@ -1,72 +0,0 @@
package main
import (
"context"
"net"
"net/http"
//"bufio"
//"fmt"
"io"
"log"
//"strings"
"time"
)
type proxy struct {
transport http.RoundTripper
addr string
prefix string
dialer *net.Dialer
network string
}
func newProxy(addr string, prefix string) *proxy {
p := &proxy{
addr: addr,
prefix: prefix,
dialer: &net.Dialer{Timeout: 2 * time.Second},
}
if addr[0] == '/' {
p.network = "unix"
} else {
p.network = "tcp"
}
p.transport = &http.Transport{
DialContext: p.dialContext,
MaxIdleConns: 5,
IdleConnTimeout: 30 * time.Second,
}
return p
}
func (p *proxy) dialContext(ctx context.Context,
network, addr string) (net.Conn, error) {
return p.dialer.DialContext(ctx, p.network, p.addr)
}
func (p *proxy) dial(network, addr string) (conn net.Conn, err error) {
return p.dialer.Dial(p.network, p.addr)
}
func (p *proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
host, _, _ := net.SplitHostPort(r.RemoteAddr)
r.Header.Add("X-Forwarded-For", host)
r.URL.Scheme = "http"
r.URL.Host = r.Host
resp, err := p.transport.RoundTrip(r)
if err != nil {
log.Print(err)
w.WriteHeader(http.StatusBadGateway)
w.Write([]byte("<h1>502 Bad Gateway</h1>"))
return
}
header := w.Header()
for k, v := range resp.Header {
for _, v1 := range v {
header.Add(k, v1)
}
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
resp.Body.Close()
}

@ -0,0 +1 @@
proxy_local*

@ -0,0 +1,9 @@
proxy_local
===========
accept http proxy request and forward to upstream proxy server via http2
usage
====
use `./proxy_local -h` to see options

@ -1,5 +1,3 @@
// +build ignore
package main
/*
@ -15,19 +13,21 @@ usage example
*/
import (
"context"
"crypto/tls"
"flag"
"fmt"
"golang.org/x/net/http2"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/http/httputil"
"os"
"sync"
"time"
log "github.com/fangdingjun/go-log"
"golang.org/x/net/http2"
)
type clientConn struct {
@ -65,7 +65,7 @@ type handler struct {
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if debug {
req, _ := httputil.DumpRequest(r, false)
log.Printf("%s", string(req))
log.Debugf("%s", string(req))
}
if r.Method == http.MethodConnect {
@ -88,9 +88,8 @@ func (h *handler) handleConnect(w http.ResponseWriter, r *http.Request) {
resp, err := h.transport.RoundTrip(r)
if err != nil {
log.Printf("roundtrip: %s", err)
w.WriteHeader(http.StatusServiceUnavailable)
fmt.Fprintf(w, "%s", err)
log.Errorf("roundtrip: %s", err)
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
@ -98,19 +97,22 @@ func (h *handler) handleConnect(w http.ResponseWriter, r *http.Request) {
if debug {
d, _ := httputil.DumpResponse(resp, false)
log.Printf("%s", string(d))
log.Debugf("%s", string(d))
}
if resp.StatusCode != http.StatusOK {
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
resp.Body.Close()
log.Infof("%s %s %d %s %s", r.Method, r.RequestURI, resp.StatusCode, r.Proto, r.UserAgent())
return
}
c, _, err := w.(http.Hijacker).Hijack()
if err != nil {
log.Println("hijack: %s", err)
w.WriteHeader(http.StatusServiceUnavailable)
fmt.Fprintf(w, "%s", err)
log.Errorf("hijack: %s", err)
http.Error(w, err.Error(), http.StatusServiceUnavailable)
log.Infof("%s %s %d %s %s", r.Method, r.RequestURI, 500, r.Proto, r.UserAgent())
return
}
@ -130,21 +132,21 @@ func (h *handler) handleConnect(w http.ResponseWriter, r *http.Request) {
}()
<-ch
log.Infof("%s %s %d %s %s", r.Method, r.RequestURI, 200, r.Proto, r.UserAgent())
}
func (h *handler) handleHTTP(w http.ResponseWriter, r *http.Request) {
resp, err := h.transport.RoundTrip(r)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusServiceUnavailable)
fmt.Fprintf(w, "%s", err)
log.Errorln(err)
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
defer resp.Body.Close()
if debug {
d, _ := httputil.DumpResponse(resp, false)
log.Printf("%s", string(d))
log.Debugf("%s", string(d))
}
hdr := w.Header()
@ -155,17 +157,39 @@ func (h *handler) handleHTTP(w http.ResponseWriter, r *http.Request) {
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
n, _ := io.Copy(w, resp.Body)
log.Infof("%s %s %d %s %d %s", r.Method, r.RequestURI, resp.StatusCode, r.Proto, n, r.UserAgent())
}
func newClientConn(host string, port string, hostname string, t *http2.Transport) *clientConn {
return &clientConn{
cc := &clientConn{
host: host,
port: port,
hostname: hostname,
transport: t,
lock: new(sync.Mutex),
}
go cc.ping()
return cc
}
func (p *clientConn) ping() {
for {
select {
case <-time.After(time.Duration(idleTimeout-5) * time.Second):
}
p.lock.Lock()
conn := p.conn
p.lock.Unlock()
if conn == nil {
continue
}
if err := conn.Ping(context.Background()); err != nil {
p.MarkDead(conn)
}
}
}
func (p *clientConn) GetClientConn(req *http.Request, addr string) (*http2.ClientConn, error) {
@ -176,17 +200,21 @@ func (p *clientConn) GetClientConn(req *http.Request, addr string) (*http2.Clien
return p.conn, nil
}
if debug {
log.Printf("dial to %s:%s", p.host, p.port)
if p.conn != nil {
p.conn.Close()
p.conn = nil
}
log.Infof("dial to %s:%s", p.host, p.port)
c, err := net.Dial("tcp", net.JoinHostPort(p.host, p.port))
if err != nil {
log.Println(err)
log.Errorln(err)
return nil, err
}
cc := &timeoutConn{c, time.Duration(idleTimeout) * time.Second}
// cc := c
config := &tls.Config{
ServerName: p.hostname,
NextProtos: []string{"h2"},
@ -195,14 +223,14 @@ func (p *clientConn) GetClientConn(req *http.Request, addr string) (*http2.Clien
conn := tls.Client(cc, config)
if err := conn.Handshake(); err != nil {
log.Println(err)
log.Errorln(err)
return nil, err
}
http2conn, err := p.transport.NewClientConn(conn)
if err != nil {
conn.Close()
log.Println(err)
log.Errorln(err)
return nil, err
}
@ -212,14 +240,14 @@ func (p *clientConn) GetClientConn(req *http.Request, addr string) (*http2.Clien
}
func (p *clientConn) MarkDead(conn *http2.ClientConn) {
//p.lock.Lock()
//defer p.lock.Unlock()
p.lock.Lock()
defer p.lock.Unlock()
if debug {
log.Println("mark dead")
if p.conn != nil {
log.Errorln("mark dead")
p.conn.Close()
p.conn = nil
}
//p.conn = nil
}
var debug bool
@ -230,12 +258,15 @@ func main() {
var addr string
var hostname string
var listen string
var logfile string
flag.StringVar(&addr, "server", "", "server address")
flag.StringVar(&hostname, "name", "", "server 's SNI name")
flag.StringVar(&listen, "listen", ":8080", "listen address")
flag.BoolVar(&debug, "debug", false, "verbose mode")
flag.BoolVar(&insecure, "insecure", false, "insecure mode, not verify the server's certificate")
flag.IntVar(&idleTimeout, "idletime", 30, "idle timeout, close connection when no data transfer")
flag.IntVar(&idleTimeout, "idletime", 20, "idle timeout, close connection when no data transfer")
flag.StringVar(&logfile, "log_file", "", "log file")
flag.Parse()
if addr == "" {
@ -243,6 +274,24 @@ func main() {
os.Exit(-1)
}
if idleTimeout < 10 {
idleTimeout = 10
}
if logfile != "" {
log.Default.Out = &log.FixedSizeFileWriter{
MaxCount: 4,
Name: logfile,
MaxSize: 10 * 1024 * 1024,
}
}
if debug {
log.Default.Level = log.DEBUG
} else {
log.Default.Level = log.INFO
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
host = addr
@ -262,10 +311,8 @@ func main() {
log.Printf("listen on %s", listen)
if debug {
log.Printf("use parent proxy https://%s:%s/", host, port)
log.Printf("server SNI name %s", hostname)
}
log.Printf("use parent proxy https://%s:%s/", host, port)
log.Printf("server SNI name %s", hostname)
if err := http.ListenAndServe(listen, &handler{transport}); err != nil {
log.Fatal(err)

@ -1,262 +0,0 @@
package main
import (
"crypto/tls"
"fmt"
auth "github.com/fangdingjun/go-http-auth"
"github.com/fangdingjun/gofast"
loghandler "github.com/gorilla/handlers"
"github.com/gorilla/mux"
"io"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"regexp"
"sync"
//"path/filepath"
"strings"
)
type logwriter struct {
w io.Writer
l *sync.Mutex
}
func (lw *logwriter) Write(buf []byte) (int, error) {
lw.l.Lock()
defer lw.l.Unlock()
return lw.w.Write(buf)
}
func initRouters(cfg conf) {
logout := os.Stdout
if logfile != "" {
fp, err := os.OpenFile(logfile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
log.Println(err)
} else {
logout = fp
}
}
w := &logwriter{logout, new(sync.Mutex)}
for _, l := range cfg {
router := mux.NewRouter()
domains := []string{}
certs := []tls.Certificate{}
// initial virtual host
for _, h := range l.Vhost {
h2 := h.Hostname
if h1, _, err := net.SplitHostPort(h.Hostname); err == nil {
h2 = h1
}
domains = append(domains, h2)
if h.Cert != "" && h.Key != "" {
if cert, err := tls.LoadX509KeyPair(h.Cert, h.Key); err == nil {
certs = append(certs, cert)
} else {
log.Fatal(err)
}
}
r := router.Host(h2).Subrouter()
for _, rule := range h.URLRules {
switch rule.Type {
case "alias":
registerAliasHandler(rule, r)
case "uwsgi":
registerUwsgiHandler(rule, r)
case "fastcgi":
registerFastCGIHandler(rule, h.Docroot, r)
case "reverse":
registerHTTPHandler(rule, r)
default:
fmt.Printf("invalid type: %s\n", rule.Type)
}
}
r.PathPrefix("/").Handler(http.FileServer(http.Dir(h.Docroot)))
}
// default host config
for _, rule := range l.URLRules {
switch rule.Type {
case "alias":
registerAliasHandler(rule, router)
case "uwsgi":
registerUwsgiHandler(rule, router)
case "fastcgi":
docroot := l.Docroot
if rule.Docroot != "" {
docroot = rule.Docroot
}
registerFastCGIHandler(rule, docroot, router)
case "reverse":
registerHTTPHandler(rule, router)
default:
fmt.Printf("invalid type: %s\n", rule.Type)
}
}
router.PathPrefix("/").Handler(http.FileServer(http.Dir(l.Docroot)))
go func(l server) {
addr := fmt.Sprintf("%s:%d", l.Host, l.Port)
hdlr := &handler{
handler: router,
enableProxy: l.EnableProxy,
enableAuth: l.EnableAuth,
localDomains: domains,
}
if l.EnableAuth {
if l.PasswdFile == "" {
log.Fatal("passwdfile required")
}
du, err := newDigestSecret(l.PasswdFile)
if err != nil {
log.Fatal(err)
}
digestAuth := auth.NewDigestAuthenticator(l.Realm, du.getPw)
digestAuth.Headers = auth.ProxyHeaders
hdlr.authMethod = digestAuth
}
if len(certs) > 0 {
tlsconfig := &tls.Config{
Certificates: certs,
}
tlsconfig.BuildNameToCertificate()
srv := http.Server{
Addr: addr,
TLSConfig: tlsconfig,
Handler: loghandler.CombinedLoggingHandler(w, hdlr),
}
log.Printf("listen https on %s", addr)
if err := srv.ListenAndServeTLS("", ""); err != nil {
log.Fatal(err)
}
} else {
log.Printf("listen http on %s", addr)
if err := http.ListenAndServe(
addr,
loghandler.CombinedLoggingHandler(w, hdlr),
); err != nil {
log.Fatal(err)
}
}
}(l)
}
}
func registerAliasHandler(r rule, router *mux.Router) {
switch r.Target.Type {
case "file":
registerFileHandler(r, router)
case "dir":
registerDirHandler(r, router)
default:
fmt.Printf("invalid type: %s, only file, dir allowed\n", r.Target.Type)
os.Exit(-1)
}
}
func registerFileHandler(r rule, router *mux.Router) {
router.HandleFunc(r.URLPrefix,
func(w http.ResponseWriter, req *http.Request) {
http.ServeFile(w, req, r.Target.Path)
})
}
func registerDirHandler(r rule, router *mux.Router) {
p := strings.TrimRight(r.URLPrefix, "/")
router.PathPrefix(r.URLPrefix).Handler(
http.StripPrefix(p,
http.FileServer(http.Dir(r.Target.Path))))
}
func registerUwsgiHandler(r rule, router *mux.Router) {
var p string
switch r.Target.Type {
case "unix":
p = r.Target.Path
case "tcp":
p = fmt.Sprintf("%s:%d", r.Target.Host, r.Target.Port)
default:
fmt.Printf("invalid scheme: %s, only support unix, tcp", r.Target.Type)
os.Exit(-1)
}
if r.IsRegex {
m1 := myURLMatch{regexp.MustCompile(r.URLPrefix)}
u := NewUwsgi(r.Target.Type, p, "")
router.MatcherFunc(m1.match).Handler(u)
} else {
u := NewUwsgi(r.Target.Type, p, r.URLPrefix)
router.PathPrefix(r.URLPrefix).Handler(u)
}
}
func registerFastCGIHandler(r rule, docroot string, router *mux.Router) {
var n, p string
switch r.Target.Type {
case "unix":
n = "unix"
p = r.Target.Path
case "tcp":
n = "tcp"
p = fmt.Sprintf("%s:%d", r.Target.Host, r.Target.Port)
default:
fmt.Printf("invalid scheme: %s, only support unix, tcp", r.Target.Type)
os.Exit(-1)
}
u := gofast.NewHandler(gofast.NewPHPFS(docroot), n, p)
if r.IsRegex {
m1 := myURLMatch{regexp.MustCompile(r.URLPrefix)}
router.MatcherFunc(m1.match).Handler(u)
} else {
router.PathPrefix(r.URLPrefix).Handler(u)
}
}
func registerHTTPHandler(r rule, router *mux.Router) {
var u http.Handler
var addr string
switch r.Target.Type {
case "unix":
addr = r.Target.Path
u = newProxy(addr, r.URLPrefix)
case "http":
addr = fmt.Sprintf("%s:%d", r.Target.Host, r.Target.Port)
u1 := &url.URL{
Scheme: "http",
Host: addr,
Path: r.Target.Path,
}
u = httputil.NewSingleHostReverseProxy(u1)
default:
fmt.Printf("invalid scheme: %s, only support unix, http", r.Target.Type)
os.Exit(-1)
}
p := strings.TrimRight(r.URLPrefix, "/")
router.PathPrefix(r.URLPrefix).Handler(
http.StripPrefix(p, u))
}
type myURLMatch struct {
re *regexp.Regexp
}
func (m myURLMatch) match(r *http.Request, route *mux.RouteMatch) bool {
ret := m.re.MatchString(r.URL.Path)
return ret
}

@ -1,23 +1,154 @@
package main
import (
"crypto/tls"
"flag"
//"fmt"
"log"
//"net/http"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/fangdingjun/go-log"
"github.com/fangdingjun/protolistener"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"golang.org/x/net/http2"
"golang.org/x/net/trace"
)
var logfile string
func initServer(c *conf) error {
mux := mux.NewRouter()
for _, vh := range c.Vhosts {
subroute := mux.Host(vh.Hostname)
subroute.PathPrefix("/").Handler(http.FileServer(http.Dir(vh.Docroot)))
}
mux.PathPrefix("/debug/").Handler(http.DefaultServeMux)
if len(c.Vhosts) > 0 {
mux.PathPrefix("/").Handler(http.FileServer(http.Dir(c.Vhosts[0].Docroot)))
} else {
mux.PathPrefix("/").Handler(http.FileServer(http.Dir("/var/www/html")))
}
for _, _l := range c.Listens {
var err error
certs := []tls.Certificate{}
tlsconfig := &tls.Config{}
for _, cert := range _l.Certificates {
if cert.CertFile != "" && cert.KeyFile != "" {
_cert, err := tls.LoadX509KeyPair(cert.CertFile, cert.KeyFile)
if err != nil {
return err
}
certs = append(certs, _cert)
}
}
var h http.Handler
h = &handler{
handler: mux,
cfg: c,
events: trace.NewEventLog("http", fmt.Sprintf("%s:%d", _l.Addr, _l.Port)),
}
h = handlers.CombinedLoggingHandler(&logout{}, h)
srv := &http.Server{
Addr: fmt.Sprintf("%s:%d", _l.Addr, _l.Port),
Handler: h,
}
var l net.Listener
l, err = net.Listen("tcp", srv.Addr)
if err != nil {
return err
}
l = protolistener.New(l)
if len(certs) > 0 {
tlsconfig.Certificates = certs
tlsconfig.BuildNameToCertificate()
srv.TLSConfig = tlsconfig
http2.ConfigureServer(srv, nil)
l = tls.NewListener(l, srv.TLSConfig)
}
go func(l net.Listener) {
defer l.Close()
err = srv.Serve(l)
if err != nil {
log.Errorln(err)
}
}(l)
}
return nil
}
type logout struct{}
func (l *logout) Write(buf []byte) (int, error) {
log.Debugf("%s", buf)
return len(buf), nil
}
func main() {
var configfile string
var loglevel string
var logfile string
var logFileCount int
var logFileSize int64
flag.StringVar(&logfile, "log_file", "", "log file, default stdout")
flag.IntVar(&logFileCount, "log_count", 10, "max count of log to keep")
flag.Int64Var(&logFileSize, "log_size", 10, "max log file size MB")
flag.StringVar(&loglevel, "log_level", "INFO",
"log level, values:\nOFF, FATAL, PANIC, ERROR, WARN, INFO, DEBUG")
flag.StringVar(&configfile, "c", "config.yaml", "config file")
flag.StringVar(&logfile, "log", "", "log file")
flag.Parse()
if logfile != "" {
log.Default.Out = &log.FixedSizeFileWriter{
MaxCount: logFileCount,
Name: logfile,
MaxSize: logFileSize * 1024 * 1024,
}
}
if loglevel != "" {
lv, err := log.ParseLevel(loglevel)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
log.Default.Level = lv
}
c, err := loadConfig(configfile)
if err != nil {
log.Fatal(err)
}
initRouters(c)
select {}
log.Infof("%+v", c)
err = initServer(c)
if err != nil {
log.Fatalln(err)
}
trace.AuthRequest = func(r *http.Request) (bool, bool) {
return true, true
}
ch := make(chan os.Signal, 2)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
select {
case sig := <-ch:
log.Errorf("received signal %s, exit", sig)
}
log.Debug("exited.")
}

@ -1,107 +0,0 @@
package main
import (
//"fmt"
uwsgi "github.com/fangdingjun/go-uwsgi"
"net"
"net/http"
"strconv"
"strings"
)
// Uwsgi is a struct for uwsgi
type Uwsgi struct {
Passenger *uwsgi.Passenger
URLPrefix string
}
// NewUwsgi create a new Uwsgi
func NewUwsgi(network, addr, urlPrefix string) *Uwsgi {
u := strings.TrimRight(urlPrefix, "/")
return &Uwsgi{
Passenger: &uwsgi.Passenger{
Net: network,
Addr: addr,
},
URLPrefix: u,
}
}
// ServeHTTP implements http.Handler interface
func (u *Uwsgi) ServeHTTP(w http.ResponseWriter, r *http.Request) {
u.UwsgiPass(w, r)
}
// UwsgiPass pass the request to uwsgi interface
func (u *Uwsgi) UwsgiPass(w http.ResponseWriter, r *http.Request) {
params := buildParams(r, u.URLPrefix)
u.Passenger.UwsgiPass(w, r, params)
}
func buildParams(req *http.Request, urlPrefix string) map[string][]string {
var err error
header := make(map[string][]string)
if urlPrefix != "" {
header["SCRIPT_NAME"] = []string{urlPrefix}
p := strings.Replace(req.URL.Path, urlPrefix, "", 1)
header["PATH_INFO"] = []string{p}
} else {
header["PATH_INFO"] = []string{req.URL.Path}
}
//fmt.Printf("url: %s, scheme: %s\n", req.URL.String(), req.URL.Scheme)
scheme := "http"
if req.TLS != nil {
scheme = "https"
}
header["REQUEST_SCHEME"] = []string{scheme}
header["HTTPS"] = []string{"off"}
/* https */
if scheme == "https" {
header["HTTPS"] = []string{"on"}
}
/* speicial port */
host, port, err := net.SplitHostPort(req.Host)
if err != nil {
host = req.Host
if scheme == "http" {
port = "80"
} else {
port = "443"
}
}
header["SERVER_NAME"] = []string{host}
header["SERVER_PORT"] = []string{port}
host, port, err = net.SplitHostPort(req.RemoteAddr)
if err != nil {
host = req.RemoteAddr
port = "80"
}
header["REMOTE_PORT"] = []string{port}
header["REMOTE_ADDR"] = []string{host}
header["REQUEST_METHOD"] = []string{req.Method}
header["REQUEST_URI"] = []string{req.RequestURI}
header["CONTENT_LENGTH"] = []string{strconv.Itoa(int(req.ContentLength))}
header["SERVER_PROTOCOL"] = []string{req.Proto}
header["QUERY_STRING"] = []string{req.URL.RawQuery}
if ctype := req.Header.Get("Content-Type"); ctype != "" {
header["CONTENT_TYPE"] = []string{ctype}
}
for k, v := range req.Header {
k = "HTTP_" + strings.ToUpper(strings.Replace(k, "-", "_", -1))
if _, ok := header[k]; ok == false {
header[k] = v
}
}
return header
}
Loading…
Cancel
Save