Compare commits

..

5 Commits

14 changed files with 254 additions and 128 deletions

View File

@ -14,13 +14,13 @@
[![Code Helper](https://www.codetriage.com/gogf/gf/badges/users.svg)](https://www.codetriage.com/gogf/gf)
-->
`GF(GoFrame)` is a modular, lightweight, loosely coupled, high performance application development framework written in Go. Supporting graceful server, hot updates, multi-domain, multi-port, multi-service, HTTP/HTTPS, dynamic/hook routing and many more features. Providing a series of core components and dozens of practical modules.
`GF(GoFrame)` is a modular, loose-coupled and production-ready application development framework written in Go. Providing a series of core components and dozens of practical modules, such as: cache, logging, array/queue/set/map, timer/timing tasks, file/memory lock, object pool, validator, database ORM, etc. Supporting web server with graceful server, hot updates, multi-domain, multi-port, multi-service, HTTP/HTTPS, dynamic/hook routing, rewrite rules and many more features.
# Installation
```
go get -u github.com/gogf/gf
```
or use `go.mod`
or use `go.mod`:
```
require github.com/gogf/gf latest
```
@ -32,6 +32,7 @@ golang version >= 1.9.2
# Documentation
* [GoDoc](https://godoc.org/github.com/gogf/gf)
* [中文文档](https://goframe.org)
# Architecture
@ -92,6 +93,8 @@ func main() {
# Donators
<a href="https://gitee.com/tiangenglan" target="_blank" title="zhuhuan12"><img src="https://images.gitee.com/uploads/99/1167099_tiangenglan.png" width="60" align="left"></a>
<a href="https://gitee.com/zhuhuan12" target="_blank" title="zhuhuan12"><img src="https://gitee.com/uploads/39/751839_zhuhuan12.png" width="60" align="left"></a>
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>

View File

@ -32,7 +32,7 @@
go get -u github.com/gogf/gf
```
或者
`go.mod`
`go.mod`:
```
require github.com/gogf/gf latest
```
@ -100,6 +100,8 @@ func main() {
# 捐赠者
<a href="https://gitee.com/tiangenglan" target="_blank" title="zhuhuan12"><img src="https://images.gitee.com/uploads/99/1167099_tiangenglan.png" width="60" align="left"></a>
<a href="https://gitee.com/zhuhuan12" target="_blank" title="zhuhuan12"><img src="https://gitee.com/uploads/39/751839_zhuhuan12.png" width="60" align="left"></a>
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>

View File

@ -9,44 +9,44 @@
package ghttp
func Get(url string) (*ClientResponse, error) {
return DoRequest("GET", url, []byte(""))
return DoRequest("GET", url)
}
func Put(url, data string) (*ClientResponse, error) {
return DoRequest("PUT", url, []byte(data))
func Put(url string, data...string) (*ClientResponse, error) {
return DoRequest("PUT", url, data...)
}
func Post(url, data string) (*ClientResponse, error) {
return DoRequest("POST", url, []byte(data))
func Post(url string, data...string) (*ClientResponse, error) {
return DoRequest("POST", url, data...)
}
func Delete(url, data string) (*ClientResponse, error) {
return DoRequest("DELETE", url, []byte(data))
func Delete(url string, data...string) (*ClientResponse, error) {
return DoRequest("DELETE", url, data...)
}
func Head(url, data string) (*ClientResponse, error) {
return DoRequest("HEAD", url, []byte(data))
func Head(url string, data...string) (*ClientResponse, error) {
return DoRequest("HEAD", url, data...)
}
func Patch(url, data string) (*ClientResponse, error) {
return DoRequest("PATCH", url, []byte(data))
func Patch(url string, data...string) (*ClientResponse, error) {
return DoRequest("PATCH", url, data...)
}
func Connect(url, data string) (*ClientResponse, error) {
return DoRequest("CONNECT", url, []byte(data))
func Connect(url string, data...string) (*ClientResponse, error) {
return DoRequest("CONNECT", url, data...)
}
func Options(url, data string) (*ClientResponse, error) {
return DoRequest("OPTIONS", url, []byte(data))
func Options(url string, data...string) (*ClientResponse, error) {
return DoRequest("OPTIONS", url, data...)
}
func Trace(url, data string) (*ClientResponse, error) {
return DoRequest("TRACE", url, []byte(data))
func Trace(url string, data...string) (*ClientResponse, error) {
return DoRequest("TRACE", url, data...)
}
// 该方法支持二进制提交数据
func DoRequest(method, url string, data []byte) (*ClientResponse, error) {
return NewClient().DoRequest(method, url, data)
func DoRequest(method, url string, data...string) (*ClientResponse, error) {
return NewClient().DoRequest(method, url, data...)
}
// GET请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)

View File

@ -76,26 +76,30 @@ func (c *Client) SetBasicAuth(user, pass string) {
// GET请求
func (c *Client) Get(url string) (*ClientResponse, error) {
return c.DoRequest("GET", url, []byte(""))
return c.DoRequest("GET", url)
}
// PUT请求
func (c *Client) Put(url, data string) (*ClientResponse, error) {
return c.DoRequest("PUT", url, []byte(data))
func (c *Client) Put(url string, data...string) (*ClientResponse, error) {
return c.DoRequest("PUT", url, data...)
}
// POST请求提交数据默认使用表单方式提交数据(绝大部分场景下也是如此)。
// 如果服务端对Content-Type有要求可使用Client对象进行请求单独设置相关属性。
// 支持文件上传需要字段格式为FieldName=@file:
func (c *Client) Post(url, data string) (*ClientResponse, error) {
func (c *Client) Post(url string, data...string) (*ClientResponse, error) {
if len(c.prefix) > 0 {
url = c.prefix + url
}
param := ""
if len(data) > 0 {
param = data[0]
}
req := (*http.Request)(nil)
if strings.Contains(data, "@file:") {
if strings.Contains(param, "@file:") {
buffer := new(bytes.Buffer)
writer := multipart.NewWriter(buffer)
for _, item := range strings.Split(data, "&") {
for _, item := range strings.Split(param, "&") {
array := strings.Split(item, "=")
if len(array[1]) > 6 && strings.Compare(array[1][0:6], "@file:") == 0 {
path := array[1][6:]
@ -126,7 +130,7 @@ func (c *Client) Post(url, data string) (*ClientResponse, error) {
req.Header.Set("Content-Type", writer.FormDataContentType())
}
} else {
if r, err := http.NewRequest("POST", url, bytes.NewReader([]byte(data))); err != nil {
if r, err := http.NewRequest("POST", url, bytes.NewReader([]byte(param))); err != nil {
return nil, err
} else {
req = r
@ -154,28 +158,28 @@ func (c *Client) Post(url, data string) (*ClientResponse, error) {
}
// DELETE请求
func (c *Client) Delete(url, data string) (*ClientResponse, error) {
return c.DoRequest("DELETE", url, []byte(data))
func (c *Client) Delete(url string, data...string) (*ClientResponse, error) {
return c.DoRequest("DELETE", url, data...)
}
func (c *Client) Head(url, data string) (*ClientResponse, error) {
return c.DoRequest("HEAD", url, []byte(data))
func (c *Client) Head(url string, data...string) (*ClientResponse, error) {
return c.DoRequest("HEAD", url, data...)
}
func (c *Client) Patch(url, data string) (*ClientResponse, error) {
return c.DoRequest("PATCH", url, []byte(data))
func (c *Client) Patch(url string, data...string) (*ClientResponse, error) {
return c.DoRequest("PATCH", url, data...)
}
func (c *Client) Connect(url, data string) (*ClientResponse, error) {
return c.DoRequest("CONNECT", url, []byte(data))
func (c *Client) Connect(url string, data...string) (*ClientResponse, error) {
return c.DoRequest("CONNECT", url, data...)
}
func (c *Client) Options(url, data string) (*ClientResponse, error) {
return c.DoRequest("OPTIONS", url, []byte(data))
func (c *Client) Options(url string, data...string) (*ClientResponse, error) {
return c.DoRequest("OPTIONS", url, data...)
}
func (c *Client) Trace(url, data string) (*ClientResponse, error) {
return c.DoRequest("TRACE", url, []byte(data))
func (c *Client) Trace(url string, data...string) (*ClientResponse, error) {
return c.DoRequest("TRACE", url, data...)
}
// GET请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
@ -220,11 +224,7 @@ func (c *Client) TraceContent(url string, data...string) string {
// 请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
func (c *Client) DoRequestContent(method string, url string, data...string) string {
content := ""
if len(data) > 0 {
content = data[0]
}
response, err := c.DoRequest(method, url, []byte(content))
response, err := c.DoRequest(method, url, data...)
if err != nil {
return ""
}
@ -233,14 +233,18 @@ func (c *Client) DoRequestContent(method string, url string, data...string) stri
}
// 请求并返回response对象该方法支持二进制提交数据
func (c *Client) DoRequest(method, url string, data []byte) (*ClientResponse, error) {
func (c *Client) DoRequest(method, url string, data...string) (*ClientResponse, error) {
if strings.EqualFold("POST", method) {
return c.Post(url, string(data))
return c.Post(url, data...)
}
if len(c.prefix) > 0 {
url = c.prefix + url
}
req, err := http.NewRequest(strings.ToUpper(method), url, bytes.NewReader(data))
param := ""
if len(data) > 0 {
param = data[0]
}
req, err := http.NewRequest(strings.ToUpper(method), url, bytes.NewReader([]byte(param)))
if err != nil {
return nil, err
}

View File

@ -17,7 +17,7 @@ type ClientResponse struct {
http.Response
}
// 获取返回的数据
// 获取返回的数据(二进制).
func (r *ClientResponse) ReadAll() []byte {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
@ -26,6 +26,11 @@ func (r *ClientResponse) ReadAll() []byte {
return body
}
// 获取返回的数据(字符串).
func (r *ClientResponse) ReadAllString() string {
return string(r.ReadAll())
}
// 关闭返回的HTTP链接
func (r *ClientResponse) Close() {
r.Response.Close = true

View File

@ -32,7 +32,6 @@ func newResponse(s *Server, w http.ResponseWriter) *Response {
Server : s,
ResponseWriter : ResponseWriter {
ResponseWriter : w,
Status : http.StatusOK,
buffer : bytes.NewBuffer(nil),
},
}
@ -137,9 +136,8 @@ func (r *Response) WriteStatus(status int, content...string) {
if status != http.StatusOK {
if f := r.request.Server.getStatusHandler(status, r.request); f != nil {
f(r.request)
// 如果是http.StatusOK那么表示回调函数内部没有设置header status
// 那么这里就可以设置status防止多次设置(http: multiple response.WriteHeader calls)
if r.Status == http.StatusOK {
// 防止多次设置(http: multiple response.WriteHeader calls)
if r.Status == 0 {
r.WriteHeader(status)
}
return

View File

@ -46,14 +46,28 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
request := newRequest(s, r, w)
defer func() {
if request.LeaveTime == 0 {
request.LeaveTime = gtime.Microsecond()
// 设置请求完成时间
request.LeaveTime = gtime.Microsecond()
// 事件 - BeforeOutput
if !request.IsExited() {
s.callHookHandler(HOOK_BEFORE_OUTPUT, request)
}
// 输出Cookie
request.Cookie.Output()
// 输出缓冲区
request.Response.OutputBuffer()
// 事件 - AfterOutput
if !request.IsExited() {
s.callHookHandler(HOOK_AFTER_OUTPUT, request)
}
// 事件 - BeforeClose
s.callHookHandler(HOOK_BEFORE_CLOSE, request)
// access log
s.handleAccessLog(request)
// error log使用recover进行判断
if e := recover(); e != nil {
request.Response.WriteStatus(http.StatusInternalServerError)
s.handleErrorLog(e, request)
}
// 更新Session会话超时时间
@ -125,22 +139,6 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
if !request.IsExited() {
s.callHookHandler(HOOK_AFTER_SERVE, request)
}
// 设置请求完成时间
request.LeaveTime = gtime.Microsecond()
// 事件 - BeforeOutput
if !request.IsExited() {
s.callHookHandler(HOOK_BEFORE_OUTPUT, request)
}
// 输出Cookie
request.Cookie.Output()
// 输出缓冲区
request.Response.OutputBuffer()
// 事件 - AfterOutput
if !request.IsExited() {
s.callHookHandler(HOOK_AFTER_OUTPUT, request)
}
}
// 查找静态文件的绝对路径

View File

@ -10,7 +10,6 @@ package ghttp
import (
"fmt"
"github.com/gogf/gf/g/os/gfile"
"net/http"
)
// 处理服务错误信息主要是panichttp请求的status由access log进行管理
@ -34,8 +33,6 @@ func (s *Server) handleAccessLog(r *Request) {
// 处理服务错误信息主要是panichttp请求的status由access log进行管理
func (s *Server) handleErrorLog(error interface{}, r *Request) {
r.Response.WriteStatus(http.StatusInternalServerError)
// 错误输出默认是开启的
if !s.IsErrorLogEnabled() && gfile.MainPkgPath() == "" {
return

View File

@ -17,6 +17,7 @@ import (
)
// 基本路由功能测试
func Test_Router_Basic(t *testing.T) {
s := g.Server(gtime.Nanosecond())
s.BindHandler("/:name", func(r *ghttp.Request){
@ -53,3 +54,155 @@ func Test_Router_Basic(t *testing.T) {
gtest.Assert(client.GetContent("/user/list/100.html"), "100")
})
}
// 测试HTTP Method注册.
func Test_Router_Method(t *testing.T) {
s := g.Server(gtime.Nanosecond())
s.BindHandler("GET:/get", func(r *ghttp.Request){
})
s.BindHandler("POST:/post", func(r *ghttp.Request){
})
s.SetPort(8105)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8105")
resp1, err := client.Get("/get")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 200)
resp2, err := client.Post("/get")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 404)
resp3, err := client.Get("/post")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 404)
resp4, err := client.Post("/post")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 200)
})
}
// 测试状态返回.
func Test_Router_Status(t *testing.T) {
s := g.Server(gtime.Nanosecond())
s.BindHandler("/200", func(r *ghttp.Request){
r.Response.WriteStatus(200)
})
s.BindHandler("/300", func(r *ghttp.Request){
r.Response.WriteStatus(300)
})
s.BindHandler("/400", func(r *ghttp.Request){
r.Response.WriteStatus(400)
})
s.BindHandler("/500", func(r *ghttp.Request){
r.Response.WriteStatus(500)
})
s.SetPort(8110)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8110")
resp1, err := client.Get("/200")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 200)
resp2, err := client.Get("/300")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 300)
resp3, err := client.Get("/400")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 400)
resp4, err := client.Get("/500")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 500)
})
}
// 自定义状态码处理.
func Test_Router_CustomStatusHandler(t *testing.T) {
s := g.Server(gtime.Nanosecond())
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Write("hello")
})
s.BindStatusHandler(404, func(r *ghttp.Request){
r.Response.Write("404 page")
})
s.SetPort(8120)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8120")
gtest.Assert(client.GetContent("/"), "hello")
resp, err := client.Get("/ThisDoesNotExist")
defer resp.Close()
gtest.Assert(err, nil)
gtest.Assert(resp.StatusCode, 404)
gtest.Assert(resp.ReadAllString(), "404 page")
})
}
// 测试不存在的路由.
func Test_Router_404(t *testing.T) {
s := g.Server(gtime.Nanosecond())
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Write("hello")
})
s.SetPort(8130)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8130")
gtest.Assert(client.GetContent("/"), "hello")
resp, err := client.Get("/ThisDoesNotExist")
defer resp.Close()
gtest.Assert(err, nil)
gtest.Assert(resp.StatusCode, 404)
})
}

View File

@ -82,6 +82,11 @@ func Test_Router_Group1(t *testing.T) {
gtest.Assert(client.GetContent ("/api/obj/show"), "Object Show")
gtest.Assert(client.DeleteContent("/api/obj/rest"), "Object REST Delete")
// 测试404
resp, err := client.Get("/ThisDoesNotExist")
defer resp.Close()
gtest.Assert(err, nil)
gtest.Assert(resp.StatusCode, 404)
})
}
@ -122,5 +127,11 @@ func Test_Router_Group2(t *testing.T) {
gtest.Assert(client.GetContent ("/api/obj/my-show"), "Object Show")
gtest.Assert(client.GetContent ("/api/obj/show"), "Object Show")
gtest.Assert(client.DeleteContent("/api/obj/rest"), "Object REST Delete")
// 测试404
resp, err := client.Get("/ThisDoesNotExist")
defer resp.Close()
gtest.Assert(err, nil)
gtest.Assert(resp.StatusCode, 404)
})
}

View File

@ -88,25 +88,6 @@ func UcWords(str string) string {
return strings.Title(str)
}
// Traverse the array to find the string index position, if not exist, return-1.
//
// 遍历数组查找字符串索引位置,如果不存在则返回-1使用完整遍历查找.
func SearchArray (a []string, s string) int {
for i, v := range a {
if s == v {
return i
}
}
return -1
}
// InArray tests whether the given string s is in string array a.
//
// 判断字符串是否在数组中
func InArray (a []string, s string) bool {
return SearchArray(a, s) != -1
}
// IsLetterLower tests whether the given byte b is in lower case.
//
// 判断给定字符是否小写
@ -490,15 +471,7 @@ func Explode(delimiter, str string) []string {
//
// 用glue将字符串数组pieces连接为一个字符串。
func Implode(glue string, pieces []string) string {
var buf bytes.Buffer
l := len(pieces)
for _, str := range pieces {
buf.WriteString(str)
if l--; l > 0 {
buf.WriteString(glue)
}
}
return buf.String()
return strings.Join(pieces, glue)
}
// Generate a single-byte string from a number.

View File

@ -68,26 +68,6 @@ func Test_UcWords(t *testing.T) {
})
}
func Test_SearchArray(t *testing.T) {
gtest.Case(t, func() {
array := []string{"a", "b", "c"}
gtest.Assert(gstr.SearchArray(array, "a"), 0)
gtest.Assert(gstr.SearchArray(array, "b"), 1)
gtest.Assert(gstr.SearchArray(array, "c"), 2)
gtest.Assert(gstr.SearchArray(array, "d"), -1)
})
}
func Test_InArray(t *testing.T) {
gtest.Case(t, func() {
array := []string{"a", "b", "c"}
gtest.Assert(gstr.InArray(array, "a"), true)
gtest.Assert(gstr.InArray(array, "b"), true)
gtest.Assert(gstr.InArray(array, "c"), true)
gtest.Assert(gstr.InArray(array, "d"), false)
})
}
func Test_IsLetterLower(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gstr.IsLetterLower('a'), true)

View File

@ -2,11 +2,13 @@ package main
import (
"fmt"
"github.com/gogf/gf/g/os/gfile"
)
func main() {
f, e := gfile.Open("/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/third")
fmt.Println(e)
fmt.Println(f)
s := "abc我是中国人é"
fmt.Println(len(s))
for i := 0; i < len(s); i++ {
fmt.Println(s[i])
}
}

View File

@ -1,5 +1,5 @@
package gf
const VERSION = "v1.5.0"
const VERSION = "v1.5.3"
const AUTHORS = "john<john@goframe.org>"