瀏覽代碼

fake-http: Fake HTTP traffic

Lu Stadler 7 年之前
父節點
當前提交
54d32fb0c2
共有 1 個文件被更改,包括 335 次插入0 次删除
  1. 335 0
      go/fake-http/fake-http.go

+ 335 - 0
go/fake-http/fake-http.go

@ -0,0 +1,335 @@
1
package main
2
3
import (
4
	"bytes"
5
	"crypto/tls"
6
	"encoding/json"
7
	"flag"
8
	"fmt"
9
	"html/template"
10
	"io"
11
	"io/ioutil"
12
	"log"
13
	"net/http"
14
	"net/http/httputil"
15
	"net/url"
16
	"strings"
17
)
18
19
var flags struct {
20
	addr            string
21
	proxyURL        string
22
	proxyClientCert string
23
	proxyClientKey  string
24
}
25
26
func init() {
27
	flag.StringVar(&flags.addr, "addr", "localhost:8080", "Address to listen on")
28
	flag.StringVar(&flags.proxyURL, "proxy-url", "", "Proxy requests to this URL")
29
	flag.StringVar(&flags.proxyClientCert, "proxy-client-cert", "", "Client certificate to use when connecting to proxy")
30
	flag.StringVar(&flags.proxyClientKey, "proxy-client-key", "", "Client key to use when connecting to proxy")
31
}
32
33
var responses = []Response{
34
	JSONResponse("GET", "/api", `{"kind": "APIVersions", "versions": ["v1"]}`),
35
	JSONResponse("GET", "/apis", `{}`),
36
	JSONResponse("GET", "/api/v1", `{"kind": "APIResourceList", "resources": [{"name": "pods", "namespaced": true, "kind": "Pod", "verbs": ["get", "list"], "categories": ["all"]}]}`),
37
	JSONResponse("GET", "/api/v1/namespaces/default/pods", `{"kind": "PodList", "apiVersion": "v1", "items": [{"metadata": {"name": "oops-v1-214fbj25k"}, "status": {"phase": "Running", "conditions": [{"type": "Ready", "status": "True"}], "startTime": "2018-06-08T09:48:22Z"}}]}`),
38
}
39
40
func main() {
41
	flag.Parse()
42
43
	requestLog := make([]Request, 0)
44
45
	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
46
		var resp *http.Response
47
		if flags.proxyURL != "" {
48
			resp = respondWithProxy(flags.proxyURL, w, req)
49
		} else {
50
			resp = respondWithStub(responses, w, req)
51
		}
52
53
		userAgent := req.Header.Get("User-Agent")
54
		log.Printf("%s %s - %d (%s, %q)", req.Method, req.URL, resp.StatusCode, req.RemoteAddr, userAgent)
55
56
		buf := new(bytes.Buffer)
57
		out, err := httputil.DumpRequest(req, true)
58
		if err != nil {
59
			log.Printf("Error: Dumping request: %s", err)
60
			return
61
		}
62
		buf.Write(out)
63
64
		if resp.Header.Get("Content-Type") == "application/json" {
65
			pretty, err := prettyfyJSON(resp.Body)
66
			if err != nil {
67
				log.Printf("Error: Prettyfying JSON: %s", err)
68
			} else {
69
				resp.Body = ioutil.NopCloser(bytes.NewReader(pretty))
70
			}
71
		}
72
		respOut, err := httputil.DumpResponse(resp, true)
73
		if err != nil {
74
			log.Printf("Error: Dumping response: %s", err)
75
			return
76
		}
77
		buf.Write([]byte("\n"))
78
		buf.Write(respOut)
79
		buf.Write([]byte("\n"))
80
81
		requestLog = append(requestLog, buf.Bytes())
82
	})
83
84
	http.HandleFunc("/_log", func(w http.ResponseWriter, req *http.Request) {
85
		for i := len(requestLog) - 1; i >= 0; i-- {
86
			w.Write([]byte("------------------------------------------------------\n"))
87
			w.Write(requestLog[i])
88
		}
89
	})
