forked from chrislusf/teeproxy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
teeproxy.go
144 lines (133 loc) · 4.19 KB
/
teeproxy.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
package main
import (
"bytes"
"flag"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
"runtime"
"time"
)
// Console flags
var (
listen = flag.String("l", ":8888", "port to accept requests")
targetProduction = flag.String("a", "localhost:8080", "where production traffic goes. http://localhost:8080/production")
altTarget = flag.String("b", "localhost:8081", "where testing traffic goes. response are skipped. http://localhost:8081/test")
debug = flag.Bool("debug", false, "more logging, showing ignored output")
productionTimeout = flag.Int("a.timeout", 3, "timeout in seconds for production traffic")
alternateTimeout = flag.Int("b.timeout", 1, "timeout in seconds for alternate site traffic")
)
// handler contains the address of the main Target and the one for the Alternative target
type handler struct {
Target string
Alternative string
}
// ServeHTTP duplicates the incoming request (req) and does the request to the Target and the Alternate target discading the Alternate response
func (h handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
req1, req2 := DuplicateRequest(req)
go func() {
defer func() {
if r := recover(); r != nil && *debug {
fmt.Println("Recovered in f", r)
}
}()
// Open new TCP connection to the server
clientTcpConn, err := net.DialTimeout("tcp", h.Alternative, time.Duration(time.Duration(*alternateTimeout)*time.Second))
if err != nil {
if *debug {
fmt.Printf("Failed to connect to %s\n", h.Alternative)
}
return
}
clientHttpConn := httputil.NewClientConn(clientTcpConn, nil) // Start a new HTTP connection on it
defer clientHttpConn.Close() // Close the connection to the server
err = clientHttpConn.Write(req1) // Pass on the request
if err != nil {
if *debug {
fmt.Printf("Failed to send to %s: %v\n", h.Alternative, err)
}
return
}
_, err = clientHttpConn.Read(req1) // Read back the reply
if err != nil {
if *debug {
fmt.Printf("Failed to receive from %s: %v\n", h.Alternative, err)
}
return
}
}()
defer func() {
if r := recover(); r != nil && *debug {
fmt.Println("Recovered in f", r)
}
}()
// Open new TCP connection to the server
clientTcpConn, err := net.DialTimeout("tcp", h.Target, time.Duration(time.Duration(*productionTimeout)*time.Second))
if err != nil {
fmt.Printf("Failed to connect to %s\n", h.Target)
return
}
clientHttpConn := httputil.NewClientConn(clientTcpConn, nil) // Start a new HTTP connection on it
defer clientHttpConn.Close() // Close the connection to the server
err = clientHttpConn.Write(req2) // Pass on the request
if err != nil {
fmt.Printf("Failed to send to %s: %v\n", h.Target, err)
return
}
resp, err := clientHttpConn.Read(req2) // Read back the reply
if err != nil {
fmt.Printf("Failed to receive from %s: %v\n", h.Target, err)
return
}
resp.Write(w) // Write the reply to the original connection
}
func main() {
flag.Parse()
runtime.GOMAXPROCS(runtime.NumCPU())
local, err := net.Listen("tcp", *listen)
if err != nil {
fmt.Printf("Failed to listen to %s\n", *listen)
return
}
h := handler{
Target: *targetProduction,
Alternative: *altTarget,
}
http.Serve(local, h)
}
type nopCloser struct {
io.Reader
}
func (nopCloser) Close() error { return nil }
func DuplicateRequest(request *http.Request) (request1 *http.Request, request2 *http.Request) {
b1 := new(bytes.Buffer)
b2 := new(bytes.Buffer)
w := io.MultiWriter(b1, b2)
io.Copy(w, request.Body)
defer request.Body.Close()
request1 = &http.Request{
Method: request.Method,
URL: request.URL,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: request.Header,
Body: nopCloser{b1},
Host: request.Host,
ContentLength: request.ContentLength,
}
request2 = &http.Request{
Method: request.Method,
URL: request.URL,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: request.Header,
Body: nopCloser{b2},
Host: request.Host,
ContentLength: request.ContentLength,
}
return
}