Compare commits

..

16 Commits
master ... lua

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

@ -0,0 +1,53 @@
local http = require "http"
local json = require "json"
local mysql = require "mysql"
local redis = require "redis"
infolog(string.format("%s %s %s", request:GetIP(),request.Method,request.RequestURI))
-- response:Write(string.format("hello %s\n",request:GetIP()))
if request.URL.Path == "/api/ip" then
local resp = http.get("http://httpbin.org/ip")
local d = json.decode(resp.body)
print(d)
infolog(string.format("ip %s",d.origin))
local hdr = response:Header()
hdr:Set("content-type","application/json")
response:Write(resp.body)
return
end
if request.URL.Path == "/api/user" then
local c = mysql.new()
local ok, err = c:connect({host="127.0.0.1",user="root",password="112233",database="game"})
if not ok then
error(err)
return
end
local res, err = c:query("select * from `user` limit 3")
if err ~= nil then
error(err)
return
end
for k,v in pairs(res) do
print(k,v)
for k1,v1 in pairs(v) do
print(k1,v1)
end
end
local hdr = response:Header()
hdr:Set("content-type","application/json")
response:Write(json.encode(res))
c:close()
return
end
if request.URL.Path == "/api/user2" then
local c = redis.new({host="127.0.0.1"})
local keys = c:Do("keys", "wallet*"):Val()
for k, v in keys() do
response:Write(string.format("%s %s\n", k, v))
end
c:Close()
end

@ -0,0 +1,82 @@
package main
import (
"bufio"
"os"
"sync"
"github.com/fangdingjun/go-log"
lua "github.com/yuin/gopher-lua"
"github.com/yuin/gopher-lua/parse"
)
// from https://github.com/yuin/gophoer-lua
// CompileLua reads the passed lua file from disk and compiles it.
func CompileLua(filePath string) (*lua.FunctionProto, error) {
file, err := os.Open(filePath)
defer file.Close()
if err != nil {
return nil, err
}
reader := bufio.NewReader(file)
chunk, err := parse.Parse(reader, filePath)
if err != nil {
return nil, err
}
proto, err := lua.Compile(chunk, filePath)
if err != nil {
return nil, err
}
return proto, nil
}
// DoCompiledFile takes a FunctionProto, as returned by CompileLua, and runs it in the LState. It is equivalent
// to calling DoFile on the LState with the original source file.
func DoCompiledFile(L *lua.LState, proto *lua.FunctionProto) error {
lfunc := L.NewFunctionFromProto(proto)
L.Push(lfunc)
return L.PCall(0, lua.MultRet, nil)
}
type filecache struct {
filepath string
proto *lua.FunctionProto
lastMod int64
}
var cache = map[string]*filecache{}
var mu = new(sync.Mutex)
func runFile(vm *lua.LState, filepath string) error {
mu.Lock()
c, ok := cache[filepath]
if !ok {
c = &filecache{
filepath: filepath,
lastMod: 0,
}
cache[filepath] = c
}
fi, err := os.Stat(filepath)
if err != nil {
mu.Unlock()
return err
}
t := fi.ModTime().Unix()
if t > c.lastMod {
log.Debugf("file %s changed, reload", filepath)
proto, err := CompileLua(filepath)
if err != nil {
mu.Unlock()
return err
}
c.proto = proto
c.lastMod = t
}
mu.Unlock()
// log.Println(c.proto)
return DoCompiledFile(vm, c.proto)
}

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

@ -0,0 +1,29 @@
# vim: set ft=yaml:
# when provide certficate file, server will listen https and enable http2
listen:
-
addr: 0.0.0.0
port: 9001
certificates:
# virtual host support
vhost:
-
hostname: www.ratafee.nl
docroot: .
# proxypass: http://nginx:80/
url_rules:
- prefix: /api/
lua_file: api.lua
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,145 +1,36 @@
# vim: set ft=yaml: # vim: set ft=yaml:
# when provide certficate file, server will listen https and enable http2 # when provide certficate file, server will listen https and enable http2
listen:
-
# http config addr: 0.0.0.0
-
# listen address
host: 0.0.0.0
# listen port
port: 9001 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 docroot: /srv/www
proxypass: http://nginx:80/
enableproxy: true url_rules:
enableauth: true - prefix: /api/
passwdfile: ./passwdfile lua_file: api.lua
realm: example.com proxy:
http1-proxy: false
# default host's url rule http2-proxy: true
# urlrules: # trust the follow domains as local virtual host
# - # when http2 proxy enabled
# urlprefix: /a localdomains:
# type: alias - localhost
# target: - localdomain
# type: dir - 127.0.0.1
# path: /home/user1/a - ratafee.nl
# - - 98.142.138.194
# 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

