From 153b7655f80cf99efa73d5e88ec5d2b9c1e97774 Mon Sep 17 00:00:00 2001 From: Daniel Albuquerque Date: Thu, 24 May 2018 14:20:28 +0100 Subject: [PATCH 1/2] add http toxic --- README.md | 6 +++ toxics/http_request_headers.go | 56 ++++++++++++++++++++ toxics/http_request_headers_test.go | 79 +++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 toxics/http_request_headers.go create mode 100644 toxics/http_request_headers_test.go diff --git a/README.md b/README.md index 137d048d..059f8c23 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,12 @@ Closes connection when transmitted data exceeded limit. - `bytes`: number of bytes it should transmit before connection is closed +#### http_request_headers + +Modifies http request headers. This toxic only has effect when the direction equals `upstream`. The most common use case would be modifying the Host header when using reverse proxies. + + - `headers`: a key value map with the headers to set. + ### HTTP API All communication with the Toxiproxy daemon from the client happens through the diff --git a/toxics/http_request_headers.go b/toxics/http_request_headers.go new file mode 100644 index 00000000..8cd1c832 --- /dev/null +++ b/toxics/http_request_headers.go @@ -0,0 +1,56 @@ +package toxics + +import ( + "bufio" + "bytes" + "io" + "net/http" + "strings" + + "github.com/Shopify/toxiproxy/stream" +) + +// HttpToxic modifies requests headers (upstream) for http requests. Not to be used with direction = downstream +type HttpToxic struct { + Headers map[string]string `json:"headers"` +} + +func (t *HttpToxic) modifyRequest(request *http.Request) { + // Add all headers to request. Host is derived from the url if we dont set it explicitly. + for k, v := range t.Headers { + if strings.EqualFold("Host", k) { + request.Host = v + } else { + request.Header.Set(k, v) + } + } +} + +func (t *HttpToxic) Pipe(stub *ToxicStub) { + buffer := bytes.NewBuffer(make([]byte, 0, 32*1024)) + writer := stream.NewChanWriter(stub.Output) + reader := stream.NewChanReader(stub.Input) + reader.SetInterrupt(stub.Interrupt) + for { + tee := io.TeeReader(reader, buffer) + req, err := http.ReadRequest(bufio.NewReader(tee)) + if err == stream.ErrInterrupted { + buffer.WriteTo(writer) + return + } else if err == io.EOF { + stub.Close() + return + } + if err != nil { + buffer.WriteTo(writer) + } else { + t.modifyRequest(req) + req.Write(writer) + } + buffer.Reset() + } +} + +func init() { + Register("http_request_headers", new(HttpToxic)) +} diff --git a/toxics/http_request_headers_test.go b/toxics/http_request_headers_test.go new file mode 100644 index 00000000..64aad3c0 --- /dev/null +++ b/toxics/http_request_headers_test.go @@ -0,0 +1,79 @@ +package toxics_test + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "strings" + "testing" + + "github.com/Shopify/toxiproxy/toxics" +) + +func echoRequestHeaders(w http.ResponseWriter, r *http.Request) { + headersMap := map[string]string{} + + for k, v := range r.Header { + // headers can contain multiple elements. for the purposes of this test we pick the 1st + headersMap[k] = v[0] + } + + mapAsJson, _ := json.Marshal(headersMap) + w.Write([]byte(mapAsJson)) +} + +func TestToxicAddsHttpHeaders(t *testing.T) { + http.HandleFunc("/", echoRequestHeaders) + + ln, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatal("Failed to create TCP server", err) + } + + go http.Serve(ln, nil) + defer ln.Close() + + proxy := NewTestProxy("test", ln.Addr().String()) + proxy.Start() + defer proxy.Stop() + + resp, err := http.Get("http://" + proxy.Listen) + if err != nil { + t.Error("Failed to connect to proxy", err) + } + + body, err := ioutil.ReadAll(resp.Body) + + AssertDoesNotContainHeader(t, string(body), "Foo", "Bar") + AssertDoesNotContainHeader(t, string(body), "Lorem", "Ipsum") + + proxy.Toxics.AddToxicJson(ToxicToJson(t, "", "http_request_headers", "upstream", &toxics.HttpToxic{Headers: map[string]string{"Foo": "Bar", "Lorem": "Ipsum"}})) + + resp, err = http.Get("http://" + proxy.Listen) + if err != nil { + t.Error("Failed to connect to proxy", err) + } + + body, err = ioutil.ReadAll(resp.Body) + + AssertContainsHeader(t, string(body), "Foo", "Bar") + AssertContainsHeader(t, string(body), "Lorem", "Ipsum") +} + +func AssertDoesNotContainHeader(t *testing.T, body string, headerKey string, headerValue string) { + containsHeader := strings.Contains(string(body), fmt.Sprintf(`"%s":"%s"`, headerKey, headerValue)) + + if containsHeader { + t.Errorf("Unexpected header found. Header=%s", headerKey) + } +} + +func AssertContainsHeader(t *testing.T, body string, headerKey string, headerValue string) { + containsHeader := strings.Contains(string(body), fmt.Sprintf(`"%s":"%s"`, headerKey, headerValue)) + + if !containsHeader { + t.Errorf("Expected header not found. Header=%s", headerKey) + } +} From 4444cf0b09caa2bc0ee1958308cb70c7cf22df9f Mon Sep 17 00:00:00 2001 From: worldtiki Date: Tue, 12 Jan 2021 15:54:21 +0000 Subject: [PATCH 2/2] Update toxics/http_request_headers_test.go --- toxics/http_request_headers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toxics/http_request_headers_test.go b/toxics/http_request_headers_test.go index 64aad3c0..87e3e2d3 100644 --- a/toxics/http_request_headers_test.go +++ b/toxics/http_request_headers_test.go @@ -24,7 +24,7 @@ func echoRequestHeaders(w http.ResponseWriter, r *http.Request) { w.Write([]byte(mapAsJson)) } -func TestToxicAddsHttpHeaders(t *testing.T) { +func TestToxicAddsHTTPHeaders(t *testing.T) { http.HandleFunc("/", echoRequestHeaders) ln, err := net.Listen("tcp", "localhost:0")