add virtual host support

master
dingjun 8 years ago
parent 22ae45beb5
commit 8512438638

@ -5,19 +5,23 @@ import (
"io/ioutil" "io/ioutil"
) )
type conf struct { type conf []server
Listen []listen
Docroot string
URLRules []rule
LocalDomains []string
}
type listen struct { type server struct {
Host string Host string
Port int Port int
Cert string Docroot string
Key string UrlRules []rule
EnableProxy bool EnableProxy bool
Vhost []vhost
}
type vhost struct {
Docroot string
Hostname string
Cert string
Key string
UrlRules []rule
} }
type rule struct { type rule struct {
@ -34,7 +38,7 @@ type target struct {
Path string Path string
} }
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
@ -45,5 +49,5 @@ func loadConfig(fn string) (*conf, error) {
return nil, err return nil, err
} }
return &c, nil return c, nil
} }

@ -1,84 +1,129 @@
# vim: set ft=yaml: # vim: set ft=yaml:
#
# server config file
# document root # when provide certficate file, server will listen https and enable http2
docroot: /var/www/html
# used for http2 to determine local path request
# or proxy request
localdomains:
- www.simicloud.com
- localhost
- 127.0.0.1
# listener # http config
listen: -
- # listen address
# listen address host: 0.0.0.0
host: 0.0.0.0
# listen port # listen port
port: 80 port: 9001
# enable proxy or not # default document root
enableproxy: false docroot: /srv/www
-
host: 0.0.0.0
port: 443
# server certificate
cert: server.crt
# server private key
key: server.key
enableproxy: false
# url rules enableproxy: false
urlrules:
-
# alias map url to path
urlprefix: /robots.txt
# available type: alias, fastcgi, uwsgi, http # default host's url rule
type: alias #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
# target # virtual host config
target: vhost:
# availabe target type for alias: file, dir - &example1_www
type: file hostname: www.example1.com
path: /home/aaa/robots.txt docroot: /var/www/html/
- # cert:
# alias map url to path # key:
urlprefix: /cc
type: alias
target:
type: dir
path: /home/cc
-
# pass to uwsgi server
urlprefix: /media
type: uwsgi
target:
# available target type for uwsgi: unix, tcp
type: unix
path: /path/to/media.sock
-
# pass all php script to fastcgi server
urlprefix: \.php$|\.php/
# use regex to match the url # url rule for www.example.com
isregex: true urlrules:
-
# url start with /APIv1/ forward to uwsg socket
urlprefix: /APIv1/
type: uwsgi
target:
type: unix
path: /run/uwsgi/APIv1.sock
-
# route php script to fastCGI socket
urlprefix: /phpmyadmin/.*\.php$
type: fastcgi # set to true means urlprefix is regex expression
target: isregex: true
# available target type for fastcgi: unix, tcp
type: unix type: fastcgi
path: /path/to/php.sock target:
- type: unix
urlprefix: /auth path: /var/run/php-fpm/www.sock
# http resverse proxy -
type: http # url start with /proxy/ forward to http://10.10.1.1
target: # this act as reverse proxy
# available http type for http: http, unix urlprefix: /proxy/
type: http type: http
host: 127.0.0.1 target:
port: 8080 type: http
host: 10.10.1.1
port: 8080
- &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,6 +1,7 @@
package main package main
import ( import (
"fmt"
"github.com/yookoala/gofast" "github.com/yookoala/gofast"
"log" "log"
"net" "net"
@ -35,6 +36,15 @@ func (f *FastCGI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// FastCGIPass pass the request to fastcgi socket // FastCGIPass pass the request to fastcgi socket
func (f *FastCGI) FastCGIPass(w http.ResponseWriter, r *http.Request) { func (f *FastCGI) FastCGIPass(w http.ResponseWriter, r *http.Request) {
// make sure server not access the file out of document root
p1 := filepath.Clean(filepath.Join(f.DocRoot, r.URL.Path))
p2 := filepath.Clean(f.DocRoot)
if !strings.HasPrefix(p1, p2) {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "invalid url")
return
}
var scriptName, pathInfo, scriptFileName string var scriptName, pathInfo, scriptFileName string
conn, err := net.Dial(f.Network, f.Addr) conn, err := net.Dial(f.Network, f.Addr)
@ -58,7 +68,7 @@ func (f *FastCGI) FastCGIPass(w http.ResponseWriter, r *http.Request) {
if len(p) < 2 { if len(p) < 2 {
if strings.HasSuffix(r.URL.Path, "/") { if strings.HasSuffix(r.URL.Path, "/") {
// redirect to index.php // redirect to index.php
scriptName = "" scriptName = filepath.Join(r.URL.Path, "index.php")
pathInfo = "" pathInfo = ""
scriptFileName = filepath.Join(f.DocRoot, urlPath, "index.php") scriptFileName = filepath.Join(f.DocRoot, urlPath, "index.php")
} else { } else {
@ -75,6 +85,10 @@ func (f *FastCGI) FastCGIPass(w http.ResponseWriter, r *http.Request) {
req := client.NewRequest(r) req := client.NewRequest(r)
req.Params["DOCUMENT_URI"] = scriptName
req.Params["SCRIPT_NAME"] = scriptName
req.Params["PHP_SELF"] = scriptName
req.Params["DOCUMENT_ROOT"] = f.DocRoot
req.Params["PATH_INFO"] = pathInfo req.Params["PATH_INFO"] = pathInfo
req.Params["SCRIPT_FILENAME"] = scriptFileName req.Params["SCRIPT_FILENAME"] = scriptFileName

@ -11,6 +11,7 @@ import (
) )
type handler struct { type handler struct {
handler http.Handler
enableProxy bool enableProxy bool
localDomains []string localDomains []string
} }
@ -25,12 +26,20 @@ 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) {
if r.ProtoMajor == 1 && r.RequestURI[0] == '/' { if r.ProtoMajor == 1 && r.RequestURI[0] == '/' {
http.DefaultServeMux.ServeHTTP(w, r) if h.handler != nil {
h.handler.ServeHTTP(w, r)
} else {
http.DefaultServeMux.ServeHTTP(w, r)
}
return return
} }
if r.ProtoMajor == 2 && h.isLocalRequest(r) { if r.ProtoMajor == 2 && h.isLocalRequest(r) {
http.DefaultServeMux.ServeHTTP(w, r) if h.handler != nil {
h.handler.ServeHTTP(w, r)
} else {
http.DefaultServeMux.ServeHTTP(w, r)
}
return return
} }

@ -1,37 +1,108 @@
package main package main
import ( import (
"crypto/tls"
"fmt" "fmt"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"net"
"net/http" "net/http"
//"net/url" //"net/url"
"log"
"os" "os"
"regexp" "regexp"
//"path/filepath" //"path/filepath"
"strings" "strings"
) )
func initRouters(cfg *conf) { func initRouters(cfg conf) {
router := mux.NewRouter()
for _, l := range cfg {
for _, r := range cfg.URLRules { router := mux.NewRouter()
switch r.Type { domains := []string{}
case "alias": certs := []tls.Certificate{}
registerAliasHandler(r, router)
case "uwsgi": // initial virtual host
registerUwsgiHandler(r, router) for _, h := range l.Vhost {
case "fastcgi": h2 := h.Hostname
registerFastCGIHandler(r, cfg.Docroot, router) if h1, _, err := net.SplitHostPort(h.Hostname); err == nil {
case "http": h2 = h1
registerHTTPHandler(r, router) }
default: domains = append(domains, h2)
fmt.Printf("invalid type: %s\n", r.Type) 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 "http":
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":
registerFastCGIHandler(rule, l.Docroot, router)
case "http":
registerHTTPHandler(rule, router)
default:
fmt.Printf("invalid type: %s\n", rule.Type)
}
} }
}
router.PathPrefix("/").Handler(http.FileServer(http.Dir(cfg.Docroot))) router.PathPrefix("/").Handler(http.FileServer(http.Dir(l.Docroot)))
http.Handle("/", router) go func(l server) {
addr := fmt.Sprintf("%s:%d", l.Host, l.Port)
hdlr := &handler{
handler: router,
enableProxy: l.EnableProxy,
localDomains: domains,
}
if len(certs) > 0 {
tlsconfig := &tls.Config{
Certificates: certs,
}
tlsconfig.BuildNameToCertificate()
srv := http.Server{
Addr: addr,
TLSConfig: tlsconfig,
Handler: 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, hdlr); err != nil {
log.Fatal(err)
}
}
}(l)
}
} }
func registerAliasHandler(r rule, router *mux.Router) { func registerAliasHandler(r rule, router *mux.Router) {
@ -45,6 +116,7 @@ func registerAliasHandler(r rule, router *mux.Router) {
os.Exit(-1) os.Exit(-1)
} }
} }
func registerFileHandler(r rule, router *mux.Router) { func registerFileHandler(r rule, router *mux.Router) {
router.HandleFunc(r.URLPrefix, router.HandleFunc(r.URLPrefix,
func(w http.ResponseWriter, req *http.Request) { func(w http.ResponseWriter, req *http.Request) {

@ -2,29 +2,11 @@ package main
import ( import (
"flag" "flag"
"fmt" //"fmt"
"log" "log"
"net/http" //"net/http"
) )
func initListeners(c *conf) {
for _, l := range c.Listen {
go func(l listen) {
addr := fmt.Sprintf("%s:%d", l.Host, l.Port)
h := &handler{enableProxy: l.EnableProxy, localDomains: c.LocalDomains}
if l.Cert != "" && l.Key != "" {
if err := http.ListenAndServeTLS(addr, l.Cert, l.Key, h); err != nil {
log.Fatal(err)
}
} else {
if err := http.ListenAndServe(addr, h); err != nil {
log.Fatal(err)
}
}
}(l)
}
}
func main() { func main() {
var configfile string var configfile string
flag.StringVar(&configfile, "c", "config.yaml", "config file") flag.StringVar(&configfile, "c", "config.yaml", "config file")
@ -34,6 +16,5 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
initRouters(c) initRouters(c)
initListeners(c)
select {} select {}
} }

Loading…
Cancel
Save