http.go
2.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package server
import (
"crypto/tls"
"fmt"
vhost "github.com/inconshreveable/go-vhost"
//"net"
"ngrok/conn"
"ngrok/log"
"strings"
"time"
)
const (
NotAuthorized = `HTTP/1.0 401 Not Authorized
WWW-Authenticate: Basic realm="ngrok"
Content-Length: 23
Authorization required
`
NotFound = `HTTP/1.0 404 Not Found
Content-Length: %d
Tunnel %s not found
`
BadRequest = `HTTP/1.0 400 Bad Request
Content-Length: 12
Bad Request
`
)
// Listens for new http(s) connections from the public internet
func startHttpListener(addr string, tlsCfg *tls.Config) (listener *conn.Listener) {
// bind/listen for incoming connections
var err error
if listener, err = conn.Listen(addr, "pub", tlsCfg); err != nil {
panic(err)
}
proto := "http"
if tlsCfg != nil {
proto = "https"
}
log.Info("Listening for public %s connections on %v", proto, listener.Addr.String())
go func() {
for conn := range listener.Conns {
go httpHandler(conn, proto)
}
}()
return
}
// Handles a new http connection from the public internet
func httpHandler(c conn.Conn, proto string) {
defer c.Close()
defer func() {
// recover from failures
if r := recover(); r != nil {
c.Warn("httpHandler failed with error %v", r)
}
}()
// Make sure we detect dead connections while we decide how to multiplex
c.SetDeadline(time.Now().Add(connReadTimeout))
// multiplex by extracting the Host header, the vhost library
vhostConn, err := vhost.HTTP(c)
if err != nil {
c.Warn("Failed to read valid %s request: %v", proto, err)
c.Write([]byte(BadRequest))
return
}
// read out the Host header and auth from the request
host := strings.ToLower(vhostConn.Host())
auth := vhostConn.Request.Header.Get("Authorization")
// done reading mux data, free up the request memory
vhostConn.Free()
// We need to read from the vhost conn now since it mucked around reading the stream
c = conn.Wrap(vhostConn, "pub")
// multiplex to find the right backend host
c.Debug("Found hostname %s in request", host)
tunnel := tunnelRegistry.Get(fmt.Sprintf("%s://%s", proto, host))
if tunnel == nil {
c.Info("No tunnel found for hostname %s", host)
c.Write([]byte(fmt.Sprintf(NotFound, len(host)+18, host)))
return
}
// If the client specified http auth and it doesn't match this request's auth
// then fail the request with 401 Not Authorized and request the client reissue the
// request with basic authdeny the request
if tunnel.req.HttpAuth != "" && auth != tunnel.req.HttpAuth {
c.Info("Authentication failed: %s", auth)
c.Write([]byte(NotAuthorized))
return
}
// dead connections will now be handled by tunnel heartbeating and the client
c.SetDeadline(time.Time{})
// let the tunnel handle the connection now
tunnel.HandlePublicConnection(c)
}