feat: simplify httpc (#1748)

* feat: simplify httpc

* chore: fix lint errors

* chore: fix log url issue

* chore: fix log url issue

* refactor: handle resp & err in ResponseHandler

* chore: remove unnecessary var names in return clause
master v1.3.2
Kevin Wan 3 years ago committed by GitHub
parent e0fa8d820d
commit 78ea0769fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,5 +4,5 @@ import "net/http"
type ( type (
Interceptor func(r *http.Request) (*http.Request, ResponseHandler) Interceptor func(r *http.Request) (*http.Request, ResponseHandler)
ResponseHandler func(*http.Response) ResponseHandler func(resp *http.Response, err error)
) )

@ -10,15 +10,21 @@ import (
func LogInterceptor(r *http.Request) (*http.Request, ResponseHandler) { func LogInterceptor(r *http.Request) (*http.Request, ResponseHandler) {
start := timex.Now() start := timex.Now()
return r, func(resp *http.Response) { return r, func(resp *http.Response, err error) {
duration := timex.Since(start) duration := timex.Since(start)
if err != nil {
logger := logx.WithContext(r.Context()).WithDuration(duration)
logger.Errorf("[HTTP] %s %s - %v", r.Method, r.URL, err)
return
}
var tc propagation.TraceContext var tc propagation.TraceContext
ctx := tc.Extract(r.Context(), propagation.HeaderCarrier(resp.Header)) ctx := tc.Extract(r.Context(), propagation.HeaderCarrier(resp.Header))
logger := logx.WithContext(ctx).WithDuration(duration) logger := logx.WithContext(ctx).WithDuration(duration)
if isOkResponse(resp.StatusCode) { if isOkResponse(resp.StatusCode) {
logger.Infof("[HTTP] %d - %s %s/%s", resp.StatusCode, r.Method, r.Host, r.RequestURI) logger.Infof("[HTTP] %d - %s %s", resp.StatusCode, r.Method, r.URL)
} else { } else {
logger.Errorf("[HTTP] %d - %s %s/%s", resp.StatusCode, r.Method, r.Host, r.RequestURI) logger.Errorf("[HTTP] %d - %s %s", resp.StatusCode, r.Method, r.URL)
} }
} }
} }

@ -16,8 +16,8 @@ func TestLogInterceptor(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
req, handler := LogInterceptor(req) req, handler := LogInterceptor(req)
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
handler(resp, err)
assert.Nil(t, err) assert.Nil(t, err)
handler(resp)
assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, http.StatusOK, resp.StatusCode)
} }
@ -30,7 +30,22 @@ func TestLogInterceptorServerError(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
req, handler := LogInterceptor(req) req, handler := LogInterceptor(req)
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
handler(resp, err)
assert.Nil(t, err) assert.Nil(t, err)
handler(resp)
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
} }
func TestLogInterceptorServerClosed(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
svr.Close()
req, handler := LogInterceptor(req)
resp, err := http.DefaultClient.Do(req)
handler(resp, err)
assert.NotNil(t, err)
assert.Nil(t, resp)
}

@ -1,21 +1,44 @@
package httpc package httpc
import ( import (
"io"
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpc/internal"
) )
// Do sends an HTTP request to the service assocated with the given key. var interceptors = []internal.Interceptor{
func Do(key string, r *http.Request) (*http.Response, error) { internal.LogInterceptor,
return NewService(key).Do(r) }
// DoRequest sends an HTTP request and returns an HTTP response.
func DoRequest(r *http.Request) (*http.Response, error) {
return request(r, defaultClient{})
}
type (
client interface {
do(r *http.Request) (*http.Response, error)
}
defaultClient struct{}
)
func (c defaultClient) do(r *http.Request) (*http.Response, error) {
return http.DefaultClient.Do(r)
}
func request(r *http.Request, cli client) (*http.Response, error) {
var respHandlers []internal.ResponseHandler
for _, interceptor := range interceptors {
var h internal.ResponseHandler
r, h = interceptor(r)
respHandlers = append(respHandlers, h)
} }
// Get sends an HTTP GET request to the service assocated with the given key. resp, err := cli.do(r)
func Get(key, url string) (*http.Response, error) { for i := len(respHandlers) - 1; i >= 0; i-- {
return NewService(key).Get(url) respHandlers[i](resp, err)
} }
// Post sends an HTTP POST request to the service assocated with the given key. return resp, err
func Post(key, url, contentType string, body io.Reader) (*http.Response, error) {
return NewService(key).Post(url, contentType, body)
} }

