|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httputil"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/fangdingjun/gnutls"
|
|
|
|
auth "github.com/fangdingjun/go-http-auth"
|
|
|
|
"github.com/fangdingjun/gofast"
|
|
|
|
nghttp2 "github.com/fangdingjun/nghttp2-go"
|
|
|
|
loghandler "github.com/gorilla/handlers"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
)
|
|
|
|
|
|
|
|
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 := []*gnutls.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 := gnutls.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 := &gnutls.Config{
|
|
|
|
Certificates: certs,
|
|
|
|
NextProtos: []string{"h2", "http/1.1"},
|
|
|
|
}
|
|
|
|
listener, err := gnutls.Listen("tcp", addr, tlsconfig)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
handler := loghandler.CombinedLoggingHandler(w, hdlr)
|
|
|
|
log.Printf("listen https on %s", addr)
|
|
|
|
go func() {
|
|
|
|
defer listener.Close()
|
|
|
|
for {
|
|
|
|
conn, err := listener.Accept()
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
go handleHTTPClient(conn, handler)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
} 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
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleHTTPClient(c net.Conn, handler http.Handler) {
|
|
|
|
tlsconn := c.(*gnutls.Conn)
|
|
|
|
if err := tlsconn.Handshake(); err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
state := tlsconn.ConnectionState()
|
|
|
|
if state.NegotiatedProtocol == "h2" {
|
|
|
|
h2conn, err := nghttp2.Server(tlsconn, handler)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
h2conn.Run()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
defer c.Close()
|
|
|
|
r := bufio.NewReader(tlsconn)
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
for {
|
|
|
|
req, err := http.ReadRequest(r)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
addr := tlsconn.RemoteAddr().String()
|
|
|
|
req.RemoteAddr = addr
|
|
|
|
rh := &responseHandler{
|
|
|
|
c: tlsconn,
|
|
|
|
header: http.Header{},
|
|
|
|
buf: buf,
|
|
|
|
}
|
|
|
|
handler.ServeHTTP(rh, req)
|
|
|
|
rh.Write(nil)
|
|
|
|
rh.buf.WriteTo(rh.c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type responseHandler struct {
|
|
|
|
c net.Conn
|
|
|
|
statusCode int
|
|
|
|
header http.Header
|
|
|
|
responseSend bool
|
|
|
|
w io.Writer
|
|
|
|
buf *bytes.Buffer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *responseHandler) WriteHeader(statusCode int) {
|
|
|
|
if r.responseSend {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
r.buf.Reset()
|
|
|
|
r.statusCode = statusCode
|
|
|
|
cl := r.header.Get("content-length")
|
|
|
|
te := r.header.Get("transfer-encoding")
|
|
|
|
if cl == "" || te != "" {
|
|
|
|
if te == "" {
|
|
|
|
r.header.Set("transfer-encoding", "chunked")
|
|
|
|
}
|
|
|
|
r.w = &chunkWriter{r.buf}
|
|
|
|
} else {
|
|
|
|
r.w = r.buf
|
|
|
|
}
|
|
|
|
fmt.Fprintf(r.buf, "HTTP/1.1 %d %s\r\n", statusCode,
|
|
|
|
http.StatusText(statusCode))
|
|
|
|
for k, v := range r.header {
|
|
|
|
fmt.Fprintf(r.buf, "%s: %s\r\n", strings.Title(k), strings.Join(v, ","))
|
|
|
|
}
|
|
|
|
fmt.Fprintf(r.buf, "\r\n")
|
|
|
|
r.responseSend = true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *responseHandler) Header() http.Header {
|
|
|
|
return r.header
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *responseHandler) Write(buf []byte) (int, error) {
|
|
|
|
if !r.responseSend {
|
|
|
|
r.WriteHeader(http.StatusOK)
|
|
|
|
}
|
|
|
|
n, err := r.w.Write(buf)
|
|
|
|
if r.buf.Len() > 2048 {
|
|
|
|
r.buf.WriteTo(r.c)
|
|
|
|
}
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ http.ResponseWriter = &responseHandler{}
|
|
|
|
|
|
|
|
type chunkWriter struct {
|
|
|
|
w io.Writer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cw *chunkWriter) Write(buf []byte) (int, error) {
|
|
|
|
n := len(buf)
|
|
|
|
if n == 0 {
|
|
|
|
return fmt.Fprintf(cw.w, "0\r\n\r\n")
|
|
|
|
}
|
|
|
|
return fmt.Fprintf(cw.w, "%x\r\n%s\r\n", n, string(buf))
|
|
|
|
}
|