xhttp

package module
v0.0.0-...-2bae6a4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 9, 2024 License: Apache-2.0 Imports: 32 Imported by: 5

README


goclub/http

介绍

goclub/http 基于 gorilla/mux 实现了接口友好并且安全便捷的http routerhttp client

特性

错误处理

对错误处理支持良好,在 http 请求中发生 error/panic 时可以捕获错误并进行自定义的处理.

路由分组和中间件

支持路由分组和中间件,可满足常见的的请求日志,登录鉴权.

请求校验

配套类型安全的验证器: goclub/validator

运行

package main

import (
	xhttp "github.com/goclub/http"
	"net/http"
)

func main() {
	r := xhttp.NewRouter(xhttp.RouterOption{})
	server := &http.Server{
		Addr:    ":2222",
		Handler: r,
	}
	r.HandleFunc(xhttp.Route{xhttp.GET, "/"}, func(c *xhttp.Context) (err error) {
		return c.WriteJSON(map[string]interface{}{"name": "goclub/http"})
	})
	r.LogPatterns(server)
	if err := server.ListenAndServe(); err != nil {
		panic(err)
	}
}

server

示例

  1. 基础示例
  2. 请求响应示例
  3. 给前端用的模拟服务器

相关的包

  1. goclub/session
  2. goclub/validator
  3. goclub/error

client

  1. Client.Send
  2. Client.Do