@ -12,9 +12,9 @@ func TestDo(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
})) }))
defer svr.Close() defer svr.Close()
_, err := Get("foo", "tcp://bad request") req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.NotNil(t, err) assert.Nil(t, err)
resp, err := Get("foo", svr.URL) resp, err := DoRequest(req)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, http.StatusOK, resp.StatusCode)
} }
@ -22,9 +22,10 @@ func TestDo(t *testing.T) {
func TestDoNotFound(t *testing.T) { func TestDoNotFound(t *testing.T) {
svr := httptest.NewServer(http.NotFoundHandler()) svr := httptest.NewServer(http.NotFoundHandler())
defer svr.Close() defer svr.Close()
_, err := Post("foo", "tcp://bad request", "application/json", nil) req, err := http.NewRequest(http.MethodPost, svr.URL, nil)
assert.NotNil(t, err) assert.Nil(t, err)
resp, err := Post("foo", svr.URL, "application/json", nil) req.Header.Set("Content-Type", "application/json")
resp, err := DoRequest(req)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, http.StatusNotFound, resp.StatusCode) assert.Equal(t, http.StatusNotFound, resp.StatusCode)
} }
@ -34,7 +35,7 @@ func TestDoMoved(t *testing.T) {
defer svr.Close() defer svr.Close()
req, err := http.NewRequest(http.MethodGet, svr.URL, nil) req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err) assert.Nil(t, err)
_, err = Do("foo", req) _, err = DoRequest(req)
// too many redirects // too many redirects
assert.NotNil(t, err) assert.NotNil(t, err)
} }

@ -20,7 +20,9 @@ func TestParse(t *testing.T) {
w.Write([]byte(`{"name":"kevin","value":100}`)) w.Write([]byte(`{"name":"kevin","value":100}`))
})) }))
defer svr.Close() defer svr.Close()
resp, err := Get("foo", svr.URL) req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := DoRequest(req)
assert.Nil(t, err) assert.Nil(t, err)
assert.Nil(t, Parse(resp, &val)) assert.Nil(t, Parse(resp, &val))
assert.Equal(t, "bar", val.Foo) assert.Equal(t, "bar", val.Foo)
@ -37,7 +39,9 @@ func TestParseHeaderError(t *testing.T) {
w.Header().Set(contentType, applicationJson) w.Header().Set(contentType, applicationJson)
})) }))
defer svr.Close() defer svr.Close()
resp, err := Get("foo", svr.URL) req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := DoRequest(req)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, Parse(resp, &val)) assert.NotNil(t, Parse(resp, &val))
} }
@ -51,7 +55,9 @@ func TestParseNoBody(t *testing.T) {
w.Header().Set(contentType, applicationJson) w.Header().Set(contentType, applicationJson)
})) }))
defer svr.Close() defer svr.Close()
resp, err := Get("foo", svr.URL) req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := DoRequest(req)
assert.Nil(t, err) assert.Nil(t, err)
assert.Nil(t, Parse(resp, &val)) assert.Nil(t, Parse(resp, &val))
assert.Equal(t, "bar", val.Foo) assert.Equal(t, "bar", val.Foo)