90
91
	http.HandleFunc("/_stub", func(w http.ResponseWriter, req *http.Request) {
92
		switch req.Method {
93
		case "GET":
94
			err := stubTmpl.Execute(w, nil)
95
			if err != nil {
96
				log.Printf("Error: Rendering stub template: %s", err)
97
				return
98
			}
99
		case "POST":
100
			err := req.ParseForm()
101
			if err != nil {
102
				log.Printf("Error: Parsing form: %s", err)
103
				return
104
			}
105
			responses = append(responses, readResponse(req.Form))
106
		default:
107
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
108
		}
109
	})
110
111
	http.HandleFunc("/_stubs", func(w http.ResponseWriter, req *http.Request) {
112
		w.Header().Set("Content-Type", "text/html")
113
		fmt.Fprintf(w, "<!doctype html><html><head><style>pre{max-width:100vw;padding:0.5em;background-color:#eee;white-space:pre-wrap;}</style></head><body><ul>\n")
114
		for _, resp := range responses {
115
			fmt.Fprintf(w, "<li><pre>%s</pre></li>\n", resp.String())
116
		}
117
		fmt.Fprintf(w, "\n</ul></body></html>")
118
	})
119
120
	http.HandleFunc("/_help", func(w http.ResponseWriter, req *http.Request) {
121
		urls := []string{
122
			"/_log",
123
			"/_stub",
124
			"/_stubs",
125
			"/_help",
126
		}
127
		fmt.Fprint(w, `<!doctype html>
128
<html>
129
	<head>
130
		<title>/_help</title>
131
	</head>
132
	<body>
133
		<ul>`)
134
		for _, url := range urls {
135
			fmt.Fprintf(w, "<li><pre><a href=\"%s\">%s</a></pre></li>", url, url)
136
		}
137
		fmt.Fprint(w, `
138
		</ul>
139
	</body>
140
</html`)
141
	})
142
143
	log.Printf("Listening on http://%s", flags.addr)
144
	log.Printf("See http://%s/_help", flags.addr)
145
	log.Fatal(http.ListenAndServe(flags.addr, nil))
146
}
147
148
func matchResponse(req *http.Request, responses []Response) *Response {
149
	for _, resp := range responses {
150
		if req.Method == resp.Method && req.URL.Path == resp.Path {
151
			return &resp
152
		}
153
	}
154
	return nil
155
}
156
157
func respondWithStub(responses []Response, w http.ResponseWriter, req *http.Request) *http.Response {
158
	resp := matchResponse(req, responses)
159
	if resp == nil {
160
		resp = &Response{Status: 404, Body: "Not found"}
161
	}
162
163
	for _, header := range resp.Headers {
164
		w.Header().Set(header.Name, header.Value)
165
	}
166
	w.WriteHeader(resp.Status)
167
	w.Write([]byte(resp.Body))
168
169
	return resp.AsHTTP()
170
}
171
172
func respondWithProxy(proxyURL string, w http.ResponseWriter, req *http.Request) *http.Response {
173
	/*proxyReq, err := http.NewRequest(req.Method, proxyURL+req.URL.Path, nil)
174
	if err != nil {
175
		log.Printf("Error: Creating proxy request: %s", err)
176
		return nil
177
	}
178
	for name, vals := range req.Header {
179
		proxyReq.Header[name] = vals
180
	}*/
181
182
	proxyTransport := &http.Transport{
183
		TLSClientConfig: &tls.Config{
184
			GetClientCertificate: func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
185
				log.Printf("TLS: client cert requested: %#v", info)
186
				if flags.proxyClientCert != "" && flags.proxyClientKey != "" {
187
					log.Printf("TLS: Loading client cert and key for proxy: %s, %s", flags.proxyClientCert, flags.proxyClientKey)
188
					cert, err := tls.LoadX509KeyPair(flags.proxyClientCert, flags.proxyClientKey)
189
					if err != nil {
190
						return nil, err
191
					}
192
					return &cert, nil
193
				}
194
				log.Println("TLS: No client cert configured, returning empty cert")
195
				return &tls.Certificate{}, nil
196
			},
197
			InsecureSkipVerify: true,
198
		},
199
	}
200
	proxyClient := &http.Client{Transport: proxyTransport}
201
202
	u, err := url.Parse(proxyURL)