@ -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,20 @@
module github.com/fangdingjun/gserver
go 1.13
require (
github.com/cjoudrey/gluahttp v0.0.0-20190104103309-101c19a37344
github.com/fangdingjun/go-log v0.0.0-20190821073628-ae332053d6dc
github.com/fangdingjun/gopher-redis v0.0.0-20190823083833-93d88f74dd35
github.com/fangdingjun/protolistener v0.0.0-20190821093313-6d5d2138f296
github.com/go-sql-driver/mysql v1.4.1 // indirect
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/junhsieh/goexamples v0.0.0-20190721045834-1c67ae74caa6 // indirect
github.com/tengattack/gluasql v0.0.0-20181229041402-2e5ed630c4cf
github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
layeh.com/gopher-json v0.0.0-20190114024228-97fed8db8427
layeh.com/gopher-luar v1.0.7
)

@ -0,0 +1,49 @@
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cjoudrey/gluahttp v0.0.0-20190104103309-101c19a37344 h1:nTK6O2RY1nHC4jNEJd1nixVP7ygY3QE4CNxCX07dHKU=
github.com/cjoudrey/gluahttp v0.0.0-20190104103309-101c19a37344/go.mod h1:X97UjDTXp+7bayQSFZk2hPvCTmTZIicUjZQRtkwgAKY=
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/gopher-redis v0.0.0-20190823083833-93d88f74dd35 h1:ylhL3mOAVv+xPvF7ml4mPJkacGyc4hDAxBRl8/DGATk=
github.com/fangdingjun/gopher-redis v0.0.0-20190823083833-93d88f74dd35/go.mod h1:mJh0kXIkwwPbU3KnTswNRDtHUtA+xlFJRHnbxuFg33Y=
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/junhsieh/goexamples v0.0.0-20190721045834-1c67ae74caa6 h1:oi85CfIzfVPMlTf6pteDMw0S3AjZ9b6Y0OaxfsLCU3M=
github.com/junhsieh/goexamples v0.0.0-20190721045834-1c67ae74caa6/go.mod h1:JNqB8Da6SnlJvmZusESDfgqUkJXO6+/a1by1etsVJ2M=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.9.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.6.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
github.com/tengattack/gluasql v0.0.0-20181229041402-2e5ed630c4cf h1:7zGSyMKHHwD0A8iJk0IlP8FhSIOZco4VyzemWYHP1Ck=
github.com/tengattack/gluasql v0.0.0-20181229041402-2e5ed630c4cf/go.mod h1:m4UsFo0sc87XS8WozZBD8HJPC2NKI/nB7w4N58HwemQ=
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
layeh.com/gopher-json v0.0.0-20190114024228-97fed8db8427 h1:RZkKxMR3jbQxdCEcglq3j7wY3PRJIopAwBlx1RE71X0=
layeh.com/gopher-json v0.0.0-20190114024228-97fed8db8427/go.mod h1:ivKkcY8Zxw5ba0jldhZCYYQfGdb2K6u9tbYK1AwMIBc=
layeh.com/gopher-luar v1.0.7 h1:53iv6CCkRs5wyofZ+qVXcyAYQOIG52s6pt4xkqZdq7k=
layeh.com/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=

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

@ -0,0 +1,17 @@
package main
import (
"github.com/fangdingjun/go-log"
)
func infoLog(msg string) {
log.Println(msg)
}
func errorLog(msg string) {
log.Errorln(msg)
}
func debugLog(msg string) {
log.Debugln(msg)
}

@ -0,0 +1,49 @@
package main
import (
"io/ioutil"
"net"
"net/http"
"github.com/fangdingjun/go-log"
luar "layeh.com/gopher-luar"
)
type luaHandler struct {
scriptFile string
}
func (l *luaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
vm := luaPool.Get()
defer luaPool.Put(vm)
vm.SetGlobal("request", luar.New(vm, &req{r}))
vm.SetGlobal("response", luar.New(vm, w))
if err := runFile(vm, l.scriptFile); err != nil {
log.Errorln(err)
http.Error(w, "server error", http.StatusInternalServerError)
}
}
type req struct {
*http.Request
}
func (r1 *req) GetBody() (string, error) {
d, err := ioutil.ReadAll(r1.Body)
return string(d), err
}
func (r1 *req) GetIP() string {
ip := r1.Header.Get("x-real-ip")
if ip != "" {
return ip
}
ip = r1.Header.Get("x-forwarded-for")
if ip != "" {
return ip
}
host, _, _ := net.SplitHostPort(r1.RemoteAddr)
return host
}

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

