Skip to content

Commit

Permalink
httpx Request 和 Response 初步设计
Browse files Browse the repository at this point in the history
  • Loading branch information
flycash committed Dec 29, 2023
1 parent 14dba09 commit 8b56bda
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 0 deletions.
63 changes: 63 additions & 0 deletions net/httpx/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2021 ecodeclub
//
// 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 httpx

import (
"context"
"io"
"net/http"

"github.com/ecodeclub/ekit/iox"
)

type Request struct {
*http.Request
err error
client *http.Client
}

func NewRequest(ctx context.Context, method, url string) *Request {
req, err := http.NewRequestWithContext(ctx, method, url, nil)
return &Request{
Request: req,
err: err,
client: http.DefaultClient,
}
}

// JSONBody 使用 JSON body
func (req *Request) JSONBody(val any) *Request {
req.Body = io.NopCloser(iox.NewJSONReader(val))
req.Header.Set("Content-Type", "application/json")
return req
}

func (req *Request) Client(cli *http.Client) *Request {
req.client = cli
return req
}

func (req *Request) Do() *Response {
if req.err != nil {
return &Response{
err: req.err,
}
}
resp, err := req.client.Do(req.Request)
return &Response{
Response: resp,
err: err,
}
}
102 changes: 102 additions & 0 deletions net/httpx/request_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2021 ecodeclub
//
// 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 httpx

import (
"context"
"errors"
"net"
"net/http"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRequest_Client(t *testing.T) {
req := NewRequest(context.Background(), http.MethodPost, "/abc")
assert.Equal(t, http.DefaultClient, req.client)
cli := &http.Client{}
req = req.Client(&http.Client{})
assert.Equal(t, cli, req.client)
}

func TestRequest_JSONBody(t *testing.T) {
req := NewRequest(context.Background(), http.MethodPost, "/abc")
assert.Nil(t, req.Body)
req = req.JSONBody(User{})
assert.NotNil(t, req.Body)
assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
}

func TestRequest_Do(t *testing.T) {
l, err := net.Listen("unix", "/tmp/test.sock")
require.NoError(t, err)
server := http.Server{}
go func() {
http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("OK"))

Check failure on line 51 in net/httpx/request_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `writer.Write` is not checked (errcheck)
})
server.Serve(l)

Check failure on line 53 in net/httpx/request_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `server.Serve` is not checked (errcheck)
}()
defer func() {
l.Close()
}()
testCases := []struct {
name string
req func() *Request
wantErr error
}{
{
name: "构造请求的时候有 error",
req: func() *Request {
return &Request{
err: errors.New("mock error"),
}
},
wantErr: errors.New("mock error"),
},
{
name: "成功",
req: func() *Request {
req := NewRequest(context.Background(), http.MethodGet, "http://localhost:8081/hello")
return req.Client(&http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context,
network, addr string) (net.Conn, error) {
return net.Dial("unix", "/tmp/test.sock")
},
},
})
},
},
}

// 确保前面的 http 端口启动成功
time.Sleep(time.Second)

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
req := tc.req()
resp := req.Do()
assert.Equal(t, tc.wantErr, resp.err)
})
}
}

type User struct {
Name string
}
34 changes: 34 additions & 0 deletions net/httpx/response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2021 ecodeclub
//
// 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 httpx

import (
"encoding/json"
"net/http"
)

type Response struct {
*http.Response
err error
}

// JSONScan 将 Body 按照 JSON 反序列化为结构体
func (r *Response) JSONScan(val any) error {
if r.err != nil {
return r.err
}
err := json.NewDecoder(r.Body).Decode(val)
return err
}
54 changes: 54 additions & 0 deletions net/httpx/response_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2021 ecodeclub
//
// 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 httpx

import (
"io"
"net/http"
"testing"

"github.com/ecodeclub/ekit/iox"
"github.com/stretchr/testify/assert"
)

func TestResponse_JSONScan(t *testing.T) {
testCases := []struct {
name string
resp *Response
wantVal User
wantErr error
}{
{
name: "scan成功",
resp: &Response{
Response: &http.Response{
Body: io.NopCloser(iox.NewJSONReader(User{Name: "Tom"})),
},
},
wantVal: User{
Name: "Tom",
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var u User
err := tc.resp.JSONScan(&u)
assert.Equal(t, tc.wantErr, err)
assert.Equal(t, tc.wantVal, u)
})
}
}

0 comments on commit 8b56bda

Please sign in to comment.