@ -1,30 +1,19 @@
package httpc package httpc
import ( import (
"io"
"net/http" "net/http"
"github.com/zeromicro/go-zero/core/breaker" "github.com/zeromicro/go-zero/core/breaker"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/rest/httpc/internal"
) )
var interceptors = []internal.Interceptor{
internal.LogInterceptor,
}
type ( type (
// Option is used to customize the *http.Client. // Option is used to customize the *http.Client.
Option func(r *http.Request) *http.Request Option func(r *http.Request) *http.Request
// Service represents a remote HTTP service. // Service represents a remote HTTP service.
Service interface { Service interface {
// Do sends an HTTP request to the service. // DoRequest sends a HTTP request to the service.
Do(r *http.Request) (*http.Response, error) DoRequest(r *http.Request) (*http.Response, error)
// Get sends an HTTP GET request to the service.
Get(url string) (*http.Response, error)
// Post sends an HTTP POST request to the service.
Post(url, contentType string, body io.Reader) (*http.Response, error)
} }
namedService struct { namedService struct {
@ -50,50 +39,12 @@ func NewServiceWithClient(name string, cli *http.Client, opts ...Option) Service
} }
} }
// Do sends an HTTP request to the service. // DoRequest sends an HTTP request to the service.
func (s namedService) Do(r *http.Request) (resp *http.Response, err error) { func (s namedService) DoRequest(r *http.Request) (*http.Response, error) {
var respHandlers []internal.ResponseHandler return request(r, s)
for _, interceptor := range interceptors {
var h internal.ResponseHandler
r, h = interceptor(r)
respHandlers = append(respHandlers, h)
}
resp, err = s.doRequest(r)
if err != nil {
logx.Errorf("[HTTP] %s %s/%s - %v", r.Method, r.Host, r.RequestURI, err)
return
}
for i := len(respHandlers) - 1; i >= 0; i-- {
respHandlers[i](resp)
}
return
}
// Get sends an HTTP GET request to the service.
func (s namedService) Get(url string) (*http.Response, error) {
r, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
return s.Do(r)
}
// Post sends an HTTP POST request to the service.
func (s namedService) Post(url, ctype string, body io.Reader) (*http.Response, error) {
r, err := http.NewRequest(http.MethodPost, url, body)
if err != nil {
return nil, err
}
r.Header.Set(contentType, ctype)
return s.Do(r)
} }
func (s namedService) doRequest(r *http.Request) (resp *http.Response, err error) { func (s namedService) do(r *http.Request) (resp *http.Response, err error) {
for _, opt := range s.opts { for _, opt := range s.opts {
r = opt(r) r = opt(r)
} }

@ -14,7 +14,7 @@ func TestNamedService_Do(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, svr.URL, nil) req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err) assert.Nil(t, err)
service := NewService("foo") service := NewService("foo")
_, err = service.Do(req) _, err = service.DoRequest(req)
// too many redirects // too many redirects
assert.NotNil(t, err) assert.NotNil(t, err)
} }
@ -28,7 +28,9 @@ func TestNamedService_Get(t *testing.T) {
r.Header.Set("foo", "bar") r.Header.Set("foo", "bar")
return r return r
}) })
resp, err := service.Get(svr.URL) req, err := http.NewRequest(http.MethodGet, svr.URL, nil)
assert.Nil(t, err)
resp, err := service.DoRequest(req)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "bar", resp.Header.Get("foo")) assert.Equal(t, "bar", resp.Header.Get("foo"))
@ -38,9 +40,10 @@ func TestNamedService_Post(t *testing.T) {
svr := httptest.NewServer(http.NotFoundHandler()) svr := httptest.NewServer(http.NotFoundHandler())
defer svr.Close() defer svr.Close()
service := NewService("foo") service := NewService("foo")
_, err := service.Post("tcp://bad request", "application/json", nil) req, err := http.NewRequest(http.MethodPost, svr.URL, nil)
assert.NotNil(t, err) assert.Nil(t, err)
resp, err := service.Post(svr.URL, "application/json", nil) req.Header.Set("Content-Type", "application/json")
resp, err := service.DoRequest(req)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, http.StatusNotFound, resp.StatusCode) assert.Equal(t, http.StatusNotFound, resp.StatusCode)
} }

Loading…
Cancel
Save