特性

  1. http server 支持 OnCatchError OnCatchPanic 拦截器,让错误处理更简单,让panic时对客户端更友好。
  2. http client xhttp.Client{}.Send() 高易用高性能的发起常见请求(query formurlencoded formdata json

Client{}.Send()

xhttp.Client{}.Send() 绑定常见请求 query formUrlencoed form-data json

是通过实现一个符合 Query() (url.Values, error) 接口的结构体完成设置请求。

type ExampleSendQuery struct {
	Published bool
	Limit int
}
// 通过实现结构体  Query() (string, error) 方法后传入 xhttp.SendRequest{}.Query
// 即可设置请求 query 参数
func (r ExampleSendQuery) Query() (string, error) {
	v := url.Values{}
	v.Set("published_eq", strconv.FormatBool(r.Published))
	v.Set("limit", strconv.Itoa(r.Limit))
	return v.Encode(), nil
}
client.Send(ctx, xhttp.GET, url, xhttp.SendRequest{
    Query:          xhttp.ExampleSendQuery{
        Published: true,
        Limit:     2,
    },
})

而没有使用结构体标签的设计

type ExampleSendQuery struct {
    Published bool `query:"published_eq"`
    Limit int `query:"Limit"`
}

原因是 Query() (string, error) 更加灵活,不使用反射性能更高。

(在一些要求将请求加密后生成 sign 的场景 Query() (string, error) 更方便)

test

你可以使用 test xhttp.NewTest 去创建测试代码.

func TestTest(t *testing.T) {
	router := newTestRouter()
	test := xhttp.NewTest(t, router)
	test.RequestJSON(xhttp.Route{xhttp.POST, "/"}, RequestHome{
		ID:   "1",
		Name: "nimo",
		Age:  18,
	}).ExpectJSON(200, ReplyHome{IDNameAge:"1:nimo:18"})

	test.RequestJSON(xhttp.Route{xhttp.GET, "/count"}, nil).ExpectString(200, "1")

	test.RequestJSON(xhttp.Route{xhttp.GET, "/count"}, nil).ExpectString(200, "2")
	test.RequestJSON(xhttp.Route{xhttp.POST, "/count"}, nil).ExpectString(405, "")

	test.RequestJSON(xhttp.Route{xhttp.GET, "/error"}, nil).ExpectString(500, "error")
	test.RequestJSON(xhttp.Route{xhttp.GET, "/panic"}, nil).ExpectString(500, "panic")
	{
		r, err := xhttp.SendRequest{
			FormData: TestFormDataReq{
				Name: "nimo",
			},
		}.HttpRequest(context.TODO(), xhttp.POST, "/form") ; assert.NoError(t, err)
		test.Request(r).ExpectString(200, "nimo")
	}

}
type TestFormDataReq struct {
	Name string
}
func (v TestFormDataReq) FormData(formWriter *multipart.Writer) (err error) {
	err = formWriter.WriteField("name", v.Name) ; if err != nil {
	    return
	}
	return
}

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func BindRequest

func BindRequest(ptr interface{}, r *http.Request) error

func DefaultRequestRetryCheck

func DefaultRequestRetryCheck(resp *http.Response, err error) (shouldRetry bool)

func DumpRequestResponse

func DumpRequestResponse(req *http.Request, resp *http.Response, body bool) (data []byte)

func DumpRequestResponseString

func DumpRequestResponseString(req *http.Request, resp *http.Response, body bool) (data string)

func GracefulClose

func GracefulClose(closeFunc func())

func MockMatchCount

func MockMatchCount(c *Context, counts map[string]string) (key string)

func MockMatchSceneCount

func MockMatchSceneCount(c *Context, routers map[string]map[string]string) (replyKey string)

Types

type Client

type Client struct {
	Core *http.Client
}

func NewClient

func NewClient(core *http.Client) *Client

func (*Client) CloseIdleConnections

func (client *Client) CloseIdleConnections()

func (*Client) Do deprecated

func (client *Client) Do(request *http.Request) (resp *http.Response, bodyClose func() error, statusCode int, err error)

Deprecated: Use client.Req Do 消费 http.Response{}.Body 后必须 Close xhttp.Client{}.Do() 的出参提供了一个安全的 bodyClose bodyClose 会在 resp 为 nil 时 不调用 resp.Body.Close 以防止 空指针错误 请求发起成功时候应该判断 响应的 statusCode,而不是忽视 statusCode xhttp.Client{}.Do() 的出参提供了一个等同于 resp.StatusCode 的 statusCode 参数 用于提醒开发人员每次调用完 Do 之后需要根据不同的状态码进行相应的处理措施 xhttp.Client{}.Do() 的实现非常简单,有兴趣的可以直接查看源码帮助理解

Example

net/http 的 &http.Client{}.Do() 函数只返回了 resp, err 实际上我们一定要记得 resp.Body.Close() ,但是 resp 可能是个 nil ,此时 运行 resp.Body.Close() 会出现空指针错误 并且一般情况应该判断 resp.StatusCode != 200 并返回错误 所以 &xhttp.Client{}.Do() 的函数签名是 (client *Client) Do(request *http.Request) (resp *http.Response, bodyClose func() error, statusCode int, err error) 这样使用者就不容易忘记处理 statusCode 和 bodyClose ,并且 bodyClose 处理了resp 空 nil的情况

package main

import (
	"context"
	xerr "github.com/goclub/error"

	xhttp "github.com/goclub/http"

	xjson "github.com/goclub/json"
	"log"
	"net/http"
)

func main() {
	log.Print("ExampleClient_Do")
	ctx := context.TODO()
	client := xhttp.NewClient(&http.Client{})
	url := "https://httpbin.org/json"
	request, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		panic(err)
	}
	resp, bodyClose, statusCode, err := client.Do(request)
	if err != nil {
		panic(err)
	}
	defer bodyClose()
	if statusCode != 200 {
		panic(xerr.New("response " + resp.Status))
	}
	var reply xhttp.ExampleReplyPost
	err = xjson.NewDecoder(resp.Body).Decode(&reply)
	if err != nil {
		panic(err)
	}
	log.Printf("response %+v", reply)
	// [{ID:6 Title:OVMVHWVHfA Views:20 Published:false CreatedAt:1936-09-10T20:19:28Z} {ID:57 Title:jOyYTxTfVV Views:20 Published:false CreatedAt:1942-04-18T13:44:54Z} {ID:82 Title:CdnSvYuNzs Views:20 Published:true CreatedAt:1907-05-01T23:53:45Z} {ID:97 Title:CWjFddEmda Views:20 Published:true CreatedAt:1971-01-13T08:22:23Z}]
}
Output:

func (*Client) Req

func (c *Client) Req(ctx context.Context, method Method, u string, req Req) (result HttpResult, err error)

func (*Client) Send deprecated

func (client *Client) Send(ctx context.Context, method Method, origin string, path string, request SendRequest) (httpResult HttpResult, bodyClose func() error, statusCode int, err error)

Deprecated: Use client.Req Send 发送 query from json 等常见请求

Example
package main

