├── .gitignore ├── common.go ├── go.mod ├── LICENSE ├── httpClient.go ├── main.go ├── bidirConnection.go ├── go.sum ├── README.md └── httpServer.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | // BufferSize is the size of the intermediate buffer for network packets 5 | BufferSize = 1024 6 | ) 7 | 8 | // Runner defines a basic interface with only a Run() function 9 | type Runner interface { 10 | Run() error 11 | } 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/trazfr/tcp-over-websocket 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/gorilla/websocket v1.5.3 7 | github.com/prometheus/client_golang v1.22.0 8 | ) 9 | 10 | require ( 11 | github.com/beorn7/perks v1.0.1 // indirect 12 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 13 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 14 | github.com/prometheus/client_model v0.6.2 // indirect 15 | github.com/prometheus/common v0.63.0 // indirect 16 | github.com/prometheus/procfs v0.16.1 // indirect 17 | golang.org/x/sys v0.32.0 // indirect 18 | google.golang.org/protobuf v1.36.6 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alexandre Blazart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /httpClient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "net/http" 7 | "net/url" 8 | "time" 9 | 10 | "github.com/gorilla/websocket" 11 | ) 12 | 13 | //////////////////////////////////////////////////////////////////////////////// 14 | // httpClient 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | // httpClient implements the Runner interface 18 | type httpClient struct { 19 | connectWS string 20 | listenTCP string 21 | } 22 | 23 | // NewHTTPClient creates a new TCP server which connects tunnels to an HTTP server 24 | func NewHTTPClient(listenTCP, connectWS string) Runner { 25 | return &httpClient{ 26 | connectWS: connectWS, 27 | listenTCP: listenTCP, 28 | } 29 | } 30 | 31 | func (h *httpClient) Run() error { 32 | tcpConnection, err := net.Listen("tcp", h.listenTCP) 33 | if err != nil { 34 | return err 35 | } 36 | defer tcpConnection.Close() 37 | 38 | log.Printf("Listening to %s", h.listenTCP) 39 | for { 40 | tcpConn, err := tcpConnection.Accept() 41 | if err != nil { 42 | log.Printf("Error: could not accept the connection: %s", err) 43 | continue 44 | } 45 | 46 | wsConn, err := h.createWsConnection(tcpConn.RemoteAddr().String()) 47 | if err != nil || wsConn == nil { 48 | log.Printf("%s - Error while dialing %s: %s", tcpConn.RemoteAddr(), h.connectWS, err) 49 | tcpConn.Close() 50 | continue 51 | } 52 | 53 | b := NewBidirConnection(tcpConn, wsConn, time.Second*10) 54 | go b.Run() 55 | } 56 | } 57 | 58 | func (h *httpClient) toWsURL(asString string) (string, error) { 59 | asURL, err := url.Parse(asString) 60 | if err != nil { 61 | return asString, err 62 | } 63 | 64 | switch asURL.Scheme { 65 | case "http": 66 | asURL.Scheme = "ws" 67 | case "https": 68 | asURL.Scheme = "wss" 69 | } 70 | return asURL.String(), nil 71 | } 72 | 73 | func (h *httpClient) createWsConnection(remoteAddr string) (wsConn *websocket.Conn, err error) { 74 | url := h.connectWS 75 | for { 76 | var wsURL string 77 | wsURL, err = h.toWsURL(url) 78 | if err != nil { 79 | return 80 | } 81 | log.Printf("%s - Connecting to %s", remoteAddr, wsURL) 82 | var httpResponse *http.Response 83 | wsConn, httpResponse, err = websocket.DefaultDialer.Dial(wsURL, nil) 84 | if httpResponse != nil { 85 | switch httpResponse.StatusCode { 86 | case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect, http.StatusPermanentRedirect: 87 | url = httpResponse.Header.Get("Location") 88 | log.Printf("%s - Redirect to %s", remoteAddr, url) 89 | continue 90 | } 91 | } 92 | return 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func globalUsage() { 11 | fmt.Fprintln(os.Stderr, os.Args[0], " [arguments]") 12 | fmt.Fprintln(os.Stderr) 13 | fmt.Fprintln(os.Stderr, "type:") 14 | fmt.Fprintln(os.Stderr, " - server: HTTP websocket server which connectes to a TCP server") 15 | fmt.Fprintln(os.Stderr, " - client: TCP server which connects to a websocket server") 16 | fmt.Fprintln(os.Stderr) 17 | fmt.Fprintln(os.Stderr, "Example: connect to ssh-server through an HTTP proxy running on ws-server") 18 | fmt.Fprintln(os.Stderr, "-", os.Args[0], "server -listen_ws :8080 -connect_tcp ssh-server.example.org:22") 19 | fmt.Fprintln(os.Stderr, "-", os.Args[0], "client -listen_tcp 127.0.0.1:1234 -connect_ws ws://ws-server.example.org:8080/") 20 | os.Exit(1) 21 | } 22 | 23 | func createHTTPServer(args []string) Runner { 24 | listen, connect := "", "" 25 | 26 | fs := flag.NewFlagSet("server", flag.ExitOnError) 27 | fs.StringVar(&listen, "listen_ws", "", 28 | "Local address to listen to\n"+ 29 | "Examples: \":8080\", \"127.0.0.1:1234\", \"[::1]:5000\"") 30 | fs.StringVar(&connect, "connect_tcp", "", 31 | "Remote address to connect to at each incoming websocket connection\n"+ 32 | "Examples: \"127.0.0.1:23\", \"ssh.example.com:22\", \"[::1]:143\"") 33 | fs.Parse(args) 34 | 35 | if listen == "" || connect == "" { 36 | fs.Usage() 37 | os.Exit(1) 38 | } 39 | 40 | return NewHTTPServer(listen, connect) 41 | } 42 | 43 | func createHTTPClient(args []string) Runner { 44 | listen, connect := "", "" 45 | 46 | fs := flag.NewFlagSet("client", flag.ExitOnError) 47 | fs.StringVar(&listen, "listen_tcp", "", 48 | "Local address to listen to\n"+ 49 | "Examples: \":8080\", \"127.0.0.1:1234\", \"[::1]:5000\")") 50 | fs.StringVar(&connect, "connect_ws", "", 51 | "Remote websocket to connect to at each incoming TCP connection \n"+ 52 | "Examples: \"ws://192.168.0.1:8080/\", \"wss://https.example.org/\", \"ws://[::1]/\"\n"+ 53 | "If the server is behind a reverse proxy, it may be something like: \"wss://https.example.org/fragment/\"") 54 | fs.Parse(args) 55 | 56 | if listen == "" || connect == "" { 57 | fs.Usage() 58 | os.Exit(1) 59 | } 60 | 61 | return NewHTTPClient(listen, connect) 62 | } 63 | 64 | func create() Runner { 65 | if len(os.Args) > 1 { 66 | switch os.Args[1] { 67 | case "server": 68 | return createHTTPServer(os.Args[2:]) 69 | case "client": 70 | return createHTTPClient(os.Args[2:]) 71 | } 72 | } 73 | globalUsage() 74 | return nil 75 | } 76 | 77 | func main() { 78 | err := create().Run() 79 | if err != nil { 80 | log.Fatalf("Failed to run: %s", err) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /bidirConnection.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net" 7 | "os" 8 | "time" 9 | 10 | "github.com/gorilla/websocket" 11 | ) 12 | 13 | //////////////////////////////////////////////////////////////////////////////// 14 | // bidirConnection 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | // bidirConnection implements the Runner interface 18 | type bidirConnection struct { 19 | tcpConn net.Conn 20 | wsConn *websocket.Conn 21 | tcpReadTimeout time.Duration 22 | } 23 | 24 | // NewBidirConnection to create an object to transfer data between the TCP socket and web connection in bidirectional way 25 | func NewBidirConnection(tcpConn net.Conn, wsConn *websocket.Conn, tcpReadTimeout time.Duration) Runner { 26 | return &bidirConnection{ 27 | tcpConn: tcpConn, 28 | wsConn: wsConn, 29 | tcpReadTimeout: tcpReadTimeout, 30 | } 31 | } 32 | 33 | func (b *bidirConnection) sendTCPToWS() { 34 | defer b.close() 35 | data := make([]byte, BufferSize) 36 | for { 37 | if b.tcpReadTimeout > 0 { 38 | b.tcpConn.SetReadDeadline(time.Now().Add(b.tcpReadTimeout)) 39 | } 40 | readSize, err := b.tcpConn.Read(data) 41 | if err != nil && !os.IsTimeout(err) { 42 | if err != io.EOF { 43 | log.Printf("TCPToWS - Error while reading from TCP: %s", err) 44 | } 45 | return 46 | } 47 | 48 | if err := b.wsConn.WriteMessage(websocket.BinaryMessage, data[:readSize]); err != nil { 49 | log.Printf("TCPToWS - Error while writing to WS: %s", err) 50 | return 51 | } 52 | } 53 | } 54 | 55 | func (b *bidirConnection) sendWSToTCP() { 56 | defer b.close() 57 | data := make([]byte, BufferSize) 58 | for { 59 | messageType, wsReader, err := b.wsConn.NextReader() 60 | if err != nil { 61 | log.Printf("WSToTCP - Error while reading from WS: %s", err) 62 | return 63 | } 64 | if messageType != websocket.BinaryMessage { 65 | log.Printf("WSToTCP - Got wrong message type from WS: %d", messageType) 66 | return 67 | } 68 | 69 | for { 70 | readSize, err := wsReader.Read(data) 71 | if err != nil { 72 | if err != io.EOF { 73 | log.Printf("WSToTCP - Error while reading from WS: %s", err) 74 | } 75 | break 76 | } 77 | 78 | if _, err := b.tcpConn.Write(data[:readSize]); err != nil { 79 | log.Printf("WSToTCP - Error while writing to TCP: %s", err) 80 | return 81 | } 82 | } 83 | } 84 | } 85 | 86 | func (b *bidirConnection) Run() error { 87 | go b.sendTCPToWS() 88 | b.sendWSToTCP() 89 | return nil 90 | } 91 | 92 | func (b *bidirConnection) close() { 93 | b.wsConn.WriteControl(websocket.CloseMessage, []byte{}, time.Now().Add(time.Second)) 94 | b.wsConn.Close() 95 | b.tcpConn.Close() 96 | } 97 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 4 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 8 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 9 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 10 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 11 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 12 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 13 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 14 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 15 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 16 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 17 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= 20 | github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= 21 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 22 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 23 | github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= 24 | github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= 25 | github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= 26 | github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 27 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 28 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 29 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 30 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 31 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 32 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 33 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 34 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tcp-over-websocket 2 | Simple TCP over Websocket tunneling 3 | 4 | ## Why TCP over Websocket 5 | Some crappy networks only allow HTTP(S). This small program allows you to tunnel 6 | anything in HTTP(S). 7 | 8 | Note that the URL path is always `/`, the server does not support SSL/TLS out of 9 | the box and there is one remote TCP address per instance. This is done on 10 | purpose as this program is intended to be run behind a reverse proxy. 11 | 12 | ## Install 13 | Having a working [Golang](https://golang.org/) environment: 14 | ``` 15 | go install github.com/trazfr/tcp-over-websocket@latest 16 | ``` 17 | 18 | ## Use 19 | 20 | ### Server side 21 | The server listens for Websocket inbound connections. Each time a client is 22 | connected, the server opens a new TCP connection to the remote service. 23 | 24 | **Example**: ssh to github 25 | ``` 26 | # on server example.org 27 | tcp-over-websocket server -listen_ws :8080 -remote_tcp github.com:22 28 | ``` 29 | 30 | ### Client side 31 | The client opens a TCP connection and creates a Websocket connection toward a 32 | tcp-over-websocket server each time a TCP connection is accepted. 33 | 34 | **Example**: open a tunnel to the server 35 | ``` 36 | # on the client, target the previous server on example.org 37 | tcp-over-websocket client -listen_tcp :1234 -remote_ws ws://example.org:8080/ 38 | ``` 39 | 40 | To use the tunnel, one may only run `ssh -p 1234 127.0.0.1` on the client. 41 | 42 | ### Note about reverse proxy usage 43 | tcp-over-websocket does not support SSL/TLS on server side. To be able to host 44 | a website and several tunnel servers at the same place, one may configure an 45 | HTTP reverse proxy. 46 | 47 | Let's assume we want to serve the previous server on SSL/TLS on the URL 48 | `wss://example.org/github-ssh/`. 49 | 50 | Using a similar configuration, one NGINX instance may be the reverse proxy for 51 | several tunnels for different services by changing the URL. 52 | 53 | #### NGINX as reverse proxy 54 | NGINX configuration extract: 55 | ``` 56 | http { 57 | map $http_upgrade $connection_upgrade { 58 | default upgrade; 59 | '' close; 60 | } 61 | 62 | server { 63 | listen 443 ssl default_server; 64 | listen [::]:443 default_server ipv6only=on ssl; 65 | server_name example.org; 66 | // put here the SSL/TLS configuration 67 | 68 | location = /github-ssh/ { 69 | limit_except GET { 70 | deny all; 71 | } 72 | // for "tcp-over-websocket server-listen_ws 127.0.0.1:8080 -remote_tcp github.com:22" 73 | proxy_pass http://127.0.0.1:8080/; 74 | proxy_http_version 1.1; 75 | proxy_set_header Upgrade $http_upgrade; 76 | proxy_set_header Connection $connection_upgrade; 77 | } 78 | // TODO configure different locations to different instances of tcp-over-websocket 79 | } 80 | } 81 | ``` 82 | 83 | To connect from the client, the command is like this: 84 | ``` 85 | tcp-over-websocket client -listen_tcp :1234 -remote_ws wss://example.org/github-ssh/ 86 | ``` 87 | 88 | ## Misc 89 | 90 | ### Prometheus service 91 | The tcp-over-websocket server hosts a Prometheus service. It only exposes some 92 | basic metrics. 93 | ``` 94 | curl http://example.org:8080/metrics 95 | ``` 96 | -------------------------------------------------------------------------------- /httpServer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "net/http" 7 | 8 | "github.com/gorilla/websocket" 9 | "github.com/prometheus/client_golang/prometheus" 10 | "github.com/prometheus/client_golang/prometheus/promauto" 11 | "github.com/prometheus/client_golang/prometheus/promhttp" 12 | ) 13 | 14 | const ( 15 | promSubSystem = "http_to_tcp" 16 | ) 17 | 18 | var ( 19 | promTotalConnections = promauto.NewCounterVec(prometheus.CounterOpts{ 20 | Subsystem: promSubSystem, 21 | Name: "connections", 22 | Help: "The total number of connections open", 23 | }, []string{"type"}) 24 | promActiveConnections = promauto.NewGauge(prometheus.GaugeOpts{ 25 | Subsystem: promSubSystem, 26 | Name: "active_connections", 27 | Help: "The total number of active connections", 28 | }) 29 | promErrors = promauto.NewCounterVec(prometheus.CounterOpts{ 30 | Subsystem: promSubSystem, 31 | Name: "error", 32 | Help: "The total number of errors", 33 | }, []string{"type"}) 34 | ) 35 | 36 | //////////////////////////////////////////////////////////////////////////////// 37 | // httpServer 38 | //////////////////////////////////////////////////////////////////////////////// 39 | 40 | // httpServer implements the Runner interface 41 | type httpServer struct { 42 | wsHandler wsHandler 43 | listenHTTP string 44 | httpMux *http.ServeMux 45 | } 46 | 47 | // NewHTTPServer creates a new websocket server which will wait for clients and open TCP connections 48 | func NewHTTPServer(listenHTTP, connectTCP string) Runner { 49 | result := &httpServer{ 50 | wsHandler: wsHandler{ 51 | connectTCP: connectTCP, 52 | wsUpgrader: websocket.Upgrader{ 53 | ReadBufferSize: BufferSize, 54 | WriteBufferSize: BufferSize, 55 | CheckOrigin: func(r *http.Request) bool { return true }, 56 | }, 57 | }, 58 | listenHTTP: listenHTTP, 59 | httpMux: &http.ServeMux{}, 60 | } 61 | 62 | result.httpMux.Handle("/", &result.wsHandler) 63 | result.httpMux.Handle("/metrics", promhttp.Handler()) 64 | return result 65 | } 66 | 67 | func (h *httpServer) Run() error { 68 | log.Printf("Listening to %s", h.listenHTTP) 69 | return http.ListenAndServe(h.listenHTTP, h.httpMux) 70 | } 71 | 72 | //////////////////////////////////////////////////////////////////////////////// 73 | // wsHandler 74 | //////////////////////////////////////////////////////////////////////////////// 75 | 76 | // wsHandler implements the http.Handler interface 77 | type wsHandler struct { 78 | connectTCP string 79 | wsUpgrader websocket.Upgrader 80 | } 81 | 82 | func (ws *wsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 83 | promActiveConnections.Inc() 84 | defer promActiveConnections.Dec() 85 | 86 | httpConn, err := ws.wsUpgrader.Upgrade(w, r, nil) 87 | if err != nil { 88 | promErrors.WithLabelValues("upgrade").Inc() 89 | log.Printf("%s - Error while upgrading: %s", r.RemoteAddr, err) 90 | return 91 | } 92 | promTotalConnections.WithLabelValues("http").Inc() 93 | log.Printf("%s - Client connected", r.RemoteAddr) 94 | 95 | tcpConn, err := net.Dial("tcp", ws.connectTCP) 96 | if err != nil { 97 | promErrors.WithLabelValues("dial_tcp").Inc() 98 | httpConn.Close() 99 | log.Printf("%s - Error while dialing %s: %s", r.RemoteAddr, ws.connectTCP, err) 100 | return 101 | } 102 | 103 | promTotalConnections.WithLabelValues("tcp").Inc() 104 | log.Printf("%s - Connected to TCP: %s", r.RemoteAddr, ws.connectTCP) 105 | NewBidirConnection(tcpConn, httpConn, 0).Run() 106 | log.Printf("%s - Client disconnected", r.RemoteAddr) 107 | } 108 | --------------------------------------------------------------------------------