From 24ce4d098e3c923450f8a8384b58f44f97592b6f Mon Sep 17 00:00:00 2001 From: John Date: Mon, 31 Dec 2018 00:22:18 +0800 Subject: [PATCH] ghttp.Client updates; add more unit test cases for web server --- ...request.go => ghttp_client_request_api.go} | 0 g/net/ghttp/ghttp_client_request_client.go | 281 ++++++++++++++++++ g/net/ghttp/ghttp_unit_3_test.go | 114 +++++++ 3 files changed, 395 insertions(+) rename g/net/ghttp/{ghttp_client_request.go => ghttp_client_request_api.go} (100%) create mode 100644 g/net/ghttp/ghttp_client_request_client.go create mode 100644 g/net/ghttp/ghttp_unit_3_test.go diff --git a/g/net/ghttp/ghttp_client_request.go b/g/net/ghttp/ghttp_client_request_api.go similarity index 100% rename from g/net/ghttp/ghttp_client_request.go rename to g/net/ghttp/ghttp_client_request_api.go diff --git a/g/net/ghttp/ghttp_client_request_client.go b/g/net/ghttp/ghttp_client_request_client.go new file mode 100644 index 000000000..ffd6b16f1 --- /dev/null +++ b/g/net/ghttp/ghttp_client_request_client.go @@ -0,0 +1,281 @@ +// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://gitee.com/johng/gf. + +// HTTP客户端请求. + +package ghttp + +import ( + "time" + "bytes" + "strings" + "net/http" + "mime/multipart" + "os" + "io" + "gitee.com/johng/gf/g/os/gfile" + "errors" + "fmt" +) + +// http客户端 +type Client struct { + http.Client // 底层http client对象 + header map[string]string // HEADER信息Map + authUser string // HTTP基本权限设置:名称 + authPass string // HTTP基本权限设置:密码 +} + +// http客户端对象指针 +func NewClient() (*Client) { + return &Client{ + Client : http.Client { + Transport: &http.Transport { + DisableKeepAlives: true, + }, + }, + header : make(map[string]string), + } +} + +// 设置HTTP Header +func (c *Client) SetHeader(key, value string) { + c.header[key] = value +} + +// 设置请求过期时间 +func (c *Client) SetTimeOut(t time.Duration) { + c.Timeout = t +} + +// 设置HTTP访问账号密码 +func (c *Client) SetBasicAuth(user, pass string) { + c.authUser = user + c.authPass = pass +} + +// GET请求 +func (c *Client) Get(url string) (*ClientResponse, error) { + return c.DoRequest("GET", url, []byte("")) +} + +// PUT请求 +func (c *Client) Put(url, data string) (*ClientResponse, error) { + return c.DoRequest("PUT", url, []byte(data)) +} + +// POST请求提交数据,默认使用表单方式提交数据(绝大部分场景下也是如此)。 +// 如果服务端对Content-Type有要求,可使用Client对象进行请求,单独设置相关属性。 +// 支持文件上传,需要字段格式为:FieldName=@file: +func (c *Client) Post(url, data string) (*ClientResponse, error) { + var req *http.Request + if strings.Contains(data, "@file:") { + buffer := new(bytes.Buffer) + writer := multipart.NewWriter(buffer) + for _, item := range strings.Split(data, "&") { + array := strings.Split(item, "=") + if len(array[1]) > 6 && strings.Compare(array[1][0:6], "@file:") == 0 { + path := array[1][6:] + if !gfile.Exists(path) { + return nil, errors.New(fmt.Sprintf(`"%s" does not exist`, path)) + } + if file, err := writer.CreateFormFile(array[0], path); err == nil { + if f, err := os.Open(path); err == nil { + defer f.Close() + if _, err = io.Copy(file, f); err != nil { + return nil, err + } + } else { + return nil, err + } + } else { + return nil, err + } + } else { + writer.WriteField(array[0], array[1]) + } + } + writer.Close() + if r, err := http.NewRequest("POST", url, buffer); err != nil { + return nil, err + } else { + req = r + req.Header.Set("Content-Type", writer.FormDataContentType()) + } + } else { + if r, err := http.NewRequest("POST", url, bytes.NewReader([]byte(data))); err != nil { + return nil, err + } else { + req = r + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } + } + // 自定义header + if len(c.header) > 0 { + for k, v := range c.header { + req.Header.Set(k, v) + } + } + // HTTP账号密码 + if len(c.authUser) > 0 { + req.SetBasicAuth(c.authUser, c.authPass) + } + // 执行请求 + resp, err := c.Do(req) + if err != nil { + return nil, err + } + r := &ClientResponse{} + r.Response = *resp + return r, nil +} + +// DELETE请求 +func (c *Client) Delete(url, data string) (*ClientResponse, error) { + return c.DoRequest("DELETE", url, []byte(data)) +} + +func (c *Client) Head(url, data string) (*ClientResponse, error) { + return c.DoRequest("HEAD", url, []byte(data)) +} + +func (c *Client) Patch(url, data string) (*ClientResponse, error) { + return c.DoRequest("PATCH", url, []byte(data)) +} + +func (c *Client) Connect(url, data string) (*ClientResponse, error) { + return c.DoRequest("CONNECT", url, []byte(data)) +} + +func (c *Client) Options(url, data string) (*ClientResponse, error) { + return c.DoRequest("OPTIONS", url, []byte(data)) +} + +func (c *Client) Trace(url, data string) (*ClientResponse, error) { + return c.DoRequest("TRACE", url, []byte(data)) +} + +// 请求并返回response对象,该方法支持二进制提交数据 +func (c *Client) DoRequest(method, url string, data []byte) (*ClientResponse, error) { + if strings.Compare("POST", strings.ToUpper(method)) == 0 { + return c.Post(url, string(data)) + } + req, err := http.NewRequest(strings.ToUpper(method), url, bytes.NewReader(data)) + if err != nil { + return nil, err + } + // 自定义header + if len(c.header) > 0 { + for k, v := range c.header { + req.Header.Set(k, v) + } + } + // 执行请求 + resp, err := c.Do(req) + if err != nil { + return nil, err + } + r := &ClientResponse{} + r.Response = *resp + return r, nil +} + + +func Get(url string) (*ClientResponse, error) { + return DoRequest("GET", url, []byte("")) +} + +func Put(url, data string) (*ClientResponse, error) { + return DoRequest("PUT", url, []byte(data)) +} + +func Post(url, data string) (*ClientResponse, error) { + return DoRequest("POST", url, []byte(data)) +} + +func Delete(url, data string) (*ClientResponse, error) { + return DoRequest("DELETE", url, []byte(data)) +} + +func Head(url, data string) (*ClientResponse, error) { + return DoRequest("HEAD", url, []byte(data)) +} + +func Patch(url, data string) (*ClientResponse, error) { + return DoRequest("PATCH", url, []byte(data)) +} + +func Connect(url, data string) (*ClientResponse, error) { + return DoRequest("CONNECT", url, []byte(data)) +} + +func Options(url, data string) (*ClientResponse, error) { + return DoRequest("OPTIONS", url, []byte(data)) +} + +func Trace(url, data string) (*ClientResponse, error) { + return DoRequest("TRACE", url, []byte(data)) +} + +// 该方法支持二进制提交数据 +func DoRequest(method, url string, data []byte) (*ClientResponse, error) { + return NewClient().DoRequest(method, url, data) +} + +// GET请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针) +func GetContent(url string, data...string) string { + return RequestContent("GET", url, data...) +} + +// PUT请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针) +func PutContent(url string, data...string) string { + return RequestContent("PUT", url, data...) +} + +// POST请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针) +func PostContent(url string, data...string) string { + return RequestContent("POST", url, data...) +} + +// DELETE请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针) +func DeleteContent(url string, data...string) string { + return RequestContent("DELETE", url, data...) +} + +func HeadContent(url string, data...string) string { + return RequestContent("HEAD", url, data...) +} + +func PatchContent(url string, data...string) string { + return RequestContent("PATCH", url, data...) +} + +func ConnectContent(url string, data...string) string { + return RequestContent("CONNECT", url, data...) +} + +func OptionsContent(url string, data...string) string { + return RequestContent("OPTIONS", url, data...) +} + +func TraceContent(url string, data...string) string { + return RequestContent("TRACE", url, data...) +} + +// 请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针) +func RequestContent(method string, url string, data...string) string { + content := "" + if len(data) > 0 { + content = data[0] + } + response, err := NewClient().DoRequest(method, url, []byte(content)) + if err != nil { + return "" + } + defer response.Close() + return string(response.ReadAll()) +} + diff --git a/g/net/ghttp/ghttp_unit_3_test.go b/g/net/ghttp/ghttp_unit_3_test.go new file mode 100644 index 000000000..7b06746dd --- /dev/null +++ b/g/net/ghttp/ghttp_unit_3_test.go @@ -0,0 +1,114 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://gitee.com/johng/gf. + +// 分组路由测试 +package ghttp_test + +import ( + "gitee.com/johng/gf/g" + "gitee.com/johng/gf/g/frame/gmvc" + "gitee.com/johng/gf/g/net/ghttp" + "gitee.com/johng/gf/g/os/gtime" + "gitee.com/johng/gf/g/util/gtest" + "testing" + "time" +) + +// 执行对象 +type Object struct {} + +func (o *Object) Show(r *ghttp.Request) { + r.Response.Write("Object Show") +} + +func (o *Object) Delete(r *ghttp.Request) { + r.Response.Write("Object REST Delete") +} + +// 控制器 +type Controller struct { + gmvc.Controller +} + +func (c *Controller) Show() { + c.Response.Write("Controller Show") +} + +func (c *Controller) Post() { + c.Response.Write("Controller REST Post") +} + +func Handler(r *ghttp.Request) { + r.Response.Write("Handler") +} + +func Test_Router_Group1(t *testing.T) { + s := g.Server(gtime.Nanosecond()) + obj := new(Object) + ctl := new(Controller) + // 分组路由方法注册 + g := s.Group("/api") + g.ALL ("/handler", Handler) + g.ALL ("/ctl", ctl) + g.GET ("/ctl/my-show", ctl, "Show") + g.REST("/ctl/rest", ctl) + g.ALL ("/obj", obj) + g.GET ("/obj/my-show", obj, "Show") + g.REST("/obj/rest", obj) + s.SetPort(8199) + s.SetDumpRouteMap(false) + go s.Run() + defer s.Shutdown() + time.Sleep(time.Second) + gtest.Case(func() { + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/handler"), "Handler") + + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/ctl/my-show"), "Controller Show") + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/ctl/post"), "Controller REST Post") + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/ctl/show"), "Controller Show") + gtest.Assert(ghttp.PostContent("http://127.0.0.1:8199/api/ctl/rest"), "Controller REST Post") + + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/obj/delete"), "Object REST Delete") + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/obj/my-show"), "Object Show") + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/obj/show"), "Object Show") + gtest.Assert(ghttp.DeleteContent("http://127.0.0.1:8199/api/obj/rest"), "Object REST Delete") + + }) +} + +func Test_Router_Group2(t *testing.T) { + s := g.Server(gtime.Nanosecond()) + obj := new(Object) + ctl := new(Controller) + // 分组路由批量注册 + s.Group("/api").Bind("/api", []ghttp.GroupItem{ + {"ALL", "/handler", Handler}, + {"ALL", "/ctl", ctl}, + {"GET", "/ctl/my-show", ctl, "Show"}, + {"REST", "/ctl/rest", ctl}, + {"ALL", "/obj", obj}, + {"GET", "/obj/my-show", obj, "Show"}, + {"REST", "/obj/rest", obj}, + }) + s.SetPort(8199) + s.SetDumpRouteMap(false) + go s.Run() + defer s.Shutdown() + time.Sleep(time.Second) + gtest.Case(func() { + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/handler"), "Handler") + + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/ctl/my-show"), "Controller Show") + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/ctl/post"), "Controller REST Post") + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/ctl/show"), "Controller Show") + gtest.Assert(ghttp.PostContent("http://127.0.0.1:8199/api/ctl/rest"), "Controller REST Post") + + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/obj/delete"), "Object REST Delete") + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/obj/my-show"), "Object Show") + gtest.Assert(ghttp.GetContent ("http://127.0.0.1:8199/api/obj/show"), "Object Show") + gtest.Assert(ghttp.DeleteContent("http://127.0.0.1:8199/api/obj/rest"), "Object REST Delete") + }) +}