Skip to content

Commit 8e075fb

Browse files
authored
contrib/sdk/httpclient: add custom response handler support, fixe #3539 (#3540)
1 parent 3b1d1d3 commit 8e075fb

File tree

5 files changed

+195
-42
lines changed

5 files changed

+195
-42
lines changed

cmd/gf/internal/consts/consts_gen_ctrl_template_sdk.go

-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contrib/sdk/httpclient/httpclient.go

+17-36
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ package httpclient
99

1010
import (
1111
"context"
12-
"encoding/json"
12+
"fmt"
1313
"net/http"
1414

1515
"github.com/gogf/gf/v2/encoding/gurl"
16-
"github.com/gogf/gf/v2/errors/gcode"
1716
"github.com/gogf/gf/v2/errors/gerror"
1817
"github.com/gogf/gf/v2/net/gclient"
1918
"github.com/gogf/gf/v2/net/ghttp"
@@ -24,48 +23,29 @@ import (
2423
"github.com/gogf/gf/v2/util/gtag"
2524
)
2625

27-
// Client is an http client for SDK.
26+
// Client is a http client for SDK.
2827
type Client struct {
2928
*gclient.Client
30-
config Config
29+
Handler
3130
}
3231

33-
// New creates and returns an http client for SDK.
32+
// New creates and returns a http client for SDK.
3433
func New(config Config) *Client {
3534
client := config.Client
3635
if client == nil {
3736
client = gclient.New()
3837
}
39-
return &Client{
40-
Client: client,
41-
config: config,
42-
}
43-
}
44-
45-
func (c *Client) handleResponse(ctx context.Context, res *gclient.Response, out interface{}) error {
46-
if c.config.RawDump {
47-
c.config.Logger.Debugf(ctx, "raw request&response:\n%s", res.Raw())
48-
}
49-
50-
var (
51-
responseBytes = res.ReadAll()
52-
result = ghttp.DefaultHandlerResponse{
53-
Data: out,
54-
}
55-
)
56-
if !json.Valid(responseBytes) {
57-
return gerror.Newf(`invalid response content: %s`, responseBytes)
38+
handler := config.Handler
39+
if handler == nil {
40+
handler = NewDefaultHandler(config.Logger, config.RawDump)
5841
}
59-
if err := json.Unmarshal(responseBytes, &result); err != nil {
60-
return gerror.Wrapf(err, `json.Unmarshal failed with content:%s`, responseBytes)
42+
if !gstr.HasPrefix(config.URL, "http") {
43+
config.URL = fmt.Sprintf("http://%s", config.URL)
6144
}
62-
if result.Code != gcode.CodeOK.Code() {
63-
return gerror.NewCode(
64-
gcode.New(result.Code, result.Message, nil),
65-
result.Message,
66-
)
45+
return &Client{
46+
Client: client.Prefix(config.URL),
47+
Handler: handler,
6748
}
68-
return nil
6949
}
7050

7151
// Request sends request to service by struct object `req`, and receives response to struct object `res`.
@@ -83,20 +63,21 @@ func (c *Client) Request(ctx context.Context, req, res interface{}) error {
8363
if err != nil {
8464
return err
8565
}
86-
return c.handleResponse(ctx, result, res)
66+
return c.HandleResponse(ctx, result, res)
8767
}
8868
}
8969

9070
// Get sends a request using GET method.
9171
func (c *Client) Get(ctx context.Context, path string, in, out interface{}) error {
92-
if urlParams := ghttp.BuildParams(in); urlParams != "" {
93-
path += "?" + ghttp.BuildParams(in)
72+
// TODO: Path params will also be built in urlParams, not graceful now.
73+
if urlParams := ghttp.BuildParams(in); urlParams != "" && urlParams != "{}" {
74+
path += "?" + urlParams
9475
}
9576
res, err := c.ContentJson().Get(ctx, c.handlePath(path, in))
9677
if err != nil {
9778
return gerror.Wrap(err, `http request failed`)
9879
}
99-
return c.handleResponse(ctx, res, out)
80+
return c.HandleResponse(ctx, res, out)
10081
}
10182

10283
func (c *Client) handlePath(path string, in interface{}) string {

contrib/sdk/httpclient/httpclient_config.go

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
type Config struct {
1616
URL string `v:"required"` // Service address. Eg: user.svc.local, http://user.svc.local
1717
Client *gclient.Client // Custom underlying client.
18+
Handler Handler // Custom response handler.
1819
Logger *glog.Logger // Custom logger.
1920
RawDump bool // Whether auto dump request&response in stdout.
2021
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
2+
//
3+
// This Source Code Form is subject to the terms of the MIT License.
4+
// If a copy of the MIT was not distributed with this file,
5+
// You can obtain one at https://github.com/gogf/gf.
6+
7+
package httpclient
8+
9+
import (
10+
"context"
11+
"encoding/json"
12+
13+
"github.com/gogf/gf/v2/errors/gcode"
14+
"github.com/gogf/gf/v2/errors/gerror"
15+
"github.com/gogf/gf/v2/frame/g"
16+
"github.com/gogf/gf/v2/net/gclient"
17+
"github.com/gogf/gf/v2/net/ghttp"
18+
"github.com/gogf/gf/v2/os/glog"
19+
)
20+
21+
// Handler is the interface for http response handling.
22+
type Handler interface {
23+
// HandleResponse handles the http response and transforms its body to the specified object.
24+
// The parameter `out` specifies the object that the response body is transformed to.
25+
HandleResponse(ctx context.Context, res *gclient.Response, out interface{}) error
26+
}
27+
28+
// DefaultHandler handle ghttp.DefaultHandlerResponse of json format.
29+
type DefaultHandler struct {
30+
Logger *glog.Logger
31+
RawDump bool
32+
}
33+
34+
func NewDefaultHandler(logger *glog.Logger, rawRump bool) *DefaultHandler {
35+
if rawRump && logger == nil {
36+
logger = g.Log()
37+
}
38+
return &DefaultHandler{
39+
Logger: logger,
40+
RawDump: rawRump,
41+
}
42+
}
43+
44+
func (h DefaultHandler) HandleResponse(ctx context.Context, res *gclient.Response, out interface{}) error {
45+
defer res.Close()
46+
if h.RawDump {
47+
h.Logger.Debugf(ctx, "raw request&response:\n%s", res.Raw())
48+
}
49+
var (
50+
responseBytes = res.ReadAll()
51+
result = ghttp.DefaultHandlerResponse{
52+
Data: out,
53+
}
54+
)
55+
if !json.Valid(responseBytes) {
56+
return gerror.Newf(`invalid response content: %s`, responseBytes)
57+
}
58+
if err := json.Unmarshal(responseBytes, &result); err != nil {
59+
return gerror.Wrapf(err, `json.Unmarshal failed with content:%s`, responseBytes)
60+
}
61+
if result.Code != gcode.CodeOK.Code() {
62+
return gerror.NewCode(
63+
gcode.New(result.Code, result.Message, nil),
64+
result.Message,
65+
)
66+
}
67+
return nil
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
2+
//
3+
// This Source Code Form is subject to the terms of the MIT License.
4+
// If a copy of the MIT was not distributed with this file,
5+
// You can obtain one at https://github.com/gogf/gf.
6+
7+
package httpclient_test
8+
9+
import (
10+
"context"
11+
"fmt"
12+
"testing"
13+
"time"
14+
15+
"github.com/gogf/gf/contrib/sdk/httpclient/v2"
16+
"github.com/gogf/gf/v2/errors/gcode"
17+
"github.com/gogf/gf/v2/errors/gerror"
18+
"github.com/gogf/gf/v2/frame/g"
19+
"github.com/gogf/gf/v2/net/gclient"
20+
"github.com/gogf/gf/v2/net/ghttp"
21+
"github.com/gogf/gf/v2/os/gctx"
22+
"github.com/gogf/gf/v2/test/gtest"
23+
"github.com/gogf/gf/v2/util/guid"
24+
)
25+
26+
func Test_HttpClient_With_Default_Handler(t *testing.T) {
27+
type Req struct {
28+
g.Meta `path:"/get" method:"get"`
29+
}
30+
type Res struct {
31+
Uid int
32+
Name string
33+
}
34+
35+
s := g.Server(guid.S())
36+
s.BindHandler("/get", func(r *ghttp.Request) {
37+
res := ghttp.DefaultHandlerResponse{
38+
Data: Res{
39+
Uid: 1,
40+
Name: "test",
41+
},
42+
}
43+
r.Response.WriteJson(res)
44+
})
45+
s.SetDumpRouterMap(false)
46+
s.Start()
47+
defer s.Shutdown()
48+
49+
time.Sleep(100 * time.Millisecond)
50+
51+
gtest.C(t, func(t *gtest.T) {
52+
client := httpclient.New(httpclient.Config{
53+
URL: fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()),
54+
})
55+
var (
56+
req = &Req{}
57+
res = &Res{}
58+
)
59+
err := client.Request(gctx.New(), req, res)
60+
t.AssertNil(err)
61+
t.AssertEQ(res.Uid, 1)
62+
t.AssertEQ(res.Name, "test")
63+
})
64+
}
65+
66+
type CustomHandler struct{}
67+
68+
func (c CustomHandler) HandleResponse(ctx context.Context, res *gclient.Response, out interface{}) error {
69+
defer res.Close()
70+
if pointer, ok := out.(*string); ok {
71+
*pointer = res.ReadAllString()
72+
} else {
73+
return gerror.NewCodef(gcode.CodeInvalidParameter, "[CustomHandler] expectedType:'*string', but realType:'%T'", out)
74+
}
75+
return nil
76+
}
77+
78+
func Test_HttpClient_With_Custom_Handler(t *testing.T) {
79+
type Req struct {
80+
g.Meta `path:"/get" method:"get"`
81+
}
82+
83+
s := g.Server(guid.S())
84+
s.BindHandler("/get", func(r *ghttp.Request) {
85+
r.Response.WriteExit("It is a test.")
86+
})
87+
s.SetDumpRouterMap(false)
88+
s.Start()
89+
defer s.Shutdown()
90+
91+
time.Sleep(100 * time.Millisecond)
92+
93+
client := httpclient.New(httpclient.Config{
94+
URL: fmt.Sprintf("127.0.0.1:%d", s.GetListenedPort()),
95+
Handler: CustomHandler{},
96+
})
97+
req := &Req{}
98+
gtest.C(t, func(t *gtest.T) {
99+
var res = new(string)
100+
err := client.Request(gctx.New(), req, res)
101+
t.AssertNil(err)
102+
t.AssertEQ(*res, "It is a test.")
103+
})
104+
gtest.C(t, func(t *gtest.T) {
105+
var res string
106+
err := client.Request(gctx.New(), req, res)
107+
t.AssertEQ(err, gerror.NewCodef(gcode.CodeInvalidParameter, "[CustomHandler] expectedType:'*string', but realType:'%T'", res))
108+
})
109+
}

0 commit comments

Comments
 (0)