Skip to content
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
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
302 changes: 302 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
/*
* 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"
"github.com/cloudwego/hertz/pkg/app/client"
"github.com/cloudwego/hertz/pkg/protocol"
"net/http"
"net/url"
"regexp"
"strings"
"sync"
)

type Client struct {
QueryParam url.Values
FormData map[string]string
PathParams map[string]string
Header http.Header
Cookies []*http.Cookie

beforeRequest []RequestMiddleware
udBeforeRequest []RequestMiddleware
afterResponse []ResponseMiddleware
afterResponseLock *sync.RWMutex
udBeforeRequestLock *sync.RWMutex

client *client.Client
}

type (
Copy link

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 了

Copy link

@FGYFFFF FGYFFFF May 23, 2024

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 内部可用就好

RequestMiddleware func(*Client, *Request) error
ResponseMiddleware func(*Client, *Response) error
)

var (
hdrUserAgentKey = http.CanonicalHeaderKey("User-Agent")

Check failure on line 52 in client.go

View workflow job for this annotation

GitHub Actions / lint

var `hdrUserAgentKey` is unused (unused)
hdrAcceptKey = http.CanonicalHeaderKey("Accept")

Check failure on line 53 in client.go

View workflow job for this annotation

GitHub Actions / lint

var `hdrAcceptKey` is unused (unused)
hdrContentTypeKey = http.CanonicalHeaderKey("Content-Type")
hdrContentLengthKey = http.CanonicalHeaderKey("Content-Length")

Check failure on line 55 in client.go

View workflow job for this annotation

GitHub Actions / lint

var `hdrContentLengthKey` is unused (unused)
hdrContentEncodingKey = http.CanonicalHeaderKey("Content-Encoding")

Check failure on line 56 in client.go

View workflow job for this annotation

GitHub Actions / lint

var `hdrContentEncodingKey` is unused (unused)
hdrLocationKey = http.CanonicalHeaderKey("Location")

Check failure on line 57 in client.go

View workflow job for this annotation

GitHub Actions / lint

var `hdrLocationKey` is unused (unused)
hdrAuthorizationKey = http.CanonicalHeaderKey("Authorization")

Check failure on line 58 in client.go

View workflow job for this annotation

GitHub Actions / lint

var `hdrAuthorizationKey` is unused (unused)
hdrWwwAuthenticateKey = http.CanonicalHeaderKey("WWW-Authenticate")

Check failure on line 59 in client.go

View workflow job for this annotation

GitHub Actions / lint

var `hdrWwwAuthenticateKey` is unused (unused)

plainTextType = "text/plain; charset=utf-8"
jsonContentType = "application/json"
formContentType = "application/x-www-form-urlencoded"
formDataContentType = "multipart/form-data"

jsonCheck = regexp.MustCompile(`(?i:(application|text)/(.*json.*)(;|$))`)

Check failure on line 66 in client.go

View workflow job for this annotation

GitHub Actions / lint

var `jsonCheck` is unused (unused)
xmlCheck = regexp.MustCompile(`(?i:(application|text)/(.*xml.*)(;|$))`)

Check failure on line 67 in client.go

View workflow job for this annotation

GitHub Actions / lint

var `xmlCheck` is unused (unused)
)

func createClient(cc *client.Client) *Client {
c := &Client{
QueryParam: url.Values{},
PathParams: make(map[string]string),
Header: http.Header{},
Cookies: make([]*http.Cookie, 0),

udBeforeRequestLock: &sync.RWMutex{},
afterResponseLock: &sync.RWMutex{},

client: cc,
}

c.beforeRequest = []RequestMiddleware{
parseRequestURL,
parseRequestHeader,
parseRequestBody,
}

c.udBeforeRequest = []RequestMiddleware{}

c.afterResponse = []ResponseMiddleware{}

return c
}

func (c *Client) SetQueryParam(param, value string) *Client {
c.QueryParam.Set(param, value)
return c
}

func (c *Client) SetQueryParams(params map[string]string) *Client {
for k, v := range params {
c.QueryParam.Set(k, v)
}
return c
}

func (c *Client) SetQueryParamsFromValues(params url.Values) *Client {
for k, v := range params {
for _, v1 := range v {
c.QueryParam.Add(k, v1)
}
}
return c
}

func (c *Client) SetQueryString(query string) *Client {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

没实现的地方可以记个 todo

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SetQueryString 可以给个示例用法吗

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SetQueryString() 是 hertz Request 的原始 API,可以直接转换一下就行https://github.com/cloudwego/hertz/blob/develop/pkg/protocol/request.go#L457

str := strings.Split(query, "&")
for _, v := range str {
kv := strings.Split(v, "=")
if len(kv) == 2 {
c.QueryParam.Set(kv[0], kv[1])
}

}
return c
}

func (c *Client) AddQueryParam(param, value string) *Client {
c.QueryParam.Add(param, value)
return c
}

func (c *Client) AddQueryParams(params map[string]string) *Client {
for k, v := range params {
c.QueryParam.Add(k, v)
}
return c
}

func (c *Client) SetPathParam(param, value string) *Client {
c.PathParams[param] = value
return c
}

func (c *Client) SetPathParams(params map[string]string) *Client {
for k, v := range params {
c.PathParams[k] = v
}
return c
}

func (c *Client) SetHeader(header, value string) *Client {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

有些 header 有 k-[]v的形式,包括上面的query,可以考虑支持下这种数组类型

c.Header.Set(header, value)
return c
}

func (c *Client) SetHeaders(headers map[string]string) *Client {
for k, v := range headers {
c.Header.Set(k, v)
}
return c
}

func (c *Client) SetHeaderMultiValues(headers map[string][]string) *Client {
for k, header := range headers {
for _, v := range header {
c.Header.Add(k, v)
}
}
return c
}

func (c *Client) AddHeader(header, value string) *Client {
c.Header.Add(header, value)
return c
}

func (c *Client) AddHeaders(headers map[string]string) *Client {
for k, v := range headers {
c.Header.Add(k, v)
}
return c
}

func (c *Client) AddHeaderMultiValues(headers map[string][]string) *Client {
for k, header := range headers {
for _, v := range header {
c.Header.Add(k, v)
}
}
return c
}

func (c *Client) SetContentType(contentType string) *Client {
c.Header.Set("Content-Type", contentType)
return c
}

func (c *Client) SetJSONContentType() *Client {
c.Header.Set("Content-Type", "application/json")
return c
}

func (c *Client) SetXMLContentType() *Client {
c.Header.Set("Content-Type", "application/xml")
return c
}

func (c *Client) SetHTMLContentType() *Client {
c.Header.Set("Content-Type", "text/html")
return c
}

func (c *Client) SetFormContentType() *Client {
c.Header.Set("Content-Type", "application/x-www-form-urlencoded")
return c

}

func (c *Client) SetXFormData() *Client {
FGYFFFF marked this conversation as resolved.
Show resolved Hide resolved
c.Header.Set("Content-Type", "multipart/form-data")
return c
}

func (c *Client) SetCookie(hc *http.Cookie) *Client {
c.Cookies = append(c.Cookies, hc)
return c
}

func (c *Client) SetCookies(hcs []*http.Cookie) *Client {
c.Cookies = append(c.Cookies, hcs...)
return c
}

func (c *Client) R() *Request {
r := &Request{
QueryParam: url.Values{},
Header: http.Header{},
PathParams: map[string]string{},
RawRequest: &protocol.Request{},

client: c,
}
return r
}

func (c *Client) NewRequest() *Request {
return c.R()
}

func (c *Client) execute(req *Request) (*Response, error) {
// Lock the user-defined pre-request hooks.
c.udBeforeRequestLock.RLock()
defer c.udBeforeRequestLock.RUnlock()

// Lock the post-request hooks.
c.afterResponseLock.RLock()
defer c.afterResponseLock.RUnlock()

// Apply Request middleware
var err error

// user defined on before request methods
// to modify the *resty.Request object
for _, f := range c.udBeforeRequest {
if err = f(c, req); err != nil {
return nil, err
}
}

for _, f := range c.beforeRequest {
if err = f(c, req); err != nil {
return nil, err
}
}

if hostHeader := req.Header.Get("Host"); hostHeader != "" {
req.RawRequest.SetHost(hostHeader)
}

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
}
20 changes: 20 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package easy_http

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestSetQueryParam(t *testing.T) {
c := MustNew()
c.SetQueryParam("test1", "test1")
c.SetQueryParams(map[string]string{"test2": "test2", "test3": "test3"})
c.SetQueryParamsFromValues(map[string][]string{"test4": {"test41", "test42"}})
c.SetQueryString("test5=test5")

assert.Equal(t, "test1", c.QueryParam.Get("test1"))
assert.Equal(t, "test2", c.QueryParam.Get("test2"))
assert.Equal(t, "test3", c.QueryParam.Get("test3"))
assert.Equal(t, []string{"test41", "test42"}, c.QueryParam["test4"])
assert.Equal(t, "test5", c.QueryParam.Get("test5"))
}
36 changes: 36 additions & 0 deletions easy_http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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"

func New() (*Client, error) {
c, err := client.NewClient()
return createClient(c), err
}

func MustNew() *Client {
c, err := client.NewClient()
if err != nil {
panic(err)
}
return createClient(c)
}

func NewWithHertzClient(c *client.Client) *Client {
return createClient(c)
}
1 change: 1 addition & 0 deletions easy_http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package easy_http
16 changes: 16 additions & 0 deletions example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import (
"fmt"
"github.com/hertz-contrib/easy_http"
)

func main() {
c := easy_http.MustNew()

res, err := c.SetHeader("test", "test").SetQueryParam("test1", "test1").R().Get("http://www.baidu.com")
if err != nil {
panic(err)
}
fmt.Println(res)
}
Loading
Loading