Skip to content

Commit

Permalink
Merge pull request #467 from eiszfuchs/feature/proxy-socket
Browse files Browse the repository at this point in the history
proxy: Support unix sockets
  • Loading branch information
mholt committed Feb 10, 2016
2 parents 57ffe5a + 7091a20 commit f1ba7fa
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 5 deletions.
1 change: 0 additions & 1 deletion middleware/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 66 additions & 0 deletions middleware/proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package proxy
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
Expand All @@ -13,7 +14,9 @@ import (
"os"
"strings"
"testing"
"runtime"
"time"
"path/filepath"

"golang.org/x/net/websocket"
)
Expand Down Expand Up @@ -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{
Expand Down
31 changes: 28 additions & 3 deletions middleware/proxy/reverseproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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
Expand All @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion middleware/proxy/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down

0 comments on commit f1ba7fa

Please sign in to comment.