diff --git a/conf.go b/conf.go
new file mode 100644
index 0000000..b668dbc
--- /dev/null
+++ b/conf.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+ "github.com/go-yaml/yaml"
+ "io/ioutil"
+)
+
+type conf struct {
+ Listen []listen
+ Docroot string
+ URLRules []rule
+}
+
+type listen struct {
+ Host string
+ Port string
+ Cert string
+ Key string
+ EnableProxy bool
+}
+
+type rule struct {
+ URLPrefix string
+ IsRegex bool
+ Type string
+ Target target
+}
+
+type target struct {
+ Type string
+ Host string
+ Port int
+ Path string
+}
+
+func loadConfig(fn string) (*conf, error) {
+ data, err := ioutil.ReadFile(fn)
+ if err != nil {
+ return nil, err
+ }
+
+ var c conf
+ if err := yaml.Unmarshal(data, &c); err != nil {
+ return nil, err
+ }
+
+ return &c, nil
+}
diff --git a/conf_test.go b/conf_test.go
new file mode 100644
index 0000000..109d7af
--- /dev/null
+++ b/conf_test.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestConf(t *testing.T) {
+ c, err := loadConfig("config_example.yaml")
+ if err != nil {
+ t.Fatal(err)
+ }
+ fmt.Printf("%#v\n", c)
+}
diff --git a/config_example.yaml b/config_example.yaml
new file mode 100644
index 0000000..af8eb8f
--- /dev/null
+++ b/config_example.yaml
@@ -0,0 +1,59 @@
+
+# document root
+docroot: /var/www/html
+
+# listener
+listen:
+ -
+ host: 0.0.0.0
+ port: 80
+ # enable proxy
+ enableproxy: true
+ -
+ host: 0.0.0.0
+ port: 443
+ # server certificate
+ cert: server.crt
+ # server private key
+ key: server.key
+ enableproxy: false
+
+# url rules
+urlrules:
+ -
+ urlprefix: /robots.txt
+ # available type: alias, fastcgi, uwsgi, http
+ type: alias
+ target:
+ # availabe type for alias: file, dir
+ type: file
+ path: /home/aaa/robots.txt
+ -
+ urlprefix: /cc
+ type: alias
+ target:
+ type: dir
+ path: /home/cc
+ -
+ urlprefix: /media
+ type: uwsgi
+ target:
+ # available type for uwsgi: unix, tcp
+ type: unix
+ path: /path/to/media.sock
+ -
+ urlprefix: \.php$|\.php/
+ isregex: true
+ type: fastcgi
+ target:
+ # available type for fastcgi: unix, tcp
+ type: unix
+ path: /path/to/php.sock
+ -
+ urlprefix: /o
+ type: http
+ target:
+ # available type for http: http, unix
+ type: http
+ host: 127.0.0.1
+ port: 8080
diff --git a/fastcgi.go b/fastcgi.go
new file mode 100644
index 0000000..24f60c2
--- /dev/null
+++ b/fastcgi.go
@@ -0,0 +1,129 @@
+package main
+
+import (
+ "github.com/yookoala/gofast"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+)
+
+// FastCGI is a fastcgi client connection
+type FastCGI struct {
+ Network string
+ Addr string
+ DocRoot string
+ URLPrefix string
+ //client gofast.Client
+}
+
+// NewFastCGI creates a new FastCGI struct
+func NewFastCGI(network, addr, docroot, urlPrefix string) (*FastCGI, error) {
+ u := strings.TrimRight(urlPrefix, "/")
+ return &FastCGI{network, addr, docroot, u}, nil
+}
+
+var fcgiPathInfo = regexp.MustCompile(`^(.*?\.php)(.*)$`)
+
+// ServeHTTP implements http.Handler interface
+func (f *FastCGI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ f.FastCGIPass(w, r)
+}
+
+// FastCGIPass pass the request to fastcgi socket
+func (f *FastCGI) FastCGIPass(w http.ResponseWriter, r *http.Request) {
+ var scriptName, pathInfo, scriptFileName string
+
+ conn, err := net.Dial(f.Network, f.Addr)
+ if err != nil {
+ log.Println(err)
+ w.WriteHeader(http.StatusBadGateway)
+ return
+ }
+
+ defer conn.Close()
+
+ client := gofast.NewClient(conn, 20)
+
+ urlPath := r.URL.Path
+ if f.URLPrefix != "" {
+ urlPath = strings.Replace(r.URL.Path, f.URLPrefix, "", 1)
+ }
+
+ p := fcgiPathInfo.FindStringSubmatch(urlPath)
+
+ if len(p) < 2 {
+ if strings.HasSuffix(r.URL.Path, "/") {
+ // redirect to index.php
+ scriptName = ""
+ pathInfo = ""
+ scriptFileName = filepath.Join(f.DocRoot, urlPath, "index.php")
+ } else {
+ // serve static file in php directory
+ fn := filepath.Join(f.DocRoot, urlPath)
+ http.ServeFile(w, r, fn)
+ return
+ }
+ } else {
+ scriptName = p[1]
+ pathInfo = p[2]
+ scriptFileName = filepath.Join(f.DocRoot, scriptName)
+ }
+
+ req := client.NewRequest(r)
+
+ req.Params["PATH_INFO"] = pathInfo
+ req.Params["SCRIPT_FILENAME"] = scriptFileName
+
+ https := "off"
+ scheme := "http"
+ if r.TLS != nil {
+ https = "on"
+ scheme = "https"
+ }
+
+ req.Params["REQUEST_SCHEME"] = scheme
+ req.Params["HTTPS"] = https
+
+ host, port, _ := net.SplitHostPort(r.RemoteAddr)
+ req.Params["REMOTE_ADDR"] = host
+ req.Params["REMOTE_PORT"] = port
+
+ host, port, err = net.SplitHostPort(r.Host)
+ if err != nil {
+ host = r.Host
+ if scheme == "http" {
+ port = "80"
+ } else {
+ port = "443"
+ }
+ }
+ req.Params["SERVER_NAME"] = host
+ req.Params["SERVER_PORT"] = port
+
+ req.Params["SERVER_PROTOCOL"] = r.Proto
+
+ for k, v := range r.Header {
+ k = "HTTP_" + strings.ToUpper(strings.Replace(k, "-", "_", -1))
+ if _, ok := req.Params[k]; ok == false {
+ req.Params[k] = strings.Join(v, ";")
+ }
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ log.Println(err)
+ w.WriteHeader(http.StatusBadGateway)
+ return
+ }
+
+ err = resp.WriteTo(w, os.Stderr)
+ if err != nil {
+ log.Println(err)
+ }
+
+ resp.Close()
+}
diff --git a/handler.go b/handler.go
new file mode 100644
index 0000000..5589129
--- /dev/null
+++ b/handler.go
@@ -0,0 +1,115 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "net/http"
+ "strings"
+ "time"
+)
+
+type handler struct {
+ enableProxy bool
+}
+
+var defaultTransport http.RoundTripper = &http.Transport{
+ DialContext: dialContext,
+ MaxIdleConns: 50,
+ IdleConnTimeout: 30 * time.Second,
+ MaxIdleConnsPerHost: 3,
+ //ResponseHeaderTimeout: 2 * time.Second,
+}
+
+func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if r.RequestURI[0] == '/' {
+ http.DefaultServeMux.ServeHTTP(w, r)
+ return
+ }
+
+ if !h.enableProxy {
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprintf(w, "
page not found!
")
+ return
+ }
+ if r.Method == http.MethodConnect {
+ h.handleCONNECT(w, r)
+ } else {
+ h.handleHTTP(w, r)
+ }
+}
+
+func (h *handler) handleHTTP(w http.ResponseWriter, r *http.Request) {
+
+ var resp *http.Response
+ var err error
+
+ r.Header.Del("proxy-connection")
+
+ 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)
+ return
+ }
+
+ defer resp.Body.Close()
+
+ hdr := w.Header()
+
+ //resp.Header.Del("connection")
+
+ for k, v := range resp.Header {
+ for _, v1 := range v {
+ hdr.Add(k, v1)
+ }
+ }
+
+ w.WriteHeader(resp.StatusCode)
+
+ io.Copy(w, resp.Body)
+}
+
+func (h *handler) handleCONNECT(w http.ResponseWriter, r *http.Request) {
+ host := r.RequestURI
+ if !strings.Contains(host, ":") {
+ host = fmt.Sprintf("%s:443", host)
+ }
+
+ var conn net.Conn
+ var err error
+
+ conn, err = 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)
+ return
+ }
+
+ hj, _ := w.(http.Hijacker)
+ conn1, _, _ := hj.Hijack()
+
+ fmt.Fprintf(conn1, "%s 200 connection established\r\n\r\n", r.Proto)
+
+ pipeAndClose(conn, conn1)
+}
+
+func pipeAndClose(r1, r2 io.ReadWriteCloser) {
+ ch := make(chan int, 2)
+ go func() {
+ io.Copy(r1, r2)
+ ch <- 1
+ }()
+
+ go func() {
+ io.Copy(r2, r1)
+ ch <- 1
+ }()
+
+ <-ch
+}
diff --git a/proxy.go b/proxy.go
new file mode 100644
index 0000000..2ae99a2
--- /dev/null
+++ b/proxy.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+ "context"
+ "net"
+ "net/http"
+ //"bufio"
+ "io"
+ "log"
+ "strings"
+ "time"
+ //"strings"
+)
+
+type proxy struct {
+ transport http.RoundTripper
+ addr string
+ prefix string
+ dialer *net.Dialer
+}
+
+func newProxy(addr string, prefix string) *proxy {
+ p := &proxy{
+ addr: addr,
+ prefix: prefix,
+ dialer: &net.Dialer{Timeout: 2 * time.Second},
+ }
+ p.transport = &http.Transport{
+ DialContext: p.dialContext,
+ MaxIdleConns: 5,
+ IdleConnTimeout: 30 * time.Second,
+ //Dial: p.dial,
+ }
+ return p
+}
+
+func (p *proxy) dialContext(ctx context.Context, network, addr string) (net.Conn, error) {
+ return p.dialer.DialContext(ctx, network, p.addr)
+}
+
+func (p *proxy) dial(network, addr string) (conn net.Conn, err error) {
+ return p.dialer.Dial(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.RequestURI = strings.TrimLeft(r.RequestURI, p.prefix)
+ //r.URL.Scheme = "http"
+ //r.URL.Host = r.Host
+ r.URL.Path = strings.TrimLeft(r.URL.Path, p.prefix)
+ resp, err := p.transport.RoundTrip(r)
+ if err != nil {
+ log.Print(err)
+ w.WriteHeader(http.StatusBadGateway)
+ w.Write([]byte("502 Bad Gateway
"))
+ 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()
+}
diff --git a/routers.go b/routers.go
new file mode 100644
index 0000000..2dfa967
--- /dev/null
+++ b/routers.go
@@ -0,0 +1,122 @@
+package main
+
+import (
+ "fmt"
+ "github.com/gorilla/mux"
+ "net/http"
+ //"net/url"
+ "os"
+ "regexp"
+ //"path/filepath"
+ //"strings"
+)
+
+func initRouters(cfg *conf) {
+ router := mux.NewRouter()
+
+ for _, r := range cfg.URLRules {
+ switch r.Type {
+ case "alias":
+ registerAliasHandler(r, router)
+ case "uwsgi":
+ registerUwsgiHandler(r, router)
+ case "fastcgi":
+ registerFastCGIHandler(r, cfg.Docroot, router)
+ case "http":
+ registerHTTPHandler(r, router)
+ default:
+ fmt.Printf("invalid type: %s\n", r.Type)
+ }
+ }
+
+ router.PathPrefix("/").Handler(http.FileServer(http.Dir(cfg.Docroot)))
+
+ http.Handle("/", router)
+}
+
+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) {
+ router.PathPrefix(r.URLPrefix).Handler(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 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, _ := NewFastCGI(r.Target.Type, p, docroot, "")
+ router.MatcherFunc(m1.match).Handler(u)
+ } else {
+ u, _ := NewFastCGI(r.Target.Type, p, docroot, r.URLPrefix)
+ router.PathPrefix(r.URLPrefix).Handler(u)
+ }
+}
+
+func registerHTTPHandler(r rule, router *mux.Router) {
+ var u http.Handler
+ switch r.Target.Type {
+ case "unix":
+ u = newProxy(r.Target.Path, r.URLPrefix)
+ case "http":
+ u = newProxy(fmt.Sprintf("%s:%d", r.Target.Host, r.Target.Port), r.URLPrefix)
+ default:
+ fmt.Printf("invalid scheme: %s, only support unix, http", r.Target.Type)
+ os.Exit(-1)
+ }
+ router.PathPrefix(r.URLPrefix).Handler(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
+}
diff --git a/server.go b/server.go
index 5582ad3..2c6f5aa 100644
--- a/server.go
+++ b/server.go
@@ -3,145 +3,38 @@ package main
import (
"flag"
"fmt"
- "io"
"log"
- "net"
"net/http"
- "os"
- "strings"
- "time"
)
-var defaultTransport http.RoundTripper = &http.Transport{
- DialContext: dialContext,
- MaxIdleConns: 50,
- IdleConnTimeout: 30 * time.Second,
- MaxIdleConnsPerHost: 3,
- //ResponseHeaderTimeout: 2 * time.Second,
+func initListeners(c *conf) {
+ for _, l := range c.Listen {
+ go func(l listen) {
+ addr := fmt.Sprintf("%s:%d", l.Host, l.Port)
+ if l.Cert != "" && l.Key != "" {
+ if err := http.ListenAndServeTLS(addr, l.Cert, l.Key,
+ &handler{enableProxy: l.EnableProxy}); err != nil {
+ log.Fatal(err)
+ }
+ } else {
+ if err := http.ListenAndServe(addr,
+ &handler{enableProxy: l.EnableProxy}); err != nil {
+ log.Fatal(err)
+ }
+ }
+ }(l)
+ }
}
func main() {
- var docroot string
- var enableProxy bool
- var port int
-
- curdir, err := os.Getwd()
- if err != nil {
- curdir = "."
- }
-
- flag.StringVar(&docroot, "docroot", curdir, "document root")
- flag.BoolVar(&enableProxy, "enable_proxy", false, "enable proxy function")
- flag.IntVar(&port, "port", 8080, "the port listen to")
+ var configfile string
+ flag.StringVar(&configfile, "-c", "config.yaml", "config file")
flag.Parse()
-
- http.Handle("/", http.FileServer(http.Dir(docroot)))
-
- log.Printf("Listen on :%d", port)
- log.Printf("document root %s", docroot)
- if enableProxy {
- log.Println("proxy enabled")
- }
- err = http.ListenAndServe(fmt.Sprintf(":%d", port), &handler{
- enableProxy: enableProxy,
- })
+ c, err := loadConfig(configfile)
if err != nil {
log.Fatal(err)
}
-}
-
-type handler struct {
- enableProxy bool
-}
-
-func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if r.RequestURI[0] == '/' {
- http.DefaultServeMux.ServeHTTP(w, r)
- return
- }
-
- if !h.enableProxy {
- w.WriteHeader(http.StatusNotFound)
- fmt.Fprintf(w, "page not found!
")
- return
- }
- if r.Method == http.MethodConnect {
- h.handleCONNECT(w, r)
- } else {
- h.handleHTTP(w, r)
- }
-}
-
-func (h *handler) handleHTTP(w http.ResponseWriter, r *http.Request) {
-
- var resp *http.Response
- var err error
-
- r.Header.Del("proxy-connection")
-
- 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)
- return
- }
-
- defer resp.Body.Close()
-
- hdr := w.Header()
-
- //resp.Header.Del("connection")
-
- for k, v := range resp.Header {
- for _, v1 := range v {
- hdr.Add(k, v1)
- }
- }
-
- w.WriteHeader(resp.StatusCode)
-
- io.Copy(w, resp.Body)
-}
-
-func (h *handler) handleCONNECT(w http.ResponseWriter, r *http.Request) {
- host := r.RequestURI
- if !strings.Contains(host, ":") {
- host = fmt.Sprintf("%s:443", host)
- }
-
- var conn net.Conn
- var err error
-
- conn, err = 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)
- return
- }
-
- hj, _ := w.(http.Hijacker)
- conn1, _, _ := hj.Hijack()
-
- fmt.Fprintf(conn1, "%s 200 connection established\r\n\r\n", r.Proto)
-
- pipeAndClose(conn, conn1)
-}
-
-func pipeAndClose(r1, r2 io.ReadWriteCloser) {
- ch := make(chan int, 2)
- go func() {
- io.Copy(r1, r2)
- ch <- 1
- }()
-
- go func() {
- io.Copy(r2, r1)
- ch <- 1
- }()
-
- <-ch
+ initRouters(c)
+ initListeners(c)
+ select {}
}
diff --git a/uwsgi.go b/uwsgi.go
new file mode 100644
index 0000000..83c7d2c
--- /dev/null
+++ b/uwsgi.go
@@ -0,0 +1,101 @@
+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{&uwsgi.Passenger{network, addr}, 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
+}