Skip to content

Commit

Permalink
Merge pull request #11 from lxzan/dev
Browse files Browse the repository at this point in the history
Add ProtoBuf/Yaml Codec
  • Loading branch information
lxzan committed Nov 10, 2023
2 parents 8588531 + 84e3a20 commit bc9b425
Show file tree
Hide file tree
Showing 23 changed files with 858 additions and 177 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ test/
debug
vendor/
examples/
bin/
bin/
go.work*
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
test:
go test -count 1 -timeout 30s -run ^Test ./...
go test -timeout 30s -run ^Test ./...
go test -timeout 30s -run ^Test ./contrib/pb/...
go test -timeout 30s -run ^Test ./contrib/yaml/...

bench:
go test -benchmem -run=^$$ -bench . github.com/lxzan/hasaki

cover:
go test -coverprofile=bin/cover.out --cover ./...
go test -coverprofile=bin/pb.out --cover ./contrib/pb/...
go test -coverprofile=bin/yaml.out --cover ./contrib/yaml/...

install:
go mod tidy
go generate ./contrib/pb/codec.go
go generate ./contrib/yaml/codec.go
98 changes: 29 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
# Hasaki

http request library for golang
Http Request Library for Go

[![Build Status][1]][2] [![codecov][3]][4]

[1]: https://github.com/lxzan/hasaki/workflows/Go%20Test/badge.svg?branch=master

[2]: https://github.com/lxzan/hasaki/actions?query=branch%3Amaster

[3]: https://codecov.io/gh/lxzan/hasaki/graph/badge.svg?token=0VY55RLS3G

[4]: https://codecov.io/gh/lxzan/hasaki

