From 9e900b0a08ef8f72f2b3a291cf2cb0348335354d Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Mon, 25 Jan 2016 20:45:23 -0700 Subject: [PATCH 01/15] godoc --- middleware/proxy/reverseproxy.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/middleware/proxy/reverseproxy.go b/middleware/proxy/reverseproxy.go index 4b18a822..32ca2378 100644 --- a/middleware/proxy/reverseproxy.go +++ b/middleware/proxy/reverseproxy.go @@ -104,6 +104,9 @@ var hopHeaders = []string{ "Upgrade", } +// InsecureTransport is used to facilitate HTTPS proxying +// when it is OK for upstream to be using a bad certificate, +// since this transport skips verification. var InsecureTransport http.RoundTripper = &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ From 73ed286309648260af92b1fd818e0e28d6991aed Mon Sep 17 00:00:00 2001 From: jungle-boogie Date: Wed, 27 Jan 2016 11:28:49 -0800 Subject: [PATCH 02/15] wrap lines to 80 also update copyright year. --- dist/README.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dist/README.txt b/dist/README.txt index 85baab0f..532e93f4 100644 --- a/dist/README.txt +++ b/dist/README.txt @@ -8,9 +8,11 @@ Source Code https://github.com/mholt/caddy -For instructions on using Caddy, please see the user guide on the website. For a list of what's new in this version, see CHANGES.txt. +For instructions on using Caddy, please see the user guide on the website. +For a list of what's new in this version, see CHANGES.txt. -If you have a question, bug report, or would like to contribute, please open an issue or submit a pull request on GitHub. Your contributions do not go unnoticed! +If you have a question, bug report, or would like to contribute, please open an +issue or submit a pull request on GitHub. Your contributions do not go unnoticed! For a good time, follow @mholt6 on Twitter. @@ -18,4 +20,4 @@ And thanks - you're awesome! --- -(c) 2015 Matthew Holt +(c) 2015 - 2016 Matthew Holt From 4d4ea94465c4a128855b7067a0b9a13110ab4ef9 Mon Sep 17 00:00:00 2001 From: Kevin Bowrin Date: Wed, 27 Jan 2016 19:00:08 -0500 Subject: [PATCH 03/15] Parse address from fastcgi directive, and pass results to fcgiclient Dial(). This allows scheme prefixes "tcp://" and "fastcgi://" in configuration. Fixes #540 --- middleware/fastcgi/fastcgi.go | 26 +++++++++++++++++++------ middleware/fastcgi/fastcgi_test.go | 31 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 middleware/fastcgi/fastcgi_test.go diff --git a/middleware/fastcgi/fastcgi.go b/middleware/fastcgi/fastcgi.go index 21404c2e..517505b6 100755 --- a/middleware/fastcgi/fastcgi.go +++ b/middleware/fastcgi/fastcgi.go @@ -70,7 +70,8 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) } // Connect to FastCGI gateway - fcgi, err := getClient(&rule) + network, address := rule.parseAddress() + fcgi, err := Dial(network, address) if err != nil { return http.StatusBadGateway, err } @@ -128,15 +129,28 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) return h.Next.ServeHTTP(w, r) } -func getClient(r *Rule) (*FCGIClient, error) { - // check if unix socket or TCP +// parseAddress returns the network and address of r. +// The first string is the network, "tcp" or "unix", implied from the scheme and address. +// The second string is r.Address, with scheme prefixes removed. +// The two returned strings can be used as parameters to the Dial() function. +func (r Rule) parseAddress() (string, string) { + // check if address has tcp scheme explicitly set + if strings.HasPrefix(r.Address, "tcp://") { + return "tcp", r.Address[len("tcp://"):] + } + // check if address has fastcgi scheme explicity set + if strings.HasPrefix(r.Address, "fastcgi://") { + return "tcp", r.Address[len("fastcgi://"):] + } + // check if unix socket if trim := strings.HasPrefix(r.Address, "unix"); strings.HasPrefix(r.Address, "/") || trim { if trim { - r.Address = r.Address[len("unix:"):] + return "unix", r.Address[len("unix:"):] } - return Dial("unix", r.Address) + return "unix", r.Address } - return Dial("tcp", r.Address) + // default case, a plain tcp address with no scheme + return "tcp", r.Address } func writeHeader(w http.ResponseWriter, r *http.Response) { diff --git a/middleware/fastcgi/fastcgi_test.go b/middleware/fastcgi/fastcgi_test.go new file mode 100644 index 00000000..69ee02f3 --- /dev/null +++ b/middleware/fastcgi/fastcgi_test.go @@ -0,0 +1,31 @@ +package fastcgi + +import ( + "testing" +) + +func TestRuleParseAddress(t *testing.T) { + + getClientTestTable := []struct { + rule *Rule + expectednetwork string + expectedaddress string + }{ + {&Rule{Address: "tcp://172.17.0.1:9000"}, "tcp", "172.17.0.1:9000"}, + {&Rule{Address: "fastcgi://localhost:9000"}, "tcp", "localhost:9000"}, + {&Rule{Address: "172.17.0.15"}, "tcp", "172.17.0.15"}, + {&Rule{Address: "/my/unix/socket"}, "unix", "/my/unix/socket"}, + {&Rule{Address: "unix:/second/unix/socket"}, "unix", "/second/unix/socket"}, + } + + for _, entry := range getClientTestTable { + if actualnetwork, _ := entry.rule.parseAddress(); actualnetwork != entry.expectednetwork { + t.Errorf("Unexpected network for address string %v. Got %v, expected %v", entry.rule.Address, actualnetwork, entry.expectednetwork) + } + if _, actualaddress := entry.rule.parseAddress(); actualaddress != entry.expectedaddress { + t.Errorf("Unexpected parsed address for address string %v. Got %v, expected %v", entry.rule.Address, actualaddress, entry.expectedaddress) + } + + } + +} From d8be787f398239f3c25b888e2b2e45d15a558011 Mon Sep 17 00:00:00 2001 From: MathiasB Date: Thu, 28 Jan 2016 15:26:33 +0100 Subject: [PATCH 04/15] FastCGI: IPv6 when parsing r.RemoteAddr --- middleware/fastcgi/fastcgi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/fastcgi/fastcgi.go b/middleware/fastcgi/fastcgi.go index 517505b6..153cae7f 100755 --- a/middleware/fastcgi/fastcgi.go +++ b/middleware/fastcgi/fastcgi.go @@ -182,7 +182,7 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, fpath string) (map[string] // Separate remote IP and port; more lenient than net.SplitHostPort var ip, port string - if idx := strings.Index(r.RemoteAddr, ":"); idx > -1 { + if idx := strings.LastIndex(r.RemoteAddr, ":"); idx > -1 { ip = r.RemoteAddr[:idx] port = r.RemoteAddr[idx+1:] } else { From ac197f1694cdacd8cfe8f5aeaddf49326d5cc21e Mon Sep 17 00:00:00 2001 From: MathiasB Date: Fri, 29 Jan 2016 11:46:06 +0100 Subject: [PATCH 05/15] FastCGI: some simple tests for buildEnv More tests are needed for the other environmental variables. These tests were specifically made for testing of IP addresses. --- middleware/fastcgi/fastcgi_test.go | 64 ++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/middleware/fastcgi/fastcgi_test.go b/middleware/fastcgi/fastcgi_test.go index 69ee02f3..0d391a23 100644 --- a/middleware/fastcgi/fastcgi_test.go +++ b/middleware/fastcgi/fastcgi_test.go @@ -1,6 +1,8 @@ package fastcgi import ( + "net/http" + "net/url" "testing" ) @@ -29,3 +31,65 @@ func TestRuleParseAddress(t *testing.T) { } } + +func BuildEnvSingle(r *http.Request, rule Rule, fpath string, envExpected map[string]string, t *testing.T) { + + h := Handler{} + + env, err := h.buildEnv(r, rule, fpath) + if err != nil { + t.Error("Unexpected error:", err.Error()) + } + + for k, v := range envExpected { + if env[k] != v { + t.Errorf("Unexpected %v. Got %v, expected %v", k, env[k], v) + } + } + +} + +func TestBuildEnv(t *testing.T) { + + rule := Rule{} + url, err := url.Parse("http://localhost:2015/fgci_test.php?test=blabla") + if err != nil { + t.Error("Unexpected error:", err.Error()) + } + + r := http.Request{ + Method: "GET", + URL: url, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Host: "localhost:2015", + RemoteAddr: "[2b02:1810:4f2d:9400:70ab:f822:be8a:9093]:51688", + RequestURI: "/fgci_test.php", + } + + fpath := "/fgci_test.php" + + var envExpected = map[string]string{ + "REMOTE_ADDR": "[2b02:1810:4f2d:9400:70ab:f822:be8a:9093]", + "REMOTE_PORT": "51688", + "SERVER_PROTOCOL": "HTTP/1.1", + "QUERY_STRING": "test=blabla", + "REQUEST_METHOD": "GET", + "HTTP_HOST": "localhost:2015", + } + + // 1. Test for full canonical IPv6 address + BuildEnvSingle(&r, rule, fpath, envExpected, t) + + // 2. Test for shorthand notation of IPv6 address + r.RemoteAddr = "[::1]:51688" + envExpected["REMOTE_ADDR"] = "[::1]" + BuildEnvSingle(&r, rule, fpath, envExpected, t) + + // 3. Test for IPv4 address + r.RemoteAddr = "192.168.0.10:51688" + envExpected["REMOTE_ADDR"] = "192.168.0.10" + BuildEnvSingle(&r, rule, fpath, envExpected, t) + +} From 8d057c861477a28b96552d892cea7c9f58d3baab Mon Sep 17 00:00:00 2001 From: Den Quixote Date: Sat, 30 Jan 2016 02:20:34 +0100 Subject: [PATCH 06/15] letsencrypt: properly retrieve hostname from request. --- caddy/letsencrypt/handler.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/caddy/letsencrypt/handler.go b/caddy/letsencrypt/handler.go index e147e00c..f1db03dd 100644 --- a/caddy/letsencrypt/handler.go +++ b/caddy/letsencrypt/handler.go @@ -23,9 +23,9 @@ func RequestCallback(w http.ResponseWriter, r *http.Request) bool { scheme = "https" } - hostname, _, err := net.SplitHostPort(r.URL.Host) + hostname, _, err := net.SplitHostPort(r.Host) if err != nil { - hostname = r.URL.Host + hostname = r.Host } upstream, err := url.Parse(scheme + "://" + hostname + ":" + AlternatePort) From c59fd1c76ed35b8f0f767c34285767ab1026be2c Mon Sep 17 00:00:00 2001 From: MathiasB Date: Mon, 1 Feb 2016 09:39:13 +0100 Subject: [PATCH 07/15] Defined test function in TestBuildEnv --- middleware/fastcgi/fastcgi_test.go | 40 +++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/middleware/fastcgi/fastcgi_test.go b/middleware/fastcgi/fastcgi_test.go index 0d391a23..1fc7446d 100644 --- a/middleware/fastcgi/fastcgi_test.go +++ b/middleware/fastcgi/fastcgi_test.go @@ -32,25 +32,25 @@ func TestRuleParseAddress(t *testing.T) { } -func BuildEnvSingle(r *http.Request, rule Rule, fpath string, envExpected map[string]string, t *testing.T) { - - h := Handler{} - - env, err := h.buildEnv(r, rule, fpath) - if err != nil { - t.Error("Unexpected error:", err.Error()) - } - - for k, v := range envExpected { - if env[k] != v { - t.Errorf("Unexpected %v. Got %v, expected %v", k, env[k], v) - } - } - -} - func TestBuildEnv(t *testing.T) { + buildEnvSingle := func(r *http.Request, rule Rule, fpath string, envExpected map[string]string, t *testing.T) { + + h := Handler{} + + env, err := h.buildEnv(r, rule, fpath) + if err != nil { + t.Error("Unexpected error:", err.Error()) + } + + for k, v := range envExpected { + if env[k] != v { + t.Errorf("Unexpected %v. Got %v, expected %v", k, env[k], v) + } + } + + } + rule := Rule{} url, err := url.Parse("http://localhost:2015/fgci_test.php?test=blabla") if err != nil { @@ -80,16 +80,16 @@ func TestBuildEnv(t *testing.T) { } // 1. Test for full canonical IPv6 address - BuildEnvSingle(&r, rule, fpath, envExpected, t) + buildEnvSingle(&r, rule, fpath, envExpected, t) // 2. Test for shorthand notation of IPv6 address r.RemoteAddr = "[::1]:51688" envExpected["REMOTE_ADDR"] = "[::1]" - BuildEnvSingle(&r, rule, fpath, envExpected, t) + buildEnvSingle(&r, rule, fpath, envExpected, t) // 3. Test for IPv4 address r.RemoteAddr = "192.168.0.10:51688" envExpected["REMOTE_ADDR"] = "192.168.0.10" - BuildEnvSingle(&r, rule, fpath, envExpected, t) + buildEnvSingle(&r, rule, fpath, envExpected, t) } From fde9bbeb3274b1ef92a769e1eb26c6f144bb81e0 Mon Sep 17 00:00:00 2001 From: MathiasB Date: Mon, 1 Feb 2016 11:17:16 +0100 Subject: [PATCH 08/15] basicauth: fixed 'go vet' printing function value --- middleware/basicauth/basicauth_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/basicauth/basicauth_test.go b/middleware/basicauth/basicauth_test.go index aad5ed39..631aaaed 100644 --- a/middleware/basicauth/basicauth_test.go +++ b/middleware/basicauth/basicauth_test.go @@ -139,7 +139,7 @@ md5:$apr1$l42y8rex$pOA2VJ0x/0TwaFeAF9nX61` if rule.Password, err = GetHtpasswdMatcher(filename, rule.Username, siteRoot); err != nil { t.Fatalf("GetHtpasswdMatcher(%q, %q): %v", htfh.Name(), rule.Username, err) } - t.Logf("%d. username=%q password=%v", i, rule.Username, rule.Password) + t.Logf("%d. username=%q", i, rule.Username) if !rule.Password(htpasswdPasswd) || rule.Password(htpasswdPasswd+"!") { t.Errorf("%d (%s) password does not match.", i, rule.Username) } From f4fcfa87937072f09cfe54ea7a7b59767bbffffc Mon Sep 17 00:00:00 2001 From: David Darrell Date: Thu, 4 Feb 2016 12:46:24 +0800 Subject: [PATCH 09/15] When the requested host is not found log the remote host. --- server/server.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index 4fe12b36..12513e81 100644 --- a/server/server.go +++ b/server/server.go @@ -337,6 +337,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } + // Get the remote host + remoteHost, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + remoteHost = r.RemoteAddr + } + if vh, ok := s.vhosts[host]; ok { status, _ := vh.stack.ServeHTTP(w, r) @@ -347,7 +353,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } else { w.WriteHeader(http.StatusNotFound) fmt.Fprintf(w, "No such host at %s", s.Server.Addr) - log.Printf("[INFO] %s - No such host at %s", host, s.Server.Addr) + log.Printf("[INFO] %s - No such host at %s (requested by %s)", host, s.Server.Addr, remoteHost) } } From 2acaf2fa6fa97b61ee0fa09e3eb8a5c782d6abf8 Mon Sep 17 00:00:00 2001 From: David Darrell Date: Thu, 4 Feb 2016 16:17:10 +0800 Subject: [PATCH 10/15] Move logic to split the port to only happen when the host is not found. --- server/server.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/server.go b/server/server.go index 12513e81..e325940d 100644 --- a/server/server.go +++ b/server/server.go @@ -337,12 +337,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } - // Get the remote host - remoteHost, _, err := net.SplitHostPort(r.RemoteAddr) - if err != nil { - remoteHost = r.RemoteAddr - } - if vh, ok := s.vhosts[host]; ok { status, _ := vh.stack.ServeHTTP(w, r) @@ -351,6 +345,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { DefaultErrorFunc(w, r, status) } } else { + // Get the remote host + remoteHost, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + remoteHost = r.RemoteAddr + } + w.WriteHeader(http.StatusNotFound) fmt.Fprintf(w, "No such host at %s", s.Server.Addr) log.Printf("[INFO] %s - No such host at %s (requested by %s)", host, s.Server.Addr, remoteHost) From fbdfc979ec992b84cd962a8c563fdf06f6a34388 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Thu, 4 Feb 2016 11:21:44 +0000 Subject: [PATCH 11/15] Markdown: enable definition lists --- middleware/markdown/process.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/markdown/process.go b/middleware/markdown/process.go index 0a732477..807ae47c 100644 --- a/middleware/markdown/process.go +++ b/middleware/markdown/process.go @@ -68,7 +68,7 @@ func (md Markdown) Process(c *Config, requestPath string, b []byte, ctx middlewa } // process markdown - extns := blackfriday.EXTENSION_TABLES | blackfriday.EXTENSION_FENCED_CODE | blackfriday.EXTENSION_STRIKETHROUGH + extns := blackfriday.EXTENSION_TABLES | blackfriday.EXTENSION_FENCED_CODE | blackfriday.EXTENSION_STRIKETHROUGH | blackfriday.EXTENSION_DEFINITION_LISTS markdown = blackfriday.Markdown(markdown, c.Renderer, extns) // set it as body for template From 86f36bdb610eadc521906a6e5072d4c789b602b8 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Thu, 4 Feb 2016 12:51:14 +0000 Subject: [PATCH 12/15] Add .Markdown directive This allows any template to use: {{.Markdown "filename"}} which will convert the markdown contents of filename to HTML and then include the HTML in the template. --- middleware/context.go | 16 ++++++++++++++++ middleware/context_test.go | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/middleware/context.go b/middleware/context.go index 58764202..4b61da29 100644 --- a/middleware/context.go +++ b/middleware/context.go @@ -9,6 +9,8 @@ import ( "strings" "text/template" "time" + + "github.com/russross/blackfriday" ) // This file contains the context and functions available for @@ -190,3 +192,17 @@ func (c Context) StripExt(path string) string { func (c Context) Replace(input, find, replacement string) string { return strings.Replace(input, find, replacement, -1) } + +// Markdown returns the HTML contents of the markdown contained in filename +// (relative to the site root). +func (c Context) Markdown(filename string) (string, error) { + body, err := c.Include(filename) + if err != nil { + return "", err + } + renderer := blackfriday.HtmlRenderer(0, "", "") + extns := blackfriday.EXTENSION_TABLES | blackfriday.EXTENSION_FENCED_CODE | blackfriday.EXTENSION_STRIKETHROUGH | blackfriday.EXTENSION_DEFINITION_LISTS + markdown := blackfriday.Markdown([]byte(body), renderer, extns) + + return string(markdown), nil +} diff --git a/middleware/context_test.go b/middleware/context_test.go index e60bd7f1..5fb883c6 100644 --- a/middleware/context_test.go +++ b/middleware/context_test.go @@ -92,6 +92,45 @@ func TestIncludeNotExisting(t *testing.T) { } } +func TestMarkdown(t *testing.T) { + context := getContextOrFail(t) + + inputFilename := "test_file" + absInFilePath := filepath.Join(fmt.Sprintf("%s", context.Root), inputFilename) + defer func() { + err := os.Remove(absInFilePath) + if err != nil && !os.IsNotExist(err) { + t.Fatalf("Failed to clean test file!") + } + }() + + tests := []struct { + fileContent string + expectedContent string + }{ + // Test 0 - test parsing of markdown + { + fileContent: "* str1\n* str2\n", + expectedContent: "
    \n
  • str1
  • \n
  • str2
  • \n
