From 15672e7a094ce8b904879ee6b0c4c8cfa6037b6d Mon Sep 17 00:00:00 2001 From: linx Date: Wed, 8 Apr 2020 19:24:03 +0800 Subject: [PATCH 1/3] #591 add Raw* method in ClientResponse to get request and response string --- net/ghttp/ghttp_client_dump.go | 107 +++++++++++++++++++++++ net/ghttp/ghttp_unit_client_dump_test.go | 57 ++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 net/ghttp/ghttp_client_dump.go create mode 100644 net/ghttp/ghttp_unit_client_dump_test.go diff --git a/net/ghttp/ghttp_client_dump.go b/net/ghttp/ghttp_client_dump.go new file mode 100644 index 000000000..bf8ff5814 --- /dev/null +++ b/net/ghttp/ghttp_client_dump.go @@ -0,0 +1,107 @@ +// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package ghttp + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "net/http/httputil" + + "github.com/gogf/gf/text/gstr" + "github.com/gogf/gf/util/gconv" +) + +const dumpTextFormat = `+---------------------------------------------+ +| ghttp %s | ++---------------------------------------------+ +%s +%s +` + +func ifDumpBody(contentType string) bool { + // only dump body when contentType in. + if gstr.Contains(contentType, "application/json") || + gstr.Contains(contentType, "application/xml") || + gstr.Contains(contentType, "multipart/form-data") || + gstr.Contains(contentType, "application/x-www-form-urlencoded") || + gstr.Contains(contentType, "text/plain") { + return true + } + return false +} + +// getRequestBody return request body string. +// if error occurs, return empty string +func getRequestBody(req *http.Request) string { + contentType := req.Header.Get("Content-Type") + if !ifDumpBody(contentType) { + return "" + } + // must use this method for reading the body more than once + bodyReader, errGetBody := req.GetBody() + if errGetBody != nil { + return "" + } + bytesBody, errReadBody := ioutil.ReadAll(bodyReader) + if errReadBody != nil { + return "" + } + return gconv.UnsafeBytesToStr(bytesBody) +} + +// getResponseBody return response body string. +// if error occurs, return empty string +func getResponseBody(resp *http.Response) string { + contentType := resp.Header.Get("Content-Type") + if !ifDumpBody(contentType) { + return "" + } + bytesBody, errReadBody := ioutil.ReadAll(resp.Body) + if errReadBody != nil { + return "" + } + // for reading the body more than once + resp.Body = ioutil.NopCloser(bytes.NewBuffer(bytesBody)) + return gconv.UnsafeBytesToStr(bytesBody) +} + +// RawRequest dump request to raw string +func (r *ClientResponse) RawRequest() string { + // this can be nil + if r == nil || r.Request == nil { + return "" + } + // DumpRequestOut will write more header than DumpRequest, such as User-Agent. + // read body using getRequestBody method. + bs, err := httputil.DumpRequestOut(r.Request, false) + if err != nil { + return "" + } + return fmt.Sprintf(dumpTextFormat, "REQUEST ", gconv.UnsafeBytesToStr(bs), getRequestBody(r.Request)) +} + +// RawResponse dump response to raw string +func (r *ClientResponse) RawResponse() string { + // this can be nil + if r == nil || r.Response == nil { + return "" + } + // read body using getResponseBody method. + bs, err := httputil.DumpResponse(r.Response, false) + if err != nil { + return "" + } + + return fmt.Sprintf(dumpTextFormat, "RESPONSE", gconv.UnsafeBytesToStr(bs), getResponseBody(r.Response)) +} + +// Raw dump request and response string +func (r *ClientResponse) Raw() string { + return fmt.Sprintf("%s\n%s", r.RawRequest(), r.RawResponse()) +} diff --git a/net/ghttp/ghttp_unit_client_dump_test.go b/net/ghttp/ghttp_unit_client_dump_test.go new file mode 100644 index 000000000..d95699a26 --- /dev/null +++ b/net/ghttp/ghttp_unit_client_dump_test.go @@ -0,0 +1,57 @@ +// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package ghttp_test + +import ( + "fmt" + "testing" + "time" + + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/test/gtest" + "github.com/gogf/gf/text/gstr" +) + +func Test_Client_Request_13_Dump(t *testing.T) { + p, _ := ports.PopRand() + s := g.Server(p) + s.BindHandler("/hello", func(r *ghttp.Request) { + r.Response.WriteHeader(200) + r.Response.WriteJson(g.Map{"field": "test_for_response_body"}) + }) + s.BindHandler("/hello2", func(r *ghttp.Request) { + r.Response.WriteHeader(200) + r.Response.Writeln(g.Map{"field": "test_for_response_body"}) + }) + s.SetPort(p) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + url := fmt.Sprintf("http://127.0.0.1:%d", p) + client := ghttp.NewClient().SetPrefix(url).ContentJson() + r, err := client.Post("/hello", g.Map{"field": "test_for_request_body"}) + t.Assert(err, nil) + dumpedText := r.RawRequest() + t.Assert(gstr.Contains(dumpedText, "test_for_request_body"), true) + dumpedText2 := r.RawResponse() + t.Assert(gstr.Contains(dumpedText2, "test_for_response_body"), true) + + client2 := ghttp.NewClient().SetPrefix(url).ContentType("text/html") + r2, err := client2.Post("/hello2", g.Map{"field": "test_for_request_body"}) + t.Assert(err, nil) + dumpedText3 := r2.RawRequest() + t.Assert(gstr.Contains(dumpedText3, "test_for_request_body"), false) + dumpedText4 := r2.RawResponse() + t.Assert(gstr.Contains(dumpedText4, "test_for_request_body"), false) + + }) + +} From e64fd088b9ff39c3d8befd98d3d32e13ef7aa64f Mon Sep 17 00:00:00 2001 From: linx Date: Wed, 8 Apr 2020 20:11:06 +0800 Subject: [PATCH 2/3] fix when no response --- net/ghttp/ghttp_client_dump.go | 20 +++++++++++++++++--- net/ghttp/ghttp_client_request.go | 18 ++++++++++-------- net/ghttp/ghttp_client_response.go | 4 +++- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/net/ghttp/ghttp_client_dump.go b/net/ghttp/ghttp_client_dump.go index bf8ff5814..6c2d66c6d 100644 --- a/net/ghttp/ghttp_client_dump.go +++ b/net/ghttp/ghttp_client_dump.go @@ -71,19 +71,33 @@ func getResponseBody(resp *http.Response) string { return gconv.UnsafeBytesToStr(bytesBody) } +func (r *ClientResponse) getRequest() *http.Request { + if r.Response != nil && r.Request != nil { + return r.Request + } + if r.req != nil { + return r.req + } + return nil +} + // RawRequest dump request to raw string func (r *ClientResponse) RawRequest() string { // this can be nil - if r == nil || r.Request == nil { + if r == nil { + return "" + } + req := r.getRequest() + if req == nil { return "" } // DumpRequestOut will write more header than DumpRequest, such as User-Agent. // read body using getRequestBody method. - bs, err := httputil.DumpRequestOut(r.Request, false) + bs, err := httputil.DumpRequestOut(req, false) if err != nil { return "" } - return fmt.Sprintf(dumpTextFormat, "REQUEST ", gconv.UnsafeBytesToStr(bs), getRequestBody(r.Request)) + return fmt.Sprintf(dumpTextFormat, "REQUEST ", gconv.UnsafeBytesToStr(bs), getRequestBody(req)) } // RawResponse dump response to raw string diff --git a/net/ghttp/ghttp_client_request.go b/net/ghttp/ghttp_client_request.go index f3f793ff2..7f3aed3d3 100644 --- a/net/ghttp/ghttp_client_request.go +++ b/net/ghttp/ghttp_client_request.go @@ -11,10 +11,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/gogf/gf/encoding/gparser" - "github.com/gogf/gf/text/gregex" - "github.com/gogf/gf/text/gstr" - "github.com/gogf/gf/util/gconv" "io" "mime/multipart" "net/http" @@ -22,6 +18,11 @@ import ( "strings" "time" + "github.com/gogf/gf/encoding/gparser" + "github.com/gogf/gf/text/gregex" + "github.com/gogf/gf/text/gstr" + "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/os/gfile" ) @@ -213,20 +214,21 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien } // Sending request. var r *http.Response + resp = &ClientResponse{} for { if r, err = c.Do(req); err != nil { if c.retryCount > 0 { c.retryCount-- } else { - return nil, err + resp.req = req + return resp, err } } else { break } } - resp = &ClientResponse{ - Response: r, - } + resp.Response = r + // Auto saving cookie content. if c.browserMode { now := time.Now() diff --git a/net/ghttp/ghttp_client_response.go b/net/ghttp/ghttp_client_response.go index cb5ff4df2..3d4b9eada 100644 --- a/net/ghttp/ghttp_client_response.go +++ b/net/ghttp/ghttp_client_response.go @@ -7,14 +7,16 @@ package ghttp import ( - "github.com/gogf/gf/util/gconv" "io/ioutil" "net/http" "time" + + "github.com/gogf/gf/util/gconv" ) // ClientResponse is the struct for client request response. type ClientResponse struct { + req *http.Request *http.Response cookies map[string]string } From 9160bee1afa7a8f69607ee784c6142ab75d1203b Mon Sep 17 00:00:00 2001 From: linx Date: Sat, 11 Apr 2020 12:16:53 +0800 Subject: [PATCH 3/3] change comments --- net/ghttp/ghttp_client_dump.go | 31 +++++++++++++++--------------- net/ghttp/ghttp_client_request.go | 2 ++ net/ghttp/ghttp_client_response.go | 2 +- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/net/ghttp/ghttp_client_dump.go b/net/ghttp/ghttp_client_dump.go index 6c2d66c6d..c744e4815 100644 --- a/net/ghttp/ghttp_client_dump.go +++ b/net/ghttp/ghttp_client_dump.go @@ -17,6 +17,7 @@ import ( "github.com/gogf/gf/util/gconv" ) +// dumpTextFormat is the format of the dumped raw string const dumpTextFormat = `+---------------------------------------------+ | ghttp %s | +---------------------------------------------+ @@ -24,8 +25,9 @@ const dumpTextFormat = `+---------------------------------------------+ %s ` +// ifDumpBody determine whether to output body according to content-type func ifDumpBody(contentType string) bool { - // only dump body when contentType in. + // the body should not be output when the body is html or stream. if gstr.Contains(contentType, "application/json") || gstr.Contains(contentType, "application/xml") || gstr.Contains(contentType, "multipart/form-data") || @@ -36,14 +38,13 @@ func ifDumpBody(contentType string) bool { return false } -// getRequestBody return request body string. -// if error occurs, return empty string +// getRequestBody returns the raw text of the request body. func getRequestBody(req *http.Request) string { contentType := req.Header.Get("Content-Type") if !ifDumpBody(contentType) { return "" } - // must use this method for reading the body more than once + // so that the request body can be read again. bodyReader, errGetBody := req.GetBody() if errGetBody != nil { return "" @@ -55,8 +56,7 @@ func getRequestBody(req *http.Request) string { return gconv.UnsafeBytesToStr(bytesBody) } -// getResponseBody return response body string. -// if error occurs, return empty string +// getResponseBody returns the text of the response body. func getResponseBody(resp *http.Response) string { contentType := resp.Header.Get("Content-Type") if !ifDumpBody(contentType) { @@ -66,24 +66,27 @@ func getResponseBody(resp *http.Response) string { if errReadBody != nil { return "" } - // for reading the body more than once + // so that the response body can be read again. resp.Body = ioutil.NopCloser(bytes.NewBuffer(bytesBody)) return gconv.UnsafeBytesToStr(bytesBody) } +// getRequest returns the request related to the response. +// will return the copy of request when the request failed. func (r *ClientResponse) getRequest() *http.Request { if r.Response != nil && r.Request != nil { return r.Request } + // r.req is the copy of request when the http request failed. if r.req != nil { return r.req } return nil } -// RawRequest dump request to raw string +// RawRequest returns the raw text of the request. func (r *ClientResponse) RawRequest() string { - // this can be nil + // ClientResponse can be nil. if r == nil { return "" } @@ -91,8 +94,7 @@ func (r *ClientResponse) RawRequest() string { if req == nil { return "" } - // DumpRequestOut will write more header than DumpRequest, such as User-Agent. - // read body using getRequestBody method. + // DumpRequestOut writes more request headers than DumpRequest, such as User-Agent. bs, err := httputil.DumpRequestOut(req, false) if err != nil { return "" @@ -100,13 +102,12 @@ func (r *ClientResponse) RawRequest() string { return fmt.Sprintf(dumpTextFormat, "REQUEST ", gconv.UnsafeBytesToStr(bs), getRequestBody(req)) } -// RawResponse dump response to raw string +// RawResponse returns the raw text of the response. func (r *ClientResponse) RawResponse() string { - // this can be nil + // ClientResponse can be nil. if r == nil || r.Response == nil { return "" } - // read body using getResponseBody method. bs, err := httputil.DumpResponse(r.Response, false) if err != nil { return "" @@ -115,7 +116,7 @@ func (r *ClientResponse) RawResponse() string { return fmt.Sprintf(dumpTextFormat, "RESPONSE", gconv.UnsafeBytesToStr(bs), getResponseBody(r.Response)) } -// Raw dump request and response string +// Raw returns the raw text of the request and the response. func (r *ClientResponse) Raw() string { return fmt.Sprintf("%s\n%s", r.RawRequest(), r.RawResponse()) } diff --git a/net/ghttp/ghttp_client_request.go b/net/ghttp/ghttp_client_request.go index 7f3aed3d3..450321f9f 100644 --- a/net/ghttp/ghttp_client_request.go +++ b/net/ghttp/ghttp_client_request.go @@ -214,12 +214,14 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien } // Sending request. var r *http.Response + // do not return nil even if the request fails resp = &ClientResponse{} for { if r, err = c.Do(req); err != nil { if c.retryCount > 0 { c.retryCount-- } else { + // we need a copy of the request when the request fails. resp.req = req return resp, err } diff --git a/net/ghttp/ghttp_client_response.go b/net/ghttp/ghttp_client_response.go index 3d4b9eada..f13981dae 100644 --- a/net/ghttp/ghttp_client_response.go +++ b/net/ghttp/ghttp_client_response.go @@ -16,7 +16,7 @@ import ( // ClientResponse is the struct for client request response. type ClientResponse struct { - req *http.Request + req *http.Request // just a copy of the request when the request failed. *http.Response cookies map[string]string }