forked from timchenxiaoyu/goproxy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconn.go
148 lines (127 loc) · 2.9 KB
/
conn.go
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package goproxy
import (
"bytes"
"fmt"
"io"
"net"
"net/textproto"
"net/url"
"strings"
"github.com/golang/glog"
"bufio"
)
type conn struct {
rwc net.Conn
brc *bufio.Reader
server *Server
}
func (c *conn) serve() {
defer c.rwc.Close()
rawHttpRequestHeader, remote, isHttps, err := c.getTunnelInfo()
if err != nil {
glog.Error(err)
return
}
glog.Info("connecting to " + remote)
remoteConn, err := net.Dial("tcp", remote)
if err != nil {
glog.Error(err)
return
}
if isHttps {
glog.V(10).Infof("get https req remote %s \n",remote)
// if https, should sent 200 to client
_, err = c.rwc.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
if err != nil {
glog.Error(err)
return
}
} else {
// if not https, should sent the request header to remote
_, err = rawHttpRequestHeader.WriteTo(remoteConn)
if err != nil {
glog.Error(err)
return
}
}
// build bidirectional-streams
glog.Info("begin tunnel", c.rwc.RemoteAddr(), "<->", remote)
c.tunnel(remoteConn)
glog.Info("stop tunnel", c.rwc.RemoteAddr(), "<->", remote)
}
// getClientInfo parse client request header to get some information:
func (c *conn) getTunnelInfo() (rawReqHeader bytes.Buffer, host string, isHttps bool, err error) {
tp := textproto.NewReader(c.brc)
// First line: GET /index.html HTTP/1.0
var requestLine string
if requestLine, err = tp.ReadLine(); err != nil {
return
}
method, requestURI, _, ok := parseRequestLine(requestLine)
if !ok {
err = &BadRequestError{"malformed HTTP request"}
return
}
// https request
if method == "CONNECT" {
isHttps = true
requestURI = "http://" + requestURI
}
// get remote host
uriInfo, err := url.ParseRequestURI(requestURI)
if err != nil {
return
}
// Subsequent lines: Key: value.
mimeHeader, err := tp.ReadMIMEHeader()
if err != nil {
return
}
if uriInfo.Host == "" {
host = mimeHeader.Get("Host")
} else {
if strings.Index(uriInfo.Host, ":") == -1 {
host = uriInfo.Host + ":80"
} else {
host = uriInfo.Host
}
}
// rebuild http request header
rawReqHeader.WriteString(requestLine + "\r\n")
for k, vs := range mimeHeader {
for _, v := range vs {
rawReqHeader.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
}
}
rawReqHeader.WriteString("\r\n")
return
}
// tunnel http message between client and server
func (c *conn) tunnel(remoteConn net.Conn) {
go func() {
_, err := c.brc.WriteTo(remoteConn)
if err != nil {
glog.Warning(err)
}
remoteConn.Close()
}()
_, err := io.Copy(c.rwc, remoteConn)
if err != nil {
glog.Warning(err)
}
}
func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {
s1 := strings.Index(line, " ")
s2 := strings.Index(line[s1+1:], " ")
if s1 < 0 || s2 < 0 {
return
}
s2 += s1 + 1
return line[:s1], line[s1+1 : s2], line[s2+1:], true
}
type BadRequestError struct {
what string
}
func (b *BadRequestError) Error() string {
return b.what
}