@ -0,0 +1,68 @@
package main
import (
"net/http"
"sync"
gluahttp "github.com/cjoudrey/gluahttp"
lua "github.com/yuin/gopher-lua"
luajson "layeh.com/gopher-json"
luar "layeh.com/gopher-luar"
mysql "github.com/tengattack/gluasql/mysql"
redis "github.com/fangdingjun/gopher-redis"
)
// from https://github.com/yuin/gophoer-lua
type lStatePool struct {
m sync.Mutex
saved []*lua.LState
}
func (pl *lStatePool) Get() *lua.LState {
pl.m.Lock()
defer pl.m.Unlock()
n := len(pl.saved)
if n == 0 {
return pl.New()
}
x := pl.saved[n-1]
pl.saved = pl.saved[0 : n-1]
return x
}
func (pl *lStatePool) New() *lua.LState {
L := lua.NewState()
luajson.Preload(L)
L.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader)
L.PreloadModule("mysql", mysql.Loader)
L.PreloadModule("redis", redis.Loader)
L.SetGlobal("infolog", luar.New(L, infoLog))
L.SetGlobal("errorlog", luar.New(L, errorLog))
L.SetGlobal("debuglog", luar.New(L, debugLog))
// setting the L up here.
// load scripts, set global variables, share channels, etc...
return L
}
func (pl *lStatePool) Put(L *lua.LState) {
pl.m.Lock()
defer pl.m.Unlock()
pl.saved = append(pl.saved, L)
}
func (pl *lStatePool) Shutdown() {
pl.m.Lock()
defer pl.m.Unlock()
for _, L := range pl.saved {
L.Close()
}
}
// Global LState pool
var luaPool = &lStatePool{
saved: make([]*lua.LState, 0, 4),
}

