-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.go
109 lines (88 loc) · 3.18 KB
/
main.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
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package main
import (
"bytes"
"flag"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"net/url"
"unicode/utf8"
)
var proxyTarget string
var listen string
var printResponse bool
func main() {
log.SetFlags(0)
flag.StringVar(&proxyTarget, "proxy", "https://app.fossa.com", "The host to which all requests should be proxied.")
flag.StringVar(&listen, "listen", ":3000", "The local address on which to listen.")
flag.BoolVar(&printResponse, "response", false, "Prints the response from the remote endpoint if enabled and if the response is text.")
flag.Parse()
proxyUrl, err := url.Parse(proxyTarget)
if err != nil {
bailf("parse proxy host url: %v", err)
}
log.Printf("✨ Serving on '%s', forwarding to '%s'", listen, proxyUrl)
proxy := httputil.NewSingleHostReverseProxy(proxyUrl)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Printf("🚀 Forward '%s' to '%s%s'", r.URL.Path, proxyTarget, r.URL.Path)
// Read the body into memory so that we can write this request multiple times.
// Typically bodies are streaming, and can only be read once.
body, err := ioutil.ReadAll(r.Body)
if err != nil {
bailf("read request body: %v", err)
}
// Replace the body in the request with a new reader that just reads from the body
// that was previously buffered above.
r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
// Write the request in wire protocol format to the logger.
// Wire protocol format means "this is the text that is actually sent over HTTP to the server".
r.Write(log.Writer())
// Add a blank line after the wire protocol output.
// This is because requests that don't have a body emit a blank line, but requests with a body
// just emit the body directly with no trailing line, which makes terminal output look messy.
// Using this gives us extra blank space at the end of requests that don't have a body,
// but that's okay because it's more consistent.
log.Print("\n\n")
// Update the request URL to match the proxy destination.
r.URL.Host = proxyUrl.Host
r.URL.Scheme = proxyUrl.Scheme
r.Host = proxyUrl.Host
// Replace the body in the request with a new reader that just reads from the body
// that was previously buffered at the start of this function, and perform the reverse proxy.
r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
if printResponse {
rr := responseRecorder{w}
proxy.ServeHTTP(rr, r)
} else {
proxy.ServeHTTP(w, r)
}
})
http.ListenAndServe(listen, nil)
}
type responseRecorder struct {
w http.ResponseWriter
}
func (rr responseRecorder) Header() http.Header {
return rr.w.Header()
}
func (rr responseRecorder) Write(content []byte) (int, error) {
if !utf8.Valid(content) {
log.Printf("<binary content>\n")
} else {
log.Printf("%s\n", content)
}
return rr.w.Write(content)
}
func (rr responseRecorder) WriteHeader(status int) {
log.Printf("✨ Response:\n%v\n", status)
rr.w.WriteHeader(status)
}
func bailf(msg string, err error) {
log.Fatalf("😵 error: "+msg, err)
}