go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/testing/httpmitm/httpmitm.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package httpmitm 16 17 import ( 18 "bytes" 19 "io" 20 "net/http" 21 ) 22 23 // Origin is an enumeration used to annotate which type of data is being 24 // fed to the callback. 25 type Origin uint 26 27 // Log transport types. 28 const ( 29 Request Origin = iota 30 Response 31 ) 32 33 // String converts a Origin to a user-friendly string. 34 func (t Origin) String() string { 35 switch t { 36 case Request: 37 return "Request" 38 case Response: 39 return "Response" 40 default: 41 return "Unknown" 42 } 43 } 44 45 // Callback is a callback method that is invoked during HTTP communications to 46 // forward captured data. 47 type Callback func(Origin, []byte, error) 48 49 // Transport is an implementation of http.RoundTripper that logs outgoing 50 // requests and incoming responses. 51 type Transport struct { 52 // Underlying RoundTripper; uses http.DefaultTransport if nil. 53 http.RoundTripper 54 55 Callback Callback // Output callback. 56 } 57 58 // RoundTrip implements the http.RoundTripper interface. 59 func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { 60 var buf, bodyBuf bytes.Buffer 61 62 reqCopy := *req // Shallow copy of req, since we modify it. 63 64 // Since "Body" is an io.Reader, it can only be read once. However, we need 65 // to read it twice, once for the request capture and once for the actual 66 // request. 67 // 68 // To that end, we will tee Body into a Buffer when we perform the initial 69 // read, then replace "reqCopy.Body" with that Buffer for RoundTrip to read 70 // from. 71 origBody := reqCopy.Body 72 if origBody != nil { 73 reqCopy.Body = io.NopCloser(io.TeeReader(origBody, &bodyBuf)) 74 } 75 reqCopy.Write(&buf) 76 if origBody != nil { 77 if err := origBody.Close(); err != nil { 78 t.callback(Request, nil, err) 79 } 80 reqCopy.Body = io.NopCloser(&bodyBuf) 81 } 82 t.callback(Request, buf.Bytes(), nil) 83 84 rt := t.RoundTripper 85 if rt == nil { 86 rt = http.DefaultTransport 87 } 88 res, err := rt.RoundTrip(&reqCopy) 89 90 if err != nil { 91 t.callback(Response, nil, err) 92 return res, err 93 } 94 95 body := res.Body 96 if body != nil { 97 bodyBuf.Reset() 98 res.Body = io.NopCloser(io.TeeReader(body, &bodyBuf)) 99 defer body.Close() 100 } 101 102 buf.Reset() 103 res.Write(&buf) 104 t.callback(Response, buf.Bytes(), nil) 105 if body != nil { 106 res.Body = io.NopCloser(&bodyBuf) 107 } 108 return res, nil 109 } 110 111 func (t *Transport) callback(o Origin, data []byte, err error) { 112 if t.Callback != nil { 113 t.Callback(o, data, err) 114 } 115 }