@ -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 package main
/* /*
@ -15,19 +13,21 @@ usage example
*/ */
import ( import (
"context"
"crypto/tls" "crypto/tls"
"flag" "flag"
"fmt" "fmt"
"golang.org/x/net/http2"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"os" "os"
"sync" "sync"
"time" "time"
log "github.com/fangdingjun/go-log"
"golang.org/x/net/http2"
) )
type clientConn struct { type clientConn struct {
@ -65,7 +65,7 @@ type handler struct {
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if debug { if debug {
req, _ := httputil.DumpRequest(r, false) req, _ := httputil.DumpRequest(r, false)
log.Printf("%s", string(req)) log.Debugf("%s", string(req))
} }
if r.Method == http.MethodConnect { 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) resp, err := h.transport.RoundTrip(r)
if err != nil { if err != nil {
log.Printf("roundtrip: %s", err) log.Errorf("roundtrip: %s", err)
w.WriteHeader(http.StatusServiceUnavailable) http.Error(w, err.Error(), http.StatusServiceUnavailable)
fmt.Fprintf(w, "%s", err)
return return
} }
@ -98,19 +97,22 @@ func (h *handler) handleConnect(w http.ResponseWriter, r *http.Request) {
if debug { if debug {
d, _ := httputil.DumpResponse(resp, false) d, _ := httputil.DumpResponse(resp, false)
log.Printf("%s", string(d)) log.Debugf("%s", string(d))
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
w.WriteHeader(resp.StatusCode) 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 return
} }
c, _, err := w.(http.Hijacker).Hijack() c, _, err := w.(http.Hijacker).Hijack()
if err != nil { if err != nil {
log.Println("hijack: %s", err) log.Errorf("hijack: %s", err)
w.WriteHeader(http.StatusServiceUnavailable) http.Error(w, err.Error(), http.StatusServiceUnavailable)
fmt.Fprintf(w, "%s", err) log.Infof("%s %s %d %s %s", r.Method, r.RequestURI, 500, r.Proto, r.UserAgent())
return return
} }
@ -130,21 +132,21 @@ func (h *handler) handleConnect(w http.ResponseWriter, r *http.Request) {
}() }()
<-ch <-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) { func (h *handler) handleHTTP(w http.ResponseWriter, r *http.Request) {
resp, err := h.transport.RoundTrip(r) resp, err := h.transport.RoundTrip(r)
if err != nil { if err != nil {
log.Println(err) log.Errorln(err)
w.WriteHeader(http.StatusServiceUnavailable) http.Error(w, err.Error(), http.StatusServiceUnavailable)
fmt.Fprintf(w, "%s", err)
return return
} }
defer resp.Body.Close() defer resp.Body.Close()
if debug { if debug {
d, _ := httputil.DumpResponse(resp, false) d, _ := httputil.DumpResponse(resp, false)
log.Printf("%s", string(d)) log.Debugf("%s", string(d))
} }
hdr := w.Header() hdr := w.Header()
@ -155,17 +157,39 @@ func (h *handler) handleHTTP(w http.ResponseWriter, r *http.Request) {
} }
w.WriteHeader(resp.StatusCode) 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 { func newClientConn(host string, port string, hostname string, t *http2.Transport) *clientConn {
return &clientConn{ cc := &clientConn{
host: host, host: host,
port: port, port: port,
hostname: hostname, hostname: hostname,
transport: t, transport: t,
lock: new(sync.Mutex), 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) { 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 return p.conn, nil
} }
if debug { if p.conn != nil {
log.Printf("dial to %s:%s", p.host, p.port) 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)) c, err := net.Dial("tcp", net.JoinHostPort(p.host, p.port))
if err != nil { if err != nil {
log.Println(err) log.Errorln(err)
return nil, err return nil, err
} }
cc := &timeoutConn{c, time.Duration(idleTimeout) * time.Second} cc := &timeoutConn{c, time.Duration(idleTimeout) * time.Second}
// cc := c
config := &tls.Config{ config := &tls.Config{
ServerName: p.hostname, ServerName: p.hostname,
NextProtos: []string{"h2"}, NextProtos: []string{"h2"},
@ -195,14 +223,14 @@ func (p *clientConn) GetClientConn(req *http.Request, addr string) (*http2.Clien
conn := tls.Client(cc, config) conn := tls.Client(cc, config)
if err := conn.Handshake(); err != nil { if err := conn.Handshake(); err != nil {
log.Println(err) log.Errorln(err)
return nil, err return nil, err
} }
http2conn, err := p.transport.NewClientConn(conn) http2conn, err := p.transport.NewClientConn(conn)
if err != nil { if err != nil {
conn.Close() conn.Close()
log.Println(err) log.Errorln(err)
return nil, 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) { func (p *clientConn) MarkDead(conn *http2.ClientConn) {
//p.lock.Lock() p.lock.Lock()
//defer p.lock.Unlock() defer p.lock.Unlock()
if debug { if p.conn != nil {
log.Println("mark dead") log.Errorln("mark dead")
p.conn.Close()
p.conn = nil
} }
//p.conn = nil
} }
var debug bool var debug bool
@ -230,12 +258,15 @@ func main() {
var addr string var addr string
var hostname string var hostname string
var listen string var listen string
var logfile string
flag.StringVar(&addr, "server", "", "server address") flag.StringVar(&addr, "server", "", "server address")
flag.StringVar(&hostname, "name", "", "server 's SNI name") flag.StringVar(&hostname, "name", "", "server 's SNI name")
flag.StringVar(&listen, "listen", ":8080", "listen address") flag.StringVar(&listen, "listen", ":8080", "listen address")
flag.BoolVar(&debug, "debug", false, "verbose mode") flag.BoolVar(&debug, "debug", false, "verbose mode")
flag.BoolVar(&insecure, "insecure", false, "insecure mode, not verify the server's certificate") 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() flag.Parse()
if addr == "" { if addr == "" {
@ -243,6 +274,24 @@ func main() {
os.Exit(-1) 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) host, port, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
host = addr host = addr
@ -262,10 +311,8 @@ func main() {
log.Printf("listen on %s", listen) log.Printf("listen on %s", listen)
if debug { log.Printf("use parent proxy https://%s:%s/", host, port)
log.Printf("use parent proxy https://%s:%s/", host, port) log.Printf("server SNI name %s", hostname)
log.Printf("server SNI name %s", hostname)
}
if err := http.ListenAndServe(listen, &handler{transport}); err != nil { if err := http.ListenAndServe(listen, &handler{transport}); err != nil {
log.Fatal(err) 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,161 @@
package main package main
import ( import (
"crypto/tls"
"flag" "flag"
//"fmt" "fmt"
"log" "net"
//"net/http" "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)
for _, rule := range vh.URLRules {
subroute.PathPrefix(rule.Prefix).Handler(&luaHandler{rule.LuaFile})
}
subroute.PathPrefix("/").Handler(http.FileServer(http.Dir(vh.Docroot)))
}
mux.PathPrefix("/debug/").Handler(http.DefaultServeMux)
if len(c.Vhosts) > 0 {
rules := c.Vhosts[0].URLRules
for _, rule := range rules {
mux.PathPrefix(rule.Prefix).Handler(&luaHandler{rule.LuaFile})
}
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() { func main() {
var configfile string 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(&configfile, "c", "config.yaml", "config file")
flag.StringVar(&logfile, "log", "", "log file")
flag.Parse() 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) c, err := loadConfig(configfile)
if err != nil { if err != nil {
log.Fatal(err) 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