import (
	"context"
	xerr "github.com/goclub/error"

	xhttp "github.com/goclub/http"

	xjson "github.com/goclub/json"
	"log"
	"net/http"
)

func main() {
	{
		log.Print("ExampleClient_Send:query")
		ctx := context.TODO()
		client := xhttp.NewClient(&http.Client{})
		err := func() (err error) {
			httpResult, bodyClose, statusCode, err := client.Send(ctx, xhttp.GET, "https://httpbin.org", "/json", xhttp.SendRequest{
				Query: xhttp.ExampleSendQuery{
					Published: true,
					Limit:     2,
				},
				JSON: map[string]interface{}{"name": "goclub"},
			})
			// 1. 遇到错误向上传递
			if err != nil {
				return
			}
			// 2. bodyClose 防止内存泄露
			defer bodyClose()
			// 3. 检查状态码
			if statusCode != 200 {
				// 状态码错误时候记录日志
				log.Print(httpResult.Dump(true))
				err = xerr.New("http response statusCode != 200")
				return
			}
			// json解码
			var reply xhttp.ExampleReplyPost
			err = httpResult.ReadBody(xjson.Unmarshal, &reply)
			if err != nil {
				// 解码错误时记录日志
				log.Print(httpResult.Dump(true))
				return
			}
			// 响应
			log.Print("reply", reply)
			return
		}()
		xerr.PrintStack(err)
	}
}
Output:

type Context

type Context struct {
	Writer  http.ResponseWriter
	Request *http.Request
	// contains filtered or unexported fields
}

Context 包含 *http.Request http.ResponseWriter 并封装一些便捷方法

func NewContext

func NewContext(w http.ResponseWriter, r *http.Request, router *Router) *Context

func (*Context) AbsURL

func (c *Context) AbsURL() (url string)

AbsURL return "http://domain.com/path?q=1"

func (*Context) BindRequest

func (c *Context) BindRequest(ptr interface{}) error

BindRequest - 绑定请求,支持自定义结构体标签 `query` `form` `param`

func (*Context) CheckError

func (c *Context) CheckError(err error)

CheckError 让 Router{}.OnCatchError 处理传入的错误

func (*Context) CheckPanic

func (c *Context) CheckPanic(r interface{})

CheckPanic 让 Router{}.OnCatchError 处理传入的错误

func (*Context) ClearCookie

func (c *Context) ClearCookie(cookie *http.Cookie)

func (*Context) Cookie

func (c *Context) Cookie(name string) (value *http.Cookie, has bool, err error)

func (*Context) Host

func (c *Context) Host() (host string)

Host return "goclub.run" or "127.0.0.1:1111"

func (*Context) Param

func (c *Context) Param(name string) (param string, err error)

Param - 获取路由参数 比如路由是 xhttp.Route{xhttp.GET, "/user/{userID}"} 当请求 "/user/11" 时候通过 c.Param("userID") 可以获取到 "11"

func (*Context) RealIP

func (c *Context) RealIP() (ip string)

RealIP return "20.205.243.166" - 获取当前请求的客户端ip

func (*Context) Redirect

func (c *Context) Redirect(url string, code int) (err error)

func (*Context) Render

func (c *Context) Render(render func(buffer *bytes.Buffer) error) error

Render 设置 header Content-Type text/html; charset=UTF-8 并输出 buffer

func (*Context) Scheme

func (c *Context) Scheme() (scheme string)

Scheme return "http" or "https", support CDN Forwarded https

func (*Context) SetCookie

func (c *Context) SetCookie(cookie *http.Cookie)

func (*Context) UnmarshalJSONFromQuery

func (c *Context) UnmarshalJSONFromQuery(queryKey string, ptr interface{}) (err error)

UnmarshalJSONFromQuery 从 query 中读取json并解析

func (*Context) WriteBytes

func (c *Context) WriteBytes(b []byte) error

WriteBytes 等同于 writer.Write(data) ,但函数签名返回 error 不返回 int

func (*Context) WriteJSON

func (c *Context) WriteJSON(v interface{}) error

WriteJSON 响应 json

func (*Context) WriteStatusCode

func (c *Context) WriteStatusCode(statusCode int)

type ExampleReplyPost