\n", + }, + } + + for i, test := range tests { + testPrefix := getTestPrefix(i) + + // WriteFile truncates the contentt + err := ioutil.WriteFile(absInFilePath, []byte(test.fileContent), os.ModePerm) + if err != nil { + t.Fatal(testPrefix+"Failed to create test file. Error was: %v", err) + } + + content, _ := context.Markdown(inputFilename) + if content != test.expectedContent { + t.Errorf(testPrefix+"Expected content [%s] but found [%s]. Input file was: %s", test.expectedContent, content, inputFilename) + } + } +} + func TestCookie(t *testing.T) { tests := []struct { From e72fc20c78bb90f0020a892d5e2c78b46e6f1a26 Mon Sep 17 00:00:00 2001 From: Craig Peterson Date: Wed, 11 Nov 2015 11:19:52 -0700 Subject: [PATCH 13/15] making directives externally registerable --- caddy/directives.go | 17 +++++++++++++++++ caddy/directives_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 caddy/directives_test.go diff --git a/caddy/directives.go b/caddy/directives.go index 39b54b7d..cf787535 100644 --- a/caddy/directives.go +++ b/caddy/directives.go @@ -68,6 +68,23 @@ var directiveOrder = []directive{ {"browse", setup.Browse}, } +// RegisterDirective adds the given directive to caddy's list of directives. +// Pass the name of a directive you want it to be placed after, +// otherwise it will be placed at the bottom of the stack. +func RegisterDirective(name string, setup SetupFunc, after string) { + dir := directive{name: name, setup: setup} + idx := len(directiveOrder) + for i := range directiveOrder { + if directiveOrder[i].name == after { + idx = i + 1 + break + } + } + newDirectives := append(directiveOrder[:idx], append([]directive{dir}, directiveOrder[idx:]...)...) + directiveOrder = newDirectives + parse.ValidDirectives[name] = struct{}{} +} + // directive ties together a directive name with its setup function. type directive struct { name string diff --git a/caddy/directives_test.go b/caddy/directives_test.go new file mode 100644 index 00000000..e37411f1 --- /dev/null +++ b/caddy/directives_test.go @@ -0,0 +1,31 @@ +package caddy + +import ( + "reflect" + "testing" +) + +func TestRegister(t *testing.T) { + directives := []directive{ + {"dummy", nil}, + {"dummy2", nil}, + } + directiveOrder = directives + RegisterDirective("foo", nil, "dummy") + if len(directiveOrder) != 3 { + t.Fatal("Should have 3 directives now") + } + getNames := func() (s []string) { + for _, d := range directiveOrder { + s = append(s, d.name) + } + return s + } + if !reflect.DeepEqual(getNames(), []string{"dummy", "foo", "dummy2"}) { + t.Fatalf("directive order doesn't match: %s", getNames()) + } + RegisterDirective("bar", nil, "ASDASD") + if !reflect.DeepEqual(getNames(), []string{"dummy", "foo", "dummy2", "bar"}) { + t.Fatalf("directive order doesn't match: %s", getNames()) + } +} From b1208d3fdfb972985c3356bd308833274f5bd373 Mon Sep 17 00:00:00 2001 From: Vadim Petrov Date: Wed, 10 Feb 2016 18:03:43 +0300 Subject: [PATCH 14/15] New function DialWithDialer to create FCGIClient with custom Dialer. --- middleware/fastcgi/fcgiclient.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/middleware/fastcgi/fcgiclient.go b/middleware/fastcgi/fcgiclient.go index 511a5219..82580137 100644 --- a/middleware/fastcgi/fcgiclient.go +++ b/middleware/fastcgi/fcgiclient.go @@ -169,12 +169,11 @@ type FCGIClient struct { reqID uint16 } -// Dial connects to the fcgi responder at the specified network address. +// DialWithDialer connects to the fcgi responder at the specified network address, using custom net.Dialer. // See func net.Dial for a description of the network and address parameters. -func Dial(network, address string) (fcgi *FCGIClient, err error) { +func DialWithDialer(network, address string, dialer net.Dialer) (fcgi *FCGIClient, err error) { var conn net.Conn - - conn, err = net.Dial(network, address) + conn, err = dialer.Dial(network, address) if err != nil { return } @@ -188,6 +187,12 @@ func Dial(network, address string) (fcgi *FCGIClient, err error) { return } +// Dial connects to the fcgi responder at the specified network address, using default net.Dialer. +// See func net.Dial for a description of the network and address parameters. +func Dial(network, address string) (fcgi *FCGIClient, err error) { + return DialWithDialer(network, address, net.Dialer{}) +} + // Close closes fcgi connnection func (c *FCGIClient) Close() { c.rwc.Close() From 7091a2090bf69ecee9d6ed7f1491b229da13d584 Mon Sep 17 00:00:00 2001 From: eiszfuchs Date: Wed, 10 Feb 2016 19:45:31 +0100 Subject: [PATCH 15/15] created http.Transport and tests for unix sockets --- middleware/proxy/proxy.go | 1 - middleware/proxy/proxy_test.go | 66 ++++++++++++++++++++++++++++++++ middleware/proxy/reverseproxy.go | 31 +++++++++++++-- middleware/proxy/upstream.go | 3 +- 4 files changed, 96 insertions(+), 5 deletions(-) diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go index 3efcf603..7be8af2a 100644 --- a/middleware/proxy/proxy.go +++ b/middleware/proxy/proxy.go @@ -63,7 +63,6 @@ var tryDuration = 60 * time.Second // ServeHTTP satisfies the middleware.Handler interface. func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - for _, upstream := range p.Upstreams { if middleware.Path(r.URL.Path).Matches(upstream.From()) && upstream.IsAllowedPath(r.URL.Path) { var replacer middleware.Replacer diff --git a/middleware/proxy/proxy_test.go b/middleware/proxy/proxy_test.go index 18e2034b..68b13567 100644 --- a/middleware/proxy/proxy_test.go +++ b/middleware/proxy/proxy_test.go @@ -3,6 +3,7 @@ package proxy import ( "bufio" "bytes" + "fmt" "io" "io/ioutil" "log" @@ -13,7 +14,9 @@ import ( "os" "strings" "testing" + "runtime" "time" + "path/filepath" "golang.org/x/net/websocket" ) @@ -160,6 +163,69 @@ func TestWebSocketReverseProxyFromWSClient(t *testing.T) { } } +func TestUnixSocketProxy(t *testing.T) { + if runtime.GOOS == "windows" { + return + } + + trialMsg := "Is it working?" + + var proxySuccess bool + + // This is our fake "application" we want to proxy to + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Request was proxied when this is called + proxySuccess = true + + fmt.Fprint(w, trialMsg) + })) + + // Get absolute path for unix: socket + socketPath, err := filepath.Abs("./test_socket") + if err != nil { + t.Fatalf("Unable to get absolute path: %v", err) + } + + // Change httptest.Server listener to listen to unix: socket + ln, err := net.Listen("unix", socketPath) + if err != nil { + t.Fatalf("Unable to listen: %v", err) + } + ts.Listener = ln + + ts.Start() + defer ts.Close() + + url := strings.Replace(ts.URL, "http://", "unix:", 1) + p := newWebSocketTestProxy(url) + + echoProxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + p.ServeHTTP(w, r) + })) + defer echoProxy.Close() + + res, err := http.Get(echoProxy.URL) + if err != nil { + t.Fatalf("Unable to GET: %v", err) + } + + greeting, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + t.Fatalf("Unable to GET: %v", err) + } + + actualMsg := fmt.Sprintf("%s", greeting) + + if !proxySuccess { + t.Errorf("Expected request to be proxied, but it wasn't") + } + + if actualMsg != trialMsg { + t.Errorf("Expected '%s' but got '%s' instead", trialMsg, actualMsg) + } +} + func newFakeUpstream(name string, insecure bool) *fakeUpstream { uri, _ := url.Parse(name) u := &fakeUpstream{ diff --git a/middleware/proxy/reverseproxy.go b/middleware/proxy/reverseproxy.go index 32ca2378..cb4ec875 100644 --- a/middleware/proxy/reverseproxy.go +++ b/middleware/proxy/reverseproxy.go @@ -59,6 +59,18 @@ func singleJoiningSlash(a, b string) string { return a + b } +// Though the relevant directive prefix is just "unix:", url.Parse +// will - assuming the regular URL scheme - add additional slashes +// as if "unix" was a request protocol. +// What we need is just the path, so if "unix:/var/run/www.socket" +// was the proxy directive, the parsed hostName would be +// "unix:///var/run/www.socket", hence the ambiguous trimming. +func socketDial(hostName string) func(network, addr string) (conn net.Conn, err error) { + return func(network, addr string) (conn net.Conn, err error) { + return net.Dial("unix", hostName[len("unix://"):]) + } +} + // NewSingleHostReverseProxy returns a new ReverseProxy that rewrites // URLs to the scheme, host, and base path provided in target. If the // target's path is "/base" and the incoming request was for "/dir", @@ -68,8 +80,15 @@ func singleJoiningSlash(a, b string) string { func NewSingleHostReverseProxy(target *url.URL, without string) *ReverseProxy { targetQuery := target.RawQuery director := func(req *http.Request) { - req.URL.Scheme = target.Scheme - req.URL.Host = target.Host + if target.Scheme == "unix" { + // to make Dial work with unix URL, + // scheme and host have to be faked + req.URL.Scheme = "http" + req.URL.Host = "socket" + } else { + req.URL.Scheme = target.Scheme + req.URL.Host = target.Host + } req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) if targetQuery == "" || req.URL.RawQuery == "" { req.URL.RawQuery = targetQuery + req.URL.RawQuery @@ -80,7 +99,13 @@ func NewSingleHostReverseProxy(target *url.URL, without string) *ReverseProxy { req.URL.Path = strings.TrimPrefix(req.URL.Path, without) } } - return &ReverseProxy{Director: director} + rp := &ReverseProxy{Director: director} + if target.Scheme == "unix" { + rp.Transport = &http.Transport{ + Dial: socketDial(target.String()), + } + } + return rp } func copyHeader(dst, src http.Header) { diff --git a/middleware/proxy/upstream.go b/middleware/proxy/upstream.go index 9d87c07a..faa11cd9 100644 --- a/middleware/proxy/upstream.go +++ b/middleware/proxy/upstream.go @@ -65,7 +65,8 @@ func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) { upstream.Hosts = make([]*UpstreamHost, len(to)) for i, host := range to { - if !strings.HasPrefix(host, "http") { + if !strings.HasPrefix(host, "http") && + !strings.HasPrefix(host, "unix:") { host = "http://" + host } uh := &UpstreamHost{