// ModifyRequest logs the request, optionally including the body. // // The format logged is: // -------------------------------------------------------------------------------- // Request to http://www.google.com/path?querystring // -------------------------------------------------------------------------------- // GET /path?querystring HTTP/1.1 // Host: www.google.com // Connection: close // Other-Header: values // // request content // -------------------------------------------------------------------------------- func (l *Logger) ModifyRequest(req *http.Request) error { b := &bytes.Buffer{} fmt.Fprintln(b, "") fmt.Fprintln(b, strings.Repeat("-", 80)) fmt.Fprintf(b, "Request to %s\n", req.URL) fmt.Fprintln(b, strings.Repeat("-", 80)) mv := messageview.New() mv.SkipBody(l.headersOnly) if err := mv.SnapshotRequest(req); err != nil { return err } var opts []messageview.Option if l.decode { opts = append(opts, messageview.Decode()) } r, err := mv.Reader(opts...) if err != nil { return err } io.Copy(b, r) fmt.Fprintln(b, "") fmt.Fprintln(b, strings.Repeat("-", 80)) l.log(b.String()) return nil }
// RecordResponse logs an HTTP response, associating it with the previously-logged // HTTP request with the same ID. func (l *Logger) RecordResponse(id string, res *http.Response) error { hres := &Response{ HTTPVersion: res.Proto, Status: res.StatusCode, StatusText: http.StatusText(res.StatusCode), HeadersSize: -1, BodySize: res.ContentLength, Headers: headers(proxyutil.ResponseHeader(res).Map()), Cookies: cookies(res.Cookies()), } if res.StatusCode >= 300 && res.StatusCode < 400 { hres.RedirectURL = res.Header.Get("Location") } hres.Content = &Content{ Encoding: "base64", MimeType: res.Header.Get("Content-Type"), } if l.bodyLogging(res) { mv := messageview.New() if err := mv.SnapshotResponse(res); err != nil { return err } br, err := mv.BodyReader(messageview.Decode()) if err != nil { return err } body, err := ioutil.ReadAll(br) if err != nil { return err } hres.Content.Text = body hres.Content.Size = int64(len(body)) } l.mu.Lock() defer l.mu.Unlock() if e, ok := l.entries[id]; ok { e.Response = hres e.Time = time.Since(e.StartedDateTime).Nanoseconds() / 1000000 } return nil }
func postData(req *http.Request) (*PostData, error) { // If the request has no body (no Content-Length and Transfer-Encoding isn't // chunked), skip the post data. if req.ContentLength <= 0 && len(req.TransferEncoding) == 0 { return nil, nil } ct := req.Header.Get("Content-Type") mt, ps, err := mime.ParseMediaType(ct) if err != nil { log.Errorf("har: cannot parse Content-Type header %q: %v", ct, err) mt = ct } pd := &PostData{ MimeType: mt, Params: []Param{}, } mv := messageview.New() if err := mv.SnapshotRequest(req); err != nil { return nil, err } br, err := mv.BodyReader() if err != nil { return nil, err } switch mt { case "multipart/form-data": mpr := multipart.NewReader(br, ps["boundary"]) for { p, err := mpr.NextPart() if err == io.EOF { break } if err != nil { return nil, err } defer p.Close() body, err := ioutil.ReadAll(p) if err != nil { return nil, err } pd.Params = append(pd.Params, Param{ Name: p.FormName(), Filename: p.FileName(), ContentType: p.Header.Get("Content-Type"), Value: string(body), }) } case "application/x-www-form-urlencoded": body, err := ioutil.ReadAll(br) if err != nil { return nil, err } vs, err := url.ParseQuery(string(body)) if err != nil { return nil, err } for n, vs := range vs { for _, v := range vs { pd.Params = append(pd.Params, Param{ Name: n, Value: v, }) } } default: body, err := ioutil.ReadAll(br) if err != nil { return nil, err } pd.Text = string(body) } return pd, nil }