type ExampleReplyPost struct {
	Slideshow struct {
		Title  string `json:"title"`
		Author string `json:"author"`
		Date   string `json:"date"`
		Slides []struct {
			Title string   `json:"title"`
			Type  string   `json:"type"`
			Items []string `json:"items,omitempty"`
		} `json:"slides"`
	} `json:"slideshow"`
}

type ExampleSendQuery

type ExampleSendQuery struct {
	Published bool
	Limit     int
}

func (ExampleSendQuery) Query

func (r ExampleSendQuery) Query() (string, error)

通过实现结构体 Query() (string, error) 方法后传入 xhttp.SendRequest{}.Query 即可设置请求 query 参数

type Group

type Group struct {
	// contains filtered or unexported fields
}

func (*Group) Handle

func (group *Group) Handle(path string, handler http.Handler)

func (*Group) HandleFunc

func (group *Group) HandleFunc(route Route, handler HandleFunc)

func (*Group) NetHttpHandleFunc

func (group *Group) NetHttpHandleFunc(path string, handler func(w http.ResponseWriter, r *http.Request))

func (*Group) Use

func (group *Group) Use(middleware func(c *Context, next Next) (err error))

type HandleFunc

type HandleFunc func(c *Context) (err error)

type Helper

type Helper struct {
}

func (Helper) JSON

func (Helper) JSON(v interface{}) []byte

type HttpResult

type HttpResult struct {
	Request  *http.Request
	Response *http.Response
	// contains filtered or unexported fields
}

func (HttpResult) Dump

func (v HttpResult) Dump(body ...bool) (dump string)

func (HttpResult) DumpBytes

func (v HttpResult) DumpBytes(body ...bool) (data []byte)

func (HttpResult) Elapsed

func (v HttpResult) Elapsed() time.Duration

func (HttpResult) GetBody

func (v HttpResult) GetBody() (body []byte, err error)

func (HttpResult) ReadBody

func (v HttpResult) ReadBody(unmarshal func(data []byte, v interface{}) error, ptr interface{}) (err error)

func (HttpResult) String

func (v HttpResult) String() (dump string)

type Method

type Method string
const CONNECT Method = "CONNECT"
const DELETE Method = "DELETE"
const GET Method = "GET"
const HEAD Method = "HEAD"
const OPTIONS Method = "OPTIONS"
const PATCH Method = "PATCH"
const POST Method = "POST"
const PUT Method = "PUT"
const TRACE Method = "TRACE"

func (Method) String

func (m Method) String() string

type Mock

type Mock struct {
	Route               Route                              `note:"路由"`
	Request             MockRequest                        `note:"请求"`
	DisableDefaultReply string                             `note:"指定禁用默认响应的key"`
	Reply               MockReply                          `note:"响应"`
	Match               func(c *Context) (replyKey string) `note:"根据请求参数决定响应结果"`
	MaxAutoCount        int64                              `note:"最大计数,默认5"`
	HandleFunc          func(c *Context, replyKey string, data interface{}) (err error)
	Render              string
}

type MockRender

type MockRender interface {
	Render(templatePath string, data interface{}, w http.ResponseWriter) error
}

type MockReply

type MockReply map[string]interface{}

type MockRequest

type MockRequest map[string]interface{}

type MockServer

type MockServer struct {
	// contains filtered or unexported fields
}

func NewMockServer

func NewMockServer(option MockServerOption) MockServer

func (MockServer) Handle

func (server MockServer) Handle(path string, handler http.Handler)

func (MockServer) Listen

func (server MockServer) Listen(port int)

func (MockServer) PrefixHandle

func (server MockServer) PrefixHandle(prefix string, handler http.Handler)

func (MockServer) URL

func (ms MockServer) URL(mock Mock)

type MockServerOption

type MockServerOption struct {
	DefaultReply map[string]interface{}
	OnlineMock   string
	Render       MockRender
}

type Next

type Next func() error

type Req

type Req struct {
	QueryEncode        func(q url.Values) (encode string)
	FormUrlencoded     func(f url.Values) (encode string)
	FormData           func(w *multipart.Writer) (err error)
	Header             func(h http.Header) http.Header
	JSON               interface{}
	Body               io.Reader
	Debug              bool
	Before             func(r *http.Request) (err error)
	NotCheckStatusCode bool
	Defer              func(result HttpResult, err error)
}

