diff --git a/Gopkg.lock b/Gopkg.lock index 7e20fb308d..47524fbe11 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -800,6 +800,14 @@ revision = "1624edc4454b8682399def8740d46db5e4362ba4" version = "v1.1.5" +[[projects]] + branch = "master" + digest = "1:81abe06bccf5e35235e8d6387508a4f3417393216e62bd4c2e383bbebd4b4fbb" + name = "github.com/koding/websocketproxy" + packages = ["."] + pruneopts = "UT" + revision = "7ed82d81a28c9ba1ed6fd1157ce714760f214c98" + [[projects]] digest = "1:0a69a1c0db3591fcefb47f115b224592c8dfa4368b7ba9fae509d5e16cdc95c8" name = "github.com/konsorten/go-windows-terminal-sequences" @@ -1749,6 +1757,7 @@ "github.com/anacrolix/torrent", "github.com/anacrolix/torrent/bencode", "github.com/anacrolix/torrent/metainfo", + "github.com/anacrolix/torrent/storage", "github.com/aokoli/goutils", "github.com/aws/aws-sdk-go/aws", "github.com/aws/aws-sdk-go/aws/credentials", @@ -1794,6 +1803,7 @@ "github.com/jinzhu/gorm", "github.com/jinzhu/gorm/dialects/mysql", "github.com/json-iterator/go", + "github.com/koding/websocketproxy", "github.com/kr/pty", "github.com/mholt/caddy", "github.com/mholt/caddy/startupshutdown", diff --git a/vendor/github.com/koding/websocketproxy/.travis.yml b/vendor/github.com/koding/websocketproxy/.travis.yml new file mode 100644 index 0000000000..8670f006c2 --- /dev/null +++ b/vendor/github.com/koding/websocketproxy/.travis.yml @@ -0,0 +1,2 @@ +language: go +go: 1.8 diff --git a/vendor/github.com/koding/websocketproxy/LICENSE.md b/vendor/github.com/koding/websocketproxy/LICENSE.md new file mode 100644 index 0000000000..f0a2a7c7f2 --- /dev/null +++ b/vendor/github.com/koding/websocketproxy/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Koding, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/koding/websocketproxy/README.md b/vendor/github.com/koding/websocketproxy/README.md new file mode 100644 index 0000000000..526bb43f86 --- /dev/null +++ b/vendor/github.com/koding/websocketproxy/README.md @@ -0,0 +1,54 @@ +# WebsocketProxy [![GoDoc](https://godoc.org/github.com/koding/websocketproxy?status.svg)](https://godoc.org/github.com/koding/websocketproxy) [![Build Status](https://travis-ci.org/koding/websocketproxy.svg)](https://travis-ci.org/koding/websocketproxy) + +WebsocketProxy is an http.Handler interface build on top of +[gorilla/websocket](https://github.com/gorilla/websocket) that you can plug +into your existing Go webserver to provide WebSocket reverse proxy. + +## Install + +```bash +go get github.com/koding/websocketproxy +``` + +## Example + +Below is a simple server that proxies to the given backend URL + +```go +package main + +import ( + "flag" + "net/http" + "net/url" + + "github.com/koding/websocketproxy" +) + +var ( + flagBackend = flag.String("backend", "", "Backend URL for proxying") +) + +func main() { + u, err := url.Parse(*flagBackend) + if err != nil { + log.Fatalln(err) + } + + err = http.ListenAndServe(":80", websocketproxy.NewProxy(u)) + if err != nil { + log.Fatalln(err) + } +} +``` + +Save it as `proxy.go` and run as: + +```bash +go run proxy.go -backend ws://example.com:3000 +``` + +Now all incoming WebSocket requests coming to this server will be proxied to +`ws://example.com:3000` + + diff --git a/vendor/github.com/koding/websocketproxy/websocketproxy.go b/vendor/github.com/koding/websocketproxy/websocketproxy.go new file mode 100644 index 0000000000..63d39ba95e --- /dev/null +++ b/vendor/github.com/koding/websocketproxy/websocketproxy.go @@ -0,0 +1,233 @@ +// Package websocketproxy is a reverse proxy for WebSocket connections. +package websocketproxy + +import ( + "fmt" + "io" + "log" + "net" + "net/http" + "net/url" + "strings" + + "github.com/gorilla/websocket" +) + +var ( + // DefaultUpgrader specifies the parameters for upgrading an HTTP + // connection to a WebSocket connection. + DefaultUpgrader = &websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + } + + // DefaultDialer is a dialer with all fields set to the default zero values. + DefaultDialer = websocket.DefaultDialer +) + +// WebsocketProxy is an HTTP Handler that takes an incoming WebSocket +// connection and proxies it to another server. +type WebsocketProxy struct { + // Director, if non-nil, is a function that may copy additional request + // headers from the incoming WebSocket connection into the output headers + // which will be forwarded to another server. + Director func(incoming *http.Request, out http.Header) + + // Backend returns the backend URL which the proxy uses to reverse proxy + // the incoming WebSocket connection. Request is the initial incoming and + // unmodified request. + Backend func(*http.Request) *url.URL + + // Upgrader specifies the parameters for upgrading a incoming HTTP + // connection to a WebSocket connection. If nil, DefaultUpgrader is used. + Upgrader *websocket.Upgrader + + // Dialer contains options for connecting to the backend WebSocket server. + // If nil, DefaultDialer is used. + Dialer *websocket.Dialer +} + +// ProxyHandler returns a new http.Handler interface that reverse proxies the +// request to the given target. +func ProxyHandler(target *url.URL) http.Handler { return NewProxy(target) } + +// NewProxy returns a new Websocket reverse proxy that rewrites the +// URL's to the scheme, host and base path provider in target. +func NewProxy(target *url.URL) *WebsocketProxy { + backend := func(r *http.Request) *url.URL { + // Shallow copy + u := *target + u.Fragment = r.URL.Fragment + u.Path = r.URL.Path + u.RawQuery = r.URL.RawQuery + return &u + } + return &WebsocketProxy{Backend: backend} +} + +// ServeHTTP implements the http.Handler that proxies WebSocket connections. +func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if w.Backend == nil { + log.Println("websocketproxy: backend function is not defined") + http.Error(rw, "internal server error (code: 1)", http.StatusInternalServerError) + return + } + + backendURL := w.Backend(req) + if backendURL == nil { + log.Println("websocketproxy: backend URL is nil") + http.Error(rw, "internal server error (code: 2)", http.StatusInternalServerError) + return + } + + dialer := w.Dialer + if w.Dialer == nil { + dialer = DefaultDialer + } + + // Pass headers from the incoming request to the dialer to forward them to + // the final destinations. + requestHeader := http.Header{} + if origin := req.Header.Get("Origin"); origin != "" { + requestHeader.Add("Origin", origin) + } + for _, prot := range req.Header[http.CanonicalHeaderKey("Sec-WebSocket-Protocol")] { + requestHeader.Add("Sec-WebSocket-Protocol", prot) + } + for _, cookie := range req.Header[http.CanonicalHeaderKey("Cookie")] { + requestHeader.Add("Cookie", cookie) + } + if req.Host != "" { + requestHeader.Set("Host", req.Host) + } + + // Pass X-Forwarded-For headers too, code below is a part of + // httputil.ReverseProxy. See http://en.wikipedia.org/wiki/X-Forwarded-For + // for more information + // TODO: use RFC7239 http://tools.ietf.org/html/rfc7239 + if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { + // If we aren't the first proxy retain prior + // X-Forwarded-For information as a comma+space + // separated list and fold multiple headers into one. + if prior, ok := req.Header["X-Forwarded-For"]; ok { + clientIP = strings.Join(prior, ", ") + ", " + clientIP + } + requestHeader.Set("X-Forwarded-For", clientIP) + } + + // Set the originating protocol of the incoming HTTP request. The SSL might + // be terminated on our site and because we doing proxy adding this would + // be helpful for applications on the backend. + requestHeader.Set("X-Forwarded-Proto", "http") + if req.TLS != nil { + requestHeader.Set("X-Forwarded-Proto", "https") + } + + // Enable the director to copy any additional headers it desires for + // forwarding to the remote server. + if w.Director != nil { + w.Director(req, requestHeader) + } + + // Connect to the backend URL, also pass the headers we get from the requst + // together with the Forwarded headers we prepared above. + // TODO: support multiplexing on the same backend connection instead of + // opening a new TCP connection time for each request. This should be + // optional: + // http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-01 + connBackend, resp, err := dialer.Dial(backendURL.String(), requestHeader) + if err != nil { + log.Printf("websocketproxy: couldn't dial to remote backend url %s", err) + if resp != nil { + // If the WebSocket handshake fails, ErrBadHandshake is returned + // along with a non-nil *http.Response so that callers can handle + // redirects, authentication, etcetera. + if err := copyResponse(rw, resp); err != nil { + log.Printf("websocketproxy: couldn't write response after failed remote backend handshake: %s", err) + } + } else { + http.Error(rw, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) + } + return + } + defer connBackend.Close() + + upgrader := w.Upgrader + if w.Upgrader == nil { + upgrader = DefaultUpgrader + } + + // Only pass those headers to the upgrader. + upgradeHeader := http.Header{} + if hdr := resp.Header.Get("Sec-Websocket-Protocol"); hdr != "" { + upgradeHeader.Set("Sec-Websocket-Protocol", hdr) + } + if hdr := resp.Header.Get("Set-Cookie"); hdr != "" { + upgradeHeader.Set("Set-Cookie", hdr) + } + + // Now upgrade the existing incoming request to a WebSocket connection. + // Also pass the header that we gathered from the Dial handshake. + connPub, err := upgrader.Upgrade(rw, req, upgradeHeader) + if err != nil { + log.Printf("websocketproxy: couldn't upgrade %s", err) + return + } + defer connPub.Close() + + errClient := make(chan error, 1) + errBackend := make(chan error, 1) + replicateWebsocketConn := func(dst, src *websocket.Conn, errc chan error) { + for { + msgType, msg, err := src.ReadMessage() + if err != nil { + m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err)) + if e, ok := err.(*websocket.CloseError); ok { + if e.Code != websocket.CloseNoStatusReceived { + m = websocket.FormatCloseMessage(e.Code, e.Text) + } + } + errc <- err + dst.WriteMessage(websocket.CloseMessage, m) + break + } + err = dst.WriteMessage(msgType, msg) + if err != nil { + errc <- err + break + } + } + } + + go replicateWebsocketConn(connPub, connBackend, errClient) + go replicateWebsocketConn(connBackend, connPub, errBackend) + + var message string + select { + case err = <-errClient: + message = "websocketproxy: Error when copying from backend to client: %v" + case err = <-errBackend: + message = "websocketproxy: Error when copying from client to backend: %v" + + } + if e, ok := err.(*websocket.CloseError); !ok || e.Code == websocket.CloseAbnormalClosure { + log.Printf(message, err) + } +} + +func copyHeader(dst, src http.Header) { + for k, vv := range src { + for _, v := range vv { + dst.Add(k, v) + } + } +} + +func copyResponse(rw http.ResponseWriter, resp *http.Response) error { + copyHeader(rw.Header(), resp.Header) + rw.WriteHeader(resp.StatusCode) + defer resp.Body.Close() + + _, err := io.Copy(rw, resp.Body) + return err +}