- [Hasaki](#hasaki)
- [Features](#features)
- [Install](#install)
- [Usage](#usage)
- [Get](#get)
- [Post](#post)
- [Stream](#stream)
- [Error Stack](#error-stack)
- [Middleware](#middleware)
- [Debug](#debug)
- [Hasaki](#hasaki)
- [Features](#features)
- [Install](#install)
- [Usage](#usage)
- [Get](#get)
- [Post](#post)
- [Stream](#stream)
- [Error Stack](#error-stack)
- [Middleware](#middleware)
- [How to get request latency in simple way](#how-to-get-request-latency-in-simple-way)

### Features

- [x] Buffer Pool
- [x] Trace the Error Stack
- [x] JSON / WWWForm Encoder
- [x] Request Before and After Middleware
- [x] Export CURL Command
- [x] Buffer Pool
- [x] Trace the Error Stack
- [x] Request Encoder Bind: JSON, YAML, Form, Stream
- [x] Response Decoder Bind: JSON, YAML, XML
- [x] Request Before and After Middleware
- [x] Export CURL Command in Debug Mode

### Install

Expand Down Expand Up @@ -63,23 +61,6 @@ resp := hasaki.
Send(nil)
```

```go
// GET https://api.example.com/search?q=hasaki&page=1
// Send get request, with Query parameter, encoded with struct

type Req struct {
Q string `form:"q"`
Page int `form:"page"`
}
resp := hasaki.
Get("https://api.example.com/search").
SetQuery(Req{
Q: "hasaki",
Page: 1,
}).
Send(nil)
```

#### Post

```go
Expand All @@ -102,16 +83,12 @@ resp := hasaki.
// POST https://api.example.com/search
// Send post request, encoded with www-form

type Req struct {
Q string `form:"q"`
Page int `form:"page"`
}
resp := hasaki.
Post("https://api.example.com/search").
SetEncoder(hasaki.FormEncoder).
Send(Req{
Q: "hasaki",
Page: 1,
Send(url.Values{
"q": []string{"hasaki"},
"page": []string{"1"},
})
```

Expand Down Expand Up @@ -145,14 +122,20 @@ if err != nil {

#### Middleware

Very useful middleware, you can use it to do something before and after the request is sent.

The middleware is a function, it receives a context and a request or response object, and returns a context and an error.

Under code is a simple middleware example , record the request latency.

```go
// Statistics on time spent on requests
// You can use the before and after middleware to do something before and after the request is sent

before := hasaki.WithBefore(func (ctx context.Context, request *http.Request) (context.Context, error) {
before := hasaki.WithBefore(func(ctx context.Context, request *http.Request) (context.Context, error) {
return context.WithValue(ctx, "t0", time.Now()), nil
})

after := hasaki.WithAfter(func (ctx context.Context, response *http.Response) (context.Context, error) {
after := hasaki.WithAfter(func(ctx context.Context, response *http.Response) (context.Context, error) {
t0 := ctx.Value("t0").(time.Time)
log.Printf("latency=%s", time.Since(t0).String())
return ctx, nil
Expand All @@ -161,27 +144,4 @@ after := hasaki.WithAfter(func (ctx context.Context, response *http.Response) (c
var url = "https://api.github.com/search/repositories"
cli, _ := hasaki.NewClient(before, after)
cli.Get(url).Send(nil)
```

#### Debug

```go
hasaki.
Post("http://127.0.0.1:3000").
Debug().
SetHeader("x-username", "123").
SetHeader("user-agent", "hasaki").
SetEncoder(hasaki.FormEncoder).
Send(url.Values{
"name": []string{"洪荒"},
"age": []string{"456"},
})
```

```bash
curl -X POST 'http://127.0.0.1:3000' \
--header 'User-Agent: hasaki' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'X-Username: 123' \
--data-raw 'age=456&name=%E6%B4%AA%E8%8D%92'
```
29 changes: 23 additions & 6 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ type Client struct {
// NewClient 新建一个客户端
// Create a new client
func NewClient(options ...Option) (*Client, error) {
var c = new(config)
options = append(options, withInitialize())
var conf = new(config)
for _, f := range options {
f(c)
f(conf)
}
var client = &Client{config: c}
withInitialize()(conf)
var client = &Client{config: conf}
return client, nil
}

Expand All @@ -39,17 +39,34 @@ func (c *Client) Delete(url string, args ...any) *Request {
return c.Request(http.MethodDelete, url, args...)
}

func (c *Client) Head(url string, args ...any) *Request {
return c.Request(http.MethodHead, url, args...)
}

func (c *Client) Options(url string, args ...any) *Request {
return c.Request(http.MethodOptions, url, args...)
}

func (c *Client) Patch(url string, args ...any) *Request {
return c.Request(http.MethodPatch, url, args...)
}

func (c *Client) Request(method string, url string, args ...any) *Request {
if len(args) > 0 {
url = fmt.Sprintf(url, args...)
}
return (&Request{

r := &Request{
ctx: context.Background(),
client: c.config.HTTPClient,
method: strings.ToUpper(method),
url: url,
before: c.config.BeforeFunc,
after: c.config.AfterFunc,
headers: http.Header{},
}).SetEncoder(JsonEncoder)
}

r.SetEncoder(JsonEncoder)

return r
}
69 changes: 40 additions & 29 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ func TestClient(t *testing.T) {
Name string `form:"name"`
}
req := c.Get("http://%s", addr).SetQuery(Req{Name: "xxx"})
exp := fmt.Sprintf("http://%s?name=xxx", addr)
assert.Equal(t, req.url, exp)
assert.True(t, errors.Is(req.err, errUnsupportedData))
}
}

Expand Down Expand Up @@ -96,6 +95,18 @@ func TestRequest(t *testing.T) {
resp := Delete("http://%s", addr).Send(nil)
assert.NoError(t, resp.Err())
}
{
resp := Patch("http://%s", addr).Send(nil)
assert.NoError(t, resp.Err())
}
{
resp := Head("http://%s", addr).Send(nil)
assert.NoError(t, resp.Err())
}
{
resp := Options("http://%s", addr).Send(nil)
assert.NoError(t, resp.Err())
}
{
resp := NewRequest(http.MethodDelete, "http://%s", addr).Send(nil)
assert.NoError(t, resp.Err())
Expand All @@ -109,19 +120,6 @@ func TestRequest(t *testing.T) {
}
}

func TestRequest_Header(t *testing.T) {
{
var req = Post("http://%s", nextAddr()).SetEncoder(FormEncoder)
var typ = req.Header().Get("Content-Type")
assert.Equal(t, typ, MimeForm)
}
{
var req = Get("http://%s", nextAddr())
var typ = req.Header().Get("Content-Type")
assert.Equal(t, typ, MimeJson)
}
}

func TestRequest_SetContext(t *testing.T) {
addr := nextAddr()
srv := &http.Server{Addr: addr}
Expand Down Expand Up @@ -166,14 +164,6 @@ func TestRequest_SetQuery(t *testing.T) {
})
assert.Equal(t, req.url, "http://"+addr+"?name=xxx")
})

t.Run("", func(t *testing.T) {
type Req struct {
Name string `form:"name"`
}
req := Get("http://%s", addr).SetQuery(Req{Name: "xxx"})
assert.Equal(t, req.url, "http://"+addr+"?name=xxx")
})
}

func TestRequest_Send(t *testing.T) {
Expand Down Expand Up @@ -290,9 +280,18 @@ func TestResponse(t *testing.T) {
case "/greet":
writer.WriteHeader(http.StatusOK)
writer.Write([]byte("hello"))
case "/test":
case "/json":
writer.WriteHeader(http.StatusOK)
writer.Write([]byte(`{"name":"caster"}`))
case "/yaml":
writer.WriteHeader(http.StatusOK)
writer.Write([]byte(`name: caster`))
case "/xml":
writer.WriteHeader(http.StatusOK)
writer.Write([]byte(`<A><name>caster</name></A>`))
case "/proto":
writer.WriteHeader(http.StatusOK)
writer.Write([]byte{10, 6, 99, 97, 115, 116, 101, 114})
case "/204":
writer.WriteHeader(http.StatusNoContent)
default:
Expand All @@ -311,35 +310,35 @@ func TestResponse(t *testing.T) {
})

t.Run("read body error 1", func(t *testing.T) {
resp := Post("http://%s/test", nextAddr()).Send(nil)
resp := Post("http://%s/json", nextAddr()).Send(nil)
_, err := resp.ReadBody()
assert.Error(t, err)
})

t.Run("read body error 2", func(t *testing.T) {
resp := Post("http://%s/test", addr).Send(nil)
resp := Post("http://%s/json", addr).Send(nil)
resp.Body = nil
_, err := resp.ReadBody()
assert.Error(t, err)
})

t.Run("bind json ok", func(t *testing.T) {
resp := Post("http://%s/test", addr).Send(nil)
resp := Post("http://%s/json", addr).Send(nil)
input := struct{ Name string }{}
err := resp.BindJSON(&input)
assert.NoError(t, err)
assert.Equal(t, input.Name, "caster")
})

t.Run("bind json error 1", func(t *testing.T) {
resp := Post("http://%s/test", nextAddr()).Send(nil)
resp := Post("http://%s/json", nextAddr()).Send(nil)
inputs := struct{ Name string }{}
err := resp.BindJSON(&inputs)
assert.Error(t, err)
})

t.Run("bind json error 2", func(t *testing.T) {
resp := Post("http://%s/test", addr).Send(map[string]any{
resp := Post("http://%s/json", addr).Send(map[string]any{
"name": "xxx",
})
resp.Body = nil
Expand Down Expand Up @@ -373,3 +372,15 @@ func TestResponse(t *testing.T) {
assert.Error(t, resp.Err())
})
}

func TestRequest_SetHeaders(t *testing.T) {
var h = http.Header{}
h.Set("Content-Type", MimeJson)
h.Set("cookie", "123")
var req = Get("https://api.github.com").
SetHeader("Cookie", "456").
SetHeader("encoding", "none").
SetHeaders(h)
assert.Equal(t, req.headers.Get("cookie"), "123")
assert.Equal(t, req.headers.Get("encoding"), "none")
}
Loading

0 comments on commit bc9b425

Please sign in to comment.