type RequestMarshaler

type RequestMarshaler interface {
	MarshalRequest(value string) error
}

type RequestRetry

type RequestRetry struct {
	Times        uint8
	Interval     time.Duration                                                  `eg:"time.Millisecond*100"`
	BackupOrigin string                                                         `note:"灾备接口域名必须以 http:// 或 https:// 开头"`
	Check        func(resp *http.Response, requestErr error) (shouldRetry bool) `note:"if Check == nil { Check = xhttp.DefaultRequestRetryCheck }"`
}

type RequestUnmarshaler

type RequestUnmarshaler interface {
	UnmarshalRequest() (string, error)
}

type Response

type Response struct {
	HttpResponse *http.Response
	// contains filtered or unexported fields
}

func (*Response) BindJSON

func (resp *Response) BindJSON(statusCode int, v interface{})

func (*Response) Bytes

func (resp *Response) Bytes(statusCode int) []byte

func (*Response) ExpectJSON

func (resp *Response) ExpectJSON(statusCode int, reply interface{})

func (*Response) ExpectString

func (resp *Response) ExpectString(statusCode int, s string)

func (*Response) String

func (resp *Response) String(statusCode int) string

type Route

type Route struct {
	Method Method
	Path   string
}

func (Route) Equal

func (p Route) Equal(target Route) bool

func (Route) ID

func (route Route) ID() string

type Router

type Router struct {
	OnCatchError func(c *Context, err error) error
	OnCatchPanic func(c *Context, recoverValue interface{}) error
	// contains filtered or unexported fields
}

func NewRouter

func NewRouter(opt RouterOption) *Router

func (*Router) FileServer

func (router *Router) FileServer(prefix string, dir string, noCache bool, middleware func(c *Context, next Next) (err error))

example: dir := path.Join(os.Getenv("GOPATH"), "src/github.com/goclub/http/example/internal/gin/public") defer r.FileServer("/public", dir, true) noCache 不使用缓存

func (*Router) Group

func (serve *Router) Group() *Group

func (*Router) Handle

func (serve *Router) Handle(path string, handler http.Handler)

func (*Router) HandleFunc

func (serve *Router) HandleFunc(route Route, handler HandleFunc)

func (Router) LogPatterns

func (router Router) LogPatterns(server *http.Server)

func (*Router) NetHttpHandleFunc

func (serve *Router) NetHttpHandleFunc(path string, handler func(w http.ResponseWriter, r *http.Request))

func (Router) PrefixHandler

func (router Router) PrefixHandler(prefix string, handler http.Handler)

func (Router) ServeHTTP

func (router Router) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*Router) Use

func (serve *Router) Use(middleware func(c *Context, next Next) (err error))

type RouterOption

type RouterOption struct {
	OnCatchError func(c *Context, err error) error
	OnCatchPanic func(c *Context, recoverValue interface{}) error
	// Configurable Handler to be used when no route matches.
	NotFoundHandler http.Handler
	// Configurable Handler to be used when the request method does not match the route.
	MethodNotAllowedHandler http.Handler
}

type SendRequest

type SendRequest struct {
	Query interface {
		Query() (string, error)
	}
	FormUrlencoded interface {
		FormUrlencoded() (string, error)
	}
	FormData interface {
		FormData(w *multipart.Writer) (err error)
	}
	Header interface {
		Header() (http.Header, error)
	}
	JSON       interface{}
	Body       io.Reader
	Debug      bool
	Retry      RequestRetry
	BeforeSend func(r *http.Request) (err error)
	// DoNotReturnRequestBody 控制返回的 httpResult.Request{}.Body 是否为空
	// 在请求 Body 大时可以设置为 true 以提高性能
	DoNotReturnRequestBody bool
}

func (SendRequest) HttpRequest

func (request SendRequest) HttpRequest(ctx context.Context, method Method, requestURL string) (httpRequest *http.Request, err error)

type Test

type Test struct {
	// contains filtered or unexported fields
}

func NewTest

func NewTest(t *testing.T, router *Router) Test

func (*Test) Request

func (test *Test) Request(r *http.Request) (resp *Response)

func (Test) RequestJSON

func (test Test) RequestJSON(route Route, jsonValue interface{}) (resp *Response)

Directories

Path Synopsis
example

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL