generated from cloudwego/.github
-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: initialize client && request #1
Open
ViolaPioggia
wants to merge
24
commits into
hertz-contrib:main
Choose a base branch
from
ViolaPioggia:feat/client
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
1cf83b3
feat: initialize client && request
ViolaPioggia 61c4c00
style: gofumpt
ViolaPioggia 5641152
chore: add license header
ViolaPioggia f7ac4a6
chore: change package name
ViolaPioggia 3c45281
feat: add response
ViolaPioggia 7f12c8e
feat: optimize api client
ViolaPioggia 4fa2fce
feat:improve request
lance-e 59839bc
Merge pull request #3 from lance-e/feat/client
ViolaPioggia 535fbc4
feat: optimize middleware
ViolaPioggia 9f0b5ab
feat:add some api
lance-e cc62cee
Merge pull request #4 from lance-e/feat/client
ViolaPioggia ac162ef
feat: initialize client_test.go
ViolaPioggia 52406c3
style: adjust config
ViolaPioggia da9bb18
feat: optimize options
ViolaPioggia ec738a7
chore: adjust fileReader
ViolaPioggia 7ce34d5
go mod tidy
ViolaPioggia ae38876
增加 client 初始化配置
ViolaPioggia df441ff
feat: add client interface
FGYFFFF 00d9903
feat: add bind body
FGYFFFF f7fc302
feat: process responese
FGYFFFF 1c43340
feat: R()
FGYFFFF b1be890
feat: add comment
FGYFFFF 6ec475e
optimize: code
FGYFFFF 86ddbe1
Merge pull request #6 from ViolaPioggia/feat/client_interface
ViolaPioggia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
/* | ||
* Copyright 2024 CloudWeGo Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package easy_http | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
"sync" | ||
|
||
"github.com/cloudwego/hertz/pkg/app/client" | ||
"github.com/cloudwego/hertz/pkg/common/config" | ||
"github.com/cloudwego/hertz/pkg/protocol" | ||
"github.com/cloudwego/hertz/pkg/protocol/consts" | ||
) | ||
|
||
type Client struct { | ||
baseURL string | ||
header http.Header | ||
|
||
beforeRequest []RequestMiddleware | ||
afterResponse []ResponseMiddleware | ||
afterResponseLock *sync.RWMutex | ||
|
||
enableDiscovery bool | ||
|
||
client *client.Client | ||
options []config.ClientOption | ||
} | ||
|
||
type ( | ||
RequestMiddleware func(*Client, *Request) error | ||
ResponseMiddleware func(*Client, *Response) error | ||
) | ||
|
||
var ( | ||
hdrContentTypeKey = http.CanonicalHeaderKey(consts.HeaderContentType) | ||
hostHeader = "Host" | ||
|
||
plainTextType = consts.MIMETextPlainUTF8 | ||
jsonContentType = consts.MIMEApplicationJSON | ||
formContentType = consts.MIMEApplicationHTMLForm | ||
formDataContentType = consts.MIMEMultipartPOSTForm | ||
) | ||
|
||
// createClient creates a new client instance with configured request and response middleware. | ||
// It accepts a client.Client pointer and optional config.ClientOption parameters. | ||
// | ||
// For Example: | ||
// | ||
// cc := &client.Client{} | ||
// opts := []config.ClientOption{...} | ||
// client := createClient(cc, opts...) | ||
// | ||
// Note: This function configures middleware for request processing and response parsing. | ||
func createClient(cc *client.Client, opts ...config.ClientOption) *Client { | ||
c := &Client{ | ||
afterResponseLock: &sync.RWMutex{}, | ||
|
||
client: cc, | ||
options: opts, | ||
} | ||
|
||
c.beforeRequest = []RequestMiddleware{ | ||
parseRequestURL, | ||
parseRequestHeader, | ||
createHTTPRequest, | ||
} | ||
|
||
c.afterResponse = []ResponseMiddleware{ | ||
parseResponseBody, | ||
} | ||
|
||
return c | ||
} | ||
|
||
// R initializes and returns a new Request instance. | ||
// It sets up QueryParam, Header, PathParams, and RawRequest fields. | ||
// | ||
// For Example: | ||
// | ||
// client := &Client{} | ||
// req := client.R() | ||
// | ||
// Note: This method does not take any parameters. | ||
func (c *Client) R() *Request { | ||
r := &Request{ | ||
QueryParam: url.Values{}, | ||
Header: http.Header{}, | ||
PathParams: map[string]string{}, | ||
RawRequest: &protocol.Request{}, | ||
|
||
client: c, | ||
} | ||
return r | ||
} | ||
|
||
// EnableServiceDiscovery enables service discovery for the client. | ||
// It sets the enableDiscovery field to true and returns the modified client. | ||
// | ||
// Example: | ||
// | ||
// client.EnableServiceDiscovery() | ||
func (c *Client) EnableServiceDiscovery() *Client { | ||
c.enableDiscovery = true | ||
return c | ||
} | ||
|
||
// UseMiddleware adds one or more middleware to the client's request processing chain. | ||
// It returns the client instance for chaining. | ||
// | ||
// For Example: | ||
// | ||
// client.UseMiddleware(middleware1, middleware2) | ||
func (c *Client) UseMiddleware(mws ...client.Middleware) *Client { | ||
c.client.Use(mws...) | ||
return c | ||
} | ||
|
||
// AddHeader method adds a custom HTTP header to the Client instance. | ||
// It accepts header name and value as parameters. | ||
// | ||
// For Example: | ||
// | ||
// client.AddHeader("Authorization", "Bearer token"). | ||
// AddHeader("Content-Type", "application/json") | ||
// | ||
// Returns the updated Client instance for chaining. | ||
func (c *Client) AddHeader(header, value string) *Client { | ||
c.header.Add(header, value) | ||
return c | ||
} | ||
|
||
// AddHeaders adds multiple HTTP headers to the client instance. | ||
// It iterates over the provided map and calls AddHeader for each key-value pair. | ||
// | ||
// For Example: | ||
// | ||
// client.AddHeaders(map[string]string{ | ||
// "Authorization": "Bearer token", | ||
// "Content-Type": "application/json", | ||
// }) | ||
// | ||
// Returns the updated client instance. | ||
func (c *Client) AddHeaders(headers map[string]string) *Client { | ||
for k, v := range headers { | ||
c.AddHeader(k, v) | ||
} | ||
return c | ||
} | ||
|
||
// GetClient retrieves the underlying client.Client instance from the Client. | ||
// It returns a pointer to the client.Client instance. | ||
// | ||
// For Example: | ||
// | ||
// client := &Client{client: &client.Client{}} | ||
// underlyingClient := client.GetClient() | ||
func (c *Client) GetClient() *client.Client { | ||
return c.client | ||
} | ||
|
||
// SetBaseURL sets the base URL for the client and trims trailing slashes. | ||
// It returns the updated client instance. | ||
// | ||
// For Example: | ||
// | ||
// client.SetBaseURL("https://example.com/") | ||
// | ||
// Note: trailing slashes are removed from the URL. | ||
func (c *Client) SetBaseURL(url string) *Client { | ||
c.baseURL = strings.TrimRight(url, "/") | ||
return c | ||
} | ||
|
||
// SetServiceName sets the service name and updates the base URL. | ||
// It formats the name as the base URL and updates the client. | ||
// | ||
// For Example: | ||
// | ||
// client.SetServiceName("example.com") | ||
// | ||
// Note: This method does not check the validity of the URL. | ||
func (c *Client) SetServiceName(name string) *Client { | ||
c.SetBaseURL(fmt.Sprintf("http://%s", name)) | ||
return c | ||
} | ||
|
||
// NewRequest creates a new Request instance. | ||
// It calls the R method of the Client. | ||
// | ||
// For Example: | ||
// | ||
// req := client.NewRequest() | ||
// | ||
// Note: This method does not take any parameters. | ||
func (c *Client) NewRequest() *Request { | ||
return c.R() | ||
} | ||
|
||
// execute method executes an HTTP request and processes the response. | ||
// It locks the post-request hooks to prevent concurrency issues. | ||
// | ||
// req := &Request{} | ||
// resp, err := client.execute(req) | ||
// if err != nil { | ||
// log.Fatalf("Request failed: %v", err) | ||
// } | ||
// | ||
// Note: Handles request and response middleware, and sets Host header. | ||
func (c *Client) execute(req *Request) (*Response, error) { | ||
// Lock the post-request hooks. | ||
c.afterResponseLock.RLock() | ||
defer c.afterResponseLock.RUnlock() | ||
// Apply Request middleware | ||
var err error | ||
for _, f := range c.beforeRequest { | ||
if err = f(c, req); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
if hostHeader := req.Header.Get(hostHeader); hostHeader != "" { | ||
req.RawRequest.SetHost(hostHeader) | ||
} | ||
req.hasCreate = true | ||
|
||
resp := &protocol.Response{} | ||
err = c.client.Do(context.Background(), req.RawRequest, resp) | ||
response := &Response{ | ||
Request: req, | ||
RawResponse: resp, | ||
} | ||
|
||
if err != nil { | ||
return response, err | ||
} | ||
|
||
// Apply Response middleware | ||
for _, f := range c.afterResponse { | ||
if err = f(c, response); err != nil { | ||
break | ||
} | ||
} | ||
|
||
return response, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package easy_http | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/cloudwego/hertz/pkg/app" | ||
"github.com/cloudwego/hertz/pkg/app/server" | ||
"net/url" | ||
"strings" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func runHertz(t *testing.T) { | ||
hertz := server.Default(server.WithHostPorts("127.0.0.1:6666")) | ||
hertz.GET("/test_query", func(c context.Context, ctx *app.RequestContext) { | ||
type Query struct { | ||
Q1 []string `query:"q1"` | ||
Q2 string `query:"q2"` | ||
Q3 string `query:"q3"` | ||
} | ||
var req Query | ||
err := ctx.BindQuery(&req) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if len(req.Q1) != 2 { | ||
t.Errorf("expected q1 has 2 element, but get %d", len(req.Q1)) | ||
} | ||
if req.Q2 != "q2" { | ||
t.Errorf("expected q2, but get %s", req.Q2) | ||
} | ||
if req.Q3 != "q3" { | ||
t.Errorf("expected q3, but get %s", req.Q3) | ||
} | ||
ctx.JSON(200, map[string]string{ | ||
"q1": strings.Join(req.Q1, ","), | ||
"q2": req.Q2, | ||
"q3": req.Q3, | ||
}) | ||
}) | ||
|
||
go hertz.Spin() | ||
time.Sleep(100 * time.Millisecond) | ||
} | ||
|
||
func TestSetQueryParam(t *testing.T) { | ||
runHertz(t) | ||
//c := MustNewClient().UseMiddleware().SetBaseURL("http://127.0.0.1:6666") | ||
res := make(map[string]string) | ||
value := url.Values{} | ||
value.Set("q3", "q3") | ||
resp, err := R(). | ||
SetResult(res). | ||
AddQueryParam("q1", "q1"). | ||
AddQueryParams(map[string]string{"q2": "q2", "q1": "q11"}). | ||
AddQueryParamsFromValues(value). | ||
Get(context.Background(), "http://127.0.0.1:6666/test_query") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
fmt.Println(resp.BodyString()) | ||
fmt.Println(resp.Result()) | ||
fmt.Println(res) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* | ||
* Copyright 2024 CloudWeGo Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package easy_http | ||
|
||
import ( | ||
"github.com/cloudwego/hertz/pkg/app/client" | ||
"github.com/cloudwego/hertz/pkg/common/config" | ||
"github.com/cloudwego/hertz/pkg/network/standard" | ||
) | ||
|
||
// NewClient creates a new client instance supporting HTTPS. | ||
// It accepts a variadic list of config.ClientOption for configuration. | ||
// | ||
// For Example: | ||
// | ||
// client, err := NewClient(config.WithDialer(customDialer)) | ||
// if err != nil { | ||
// log.Fatalf("Failed to create client: %v", err) | ||
// } | ||
// | ||
// Note: Uses standard library dialer by default for HTTPS support. | ||
func NewClient(opts ...config.ClientOption) (*Client, error) { | ||
// use standard network library to support https by default | ||
opts = append(opts, client.WithDialer(standard.NewDialer())) | ||
c, err := client.NewClient(opts...) | ||
return createClient(c, opts...), err | ||
} | ||
|
||
// MustNewClient creates a new client instance with given options. | ||
// It defaults to using the standard library for HTTPS support. | ||
// | ||
// For Example: | ||
// | ||
// client := MustNewClient(config.WithTimeout(5 * time.Second)) | ||
// | ||
// Note: It panics if client creation fails. | ||
func MustNewClient(opts ...config.ClientOption) *Client { | ||
// use standard network library to support https by default | ||
opts = append(opts, client.WithDialer(standard.NewDialer())) | ||
c, err := client.NewClient(opts...) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return createClient(c, opts...) | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
middleware 可以不用支持, hertz client 本身就有 mw 能力,可以把 hertz client mw 以配置的形式注入,就别在 封装的这一层再搞一层 Middleware 了
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
看了下面这个 Middleware 是在用来创建 hertz 的 Request;这块进行不要对外暴露就好,保证easy_http 内部可用就好