203
	if err != nil {
204
		log.Printf("Error: Parsing proxy url: %s", err)
205
		return nil
206
	}
207
208
	req.URL.Scheme = u.Scheme
209
	req.URL.Host = u.Host
210
	req.RequestURI = ""
211
	resp, err := proxyClient.Do(req)
212
	if err != nil {
213
		log.Printf("Error: Proxying %s: %s", req.URL.Path, err)
214
		return nil
215
	}
216
	defer resp.Body.Close()
217
218
	for name, vals := range resp.Header {
219
		w.Header()[name] = vals
220
	}
221
	w.WriteHeader(resp.StatusCode)
222
223
	buf := new(bytes.Buffer)
224
	io.Copy(buf, resp.Body)
225
	resp.Body = ioutil.NopCloser(bytes.NewReader(buf.Bytes()))
226
	io.Copy(w, buf)
227
228
	return resp
229
}
230
231
func prettyfyJSON(r io.Reader) ([]byte, error) {
232
	dec := json.NewDecoder(r)
233
	var val interface{}
234
	err := dec.Decode(&val)
235
	if err != nil {
236
		return nil, err
237
	}
238
239
	return json.MarshalIndent(val, "", "    ")
240
}
241
242
// Request is a stored serialized HTTP request.
243
type Request []byte
244
245
// Response is a mocked HTTP response.
246
type Response struct {
247
	Method string
248
	Path   string
249
250
	Status  int
251
	Headers []Header
252
	Body    string
253
}
254
255
func (resp Response) String() string {
256
	buf := new(bytes.Buffer)
257
	fmt.Fprintf(buf, "%s %s\r\n", resp.Method, resp.Path)
258
	for _, header := range resp.Headers {
259
		fmt.Fprintf(buf, "%s: %s\r\n", header.Name, header.Value)
260
	}
261
	fmt.Fprintf(buf, "\r\n%s", resp.Body)
262
	return buf.String()
263
}
264
265
// AsHTTP returns a http.Response representation.
266
func (resp Response) AsHTTP() *http.Response {
267
	headers := make(map[string][]string)
268
	for _, header := range resp.Headers {
269
		h, ok := headers[header.Name]
270
		if !ok {
271
			h = []string{}
272
		}
273
		h = append(h, header.Value)
274
		headers[header.Name] = h
275
	}
276
	return &http.Response{
277
		ProtoMajor: 1,
278
		ProtoMinor: 1,
279
280
		StatusCode: resp.Status,
281
		Header:     headers,
282
		Body:       ioutil.NopCloser(strings.NewReader(resp.Body)),
283
	}
284
}
285
286
// Header is a single-valued HTTP header name and value
287
type Header struct {
288
	Name  string
289
	Value string
290
}
291
292
// JSONResponse creates a Response with "Content-Type: application/json".
293
func JSONResponse(method, path, body string) Response {
294
	return Response{
295
		Method:  method,
296
		Path:    path,
297
		Status:  200,
298
		Headers: []Header{Header{Name: "Content-Type", Value: "application/json"}},
299
		Body:    body,
300
	}
301
}
302
303
func readResponse(form url.Values) Response {
304
	r := Response{}
305
	r.Method = form.Get("method")
306
	r.Path = form.Get("path")
307
	r.Status = 200
308
	headers := make([]Header, 0)
309
	for i, name := range form["header"] {
310
		headers = append(headers, Header{Name: name, Value: form["value"][i]})
311
	}
312
	r.Body = form.Get("body")
313
	return r
314
}
315
316
var stubTmpl = template.Must(template.New("").Parse(`<!doctype html>
317
<html>
318
	<head>
319
	</head>
320
321
	<body>
322
		<form method="POST" action="/_stub">
323
			<input type="text" name="method" placeholder="GET" />
324
			<input type="text" name="path" placeholder="/request/path?query" />
325
			<ul>
326
				<li>
327
					<input type="text" name="header" placeholder="Content-Type" />
328
					<input type="text" name="value" placeholder="application/json" />
329
				</li>
330
			</ul>
331
			<textarea name="body" placeholder="{}"></textarea>
332
			<input type="submit" />
333
		</form>
334
	</body>
335
</html>`))