Compare commits

...

55 Commits

Author SHA1 Message Date
edb745ed92 version updates 2020-05-07 11:31:34 +08:00
61f4e0da6f improve package gcache 2020-05-06 21:09:02 +08:00
767afa3106 improve package gcache 2020-05-06 19:06:49 +08:00
16779359ee remove package gchan 2020-05-06 18:43:19 +08:00
770653fafa enable the route dump for unit testing of logging feature for ghttp.Server 2020-05-05 00:09:39 +08:00
0c021b6da7 add unit testing case of status handler feature for ghttp.Server 2020-05-05 00:01:09 +08:00
ec92b08f25 add function Maps for package gvar; add function GetMaps for package gjson; improve MapToMap* functions for package gconv 2020-05-04 23:42:51 +08:00
13e2353729 fix lock issue in function search for package garray 2020-05-03 23:08:18 +08:00
90d19a84ce improve package garray/gset, adding function ContainsI 2020-05-03 22:52:04 +08:00
b4c0b95d8a add support for old version of gres 2020-05-03 22:16:12 +08:00
b7e8255066 add support for old version of gre 2020-05-03 22:08:08 +08:00
5f8e6ad9ed comment update for package ghttp 2020-05-01 03:31:04 +08:00
c8d253eb56 change binary content from hex string to base64 string for package gres 2020-05-01 02:35:24 +08:00
4814624cff change binary content from hex string to base64 string for package gres 2020-05-01 02:16:42 +08:00
cc67f3d388 improve package gcompress 2020-05-01 01:47:02 +08:00
f7c2a51c9f fix issue in zip feature for package gcompress; improve package gres 2020-05-01 00:18:45 +08:00
3db83e1159 improve package gtimer 2020-04-30 22:22:35 +08:00
3bb002796c donator update 2020-04-30 20:46:27 +08:00
45170bc53e add ClientMaxBodySize configuration for ghttp.Server 2020-04-30 20:37:09 +08:00
b79ff84c6f add struct slice conversion for request parameters for ghttp.Request; improve package gconv 2020-04-30 16:53:47 +08:00
938c46fec9 readme update 2020-04-29 19:33:14 +08:00
8a13d94526 improve Record.Struct for package gdb 2020-04-29 09:12:13 +08:00
1e844d505a comment update for package ghttp/gconv; readme update 2020-04-29 00:14:29 +08:00
a123a2c086 improve struct conversion of empty result/record for package gdb 2020-04-28 21:03:25 +08:00
1eeeeb853e improve unit testing case of error logging for ghttp.Server 2020-04-28 15:21:17 +08:00
6e7224e306 improve error handling for gconv.Struct/ghttp.Server; add NewSkip/NewfSkip function for package gerror 2020-04-28 15:04:07 +08:00
9e064e2651 donator updates 2020-04-27 23:34:22 +08:00
8d9dd17eac add Walk function for package gset; improve fields handling feature for package gdb 2020-04-27 21:18:42 +08:00
cf1d3d3d2b improve package gdebug; add more unit testing case for package gdb 2020-04-27 17:56:04 +08:00
9480ffcdc0 improve function SetPath/AddPath for package gview 2020-04-27 17:07:00 +08:00
5db10add4a fix issue in unnacessary quoting of fields in select statement of gdb.Model 2020-04-27 16:30:53 +08:00
fa66bf5d9d improve cache feature of package gdb.Model 2020-04-26 21:31:55 +08:00
f69da3ace1 add function Transaction for package gdb 2020-04-26 17:47:19 +08:00
e01bfa05c3 listening ports change for unit testing cases of ghttp.Server 2020-04-26 17:13:48 +08:00
231238c157 improve parameter handing for ghttp.Request 2020-04-26 17:08:07 +08:00
7edec099ab improve raw request/response content dump for ghttp.Client 2020-04-24 00:00:52 +08:00
83eb8be064 Merge pull request #609 from kirileec/master
add Raw* method in ClientResponse to get request and response string
2020-04-23 22:58:04 +08:00
7af30df494 Merge branch 'master' into master 2020-04-23 22:57:23 +08:00
f026686fda fix issue in dupicated expiration handling in response cookies og ghttp.Client 2020-04-23 21:12:32 +08:00
35a50b9c6c readme update 2020-04-23 21:06:42 +08:00
010e2f951a downgrade the required golang version from v1.13 to v1.11 2020-04-23 20:38:25 +08:00
f7f86ad65a downgrade the required golang version from v1.13 to v1.11 2020-04-23 20:25:59 +08:00
1e19f447d1 improve ghttp.Client in context handling 2020-04-23 20:23:23 +08:00
8cc378331d add one unit testing case for ghttp.Server 2020-04-23 20:10:10 +08:00
4721f68fd8 add performance testing result 2020-04-23 19:51:08 +08:00
0d11c0a1f8 add performance testing result 2020-04-23 19:41:34 +08:00
5076613a8f add performance testing result 2020-04-23 17:25:11 +08:00
c5a44daa65 add performance testing result 2020-04-23 17:23:57 +08:00
520970b71f add performance testing result 2020-04-23 17:19:08 +08:00
ebfb08ee3f add performance testing result 2020-04-23 17:18:15 +08:00
9e38b2cb90 add performance testing result 2020-04-23 17:14:11 +08:00
71b1f00dc5 improve package gdb/gstr/gvalid 2020-04-20 22:36:28 +08:00
9160bee1af change comments 2020-04-11 12:16:53 +08:00
e64fd088b9 fix when no response 2020-04-08 20:11:06 +08:00
15672e7a09 #591 add Raw* method in ClientResponse to get request and response string 2020-04-08 19:24:03 +08:00
126 changed files with 3317 additions and 1539 deletions

View File

@ -1,16 +1,68 @@
package main
import (
"archive/zip"
"fmt"
"github.com/gogf/gf/encoding/gcompress"
"io"
"os"
"path/filepath"
"strings"
)
func main() {
err := gcompress.ZipPath(
`D:\Workspace\Go\GOPATH\src\github.com\gogf\gf\geg`,
`D:\Workspace\Go\GOPATH\src\github.com\gogf\gf\geg\encoding\gcompress\data.zip`,
"my-dir",
)
fmt.Println(err)
// srcFile could be a single file or a directory
func Zip(srcFile string, destZip string) error {
zipfile, err := os.Create(destZip)
if err != nil {
return err
}
defer zipfile.Close()
archive := zip.NewWriter(zipfile)
defer archive.Close()
filepath.Walk(srcFile, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = strings.TrimPrefix(path, filepath.Dir(srcFile)+"/")
// header.Name = path
if info.IsDir() {
header.Name += "/"
} else {
header.Method = zip.Deflate
}
writer, err := archive.CreateHeader(header)
if err != nil {
return err
}
if !info.IsDir() {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(writer, file)
}
return err
})
return err
}
func main() {
src := `/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/test`
dst := `/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/test.zip`
//src := `/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/README.MD`
//dst := `/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/README.MD.zip`
fmt.Println(gcompress.ZipPath(src, dst))
//fmt.Println(Zip(src, dst))
}

View File

@ -9,7 +9,7 @@ import (
func Upload(r *ghttp.Request) {
saveDirPath := "/tmp/"
files := r.GetUploadFiles("upload-file")
if err := files.Save(saveDirPath); err != nil {
if _, err := files.Save(saveDirPath); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit("upload successfully")

View File

@ -9,7 +9,7 @@ import (
func Upload(r *ghttp.Request) {
saveDirPath := "/tmp/"
files := r.GetUploadFiles("upload-file")
if err := files.Save(saveDirPath); err != nil {
if _, err := files.Save(saveDirPath); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit("upload successfully")

View File

@ -1,5 +1,29 @@
package main
func main() {
import (
"encoding/hex"
"fmt"
"github.com/gogf/gf/encoding/gbase64"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/grand"
)
// bytesToHexString converts binary content to hex string content.
func bytesToHexStr(b []byte) string {
dst := make([]byte, hex.EncodedLen(len(b)))
hex.Encode(dst, b)
return gconv.UnsafeBytesToStr(dst)
}
func main() {
b := make([]byte, 1024)
for i := 0; i < 1024; i++ {
b[i] = byte(grand.N(0, 255))
}
fmt.Println(bytesToHexStr(b))
fmt.Println(len(b))
fmt.Println(len(bytesToHexStr(b)))
fmt.Println(gbase64.EncodeToString(b))
fmt.Println(len(gbase64.EncodeToString(b)))
}

View File

@ -1,6 +1,8 @@
language: go
go:
- "1.11.x"
- "1.12.x"
- "1.13.x"
- "1.14.x"

View File

@ -73,6 +73,10 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|蔡蔡|wechat|¥666.00| gf真强大让项目省心
|jack|wechat|¥100.00|
|sbilly|wechat|¥100.00| 祝好!
|米司|wechat|¥166.66| 大佬加油!
|*秦|wechat|¥20.00| 给群主献上一杯咖啡...
|[zhuhuan12](https://gitee.com/zhuhuan12)|wechat|¥50.00|
|faddei|qq|¥9.99|
<img src="https://goframe.org/images/donate.png"/>

View File

@ -9,12 +9,13 @@
English | [简体中文](README_ZH.MD)
`GF(GoFrame)` is a modular, full-featured and production-ready application development framework
`GF(GoFrame)` is a modular, powerful, high-performance and production-ready application development framework
of golang. Providing a series of core components and dozens of practical modules, such as:
cache, logging, containers, timer, resource, validator, database orm, etc.
Supporting web server integrated with router, cookie, session, middleware, logger, configure,
template, https, hooks, rewrites and many more features.
> If you're a newbie to `Go`, you may consider `GoFrame` easy and great as `Laravel` in `PHP`, `SpringBoot` in `Java` or `Django` in `Python`.
# Installation
```
@ -27,9 +28,67 @@ require github.com/gogf/gf latest
# Limitation
```
golang version >= 1.13
golang version >= 1.11
```
# Architecture
<div align=center>
<img src="https://goframe.org/images/arch.png?v=11"/>
</div>
# Performance
Here's the most popular Golang frameworks and libraries performance testing result in `WEB Server`. Performance testing cases source codes are hosted at: https://github.com/gogf/gf-performance
## Environment
OS : Ubuntu 18.04 amd64
CPU : AMD A8-6600K x 4
MEM : 32GB
GO : v1.13.4
## Testing Tool
`ab`: Apache HTTP server benchmarking tool.
Command:
```
ab -t 10 -c 100 http://127.0.0.1:3000/hello
ab -t 10 -c 100 http://127.0.0.1:3000/query?id=10000
ab -t 10 -c 100 http://127.0.0.1:3000/json
```
The concurrency starts from `100` to `10000`.
> Run `5` times for each case of each project and pick up the best testing result.
## 1. Hello World
<table>
<tr>
<th>Throughputs</th>
<th>Mean Latency</th>
<th>P99 Latency</th>
</tr>
<tr>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/throughputs1.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/meanlatency1.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/p99latency1.jpeg"></td>
</tr>
</table>
## 2. Json Response
<table>
<tr>
<th>Throughputs</th>
<th>Mean Latency</th>
<th>P99 Latency</th>
</tr>
<tr>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/throughputs3.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/meanlatency3.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/p99latency3.jpeg"></td>
</tr>
</table>
# Documentation
* 中文官网: https://goframe.org
@ -43,12 +102,6 @@ golang version >= 1.13
> It's recommended learning `GoFrame` through its awesome source codes and API reference.
# Architecture
<div align=center>
<img src="https://goframe.org/images/arch.png?v=11"/>
</div>
# License
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.

View File

@ -15,16 +15,17 @@
并提供了Web服务开发的系列核心组件Router、Cookie、Session、Middleware、服务注册、模板引擎等等
支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。
> 如果您初识`Go`语言,您可以将`GoFrame`类似于`PHP`中的`Laravel`, `Java`中的`SpringBoot`或者`Python`中的`Django`。
# 特点
* 模块化、松耦合设计;
* 模块丰富开箱即用;
* 简便易用易于维护;
* 社区活跃,大牛谦逊低调脾气好;
* 模块丰富开箱即用;
* 简便易用易于维护;
* 高代码质量、高单元测试覆盖率;
* 社区活跃,大牛谦逊低调脾气好;
* 详尽的开发文档及示例;
* 完善的本地中文化支持;
* 更适合企业及团队使用;
* 设计为团队及企业使用;
# 地址
- **主库**https://github.com/gogf/gf
@ -41,7 +42,7 @@ require github.com/gogf/gf latest
# 限制
```shell
golang版本 >= 1.13
golang版本 >= 1.11
```
# 架构
@ -49,6 +50,59 @@ golang版本 >= 1.13
<img src="https://goframe.org/images/arch.png?v=11"/>
</div>
# 性能
以下是目前最流行的`WEB Server` Golang框架/类库性能测试结果。
性能测试用例源代码仓库: https://github.com/gogf/gf-performance
## 环境:
OS : Ubuntu 18.04 amd64
CPU : AMD A8-6600K x 4
MEM : 32GB
GO : v1.13.4
## 工具
`ab`: Apache HTTP server benchmarking tool.
测试命令:
```
ab -t 10 -c 100 http://127.0.0.1:3000/hello
ab -t 10 -c 100 http://127.0.0.1:3000/query?id=10000
ab -t 10 -c 100 http://127.0.0.1:3000/json
```
并发客户端数量从 `100` 递增到 `10000`。
> 每个项目的每个用例均运行`5`次,取最优的结果展示。
## 1. Hello World
<table>
<tr>
<th>Throughputs</th>
<th>Mean Latency</th>
<th>P99 Latency</th>
</tr>
<tr>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/throughputs1.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/meanlatency1.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/p99latency1.jpeg"></td>
</tr>
</table>
## 2. Json Response
<table>
<tr>
<th>Throughputs</th>
<th>Mean Latency</th>
<th>P99 Latency</th>
</tr>
<tr>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/throughputs3.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/meanlatency3.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/p99latency3.jpeg"></td>
</tr>
</table>
# 文档

View File

@ -473,18 +473,7 @@ func (a *Array) Contains(value interface{}) bool {
// or returns -1 if not exists.
func (a *Array) Search(value interface{}) int {
a.mu.RLock()
result := -1
for index, v := range a.array {
if v == value {
result = index
break
}
}
a.mu.RUnlock()
return result
}
func (a *Array) doSearch(value interface{}) int {
defer a.mu.RUnlock()
if len(a.array) == 0 {
return -1
}

View File

@ -489,6 +489,10 @@ func (a *IntArray) Contains(value int) bool {
// or returns -1 if not exists.
func (a *IntArray) Search(value int) int {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return -1
}
result := -1
for index, v := range a.array {
if v == value {
@ -496,7 +500,6 @@ func (a *IntArray) Search(value int) int {
break
}
}
a.mu.RUnlock()
return result
}

View File

@ -473,13 +473,30 @@ func (a *StrArray) Contains(value string) bool {
return a.Search(value) != -1
}
// ContainsI checks whether a value exists in the array with case-insensitively.
// Note that it internally iterates the whole array to do the comparison with case-insensitively.
func (a *StrArray) ContainsI(value string) bool {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return false
}
for _, v := range a.array {
if strings.EqualFold(v, value) {
return true
}
}
return false
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *StrArray) Search(value string) int {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if strings.Compare(v, value) == 0 {
@ -487,7 +504,6 @@ func (a *StrArray) Search(value string) int {
break
}
}
a.mu.RUnlock()
return result
}

View File

@ -122,7 +122,13 @@ func (a *SortedArray) Sort() *SortedArray {
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
// It's alias of function Append, see Append.
func (a *SortedArray) Add(values ...interface{}) *SortedArray {
return a.Append(values...)
}
// Append adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedArray) Append(values ...interface{}) *SortedArray {
if len(values) == 0 {
return a
}
@ -425,13 +431,13 @@ func (a *SortedArray) Search(value interface{}) (index int) {
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
// If <result> greater than 0, it means the value at <index> is greater than <value>.
func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result int) {
if len(a.array) == 0 {
return -1, -2
}
if lock {
a.mu.RLock()
defer a.mu.RUnlock()
}
if len(a.array) == 0 {
return -1, -2
}
min := 0
max := len(a.array) - 1
mid := 0

View File

@ -107,7 +107,13 @@ func (a *SortedIntArray) Sort() *SortedIntArray {
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
// It's alias of function Append, see Append.
func (a *SortedIntArray) Add(values ...int) *SortedIntArray {
return a.Append(values...)
}
// Append adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedIntArray) Append(values ...int) *SortedIntArray {
if len(values) == 0 {
return a
}
@ -422,13 +428,13 @@ func (a *SortedIntArray) Search(value int) (index int) {
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
// If <result> greater than 0, it means the value at <index> is greater than <value>.
func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int) {
if len(a.array) == 0 {
return -1, -2
}
if lock {
a.mu.RLock()
defer a.mu.RUnlock()
}
if len(a.array) == 0 {
return -1, -2
}
min := 0
max := len(a.array) - 1
mid := 0

View File

@ -12,6 +12,7 @@ import (
"github.com/gogf/gf/text/gstr"
"math"
"sort"
"strings"
"github.com/gogf/gf/internal/rwmutex"
"github.com/gogf/gf/util/gconv"
@ -92,7 +93,13 @@ func (a *SortedStrArray) Sort() *SortedStrArray {
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
// It's alias of function Append, see Append.
func (a *SortedStrArray) Add(values ...string) *SortedStrArray {
return a.Append(values...)
}
// Append adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedStrArray) Append(values ...string) *SortedStrArray {
if len(values) == 0 {
return a
}
@ -392,6 +399,22 @@ func (a *SortedStrArray) Contains(value string) bool {
return a.Search(value) != -1
}
// ContainsI checks whether a value exists in the array with case-insensitively.
// Note that it internally iterates the whole array to do the comparison with case-insensitively.
func (a *SortedStrArray) ContainsI(value string) bool {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return false
}
for _, v := range a.array {
if strings.EqualFold(v, value) {
return true
}
}
return false
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *SortedStrArray) Search(value string) (index int) {
@ -407,13 +430,13 @@ func (a *SortedStrArray) Search(value string) (index int) {
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
// If <result> greater than 0, it means the value at <index> is greater than <value>.
func (a *SortedStrArray) binSearch(value string, lock bool) (index int, result int) {
if len(a.array) == 0 {
return -1, -2
}
if lock {
a.mu.RLock()
defer a.mu.RUnlock()
}
if len(a.array) == 0 {
return -1, -2
}
min := 0
max := len(a.array) - 1
mid := 0

View File

@ -64,6 +64,16 @@ func Test_StrArray_Basic(t *testing.T) {
})
}
func TestStrArray_ContainsI(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := garray.NewStrArray()
s.Append("a", "b", "C")
t.Assert(s.Contains("A"), false)
t.Assert(s.Contains("a"), true)
t.Assert(s.ContainsI("A"), true)
})
}
func TestStrArray_Sort(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
expect1 := []string{"0", "1", "2", "3"}

View File

@ -51,6 +51,16 @@ func TestSortedStrArray_SetArray(t *testing.T) {
})
}
func TestSortedStrArray_ContainsI(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := garray.NewSortedStrArray()
s.Append("a", "b", "C")
t.Assert(s.Contains("A"), false)
t.Assert(s.Contains("a"), true)
t.Assert(s.ContainsI("A"), true)
})
}
func TestSortedStrArray_Sort(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []string{"a", "d", "c", "b"}

View File

@ -1,70 +0,0 @@
// Copyright 2017 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
// Package gchan provides graceful channel for no panic operations.
//
// It's safe to call Chan.Push/Close functions repeatedly.
package gchan
import (
"errors"
"github.com/gogf/gf/container/gtype"
)
// Graceful channel.
type Chan struct {
channel chan interface{}
closed *gtype.Bool
}
// New creates a graceful channel with given <limit>.
func New(limit int) *Chan {
return &Chan{
channel: make(chan interface{}, limit),
closed: gtype.NewBool(),
}
}
// Push pushes <value> to channel.
// It is safe to be called repeatedly.
func (c *Chan) Push(value interface{}) error {
if c.closed.Val() {
return errors.New("channel is closed")
}
c.channel <- value
return nil
}
// Pop pops value from channel.
// If there's no value in channel, it would block to wait.
// If the channel is closed, it will return a nil value immediately.
func (c *Chan) Pop() interface{} {
return <-c.channel
}
// Close closes the channel.
// It is safe to be called repeatedly.
func (c *Chan) Close() {
if !c.closed.Set(true) {
close(c.channel)
}
}
// See Len.
func (c *Chan) Size() int {
return c.Len()
}
// Len returns the length of the channel.
func (c *Chan) Len() int {
return len(c.channel)
}
// Cap returns the capacity of the channel.
func (c *Chan) Cap() int {
return cap(c.channel)
}

View File

@ -1,30 +0,0 @@
// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gchan_test
import (
"fmt"
"github.com/gogf/gf/container/gchan"
)
func Example_basic() {
n := 10
c := gchan.New(n)
for i := 0; i < n; i++ {
c.Push(i)
}
fmt.Println(c.Len(), c.Cap())
for i := 0; i < n; i++ {
fmt.Print(c.Pop())
}
c.Close()
// Output:
//10 10
//0123456789
}

View File

@ -1,47 +0,0 @@
package gchan_test
import (
"errors"
"testing"
"github.com/gogf/gf/container/gchan"
"github.com/gogf/gf/test/gtest"
)
func Test_Gchan(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
ch := gchan.New(10)
t.Assert(ch.Cap(), 10)
t.Assert(ch.Push(1), nil)
t.Assert(ch.Len(), 1)
t.Assert(ch.Size(), 1)
ch.Pop()
t.Assert(ch.Len(), 0)
t.Assert(ch.Size(), 0)
ch.Close()
t.Assert(ch.Push(1), errors.New("channel is closed"))
ch = gchan.New(0)
ch1 := gchan.New(0)
go func() {
var i = 0
for {
v := ch.Pop()
if v == nil {
ch1.Push(i)
break
}
t.Assert(v, i)
i++
}
}()
for index := 0; index < 10; index++ {
ch.Push(index)
}
ch.Close()
t.Assert(ch1.Pop(), 10)
ch1.Close()
})
}

View File

@ -452,6 +452,16 @@ func (set *Set) Pops(size int) []interface{} {
return array
}
// Walk applies a user supplied function <f> to every item of set.
func (set *Set) Walk(f func(item interface{}) interface{}) *Set {
set.mu.Lock()
defer set.mu.Unlock()
for k, v := range set.data {
set.data[f(k)] = v
}
return set
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (set *Set) MarshalJSON() ([]byte, error) {
return json.Marshal(set.Slice())

View File

@ -412,6 +412,16 @@ func (set *IntSet) Pops(size int) []int {
return array
}
// Walk applies a user supplied function <f> to every item of set.
func (set *IntSet) Walk(f func(item int) int) *IntSet {
set.mu.Lock()
defer set.mu.Unlock()
for k, v := range set.data {
set.data[f(k)] = v
}
return set
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (set *IntSet) MarshalJSON() ([]byte, error) {
return json.Marshal(set.Slice())

View File

@ -13,6 +13,7 @@ import (
"github.com/gogf/gf/internal/rwmutex"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"strings"
)
type StrSet struct {
@ -139,6 +140,19 @@ func (set *StrSet) Contains(item string) bool {
return ok
}
// ContainsI checks whether a value exists in the set with case-insensitively.
// Note that it internally iterates the whole set to do the comparison with case-insensitively.
func (set *StrSet) ContainsI(item string) bool {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.data {
if strings.EqualFold(k, item) {
return true
}
}
return false
}
// Remove deletes <item> from set.
func (set *StrSet) Remove(item string) {
set.mu.Lock()
@ -426,6 +440,16 @@ func (set *StrSet) Pops(size int) []string {
return array
}
// Walk applies a user supplied function <f> to every item of set.
func (set *StrSet) Walk(f func(item string) string) *StrSet {
set.mu.Lock()
defer set.mu.Unlock()
for k, v := range set.data {
set.data[f(k)] = v
}
return set
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (set *StrSet) MarshalJSON() ([]byte, error) {
return json.Marshal(set.Slice())

View File

@ -62,6 +62,16 @@ func TestStrSet_Basic(t *testing.T) {
})
}
func TestStrSet_ContainsI(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet()
s.Add("a", "b", "C")
t.Assert(s.Contains("A"), false)
t.Assert(s.Contains("a"), true)
t.Assert(s.ContainsI("A"), true)
})
}
func TestStrSet_Iterator(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet()

View File

@ -235,100 +235,6 @@ func (v *Var) GTime(format ...string) *gtime.Time {
return gconv.GTime(v.Val(), format...)
}
// Map converts <v> to map[string]interface{}.
func (v *Var) Map(tags ...string) map[string]interface{} {
return gconv.Map(v.Val(), tags...)
}
// MapStrStr converts <v> to map[string]string.
func (v *Var) MapStrStr(tags ...string) map[string]string {
return gconv.MapStrStr(v.Val(), tags...)
}
// MapStrVar converts <v> to map[string]*Var.
func (v *Var) MapStrVar(tags ...string) map[string]*Var {
m := v.Map(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var)
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
}
// MapDeep converts <v> to map[string]interface{} recursively.
func (v *Var) MapDeep(tags ...string) map[string]interface{} {
return gconv.MapDeep(v.Val(), tags...)
}
// MapDeep converts <v> to map[string]string recursively.
func (v *Var) MapStrStrDeep(tags ...string) map[string]string {
return gconv.MapStrStrDeep(v.Val(), tags...)
}
// MapStrVarDeep converts <v> to map[string]*Var recursively.
func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var {
m := v.MapDeep(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var)
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
}
// Struct maps value of <v> to <pointer>.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
return gconv.Struct(v.Val(), pointer, mapping...)
}
// Struct maps value of <v> to <pointer> recursively.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error {
return gconv.StructDeep(v.Val(), pointer, mapping...)
}
// Structs converts <v> to given struct slice.
func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.Structs(v.Val(), pointer, mapping...)
}
// StructsDeep converts <v> to given struct slice recursively.
func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.StructsDeep(v.Val(), pointer, mapping...)
}
// MapToMap converts map type variable <params> to another map type variable <pointer>.
// The elements of <pointer> should be type of struct/*struct.
func (v *Var) MapToMap(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMap(v.Val(), pointer, mapping...)
}
// MapToMapDeep recursively converts map type variable <params> to another map type variable <pointer>.
// The elements of <pointer> should be type of struct/*struct.
func (v *Var) MapToMapDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMapDeep(v.Val(), pointer, mapping...)
}
// MapToMaps converts map type variable <params> to another map type variable <pointer>.
// The elements of <pointer> should be type of []struct/[]*struct.
func (v *Var) MapToMaps(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMaps(v.Val(), pointer, mapping...)
}
// MapToMapsDeep recursively converts map type variable <params> to another map type variable <pointer>.
// The elements of <pointer> should be type of []struct/[]*struct.
func (v *Var) MapToMapsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMapsDeep(v.Val(), pointer, mapping...)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (v *Var) MarshalJSON() ([]byte, error) {
return json.Marshal(v.Val())

View File

@ -0,0 +1,87 @@
// Copyright 2020 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gvar
import "github.com/gogf/gf/util/gconv"
// Map converts and returns <v> as map[string]interface{}.
func (v *Var) Map(tags ...string) map[string]interface{} {
return gconv.Map(v.Val(), tags...)
}
// MapStrStr converts and returns <v> as map[string]string.
func (v *Var) MapStrStr(tags ...string) map[string]string {
return gconv.MapStrStr(v.Val(), tags...)
}
// MapStrVar converts and returns <v> as map[string]*Var.
func (v *Var) MapStrVar(tags ...string) map[string]*Var {
m := v.Map(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var)
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
}
// MapDeep converts and returns <v> as map[string]interface{} recursively.
func (v *Var) MapDeep(tags ...string) map[string]interface{} {
return gconv.MapDeep(v.Val(), tags...)
}
// MapDeep converts and returns <v> as map[string]string recursively.
func (v *Var) MapStrStrDeep(tags ...string) map[string]string {
return gconv.MapStrStrDeep(v.Val(), tags...)
}
// MapStrVarDeep converts and returns <v> as map[string]*Var recursively.
func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var {
m := v.MapDeep(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var)
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
}
// Maps converts and returns <v> as map[string]string.
// See gconv.Maps.
func (v *Var) Maps(tags ...string) []map[string]interface{} {
return gconv.Maps(v.Val(), tags...)
}
// MapToMap converts map type variable <params> to another map type variable <pointer>.
// See gconv.MapToMap.
func (v *Var) MapToMap(pointer interface{}) (err error) {
return gconv.MapToMap(v.Val(), pointer)
}
// MapToMapDeep converts map type variable <params> to another map type variable
// <pointer> recursively.
// See gconv.MapToMapDeep.
func (v *Var) MapToMapDeep(pointer interface{}) (err error) {
return gconv.MapToMapDeep(v.Val(), pointer)
}
// MapToMaps converts map type variable <params> to another map type variable <pointer>.
// See gconv.MapToMaps.
func (v *Var) MapToMaps(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMaps(v.Val(), pointer, mapping...)
}
// MapToMapsDeep converts map type variable <params> to another map type variable
// <pointer> recursively.
// See gconv.MapToMapsDeep.
func (v *Var) MapToMapsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMapsDeep(v.Val(), pointer, mapping...)
}

View File

@ -0,0 +1,33 @@
// Copyright 2020 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gvar
import "github.com/gogf/gf/util/gconv"
// Struct maps value of <v> to <pointer>.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
return gconv.Struct(v.Val(), pointer, mapping...)
}
// Struct maps value of <v> to <pointer> recursively.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error {
return gconv.StructDeep(v.Val(), pointer, mapping...)
}
// Structs converts and returns <v> as given struct slice.
func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.Structs(v.Val(), pointer, mapping...)
}
// StructsDeep converts and returns <v> as given struct slice recursively.
func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.StructsDeep(v.Val(), pointer, mapping...)
}

View File

@ -9,14 +9,10 @@ package gvar_test
import (
"bytes"
"encoding/binary"
"encoding/json"
"github.com/gogf/gf/util/gconv"
"math"
"testing"
"time"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/test/gtest"
)
@ -305,90 +301,6 @@ func Test_Duration(t *testing.T) {
})
}
func Test_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := g.Map{
"k1": "v1",
"k2": "v2",
}
objOne := gvar.New(m, true)
t.Assert(objOne.Map()["k1"], m["k1"])
t.Assert(objOne.Map()["k2"], m["k2"])
})
}
func Test_Struct(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type StTest struct {
Test int
}
Kv := make(map[string]int, 1)
Kv["Test"] = 100
testObj := &StTest{}
objOne := gvar.New(Kv, true)
objOne.Struct(testObj)
t.Assert(testObj.Test, Kv["Test"])
})
gtest.C(t, func(t *gtest.T) {
type StTest struct {
Test int8
}
o := &StTest{}
v := gvar.New(g.Slice{"Test", "-25"})
v.Struct(o)
t.Assert(o.Test, -25)
})
}
func Test_Json(t *testing.T) {
// Marshal
gtest.C(t, func(t *gtest.T) {
s := "i love gf"
v := gvar.New(s)
b1, err1 := json.Marshal(v)
b2, err2 := json.Marshal(s)
t.Assert(err1, err2)
t.Assert(b1, b2)
})
gtest.C(t, func(t *gtest.T) {
s := int64(math.MaxInt64)
v := gvar.New(s)
b1, err1 := json.Marshal(v)
b2, err2 := json.Marshal(s)
t.Assert(err1, err2)
t.Assert(b1, b2)
})
// Unmarshal
gtest.C(t, func(t *gtest.T) {
s := "i love gf"
v := gvar.New(nil)
b, err := json.Marshal(s)
t.Assert(err, nil)
err = json.Unmarshal(b, v)
t.Assert(err, nil)
t.Assert(v.String(), s)
})
gtest.C(t, func(t *gtest.T) {
var v gvar.Var
s := "i love gf"
b, err := json.Marshal(s)
t.Assert(err, nil)
err = json.Unmarshal(b, &v)
t.Assert(err, nil)
t.Assert(v.String(), s)
})
}
func Test_UnmarshalValue(t *testing.T) {
type V struct {
Name string

View File

@ -0,0 +1,59 @@
// Copyright 2020 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gvar_test
import (
"encoding/json"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/test/gtest"
"math"
"testing"
)
func Test_Json(t *testing.T) {
// Marshal
gtest.C(t, func(t *gtest.T) {
s := "i love gf"
v := gvar.New(s)
b1, err1 := json.Marshal(v)
b2, err2 := json.Marshal(s)
t.Assert(err1, err2)
t.Assert(b1, b2)
})
gtest.C(t, func(t *gtest.T) {
s := int64(math.MaxInt64)
v := gvar.New(s)
b1, err1 := json.Marshal(v)
b2, err2 := json.Marshal(s)
t.Assert(err1, err2)
t.Assert(b1, b2)
})
// Unmarshal
gtest.C(t, func(t *gtest.T) {
s := "i love gf"
v := gvar.New(nil)
b, err := json.Marshal(s)
t.Assert(err, nil)
err = json.Unmarshal(b, v)
t.Assert(err, nil)
t.Assert(v.String(), s)
})
gtest.C(t, func(t *gtest.T) {
var v gvar.Var
s := "i love gf"
b, err := json.Marshal(s)
t.Assert(err, nil)
err = json.Unmarshal(b, &v)
t.Assert(err, nil)
t.Assert(v.String(), s)
})
}

View File

@ -0,0 +1,26 @@
// Copyright 2020 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gvar_test
import (
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
"testing"
)
func Test_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := g.Map{
"k1": "v1",
"k2": "v2",
}
objOne := gvar.New(m, true)
t.Assert(objOne.Map()["k1"], m["k1"])
t.Assert(objOne.Map()["k2"], m["k2"])
})
}

View File

@ -0,0 +1,69 @@
// Copyright 2020 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gvar_test
import (
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
"testing"
)
func Test_MapToMap(t *testing.T) {
// map[int]int -> map[string]string
// empty original map.
gtest.C(t, func(t *gtest.T) {
m1 := g.MapIntInt{}
m2 := g.MapStrStr{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(len(m1), len(m2))
})
// map[int]int -> map[string]string
gtest.C(t, func(t *gtest.T) {
m1 := g.MapIntInt{
1: 100,
2: 200,
}
m2 := g.MapStrStr{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(m2["1"], m1[1])
t.Assert(m2["2"], m1[2])
})
// map[string]interface{} -> map[string]string
gtest.C(t, func(t *gtest.T) {
m1 := g.Map{
"k1": "v1",
"k2": "v2",
}
m2 := g.MapStrStr{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(m2["k1"], m1["k1"])
t.Assert(m2["k2"], m1["k2"])
})
// map[string]string -> map[string]interface{}
gtest.C(t, func(t *gtest.T) {
m1 := g.MapStrStr{
"k1": "v1",
"k2": "v2",
}
m2 := g.Map{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(m2["k1"], m1["k1"])
t.Assert(m2["k2"], m1["k2"])
})
// map[string]interface{} -> map[interface{}]interface{}
gtest.C(t, func(t *gtest.T) {
m1 := g.MapStrStr{
"k1": "v1",
"k2": "v2",
}
m2 := g.MapAnyAny{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(m2["k1"], m1["k1"])
t.Assert(m2["k2"], m1["k2"])
})
}

View File

@ -0,0 +1,42 @@
// Copyright 2020 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gvar_test
import (
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
"testing"
)
func Test_Struct(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type StTest struct {
Test int
}
Kv := make(map[string]int, 1)
Kv["Test"] = 100
testObj := &StTest{}
objOne := gvar.New(Kv, true)
objOne.Struct(testObj)
t.Assert(testObj.Test, Kv["Test"])
})
gtest.C(t, func(t *gtest.T) {
type StTest struct {
Test int8
}
o := &StTest{}
v := gvar.New(g.Slice{"Test", "-25"})
v.Struct(o)
t.Assert(o.Test, -25)
})
}

View File

@ -64,6 +64,7 @@ type DB interface {
// Transaction.
Begin() (*TX, error)
Transaction(f func(tx *TX) error) (err error)
Insert(table string, data interface{}, batch ...int) (sql.Result, error)
InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error)

View File

@ -203,9 +203,6 @@ func (c *Core) GetStruct(pointer interface{}, sql string, args ...interface{}) e
if err != nil {
return err
}
if len(one) == 0 {
return ErrNoRows
}
return one.Struct(pointer)
}
@ -216,9 +213,6 @@ func (c *Core) GetStructs(pointer interface{}, sql string, args ...interface{})
if err != nil {
return err
}
if len(all) == 0 {
return ErrNoRows
}
return all.Structs(pointer)
}
@ -310,6 +304,34 @@ func (c *Core) Begin() (*TX, error) {
}
}
// Transaction wraps the transaction logic using function <f>.
// It rollbacks the transaction and returns the error from function <f> if
// it returns non-nil error. It commits the transaction and returns nil if
// function <f> returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function <f>
// as it is automatically handled by this function.
func (c *Core) Transaction(f func(tx *TX) error) (err error) {
var tx *TX
tx, err = c.DB.Begin()
if err != nil {
return err
}
defer func() {
if err != nil {
if e := tx.Rollback(); e != nil {
err = e
}
} else {
if e := tx.Commit(); e != nil {
err = e
}
}
}()
err = f(tx)
return
}
// Insert does "INSERT INTO ..." statement for the table.
// If there's already one unique record of the data in the table, it returns error.
//
@ -507,7 +529,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
}
case reflect.Map, reflect.Struct:
listMap = List{DataToMapDeep(list)}
listMap = List{DataToMapDeep(v)}
default:
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
}

View File

@ -83,12 +83,15 @@ func (d *DriverMysql) Tables(schema ...string) (tables []string, err error) {
return
}
// TableFields retrieves and returns the fields information of specified table of current schema.
// TableFields retrieves and returns the fields information of specified table of current
// schema.
//
// Note that it returns a map containing the field name and its corresponding fields.
// As a map is unsorted, the TableField struct has a "Index" field marks its sequence in the fields.
// As a map is unsorted, the TableField struct has a "Index" field marks its sequence in
// the fields.
//
// It's using cache feature to enhance the performance, which is never expired util the process restarts.
// It's using cache feature to enhance the performance, which is never expired util the
// process restarts.
func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)

View File

@ -206,15 +206,16 @@ func GetPrimaryKey(pointer interface{}) string {
// GetPrimaryKeyCondition returns a new where condition by primary field name.
// The optional parameter <where> is like follows:
// 123
// []int{1, 2, 3}
// "john"
// []string{"john", "smith"}
// g.Map{"id": g.Slice{1,2,3}}
// g.Map{"id": 1, "name": "john"}
// 123 => primary=123
// []int{1, 2, 3} => primary IN(1,2,3)
// "john" => primary='john'
// []string{"john", "smith"} => primary IN('john','smith')
// g.Map{"id": g.Slice{1,2,3}} => id IN(1,2,3)
// g.Map{"id": 1, "name": "john"} => id=1 AND name='john'
// etc.
//
// Note that it returns the given <where> parameter directly if there's the <primary> is empty.
// Note that it returns the given <where> parameter directly if the <primary> is empty
// or length of <where> > 1.
func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondition []interface{}) {
if len(where) == 0 {
return nil
@ -231,6 +232,7 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
}
switch kind {
case reflect.Map, reflect.Struct:
// Ignore the parameter <primary>.
break
default:

View File

@ -18,19 +18,26 @@ import (
// If the parameter <duration> = 0, which means it never expires.
// If the parameter <duration> > 0, which means it expires after <duration>.
//
// The optional parameter <name> is used to bind a name to the cache, which means you can later
// control the cache like changing the <duration> or clearing the cache with specified <name>.
// The optional parameter <name> is used to bind a name to the cache, which means you can
// later control the cache like changing the <duration> or clearing the cache with specified
// <name>.
//
// Note that, the cache feature is disabled if the model is operating on a transaction.
// Note that, the cache feature is disabled if the model is performing select statement
// on a transaction.
func (m *Model) Cache(duration time.Duration, name ...string) *Model {
model := m.getModel()
model.cacheDuration = duration
if len(name) > 0 {
model.cacheName = name[0]
}
// It does not support cache on transaction.
if model.tx == nil {
model.cacheEnabled = true
}
model.cacheEnabled = true
return model
}
// checkAndRemoveCache checks and removes the cache in insert/update/delete statement if
// cache feature is enabled.
func (m *Model) checkAndRemoveCache() {
if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 {
m.db.GetCache().Remove(m.cacheName)
}
}

View File

@ -6,7 +6,9 @@
package gdb
import "github.com/gogf/gf/util/gconv"
import (
"strings"
)
// Where sets the condition statement for the model. The parameter <where> can be type of
// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
@ -46,7 +48,8 @@ func (m *Model) Having(having interface{}, args ...interface{}) *Model {
// WherePri does the same logic as Model.Where except that if the parameter <where>
// is a single condition like int/string/float/slice, it treats the condition as the primary
// key value. That is, if primary key is "id" and given <where> parameter as "123", the
// WherePri function treats it as "id=123", but Model.Where treats it as string "123".
// WherePri function treats the condition as "id=123", but Model.Where treats the condition
// as string "123".
func (m *Model) WherePri(where interface{}, args ...interface{}) *Model {
if len(args) > 0 {
return m.Where(where, args...)
@ -98,9 +101,9 @@ func (m *Model) GroupBy(groupBy string) *Model {
}
// Order sets the "ORDER BY" statement for the model.
func (m *Model) Order(orderBy string) *Model {
func (m *Model) Order(orderBy ...string) *Model {
model := m.getModel()
model.orderBy = m.db.QuoteString(orderBy)
model.orderBy = m.db.QuoteString(strings.Join(orderBy, " "))
return model
}
@ -154,28 +157,3 @@ func (m *Model) Page(page, limit int) *Model {
func (m *Model) ForPage(page, limit int) *Model {
return m.Page(page, limit)
}
// getAll does the query from database.
func (m *Model) getAll(sql string, args ...interface{}) (result Result, err error) {
cacheKey := ""
// Retrieve from cache.
if m.cacheEnabled {
cacheKey = m.cacheName
if len(cacheKey) == 0 {
cacheKey = sql + "/" + gconv.String(args)
}
if v := m.db.GetCache().Get(cacheKey); v != nil {
return v.(Result), nil
}
}
result, err = m.db.DoGetAll(m.getLink(false), sql, m.mergeArguments(args)...)
// Cache the result.
if len(cacheKey) > 0 && err == nil {
if m.cacheDuration < 0 {
m.db.GetCache().Remove(cacheKey)
} else {
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
}
}
return result, err
}

View File

@ -7,7 +7,7 @@
package gdb
import (
"github.com/gogf/gf/container/garray"
"fmt"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/text/gstr"
)
@ -34,21 +34,31 @@ func (m *Model) FieldsEx(fields string) *Model {
if gstr.Contains(m.tables, " ") {
panic("function FieldsEx supports only single table operations")
}
tableFields, err := m.db.TableFields(m.tables)
if err != nil {
panic(err)
}
if len(tableFields) == 0 {
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
}
model := m.getModel()
model.fieldsEx = fields
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
if m, err := m.db.TableFields(m.tables); err == nil {
model.fields = ""
for k, _ := range m {
if fieldsExSet.Contains(k) {
continue
}
if len(model.fields) > 0 {
model.fields += ","
}
model.fields += k
}
fieldsArray := make([]string, len(tableFields))
for k, v := range tableFields {
fieldsArray[v.Index] = k
}
model.fields = ""
for _, k := range fieldsArray {
if fieldsExSet.Contains(k) {
continue
}
if len(model.fields) > 0 {
model.fields += ","
}
model.fields += k
}
model.fields = model.db.QuoteString(model.fields)
return model
}
@ -59,14 +69,26 @@ func (m *Model) FieldsStr(prefix ...string) string {
if len(prefix) > 0 {
prefixStr = prefix[0]
}
if m, err := m.db.TableFields(m.tables); err == nil {
fieldsArray := garray.NewStrArraySize(len(m), len(m))
for _, field := range m {
fieldsArray.Set(field.Index, prefixStr+field.Name)
}
return fieldsArray.Join(",")
tableFields, err := m.db.TableFields(m.tables)
if err != nil {
panic(err)
}
return ""
if len(tableFields) == 0 {
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
}
fieldsArray := make([]string, len(tableFields))
for k, v := range tableFields {
fieldsArray[v.Index] = k
}
newFields := ""
for _, k := range fieldsArray {
if len(newFields) > 0 {
newFields += ","
}
newFields += prefixStr + k
}
newFields = m.db.QuoteString(newFields)
return newFields
}
// FieldsExStr retrieves and returns fields which are not in parameter <fields> from the table,
@ -78,17 +100,28 @@ func (m *Model) FieldsExStr(fields string, prefix ...string) string {
if len(prefix) > 0 {
prefixStr = prefix[0]
}
if m, err := m.db.TableFields(m.tables); err == nil {
fieldsArray := garray.NewStrArraySize(len(m), len(m))
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
for _, field := range m {
if fieldsExSet.Contains(field.Name) {
continue
}
fieldsArray.Set(field.Index, prefixStr+field.Name)
}
fieldsArray.FilterEmpty()
return fieldsArray.Join(",")
tableFields, err := m.db.TableFields(m.tables)
if err != nil {
panic(err)
}
return ""
if len(tableFields) == 0 {
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
}
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
fieldsArray := make([]string, len(tableFields))
for k, v := range tableFields {
fieldsArray[v.Index] = k
}
newFields := ""
for _, k := range fieldsArray {
if fieldsExSet.Contains(k) {
continue
}
if len(newFields) > 0 {
newFields += ","
}
newFields += prefixStr + k
}
newFields = m.db.QuoteString(newFields)
return newFields
}

View File

@ -7,7 +7,6 @@
package gdb
import (
"database/sql"
"fmt"
"github.com/gogf/gf/util/gconv"
"reflect"
@ -42,10 +41,12 @@ func (m *Model) All(where ...interface{}) (Result, error) {
}
conditionWhere += softDeletingCondition
}
return m.getAll(
// DO NOT quote the m.fields where, in case of fields like:
// DISTINCT t.user_id uid
return m.doGetAll(
fmt.Sprintf(
"SELECT %s FROM %s%s",
m.db.QuoteString(m.fields),
m.fields,
m.tables,
conditionWhere+conditionExtra,
),
@ -169,9 +170,6 @@ func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
if err != nil {
return err
}
if len(one) == 0 {
return sql.ErrNoRows
}
return one.Struct(pointer)
}
@ -196,9 +194,6 @@ func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
if err != nil {
return err
}
if len(all) == 0 {
return sql.ErrNoRows
}
return all.Structs(pointer)
}
@ -249,7 +244,9 @@ func (m *Model) Count(where ...interface{}) (int, error) {
}
countFields := "COUNT(1)"
if m.fields != "" && m.fields != "*" {
countFields = fmt.Sprintf(`COUNT(%s)`, m.db.QuoteString(m.fields))
// DO NOT quote the m.fields here, in case of fields like:
// DISTINCT t.user_id uid
countFields = fmt.Sprintf(`COUNT(%s)`, m.fields)
}
var (
softDeletingCondition = m.getConditionForSoftDeleting()
@ -268,7 +265,7 @@ func (m *Model) Count(where ...interface{}) (int, error) {
if len(m.groupBy) > 0 {
s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s)
}
list, err := m.getAll(s, conditionArgs...)
list, err := m.doGetAll(s, conditionArgs...)
if err != nil {
return 0, err
}
@ -340,3 +337,28 @@ func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
}
return m.Scan(pointer)
}
// doGetAll does the select statement on the database.
func (m *Model) doGetAll(sql string, args ...interface{}) (result Result, err error) {
cacheKey := ""
// Retrieve from cache.
if m.cacheEnabled && m.tx == nil {
cacheKey = m.cacheName
if len(cacheKey) == 0 {
cacheKey = sql + "/" + gconv.String(args)
}
if v := m.db.GetCache().Get(cacheKey); v != nil {
return v.(Result), nil
}
}
result, err = m.db.DoGetAll(m.getLink(false), sql, m.mergeArguments(args)...)
// Cache the result.
if cacheKey != "" && err == nil {
if m.cacheDuration < 0 {
m.db.GetCache().Remove(cacheKey)
} else {
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
}
}
return result, err
}

View File

@ -25,39 +25,39 @@ const (
// If there's no field name for storing creating time, it returns an empty string.
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
func (m *Model) getSoftFieldNameCreate(table ...string) string {
name := ""
tableName := ""
if len(table) > 0 {
name = table[0]
tableName = table[0]
} else {
name = m.getPrimaryTableName()
tableName = m.getPrimaryTableName()
}
return m.getSoftFieldName(name, gSOFT_FIELD_NAME_CREATE)
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_CREATE)
}
// getSoftFieldNameUpdate checks and returns the field name for record updating time.
// If there's no field name for storing updating time, it returns an empty string.
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
func (m *Model) getSoftFieldNameUpdate(table ...string) (field string) {
name := ""
tableName := ""
if len(table) > 0 {
name = table[0]
tableName = table[0]
} else {
name = m.getPrimaryTableName()
tableName = m.getPrimaryTableName()
}
return m.getSoftFieldName(name, gSOFT_FIELD_NAME_UPDATE)
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_UPDATE)
}
// getSoftFieldNameDelete checks and returns the field name for record deleting time.
// If there's no field name for storing deleting time, it returns an empty string.
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
func (m *Model) getSoftFieldNameDelete(table ...string) (field string) {
name := ""
tableName := ""
if len(table) > 0 {
name = table[0]
tableName = table[0]
} else {
name = m.getPrimaryTableName()
tableName = m.getPrimaryTableName()
}
return m.getSoftFieldName(name, gSOFT_FIELD_NAME_DELETE)
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_DELETE)
}
// getSoftFieldName retrieves and returns the field name of the table for possible key.

View File

@ -80,6 +80,9 @@ func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool)
charL, charR = m.db.GetChars()
chars = charL + charR
)
set.Walk(func(item string) string {
return gstr.Trim(item, chars)
})
for k := range data {
k = gstr.Trim(k, chars)
if !set.Contains(k) {
@ -143,13 +146,6 @@ func (m *Model) getPrimaryKey() string {
return ""
}
// checkAndRemoveCache checks and remove the cache if necessary.
func (m *Model) checkAndRemoveCache() {
if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 {
m.db.GetCache().Remove(m.cacheName)
}
}
// formatCondition formats where arguments of the model and returns a new condition sql and its arguments.
// Note that this function does not change any attribute value of the <m>.
//

View File

@ -8,10 +8,11 @@ package gdb
import (
"database/sql"
"errors"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/encoding/gparser"
"github.com/gogf/gf/util/gconv"
"reflect"
)
// Json converts <r> to JSON format content.
@ -42,10 +43,32 @@ func (r Record) GMap() *gmap.StrAnyMap {
// Struct converts <r> to a struct.
// Note that the parameter <pointer> should be type of *struct/**struct.
//
// Note that it returns sql.ErrNoRows if <r> is empty.
func (r Record) Struct(pointer interface{}) error {
if r == nil {
// If the record is empty, it returns error.
if r.IsEmpty() {
return sql.ErrNoRows
}
// Special handling for parameter type: reflect.Value
if _, ok := pointer.(reflect.Value); ok {
return mapToStruct(r.Map(), pointer)
}
var (
reflectValue = reflect.ValueOf(pointer)
reflectKind = reflectValue.Kind()
)
if reflectKind != reflect.Ptr {
return errors.New("parameter should be type of *struct/**struct")
}
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
if reflectKind == reflect.Invalid {
return errors.New("parameter is an invalid pointer, maybe nil")
}
if reflectKind != reflect.Ptr && reflectKind != reflect.Struct {
return errors.New("parameter should be type of *struct/**struct")
}
return mapToStruct(r.Map(), pointer)
}

View File

@ -6,36 +6,22 @@
package gdb
import (
"database/sql"
"github.com/gogf/gf/encoding/gparser"
)
// Deprecated.
func (r Record) ToJson() string {
content, _ := gparser.VarToJson(r.Map())
return string(content)
return r.Json()
}
// Deprecated.
func (r Record) ToXml(rootTag ...string) string {
content, _ := gparser.VarToXml(r.Map(), rootTag...)
return string(content)
return r.Xml(rootTag...)
}
// Deprecated.
func (r Record) ToMap() Map {
m := make(map[string]interface{})
for k, v := range r {
m[k] = v.Val()
}
return m
return r.Map()
}
// Deprecated.
func (r Record) ToStruct(pointer interface{}) error {
if r == nil {
return sql.ErrNoRows
}
return mapToStruct(r.Map(), pointer)
return r.Struct(pointer)
}

View File

@ -148,27 +148,48 @@ func (r Result) RecordKeyUint(key string) map[uint]Record {
// Structs converts <r> to struct slice.
// Note that the parameter <pointer> should be type of *[]struct/*[]*struct.
func (r Result) Structs(pointer interface{}) (err error) {
l := len(r)
if l == 0 {
return sql.ErrNoRows
var (
reflectValue = reflect.ValueOf(pointer)
reflectKind = reflectValue.Kind()
)
if reflectKind != reflect.Ptr {
return fmt.Errorf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
}
t := reflect.TypeOf(pointer)
if t.Kind() != reflect.Ptr {
return fmt.Errorf("pointer should be type of pointer, but got: %v", t.Kind())
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
if reflectKind != reflect.Slice && reflectKind != reflect.Array {
return fmt.Errorf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
}
array := reflect.MakeSlice(t.Elem(), l, l)
itemType := array.Index(0).Type()
for i := 0; i < l; i++ {
if itemType.Kind() == reflect.Ptr {
length := len(r)
if length == 0 {
// The pointed slice is not empty.
if reflectValue.Len() > 0 {
// It here checks if it has struct item, which is already initialized.
// It then returns error to warn the developer its empty and no conversion.
if v := reflectValue.Index(0); v.Kind() != reflect.Ptr {
return sql.ErrNoRows
}
}
// Do nothing for empty struct slice.
return nil
}
var (
reflectType = reflect.TypeOf(pointer)
array = reflect.MakeSlice(reflectType.Elem(), length, length)
itemType = array.Index(0).Type()
itemKind = itemType.Kind()
)
for i := 0; i < length; i++ {
if itemKind == reflect.Ptr {
e := reflect.New(itemType.Elem()).Elem()
if err = r[i].Struct(e); err != nil {
return err
return fmt.Errorf(`slice element conversion failed: %s`, err.Error())
}
array.Index(i).Set(e.Addr())
} else {
e := reflect.New(itemType).Elem()
if err = r[i].Struct(e); err != nil {
return err
return fmt.Errorf(`slice element conversion failed: %s`, err.Error())
}
array.Index(i).Set(e)
}

View File

@ -6,128 +6,52 @@
package gdb
import (
"database/sql"
"fmt"
"reflect"
"github.com/gogf/gf/encoding/gparser"
)
// Deprecated.
func (r Result) ToJson() string {
content, _ := gparser.VarToJson(r.List())
return string(content)
return r.Json()
}
// Deprecated.
func (r Result) ToXml(rootTag ...string) string {
content, _ := gparser.VarToXml(r.List(), rootTag...)
return string(content)
return r.Xml(rootTag...)
}
// Deprecated.
func (r Result) ToList() List {
l := make(List, len(r))
for k, v := range r {
l[k] = v.Map()
}
return l
return r.List()
}
// Deprecated.
func (r Result) ToStringMap(key string) map[string]Map {
m := make(map[string]Map)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.String()] = item.Map()
}
}
return m
return r.MapKeyStr(key)
}
// Deprecated.
func (r Result) ToIntMap(key string) map[int]Map {
m := make(map[int]Map)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Int()] = item.Map()
}
}
return m
return r.MapKeyInt(key)
}
// Deprecated.
func (r Result) ToUintMap(key string) map[uint]Map {
m := make(map[uint]Map)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Uint()] = item.Map()
}
}
return m
return r.MapKeyUint(key)
}
// Deprecated.
func (r Result) ToStringRecord(key string) map[string]Record {
m := make(map[string]Record)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.String()] = item
}
}
return m
return r.RecordKeyStr(key)
}
// Deprecated.
func (r Result) ToIntRecord(key string) map[int]Record {
m := make(map[int]Record)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Int()] = item
}
}
return m
return r.RecordKeyInt(key)
}
// Deprecated.
func (r Result) ToUintRecord(key string) map[uint]Record {
m := make(map[uint]Record)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Uint()] = item
}
}
return m
return r.RecordKeyUint(key)
}
// Deprecated.
func (r Result) ToStructs(pointer interface{}) (err error) {
l := len(r)
if l == 0 {
return sql.ErrNoRows
}
t := reflect.TypeOf(pointer)
if t.Kind() != reflect.Ptr {
return fmt.Errorf("pointer should be type of pointer, but got: %v", t.Kind())
}
array := reflect.MakeSlice(t.Elem(), l, l)
itemType := array.Index(0).Type()
for i := 0; i < l; i++ {
if itemType.Kind() == reflect.Ptr {
e := reflect.New(itemType.Elem()).Elem()
if err = r[i].Struct(e); err != nil {
return err
}
array.Index(i).Set(e.Addr())
} else {
e := reflect.New(itemType).Elem()
if err = r[i].Struct(e); err != nil {
return err
}
array.Index(i).Set(e)
}
}
reflect.ValueOf(pointer).Elem().Set(array)
return nil
return r.Structs(pointer)
}

View File

@ -820,7 +820,7 @@ func Test_Model_Structs(t *testing.T) {
}
var users []*User
err := db.Table(table).Where("id<0").Structs(&users)
t.Assert(err, sql.ErrNoRows)
t.Assert(err, nil)
})
}
@ -905,12 +905,14 @@ func Test_Model_Scan(t *testing.T) {
NickName string
CreateTime *gtime.Time
}
user := new(User)
users := new([]*User)
var (
user = new(User)
users = new([]*User)
)
err1 := db.Table(table).Where("id < 0").Scan(user)
err2 := db.Table(table).Where("id < 0").Scan(users)
t.Assert(err1, sql.ErrNoRows)
t.Assert(err2, sql.ErrNoRows)
t.Assert(err2, nil)
})
}
@ -1870,8 +1872,8 @@ func Test_Model_FieldsStr(t *testing.T) {
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
t.Assert(db.Table(table).FieldsStr(), "id,passport,password,nickname,create_time")
t.Assert(db.Table(table).FieldsStr("a."), "a.id,a.passport,a.password,a.nickname,a.create_time")
t.Assert(db.Table(table).FieldsStr(), "`id`,`passport`,`password`,`nickname`,`create_time`")
t.Assert(db.Table(table).FieldsStr("a."), "`a`.`id`,`a`.`passport`,`a`.`password`,`a`.`nickname`,`a`.`create_time`")
})
}
@ -1880,8 +1882,8 @@ func Test_Model_FieldsExStr(t *testing.T) {
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
t.Assert(db.Table(table).FieldsExStr("create_time,nickname"), "id,passport,password")
t.Assert(db.Table(table).FieldsExStr("create_time,nickname", "a."), "a.id,a.passport,a.password")
t.Assert(db.Table(table).FieldsExStr("create_time,nickname"), "`id`,`passport`,`password`")
t.Assert(db.Table(table).FieldsExStr("create_time,nickname", "a."), "`a`.`id`,`a`.`passport`,`a`.`password`")
})
}
@ -2213,6 +2215,63 @@ func Test_Model_Cache(t *testing.T) {
t.Assert(err, nil)
t.Assert(one["passport"], "user_200")
})
// transaction.
gtest.C(t, func(t *gtest.T) {
// make cache for id 3
one, err := db.Table(table).Cache(time.Second, "test3").FindOne(3)
t.Assert(err, nil)
t.Assert(one["passport"], "user_3")
r, err := db.Table(table).Data("passport", "user_300").Cache(time.Second, "test3").WherePri(3).Update()
t.Assert(err, nil)
n, err := r.RowsAffected()
t.Assert(err, nil)
t.Assert(n, 1)
err = db.Transaction(func(tx *gdb.TX) error {
one, err := tx.Table(table).Cache(time.Second, "test3").FindOne(3)
t.Assert(err, nil)
t.Assert(one["passport"], "user_300")
return nil
})
t.Assert(err, nil)
one, err = db.Table(table).Cache(time.Second, "test3").FindOne(3)
t.Assert(err, nil)
t.Assert(one["passport"], "user_3")
})
gtest.C(t, func(t *gtest.T) {
// make cache for id 4
one, err := db.Table(table).Cache(time.Second, "test4").FindOne(4)
t.Assert(err, nil)
t.Assert(one["passport"], "user_4")
r, err := db.Table(table).Data("passport", "user_400").Cache(time.Second, "test3").WherePri(4).Update()
t.Assert(err, nil)
n, err := r.RowsAffected()
t.Assert(err, nil)
t.Assert(n, 1)
err = db.Transaction(func(tx *gdb.TX) error {
// Cache feature disabled.
one, err := tx.Table(table).Cache(time.Second, "test4").FindOne(4)
t.Assert(err, nil)
t.Assert(one["passport"], "user_400")
// Update the cache.
r, err := tx.Table(table).Data("passport", "user_4000").
Cache(-1, "test4").WherePri(4).Update()
t.Assert(err, nil)
n, err := r.RowsAffected()
t.Assert(err, nil)
t.Assert(n, 1)
return nil
})
t.Assert(err, nil)
// Read from db.
one, err = db.Table(table).Cache(time.Second, "test4").FindOne(4)
t.Assert(err, nil)
t.Assert(one["passport"], "user_4000")
})
}
func Test_Model_Having(t *testing.T) {
@ -2240,3 +2299,58 @@ func Test_Model_Having(t *testing.T) {
t.Assert(len(all), 1)
})
}
func Test_Model_Distinct(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table, "t").Fields("distinct t.id").Where("id > 1").Having("id > 8").All()
t.Assert(err, nil)
t.Assert(len(all), 2)
})
}
func Test_Model_Min_Max(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
value, err := db.Table(table, "t").Fields("min(t.id)").Where("id > 1").Value()
t.Assert(err, nil)
t.Assert(value.Int(), 2)
})
gtest.C(t, func(t *gtest.T) {
value, err := db.Table(table, "t").Fields("max(t.id)").Where("id > 1").Value()
t.Assert(err, nil)
t.Assert(value.Int(), 10)
})
}
func Test_Model_NullField(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport *string
}
data := g.Map{
"id": 1,
"passport": nil,
}
result, err := db.Table(table).Data(data).Insert()
t.Assert(err, nil)
n, _ := result.RowsAffected()
t.Assert(n, 1)
one, err := db.Table(table).FindOne(1)
t.Assert(err, nil)
var user *User
err = one.Struct(&user)
t.Assert(err, nil)
t.Assert(user.Id, data["id"])
t.Assert(user.Passport, data["passport"])
})
}

View File

@ -94,5 +94,87 @@ func Test_Model_Inherit_MapToStruct(t *testing.T) {
t.Assert(user.CreateTime, data["create_time"])
})
}
func Test_Struct_Empty(t *testing.T) {
table := createTable()
defer dropTable(table)
type User struct {
Id int
Passport string
Password string
Nickname string
}
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).Where("id=100").One()
t.Assert(err, nil)
user := new(User)
t.AssertNE(one.Struct(user), nil)
})
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).Where("id=100").One()
t.Assert(err, nil)
var user *User
t.AssertNE(one.Struct(&user), nil)
})
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).Where("id=100").One()
t.Assert(err, nil)
var user *User
t.AssertNE(one.Struct(user), nil)
})
}
func Test_Structs_Empty(t *testing.T) {
table := createTable()
defer dropTable(table)
type User struct {
Id int
Passport string
Password string
Nickname string
}
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id>100").All()
t.Assert(err, nil)
users := make([]User, 0)
t.Assert(all.Structs(&users), nil)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id>100").All()
t.Assert(err, nil)
users := make([]User, 10)
t.AssertNE(all.Structs(&users), nil)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id>100").All()
t.Assert(err, nil)
var users []User
t.Assert(all.Structs(&users), nil)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id>100").All()
t.Assert(err, nil)
users := make([]*User, 0)
t.Assert(all.Structs(&users), nil)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id>100").All()
t.Assert(err, nil)
users := make([]*User, 10)
t.Assert(all.Structs(&users), nil)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id>100").All()
t.Assert(err, nil)
var users []*User
t.Assert(all.Structs(&users), nil)
})
}

View File

@ -7,7 +7,9 @@
package gdb_test
import (
"errors"
"fmt"
"github.com/gogf/gf/database/gdb"
"testing"
"github.com/gogf/gf/frame/g"
@ -300,7 +302,6 @@ func Test_TX_Replace(t *testing.T) {
t.Assert(value.String(), "name_1")
}
})
}
func Test_TX_Save(t *testing.T) {
@ -713,5 +714,53 @@ func Test_TX_Delete(t *testing.T) {
t.AssertNE(n, 0)
}
})
}
func Test_Transaction(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
err := db.Transaction(func(tx *gdb.TX) error {
if _, err := tx.Replace(table, g.Map{
"id": 1,
"passport": "USER_1",
"password": "PASS_1",
"nickname": "NAME_1",
"create_time": gtime.Now().String(),
}); err != nil {
t.Error(err)
}
return errors.New("error")
})
t.AssertNE(err, nil)
if value, err := db.Table(table).Fields("nickname").Where("id", 1).Value(); err != nil {
gtest.Error(err)
} else {
t.Assert(value.String(), "name_1")
}
})
gtest.C(t, func(t *gtest.T) {
err := db.Transaction(func(tx *gdb.TX) error {
if _, err := tx.Replace(table, g.Map{
"id": 1,
"passport": "USER_1",
"password": "PASS_1",
"nickname": "NAME_1",
"create_time": gtime.Now().String(),
}); err != nil {
t.Error(err)
}
return nil
})
t.Assert(err, nil)
if value, err := db.Table(table).Fields("nickname").Where("id", 1).Value(); err != nil {
gtest.Error(err)
} else {
t.Assert(value.String(), "NAME_1")
}
})
}

View File

@ -58,6 +58,10 @@ func StackWithFilters(filters []string, skip ...int) string {
pc, file, line, ok = runtime.Caller(i)
}
if ok {
// Filter empty file.
if file == "" {
continue
}
// GOROOT filter.
if goRootForFilter != "" &&
len(file) >= len(goRootForFilter) &&

View File

@ -19,9 +19,11 @@ import (
//
// Note that it returns error if given <level> is invalid.
func Gzip(data []byte, level ...int) ([]byte, error) {
var writer *gzip.Writer
var buf bytes.Buffer
var err error
var (
writer *gzip.Writer
buf bytes.Buffer
err error
)
if len(level) > 0 {
writer, err = gzip.NewWriterLevel(&buf, level[0])
if err != nil {
@ -41,8 +43,10 @@ func Gzip(data []byte, level ...int) ([]byte, error) {
// GzipFile compresses the file <src> to <dst> using gzip algorithm.
func GzipFile(src, dst string, level ...int) error {
var writer *gzip.Writer
var err error
var (
writer *gzip.Writer
err error
)
srcFile, err := gfile.Open(src)
if err != nil {
return err

View File

@ -24,25 +24,88 @@ func Test_ZipPath(t *testing.T) {
dstPath := gdebug.TestDataPath("zip", "zip.zip")
t.Assert(gfile.Exists(dstPath), false)
err := gcompress.ZipPath(srcPath, dstPath)
t.Assert(gcompress.ZipPath(srcPath, dstPath), nil)
t.Assert(gfile.Exists(dstPath), true)
defer gfile.Remove(dstPath)
// unzip to temporary dir.
tempDirPath := gfile.TempDir(gtime.TimestampNanoStr())
t.Assert(gfile.Mkdir(tempDirPath), nil)
t.Assert(gcompress.UnZipFile(dstPath, tempDirPath), nil)
defer gfile.Remove(tempDirPath)
t.Assert(
gfile.GetContents(gfile.Join(tempDirPath, "1.txt")),
gfile.GetContents(srcPath),
)
})
// multiple files
gtest.C(t, func(t *gtest.T) {
var (
srcPath1 = gdebug.TestDataPath("zip", "path1", "1.txt")
srcPath2 = gdebug.TestDataPath("zip", "path2", "2.txt")
dstPath = gfile.TempDir(gtime.TimestampNanoStr(), "zip.zip")
)
if p := gfile.Dir(dstPath); !gfile.Exists(p) {
t.Assert(gfile.Mkdir(p), nil)
}
t.Assert(gfile.Exists(dstPath), false)
err := gcompress.ZipPath(srcPath1+","+srcPath2, dstPath)
t.Assert(err, nil)
t.Assert(gfile.Exists(dstPath), true)
defer gfile.Remove(dstPath)
// unzip to another temporary dir.
tempDirPath := gfile.TempDir(gtime.TimestampNanoStr())
err = gfile.Mkdir(tempDirPath)
t.Assert(err, nil)
t.Assert(gfile.Mkdir(tempDirPath), nil)
err = gcompress.UnZipFile(dstPath, tempDirPath)
t.Assert(err, nil)
defer gfile.Remove(tempDirPath)
t.Assert(
gfile.GetContents(gfile.Join(tempDirPath, "1.txt")),
gfile.GetContents(gfile.Join(srcPath, "path1", "1.txt")),
gfile.GetContents(srcPath1),
)
t.Assert(
gfile.GetContents(gfile.Join(tempDirPath, "2.txt")),
gfile.GetContents(srcPath2),
)
})
// directory
// one dir and one file.
gtest.C(t, func(t *gtest.T) {
var (
srcPath1 = gdebug.TestDataPath("zip", "path1")
srcPath2 = gdebug.TestDataPath("zip", "path2", "2.txt")
dstPath = gfile.TempDir(gtime.TimestampNanoStr(), "zip.zip")
)
if p := gfile.Dir(dstPath); !gfile.Exists(p) {
t.Assert(gfile.Mkdir(p), nil)
}
t.Assert(gfile.Exists(dstPath), false)
err := gcompress.ZipPath(srcPath1+","+srcPath2, dstPath)
t.Assert(err, nil)
t.Assert(gfile.Exists(dstPath), true)
defer gfile.Remove(dstPath)
// unzip to another temporary dir.
tempDirPath := gfile.TempDir(gtime.TimestampNanoStr())
t.Assert(gfile.Mkdir(tempDirPath), nil)
err = gcompress.UnZipFile(dstPath, tempDirPath)
t.Assert(err, nil)
defer gfile.Remove(tempDirPath)
t.Assert(
gfile.GetContents(gfile.Join(tempDirPath, "path1", "1.txt")),
gfile.GetContents(gfile.Join(srcPath1, "1.txt")),
)
t.Assert(
gfile.GetContents(gfile.Join(tempDirPath, "2.txt")),
gfile.GetContents(srcPath2),
)
})
// directory.
gtest.C(t, func(t *gtest.T) {
srcPath := gdebug.TestDataPath("zip")
dstPath := gdebug.TestDataPath("zip", "zip.zip")
@ -75,13 +138,14 @@ func Test_ZipPath(t *testing.T) {
gfile.GetContents(gfile.Join(srcPath, "path2", "2.txt")),
)
})
// multiple paths joined using char ','
// multiple directory paths joined using char ','.
gtest.C(t, func(t *gtest.T) {
srcPath := gdebug.TestDataPath("zip")
srcPath1 := gdebug.TestDataPath("zip", "path1")
srcPath2 := gdebug.TestDataPath("zip", "path2")
dstPath := gdebug.TestDataPath("zip", "zip.zip")
var (
srcPath = gdebug.TestDataPath("zip")
srcPath1 = gdebug.TestDataPath("zip", "path1")
srcPath2 = gdebug.TestDataPath("zip", "path2")
dstPath = gdebug.TestDataPath("zip", "zip.zip")
)
pwd := gfile.Pwd()
err := gfile.Chdir(srcPath)
defer gfile.Chdir(pwd)
@ -116,10 +180,11 @@ func Test_ZipPath(t *testing.T) {
func Test_ZipPathWriter(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
srcPath := gdebug.TestDataPath("zip")
srcPath1 := gdebug.TestDataPath("zip", "path1")
srcPath2 := gdebug.TestDataPath("zip", "path2")
var (
srcPath = gdebug.TestDataPath("zip")
srcPath1 = gdebug.TestDataPath("zip", "path1")
srcPath2 = gdebug.TestDataPath("zip", "path2")
)
pwd := gfile.Pwd()
err := gfile.Chdir(srcPath)
defer gfile.Chdir(pwd)

View File

@ -10,16 +10,12 @@ import (
"archive/zip"
"bytes"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/text/gstr"
"io"
"os"
"path/filepath"
"strings"
"time"
"github.com/gogf/gf/internal/fileinfo"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/text/gstr"
)
// ZipPath compresses <paths> to <dest> using zip compressing algorithm.
@ -85,11 +81,13 @@ func doZipPathWriter(path string, exclude string, zipWriter *zip.Writer, prefix
headerPrefix = prefix[0]
}
headerPrefix = strings.TrimRight(headerPrefix, "\\/")
if len(headerPrefix) > 0 && gfile.IsDir(path) {
headerPrefix += "/"
}
if headerPrefix == "" {
headerPrefix = gfile.Basename(path)
if gfile.IsDir(path) {
if len(headerPrefix) > 0 {
headerPrefix += "/"
} else {
headerPrefix = gfile.Basename(path)
}
}
headerPrefix = strings.Replace(headerPrefix, "//", "/", -1)
for _, file := range files {
@ -97,23 +95,15 @@ func doZipPathWriter(path string, exclude string, zipWriter *zip.Writer, prefix
intlog.Printf(`exclude file path: %s`, file)
continue
}
err := zipFile(file, headerPrefix+gfile.Dir(file[len(path):]), zipWriter)
dir := gfile.Dir(file[len(path):])
if dir == "." {
dir = ""
}
err := zipFile(file, headerPrefix+dir, zipWriter)
if err != nil {
return err
}
}
// Add prefix to zip archive.
path = headerPrefix
for {
err := zipFileVirtual(fileinfo.New(gfile.Basename(path), 0, os.ModeDir, time.Now()), path, zipWriter)
if err != nil {
return err
}
if path == "/" || !strings.Contains(path, "/") {
break
}
path = gfile.Dir(path)
}
return nil
}
@ -203,14 +193,23 @@ func zipFile(path string, prefix string, zw *zip.Writer) error {
return nil
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return err
}
header, err := createFileHeader(info, prefix)
if err != nil {
return err
}
if info.IsDir() {
header.Name += "/"
} else {
header.Method = zip.Deflate
}
writer, err := zw.CreateHeader(header)
if err != nil {
return err
@ -223,23 +222,12 @@ func zipFile(path string, prefix string, zw *zip.Writer) error {
return nil
}
func zipFileVirtual(info os.FileInfo, path string, zw *zip.Writer) error {
header, err := createFileHeader(info, "")
if err != nil {
return err
}
header.Name = path
if _, err := zw.CreateHeader(header); err != nil {
return err
}
return nil
}
func createFileHeader(info os.FileInfo, prefix string) (*zip.FileHeader, error) {
header, err := zip.FileInfoHeader(info)
if err != nil {
return nil, err
}
if len(prefix) > 0 {
prefix = strings.Replace(prefix, `\`, `/`, -1)
prefix = strings.TrimRight(prefix, `/`)

View File

@ -35,8 +35,8 @@ func (j *Json) IsNil() bool {
// It returns all values of current Json object if <pattern> is given empty or string ".".
// It returns nil if no value found by <pattern>.
//
// We can also access slice item by its index number in <pattern>,
// eg: "items.name.first", "list.10".
// We can also access slice item by its index number in <pattern> like:
// "list.10", "array.0.name", "array.0.1.id".
//
// It returns a default value specified by <def> if value for <pattern> is not found.
func (j *Json) Get(pattern string, def ...interface{}) interface{} {
@ -78,8 +78,7 @@ func (j *Json) GetVars(pattern string, def ...interface{}) []*gvar.Var {
return gvar.New(j.Get(pattern, def...)).Vars()
}
// GetMap retrieves the value by specified <pattern>,
// and converts it to map[string]interface{}.
// GetMap retrieves and returns the value by specified <pattern> as map[string]interface{}.
func (j *Json) GetMap(pattern string, def ...interface{}) map[string]interface{} {
result := j.Get(pattern, def...)
if result != nil {
@ -88,8 +87,7 @@ func (j *Json) GetMap(pattern string, def ...interface{}) map[string]interface{}
return nil
}
// GetMapStrStr retrieves the value by specified <pattern>,
// and converts it to map[string]string.
// GetMapStrStr retrieves and returns the value by specified <pattern> as map[string]string.
func (j *Json) GetMapStrStr(pattern string, def ...interface{}) map[string]string {
result := j.Get(pattern, def...)
if result != nil {
@ -98,6 +96,15 @@ func (j *Json) GetMapStrStr(pattern string, def ...interface{}) map[string]strin
return nil
}
// GetMaps retrieves and returns the value by specified <pattern> as []map[string]interface{}.
func (j *Json) GetMaps(pattern string, def ...interface{}) []map[string]interface{} {
result := j.Get(pattern, def...)
if result != nil {
return gconv.Maps(result)
}
return nil
}
// GetJson gets the value by specified <pattern>,
// and converts it to a un-concurrent-safe Json object.
func (j *Json) GetJson(pattern string, def ...interface{}) *Json {
@ -323,25 +330,28 @@ func (j *Json) GetStructsDeep(pattern string, pointer interface{}, mapping ...ma
}
// GetMapToMap retrieves the value by specified <pattern> and converts it specified map variable.
// The parameter of <pointer> should be type of *map.
// See gconv.MapToMap.
func (j *Json) GetMapToMap(pattern string, pointer interface{}, mapping ...map[string]string) error {
return gconv.MapToMap(j.Get(pattern), pointer, mapping...)
}
// GetMapToMapDeep retrieves the value by specified <pattern> and converts it specified map
// variable recursively. The parameter of <pointer> should be type of *map.
// variable recursively.
// See gconv.MapToMapDeep.
func (j *Json) GetMapToMapDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
return gconv.MapToMapDeep(j.Get(pattern), pointer, mapping...)
}
// GetMapToMaps retrieves the value by specified <pattern> and converts it specified map slice
// variable. The parameter of <pointer> should be type of []map/*map.
// variable.
// See gconv.MapToMaps.
func (j *Json) GetMapToMaps(pattern string, pointer interface{}, mapping ...map[string]string) error {
return gconv.MapToMaps(j.Get(pattern), pointer, mapping...)
}
// GetMapToMapsDeep retrieves the value by specified <pattern> and converts it specified map slice
// variable recursively. The parameter of <pointer> should be type of []map/*map.
// variable recursively.
// See gconv.MapToMapsDeep.
func (j *Json) GetMapToMapsDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
return gconv.MapToMapsDeep(j.Get(pattern), pointer, mapping...)
}

View File

@ -5,21 +5,28 @@
// You can obtain one at https://github.com/gogf/gf.
// Package errors provides simple functions to manipulate errors.
//
// Very note that, this package is quite a base package, which should not import extra
// packages except standard packages, to avoid cycle imports.
package gerror
import (
"fmt"
)
// ApiStack is the interface for Stack feature.
type ApiStack interface {
Error() string // It should be en error.
Stack() string
}
// ApiCause is the interface for Cause feature.
type ApiCause interface {
Error() string // It should be en error.
Cause() error
}
// New returns an error that formats as the given text.
// New creates and returns an error which is formatted from given text.
func New(text string) error {
if text == "" {
return nil
@ -30,7 +37,19 @@ func New(text string) error {
}
}
// Newf returns an error that formats as the given text.
// NewSkip creates and returns an error which is formatted from given text.
// The parameter <skip> specifies the stack callers skipped amount.
func NewSkip(skip int, text string) error {
if text == "" {
return nil
}
return &Error{
stack: callers(skip),
text: text,
}
}
// Newf returns an error that formats as the given format and args.
func Newf(format string, args ...interface{}) error {
if format == "" {
return nil
@ -41,6 +60,18 @@ func Newf(format string, args ...interface{}) error {
}
}
// NewfSkip returns an error that formats as the given format and args.
// The parameter <skip> specifies the stack callers skipped amount.
func NewfSkip(skip int, format string, args ...interface{}) error {
if format == "" {
return nil
}
return &Error{
stack: callers(skip),
text: fmt.Sprintf(format, args...),
}
}
// Wrap wraps error with text.
// It returns nil if given err is nil.
func Wrap(err error, text string) error {
@ -56,7 +87,7 @@ func Wrap(err error, text string) error {
// Wrapf returns an error annotating err with a stack trace
// at the point Wrapf is called, and the format specifier.
// It returns nil if given err is nil.
// It returns nil if given <err> is nil.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
@ -68,7 +99,7 @@ func Wrapf(err error, format string, args ...interface{}) error {
}
}
// Cause returns the root cause error.
// Cause returns the root cause error of <err>.
func Cause(err error) error {
if err != nil {
if e, ok := err.(ApiCause); ok {
@ -79,7 +110,7 @@ func Cause(err error) error {
}
// Stack returns the stack callers as string.
// It returns an empty string id the <err> does not support stacks.
// It returns an empty string if the <err> does not support stacks.
func Stack(err error) string {
if err == nil {
return ""

View File

@ -9,7 +9,6 @@ package gerror
import (
"bytes"
"fmt"
"github.com/gogf/gf/internal/intlog"
"io"
"runtime"
"strings"
@ -94,7 +93,7 @@ func (err *Error) Format(s fmt.State, verb rune) {
}
// Stack returns the stack callers as string.
// It returns an empty string id the <err> does not support stacks.
// It returns an empty string if the <err> does not support stacks.
func (err *Error) Stack() string {
if err == nil {
return ""
@ -131,15 +130,6 @@ func formatSubStack(st stack, buffer *bytes.Buffer) {
if strings.Contains(file, gFILTER_KEY) {
continue
}
// Avoid GF stacks if not in GF development.
if !intlog.IsEnabled() {
if strings.Contains(file, "github.com/gogf/gf/") {
continue
}
if strings.Contains(file, "github.com/gogf/gf@") {
continue
}
}
// Avoid stack string like "<autogenerated>"
if strings.Contains(file, "<") {
continue

View File

@ -15,8 +15,14 @@ const (
gMAX_STACK_DEPTH = 32
)
func callers() stack {
var pcs [gMAX_STACK_DEPTH]uintptr
n := runtime.Callers(3, pcs[:])
return pcs[0:n]
// callers returns the stack callers.
func callers(skip ...int) stack {
var (
pcs [gMAX_STACK_DEPTH]uintptr
n = 3
)
if len(skip) > 0 {
n += skip[0]
}
return pcs[:runtime.Callers(n, pcs[:])]
}

View File

@ -15,10 +15,6 @@ import (
"github.com/gogf/gf/test/gtest"
)
func interfaceNil() interface{} {
return nil
}
func nilError() error {
return nil
}

4
go.mod
View File

@ -1,16 +1,16 @@
module github.com/gogf/gf
go 1.13
go 1.11
require (
github.com/BurntSushi/toml v0.3.1
github.com/clbanning/mxj v1.8.4
github.com/fatih/structs v1.1.0
github.com/fsnotify/fsnotify v1.4.7
github.com/go-sql-driver/mysql v1.5.0
github.com/gomodule/redigo v2.0.0+incompatible
github.com/google/uuid v1.1.1
github.com/gorilla/websocket v1.4.1
github.com/gqcn/structs v1.1.1
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/olekukonko/tablewriter v0.0.1

View File

@ -7,7 +7,7 @@
// Package structs provides functions for struct conversion.
package structs
import "github.com/fatih/structs"
import "github.com/gqcn/structs"
// Field is alias of structs.Field.
type Field struct {

View File

@ -9,7 +9,7 @@ package structs
import (
"reflect"
"github.com/fatih/structs"
"github.com/gqcn/structs"
)
// MapField retrieves struct field as map[name/tag]*Field from <pointer>, and returns the map.
@ -22,15 +22,19 @@ import (
//
// Note that it only retrieves the exported attributes with first letter up-case from struct.
func MapField(pointer interface{}, priority []string, recursive bool) map[string]*Field {
fieldMap := make(map[string]*Field)
fields := ([]*structs.Field)(nil)
var (
fields []*structs.Field
fieldMap = make(map[string]*Field)
)
if v, ok := pointer.(reflect.Value); ok {
fields = structs.Fields(v.Interface())
} else {
fields = structs.Fields(pointer)
}
tag := ""
name := ""
var (
tag = ""
name = ""
)
for _, field := range fields {
name = field.Name()
// Only retrieve exported attributes.

View File

@ -9,7 +9,7 @@ package structs
import (
"reflect"
"github.com/fatih/structs"
"github.com/gqcn/structs"
)
// TagFields retrieves struct tags as []*Field from <pointer>, and returns it.
@ -31,8 +31,10 @@ func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap
if v, ok := pointer.(reflect.Value); ok {
fields = structs.Fields(v.Interface())
} else {
rv := reflect.ValueOf(pointer)
kind := rv.Kind()
var (
rv = reflect.ValueOf(pointer)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
@ -45,8 +47,10 @@ func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap
fields = structs.Fields(pointer)
}
}
tag := ""
name := ""
var (
tag = ""
name = ""
)
tagFields := make([]*Field, 0)
for _, field := range fields {
name = field.Name()
@ -72,8 +76,10 @@ func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap
})
}
if recursive {
rv := reflect.ValueOf(field.Value())
kind := rv.Kind()
var (
rv = reflect.ValueOf(field.Value())
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()

View File

@ -6,9 +6,3 @@
// Package ghttp provides powerful http server and simple client implements.
package ghttp
var (
// paramTagPriority is the priority tag array for request parameter
// to struct field mapping.
paramTagPriority = []string{"param", "params", "p"}
)

View File

@ -43,7 +43,6 @@ func NewClient() *Client {
DisableKeepAlives: true,
},
},
ctx: context.Background(),
header: make(map[string]string),
cookies: make(map[string]string),
}

View File

@ -0,0 +1,118 @@
// Copyright 2020 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package ghttp
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/http/httputil"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
)
// dumpTextFormat is the format of the dumped raw string
const dumpTextFormat = `+---------------------------------------------+
| %s |
+---------------------------------------------+
%s
%s
`
// ifDumpBody determines whether to output body according to content-type.
func ifDumpBody(contentType string) bool {
// the body should not be output when the body is html or stream.
if gstr.Contains(contentType, "application/json") ||
gstr.Contains(contentType, "application/xml") ||
gstr.Contains(contentType, "multipart/form-data") ||
gstr.Contains(contentType, "application/x-www-form-urlencoded") ||
gstr.Contains(contentType, "text/plain") {
return true
}
return false
}
// getRequestBody returns the raw text of the request body.
func getRequestBody(req *http.Request) string {
contentType := req.Header.Get("Content-Type")
if !ifDumpBody(contentType) {
return ""
}
// so that the request body can be read again.
bodyReader, errGetBody := req.GetBody()
if errGetBody != nil {
return ""
}
bytesBody, errReadBody := ioutil.ReadAll(bodyReader)
if errReadBody != nil {
return ""
}
return gconv.UnsafeBytesToStr(bytesBody)
}
// getResponseBody returns the text of the response body.
func getResponseBody(resp *http.Response) string {
contentType := resp.Header.Get("Content-Type")
if !ifDumpBody(contentType) {
return ""
}
bytesBody, errReadBody := ioutil.ReadAll(resp.Body)
if errReadBody != nil {
return ""
}
// So the response body can be read again.
resp.Body = ioutil.NopCloser(bytes.NewBuffer(bytesBody))
return gconv.UnsafeBytesToStr(bytesBody)
}
// RawRequest returns the raw content of the request.
func (r *ClientResponse) RawRequest() string {
// ClientResponse can be nil.
if r == nil {
return ""
}
if r.request == nil {
return ""
}
// DumpRequestOut writes more request headers than DumpRequest, such as User-Agent.
bs, err := httputil.DumpRequestOut(r.request, false)
if err != nil {
return ""
}
return fmt.Sprintf(
dumpTextFormat,
"REQUEST ",
gconv.UnsafeBytesToStr(bs),
getRequestBody(r.request),
)
}
// RawResponse returns the raw content of the response.
func (r *ClientResponse) RawResponse() string {
// ClientResponse can be nil.
if r == nil || r.Response == nil {
return ""
}
bs, err := httputil.DumpResponse(r.Response, false)
if err != nil {
return ""
}
return fmt.Sprintf(
dumpTextFormat,
"RESPONSE",
gconv.UnsafeBytesToStr(bs),
getResponseBody(r.Response),
)
}
// Raw returns the raw text of the request and the response.
func (r *ClientResponse) Raw() string {
return fmt.Sprintf("%s\n%s", r.RawRequest(), r.RawResponse())
}

View File

@ -11,10 +11,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/encoding/gparser"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"io"
"mime/multipart"
"net/http"
@ -22,6 +18,11 @@ import (
"strings"
"time"
"github.com/gogf/gf/encoding/gparser"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/os/gfile"
)
@ -156,7 +157,8 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien
if err = writer.Close(); err != nil {
return nil, err
}
if req, err = http.NewRequestWithContext(c.ctx, method, url, buffer); err != nil {
if req, err = http.NewRequest(method, url, buffer); err != nil {
return nil, err
} else {
req.Header.Set("Content-Type", writer.FormDataContentType())
@ -164,9 +166,7 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien
} else {
// Normal request.
paramBytes := []byte(param)
if req, err = http.NewRequestWithContext(
c.ctx, method, url, bytes.NewReader(paramBytes),
); err != nil {
if req, err = http.NewRequest(method, url, bytes.NewReader(paramBytes)); err != nil {
return nil, err
} else {
if v, ok := c.header["Content-Type"]; ok {
@ -183,6 +183,10 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien
}
}
}
// Context.
if c.ctx != nil {
req = req.WithContext(c.ctx)
}
// Custom header.
if len(c.header) > 0 {
for k, v := range c.header {
@ -211,27 +215,28 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien
if len(c.authUser) > 0 {
req.SetBasicAuth(c.authUser, c.authPass)
}
// Sending request.
var r *http.Response
// do not return nil even if the request fails
resp = &ClientResponse{}
for {
if r, err = c.Do(req); err != nil {
if resp.Response, err = c.Do(req); err != nil {
if c.retryCount > 0 {
c.retryCount--
time.Sleep(c.retryInterval)
} else {
return nil, err
// we need a copy of the request when the request fails.
resp.request = req
return resp, err
}
} else {
resp.request = resp.Request
break
}
}
resp = &ClientResponse{
Response: r,
}
// Auto saving cookie content.
if c.browserMode {
now := time.Now()
for _, v := range r.Cookies() {
for _, v := range resp.Response.Cookies() {
if v.Expires.UnixNano() < now.UnixNano() {
delete(c.cookies, v.Name)
} else {

View File

@ -7,27 +7,24 @@
package ghttp
import (
"github.com/gogf/gf/util/gconv"
"io/ioutil"
"net/http"
"time"
"github.com/gogf/gf/util/gconv"
)
// ClientResponse is the struct for client request response.
type ClientResponse struct {
*http.Response
request *http.Request
cookies map[string]string
}
// initCookie initializes the cookie map attribute of ClientResponse.
func (r *ClientResponse) initCookie() {
if r.cookies == nil {
now := time.Now()
r.cookies = make(map[string]string)
for _, v := range r.Cookies() {
if v.Expires.UnixNano() < now.UnixNano() {
continue
}
r.cookies[v.Name] = v.Value
}
}
@ -51,7 +48,7 @@ func (r *ClientResponse) GetCookieMap() map[string]string {
// ReadAll retrieves and returns the response content as []byte.
func (r *ClientResponse) ReadAll() []byte {
body, err := ioutil.ReadAll(r.Body)
body, err := ioutil.ReadAll(r.Response.Body)
if err != nil {
return nil
}
@ -66,5 +63,5 @@ func (r *ClientResponse) ReadAllString() string {
// Close closes the response when it will never be used.
func (r *ClientResponse) Close() error {
r.Response.Close = true
return r.Body.Close()
return r.Response.Body.Close()
}

View File

@ -7,6 +7,7 @@
package ghttp
import (
"github.com/gogf/gf/errors/gerror"
"strings"
"github.com/gogf/gf/encoding/gurl"
@ -54,12 +55,20 @@ func BuildParams(params interface{}, noUrlEncode ...bool) (encodedParamStr strin
// niceCallFunc calls function <f> with exception capture logic.
func niceCallFunc(f func()) {
defer func() {
if err := recover(); err != nil {
switch err {
if e := recover(); e != nil {
switch e {
case gEXCEPTION_EXIT, gEXCEPTION_EXIT_ALL:
return
default:
panic(err)
if _, ok := e.(gerror.ApiStack); ok {
// It's already an error that has stack info.
panic(e)
} else {
// Create a new error with stack info.
// Note that there's a skip pointing the start stacktrace
// of the real error point.
panic(gerror.NewfSkip(1, "%v", e))
}
}
}
}()

View File

@ -7,11 +7,10 @@
package ghttp
import (
"github.com/gogf/gf/errors/gerror"
"net/http"
"reflect"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/util/gutil"
)
@ -122,7 +121,15 @@ func (m *Middleware) Next() {
loop = false
}
}, func(exception interface{}) {
m.request.error = gerror.Newf("%v", exception)
if e, ok := exception.(gerror.ApiStack); ok {
// It's already an error that has stack info.
m.request.error = e.(error)
} else {
// Create a new error with stack info.
// Note that there's a skip pointing the start stacktrace
// of the real error point.
m.request.error = gerror.NewfSkip(1, "%v", exception)
}
m.request.Response.WriteStatus(http.StatusInternalServerError, exception)
loop = false
})

View File

@ -20,6 +20,7 @@ import (
"github.com/gogf/gf/util/gvalid"
"io/ioutil"
"mime/multipart"
"reflect"
"strings"
)
@ -28,17 +29,53 @@ var (
xmlHeaderBytes = []byte("<?xml")
)
// Parse calls r.GetStruct to convert the parameters, which are sent from client,
// to given struct, and then calls gvalid.CheckStruct validating the struct according
// Parse is the most commonly used function, which converts request parameters to struct or struct
// slice. It also automatically validates the struct or every element of the struct slice according
// to the validation tag of the struct.
//
// See r.GetStruct, gvalid.CheckStruct.
// The parameter <pointer> can be type of: *struct/**struct/*[]struct/*[]*struct.
//
// TODO: Improve the performance by reducing duplicated reflect usage on the same variable across packages.
func (r *Request) Parse(pointer interface{}) error {
if err := r.GetStruct(pointer); err != nil {
return err
var (
reflectVal1 = reflect.ValueOf(pointer)
reflectKind1 = reflectVal1.Kind()
)
if reflectKind1 != reflect.Ptr {
return fmt.Errorf(
"parameter should be type of *struct/**struct/*[]struct/*[]*struct, but got: %v",
reflectKind1,
)
}
if err := gvalid.CheckStruct(pointer, nil); err != nil {
return err
var (
reflectVal2 = reflectVal1.Elem()
reflectKind2 = reflectVal2.Kind()
)
switch reflectKind2 {
case reflect.Ptr, reflect.Struct:
// Struct conversion.
if err := r.GetStruct(pointer); err != nil {
return err
}
// Struct validation.
if err := gvalid.CheckStruct(pointer, nil); err != nil {
return err
}
case reflect.Array, reflect.Slice:
// If struct slice conversion, it might post JSON/XML content,
// so it uses gjson for the conversion.
j, err := gjson.LoadContent(r.GetBody())
if err != nil {
return err
}
if err := j.GetStructs(".", pointer); err != nil {
return err
}
for i := 0; i < reflectVal2.Len(); i++ {
if err := gvalid.CheckStruct(reflectVal2.Index(i), nil); err != nil {
return err
}
}
}
return nil
}
@ -278,13 +315,15 @@ func (r *Request) parseForm() {
for name, values := range r.PostForm {
// Invalid parameter name.
// Only allow chars of: '\w', '[', ']', '-'.
if !gregex.IsMatchString(`^[\w\-\[\]]+$`, name) {
if len(r.PostForm) == 1 {
// It might be JSON/XML content.
r.bodyContent = gconv.UnsafeStrToBytes(name + strings.Join(values, " "))
if !gregex.IsMatchString(`^[\w\-\[\]]+$`, name) && len(r.PostForm) == 1 {
// It might be JSON/XML content.
if s := gstr.Trim(name + strings.Join(values, " ")); len(s) > 0 {
if s[0] == '{' && s[len(s)-1] == '}' || s[0] == '<' && s[len(s)-1] == '>' {
r.bodyContent = gconv.UnsafeStrToBytes(s)
params = ""
break
}
}
params = ""
break
}
if len(values) == 1 {
if len(params) > 0 {
@ -308,8 +347,10 @@ func (r *Request) parseForm() {
}
}
}
if r.formMap, err = gstr.Parse(params); err != nil {
panic(err)
if params != "" {
if r.formMap, err = gstr.Parse(params); err != nil {
panic(err)
}
}
}
if r.formMap == nil {

View File

@ -8,7 +8,6 @@ package ghttp
import (
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/internal/structs"
"github.com/gogf/gf/util/gconv"
)
@ -190,13 +189,7 @@ func (r *Request) GetFormMapStrVar(kvMap ...map[string]interface{}) map[string]*
// The optional parameter <mapping> is used to specify the key to attribute mapping.
func (r *Request) GetFormStruct(pointer interface{}, mapping ...map[string]string) error {
r.parseForm()
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
for k, v := range mapping[0] {
tagMap[k] = v
}
}
return gconv.StructDeep(r.formMap, pointer, tagMap)
return gconv.StructDeep(r.formMap, pointer, mapping...)
}
// GetFormToStruct is alias of GetFormStruct. See GetFormStruct.

View File

@ -8,7 +8,6 @@ package ghttp
import (
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/internal/structs"
"github.com/gogf/gf/util/gconv"
)
@ -205,13 +204,7 @@ func (r *Request) GetPostMapStrVar(kvMap ...map[string]interface{}) map[string]*
//
// Deprecated.
func (r *Request) GetPostStruct(pointer interface{}, mapping ...map[string]string) error {
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
for k, v := range mapping[0] {
tagMap[k] = v
}
}
return gconv.StructDeep(r.GetPostMap(), pointer, tagMap)
return gconv.StructDeep(r.GetPostMap(), pointer, mapping...)
}
// GetPostToStruct is alias of GetQueryStruct. See GetPostStruct.

View File

@ -9,7 +9,6 @@ package ghttp
import (
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/internal/structs"
"github.com/gogf/gf/util/gconv"
)
@ -194,13 +193,7 @@ func (r *Request) GetQueryMapStrVar(kvMap ...map[string]interface{}) map[string]
// attribute mapping.
func (r *Request) GetQueryStruct(pointer interface{}, mapping ...map[string]string) error {
r.parseQuery()
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
for k, v := range mapping[0] {
tagMap[k] = v
}
}
return gconv.StructDeep(r.GetQueryMap(), pointer, tagMap)
return gconv.StructDeep(r.GetQueryMap(), pointer, mapping...)
}
// GetQueryToStruct is alias of GetQueryStruct. See GetQueryStruct.

View File

@ -8,7 +8,6 @@ package ghttp
import (
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/internal/structs"
"github.com/gogf/gf/util/gconv"
)
@ -268,13 +267,7 @@ func (r *Request) GetRequestMapStrVar(kvMap ...map[string]interface{}) map[strin
// the parameter <pointer> is a pointer to the struct object.
// The optional parameter <mapping> is used to specify the key to attribute mapping.
func (r *Request) GetRequestStruct(pointer interface{}, mapping ...map[string]string) error {
tagMap := structs.TagMapName(pointer, paramTagPriority, true)
if len(mapping) > 0 {
for k, v := range mapping[0] {
tagMap[k] = v
}
}
return gconv.StructDeep(r.GetRequestMap(), pointer, tagMap)
return gconv.StructDeep(r.GetRequestMap(), pointer, mapping...)
}
// GetRequestToStruct is alias of GetRequestStruct. See GetRequestStruct.

View File

@ -179,29 +179,21 @@ func init() {
}
}
// 主要用于开发者在HTTP处理中自定义异常捕获时判断捕获的异常是否Server抛出的自定义退出异常
func IsExitError(err interface{}) bool {
errStr := gconv.String(err)
if strings.EqualFold(errStr, gEXCEPTION_EXIT) ||
strings.EqualFold(errStr, gEXCEPTION_EXIT_ALL) ||
strings.EqualFold(errStr, gEXCEPTION_EXIT_HOOK) {
return true
}
return false
}
// 是否开启平滑重启特性
// SetGraceful enables/disables the graceful reload feature for server,
// which is false in default.
//
// Note that this feature switch is not for single server instance but for whole process.
func SetGraceful(enabled bool) {
gracefulEnabled = enabled
}
// Web Server进程初始化.
// 注意该方法不能放置于包初始化方法init中不使用ghttp.Server的功能便不能初始化对应的协程goroutine逻辑.
// serverProcessInit initializes some process configurations, which can only be done once.
func serverProcessInit() {
if !serverProcessInited.Cas(false, true) {
return
}
// 如果是完整重启,那么需要等待主进程销毁后,才开始执行监听,防止端口冲突
// This means it is a restart server, it should kill its parent before starting its listening,
// to avoid duplicated port listening in two processes.
if genv.Get(gADMIN_ACTION_RESTART_ENVKEY) != "" {
if p, e := os.FindProcess(gproc.PPid()); e == nil {
p.Kill()
@ -211,21 +203,24 @@ func serverProcessInit() {
}
}
// 信号量管理操作监听
// Signal handler.
go handleProcessSignal()
// 异步监听进程间消息
// Process message handler.
// It's enabled only graceful feature is enabled.
if gracefulEnabled {
go handleProcessMessage()
}
// 是否处于开发环境这里调用该方法初始化main包路径值
// 防止异步服务goroutine获取main包路径失败
// 该方法只有在main协程中才会执行。
// It's an ugly calling for better initializing the main package path
// in source development environment. It is useful only be used in main goroutine.
// It fails retrieving the main package path in asynchronized goroutines.
gfile.MainPkgPath()
}
// 获取/创建一个默认配置的HTTP Server(默认监听端口是80)
// 单例模式请保证name的唯一性
// GetServer creates and returns a server instance using given name and default configurations.
// Note that the parameter <name> should be unique for different servers. It returns an existing
// server instance if given <name> is already existing in the server mapping.
func GetServer(name ...interface{}) *Server {
serverName := gDEFAULT_SERVER
if len(name) > 0 && name[0] != "" {
@ -234,7 +229,6 @@ func GetServer(name ...interface{}) *Server {
if s := serverMapping.Get(serverName); s != nil {
return s.(*Server)
}
c := Config()
s := &Server{
name: serverName,
plugins: make([]Plugin, 0),
@ -246,17 +240,17 @@ func GetServer(name ...interface{}) *Server {
serveCache: gcache.New(),
routesMap: make(map[string][]registeredRouteItem),
}
// 初始化时使用默认配置
if err := s.SetConfig(c); err != nil {
// Initialize the server using default configurations.
if err := s.SetConfig(Config()); err != nil {
panic(err)
}
// 记录到全局ServerMap中
// Record the server to internal server mapping by name.
serverMapping.Set(serverName, s)
return s
}
// 作为守护协程异步执行(当同一进程中存在多个Web Server时需要采用这种方式执行),
// 需要结合Wait方式一起使用.
// Start starts listening on configured port.
// This function does not block the process, you can use function Wait blocking the process.
func (s *Server) Start() error {
// Register group routes.
s.handlePreBindItems()
@ -481,14 +475,11 @@ func Wait() {
glog.Printf("[ghttp] %d: all servers shutdown", gproc.Pid())
}
// 开启底层Web Server执行
// startServer starts the underlying server listening.
func (s *Server) startServer(fdMap listenerFdMap) {
var httpsEnabled bool
// 判断是否启用HTTPS
// HTTPS
if s.config.TLSConfig != nil || (s.config.HTTPSCertPath != "" && s.config.HTTPSKeyPath != "") {
// ================
// HTTPS
// ================
if len(s.config.HTTPSAddr) == 0 {
if len(s.config.Address) > 0 {
s.config.HTTPSAddr = s.config.Address
@ -513,7 +504,8 @@ func (s *Server) startServer(fdMap listenerFdMap) {
array := strings.Split(v, "#")
if len(array) > 1 {
itemFunc = array[0]
// windows系统不支持文件描述符传递socket通信平滑交接因此只能完整重启
// The windows OS does not support socket file descriptor passing
// from parent process.
if runtime.GOOS != "windows" {
fd = gconv.Int(array[1])
}
@ -526,10 +518,7 @@ func (s *Server) startServer(fdMap listenerFdMap) {
s.servers[len(s.servers)-1].isHttps = true
}
}
// ================
// HTTP
// ================
// 当HTTPS服务未启用时默认HTTP地址才会生效
if !httpsEnabled && len(s.config.Address) == 0 {
s.config.Address = gDEFAULT_HTTP_ADDR
}
@ -548,7 +537,8 @@ func (s *Server) startServer(fdMap listenerFdMap) {
array := strings.Split(v, "#")
if len(array) > 1 {
itemFunc = array[0]
// windows系统不支持文件描述符传递socket通信平滑交接因此只能完整重启
// The windows OS does not support socket file descriptor passing
// from parent process.
if runtime.GOOS != "windows" {
fd = gconv.Int(array[1])
}
@ -559,7 +549,7 @@ func (s *Server) startServer(fdMap listenerFdMap) {
s.servers = append(s.servers, s.newGracefulServer(itemFunc))
}
}
// 开始执行异步监听
// Start listening asynchronizedly.
serverRunning.Add(1)
for _, v := range s.servers {
go func(server *gracefulServer) {
@ -570,14 +560,13 @@ func (s *Server) startServer(fdMap listenerFdMap) {
} else {
err = server.ListenAndServe()
}
// 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作
// The process exits if the server is closed with none closing error.
if err != nil && !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) {
s.Logger().Fatal(err)
}
// 如果所有异步的http.Server都已经停止那么WebServer就可以退出了
// If all the underlying servers shutdown, the process exits.
if s.serverCount.Add(-1) < 1 {
s.closeChan <- struct{}{}
// 如果所有WebServer都退出那么退出Wait等待
if serverRunning.Add(-1) < 1 {
serverMapping.Remove(s.name)
allDoneChan <- struct{}{}
@ -587,13 +576,12 @@ func (s *Server) startServer(fdMap listenerFdMap) {
}
}
// 获取当前服务器的状态
// Status retrieves and returns the server status.
func (s *Server) Status() int {
// 当全局运行的Web Server数量为0时表示所有Server都是停止状态
if serverRunning.Val() == 0 {
return SERVER_STATUS_STOPPED
}
// 只要有一个Server处于运行状态那么都表示运行状态
// If any underlying server is running, the server status is running.
for _, v := range s.servers {
if v.status == SERVER_STATUS_RUNNING {
return SERVER_STATUS_RUNNING
@ -602,28 +590,39 @@ func (s *Server) Status() int {
return SERVER_STATUS_STOPPED
}
// 获取当前监听的文件描述符信息构造成map返回
// getListenerFdMap retrieves and returns the socket file descriptors.
// The key of the returned map is "http" and "https".
func (s *Server) getListenerFdMap() map[string]string {
m := map[string]string{
"https": "",
"http": "",
}
// s.servers是从HTTPS到HTTP优先级遍历解析的时候也应当按照这个顺序读取fd
for _, v := range s.servers {
str := v.itemFunc + "#" + gconv.String(v.Fd()) + ","
str := v.address + "#" + gconv.String(v.Fd()) + ","
if v.isHttps {
if len(m["https"]) > 0 {
m["https"] += ","
}
m["https"] += str
} else {
if len(m["http"]) > 0 {
m["http"] += ","
}
m["http"] += str
}
}
// 去掉末尾的","号
if len(m["https"]) > 0 {
m["https"] = m["https"][0 : len(m["https"])-1]
}
if len(m["http"]) > 0 {
m["http"] = m["http"][0 : len(m["http"])-1]
}
return m
}
// IsExitError checks if given error is an exit error of server.
// This is used in old version of server for custom error handler.
// Deprecated.
func IsExitError(err interface{}) bool {
errStr := gconv.String(err)
if strings.EqualFold(errStr, gEXCEPTION_EXIT) ||
strings.EqualFold(errStr, gEXCEPTION_EXIT_ALL) ||
strings.EqualFold(errStr, gEXCEPTION_EXIT_HOOK) {
return true
}
return false
}

View File

@ -3,7 +3,6 @@
// 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://github.com/gogf/gf.
// pprof封装.
package ghttp
@ -17,7 +16,10 @@ import (
"github.com/gogf/gf/os/gview"
)
// 服务管理首页
// utilAdmin is the controller for administration.
type utilAdmin struct{}
// Index shows the administration page.
func (p *utilAdmin) Index(r *Request) {
data := map[string]interface{}{
"pid": gproc.Pid(),
@ -38,38 +40,40 @@ func (p *utilAdmin) Index(r *Request) {
r.Response.Write(buffer)
}
// 服务重启
// Restart restarts all the servers in the process.
func (p *utilAdmin) Restart(r *Request) {
var err error = nil
// 必须检查可执行文件的权限
// Custom start binary path when this process exits.
path := r.GetQueryString("newExeFilePath")
if path == "" {
path = os.Args[0]
}
// 执行重启操作
if len(path) > 0 {
err = RestartAllServer(path)
} else {
err = RestartAllServer()
}
if err == nil {
r.Response.Write("server restarted")
r.Response.WriteExit("server restarted")
} else {
r.Response.Write(err.Error())
r.Response.WriteExit(err.Error())
}
}
// 服务关闭
// Shutdown shuts down all the servers.
func (p *utilAdmin) Shutdown(r *Request) {
r.Server.Shutdown()
if err := r.Server.Shutdown(); err != nil {
r.Response.WriteExit(err.Error())
}
if err := ShutdownAllServer(); err == nil {
r.Response.Write("server shutdown")
r.Response.WriteExit("server shutdown")
} else {
r.Response.Write(err.Error())
r.Response.WriteExit(err.Error())
}
}
// 开启服务管理支持
// EnableAdmin enables the administration feature for the process.
// The optional parameter <pattern> specifies the URI for the administration page.
func (s *Server) EnableAdmin(pattern ...string) {
p := "/debug/admin"
if len(pattern) > 0 {
@ -78,12 +82,13 @@ func (s *Server) EnableAdmin(pattern ...string) {
s.BindObject(p, &utilAdmin{})
}
// 关闭当前Web Server
// Shutdown shuts down current server.
func (s *Server) Shutdown() error {
// 非终端信号下异步1秒后再执行关闭
// 目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了)
// It shuts down the server after 1 second, which is not triggered by system signal,
// to ensure the response successfully to the client.
gtimer.SetTimeout(time.Second, func() {
// 只关闭当前的Web Server
// Only shut down current server.
// It may have multiple underlying http servers.
for _, v := range s.servers {
v.close()
}

View File

@ -3,7 +3,6 @@
// 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://github.com/gogf/gf.
// pprof封装.
package ghttp
@ -27,7 +26,8 @@ import (
)
const (
gADMIN_ACTION_INTERVAL_LIMIT = 2000 // (毫秒)服务开启后允许执行管理操作的间隔限制
// Allow executing management command after server starts after this interval in milliseconds.
gADMIN_ACTION_INTERVAL_LIMIT = 2000
gADMIN_ACTION_NONE = 0
gADMIN_ACTION_RESTARTING = 1
gADMIN_ACTION_SHUTINGDOWN = 2
@ -36,48 +36,44 @@ const (
gADMIN_GPROC_COMM_GROUP = "GF_GPROC_HTTP_SERVER"
)
// 用于服务管理的对象
type utilAdmin struct{}
// (进程级别)用于Web Server管理操作的互斥锁保证管理操作的原子性
// serverActionLocker is the locker for server administration operations.
var serverActionLocker sync.Mutex
// (进程级别)用于记录上一次操作的时间(毫秒)
// serverActionLastTime is timestamp in milliseconds of last administration operation.
var serverActionLastTime = gtype.NewInt64(gtime.TimestampMilli())
// 当前服务进程所处的互斥管理操作状态
// serverProcessStatus is the server status for operation of current process.
var serverProcessStatus = gtype.NewInt()
// 重启Web Server参数支持自定义重启的可执行文件路径不传递时默认和原有可执行文件路径一致。
// 针对*niux系统: 平滑重启
// 针对windows : 完整重启
// RestartAllServer restarts all the servers of the process.
// The optional parameter <newExeFilePath> specifies the new binary file for creating process.
func RestartAllServer(newExeFilePath ...string) error {
serverActionLocker.Lock()
defer serverActionLocker.Unlock()
if err := checkProcessStatus(); err != nil {
return err
}
if err := checkActionFrequence(); err != nil {
if err := checkActionFrequency(); err != nil {
return err
}
return restartWebServers("", newExeFilePath...)
}
// 关闭所有的WebServer
// ShutdownAllServer shuts down all servers of current process.
func ShutdownAllServer() error {
serverActionLocker.Lock()
defer serverActionLocker.Unlock()
if err := checkProcessStatus(); err != nil {
return err
}
if err := checkActionFrequence(); err != nil {
if err := checkActionFrequency(); err != nil {
return err
}
shutdownWebServers()
return nil
}
// 检查当前服务进程的状态
// checkProcessStatus checks the server status of current process.
func checkProcessStatus() error {
status := serverProcessStatus.Val()
if status > 0 {
@ -91,8 +87,9 @@ func checkProcessStatus() error {
return nil
}
// 检测当前操作的频繁度
func checkActionFrequence() error {
// checkActionFrequency checks the operation frequency.
// It returns error if it is too frequency.
func checkActionFrequency() error {
interval := gtime.TimestampMilli() - serverActionLastTime.Val()
if interval < gADMIN_ACTION_INTERVAL_LIMIT {
return errors.New(fmt.Sprintf("too frequent action, please retry in %d ms", gADMIN_ACTION_INTERVAL_LIMIT-interval))
@ -101,16 +98,16 @@ func checkActionFrequence() error {
return nil
}
// 平滑重启:创建一个子进程,通过环境变量传参
// forkReloadProcess creates a new child process and copies the fd to child process.
func forkReloadProcess(newExeFilePath ...string) error {
path := os.Args[0]
if len(newExeFilePath) > 0 {
path = newExeFilePath[0]
}
p := gproc.NewProcess(path, os.Args, os.Environ())
// 创建新的服务进程,子进程自动从父进程复制文件描述来监听同样的端口
sfm := getServerFdMap()
// 将sfm中的fd按照子进程创建时的文件描述符顺序进行整理以便子进程获取到正确的fd
var (
p = gproc.NewProcess(path, os.Args, os.Environ())
sfm = getServerFdMap()
)
for name, m := range sfm {
for fdk, fdv := range m {
if len(fdv) > 0 {
@ -138,13 +135,12 @@ func forkReloadProcess(newExeFilePath ...string) error {
return nil
}
// 完整重启:创建一个新的子进程
// forkRestartProcess creates a new server process.
func forkRestartProcess(newExeFilePath ...string) error {
path := os.Args[0]
if len(newExeFilePath) > 0 {
path = newExeFilePath[0]
}
// 去掉平滑重启的环境变量参数
os.Unsetenv(gADMIN_ACTION_RELOAD_ENVKEY)
env := os.Environ()
env = append(env, gADMIN_ACTION_RESTART_ENVKEY+"=1")
@ -156,7 +152,7 @@ func forkRestartProcess(newExeFilePath ...string) error {
return nil
}
// 获取所有Web Server的文件描述符map
// getServerFdMap returns all the servers name to file descriptor mapping as map.
func getServerFdMap() map[string]listenerFdMap {
sfm := make(map[string]listenerFdMap)
serverMapping.RLockFunc(func(m map[string]interface{}) {
@ -167,7 +163,7 @@ func getServerFdMap() map[string]listenerFdMap {
return sfm
}
// 二进制转换为FdMap
// bufferToServerFdMap converts binary content to fd map.
func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap {
sfm := make(map[string]listenerFdMap)
if len(buffer) > 0 {
@ -183,16 +179,17 @@ func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap {
return sfm
}
// Web Server重启
// restartWebServers restarts all servers.
func restartWebServers(signal string, newExeFilePath ...string) error {
serverProcessStatus.Set(gADMIN_ACTION_RESTARTING)
if runtime.GOOS == "windows" {
if len(signal) > 0 {
// 在终端信号下,立即执行重启操作
// Controlled by signal.
forceCloseWebServers()
forkRestartProcess(newExeFilePath...)
} else {
// 非终端信号下异步1秒后再执行重启目的是让接口能够正确返回结果否则接口会报错(因为web server关闭了)
// Controlled by web page.
// It should ensure the response wrote to client and then close all servers gracefully.
gtimer.SetTimeout(time.Second, func() {
forceCloseWebServers()
forkRestartProcess(newExeFilePath...)
@ -215,18 +212,15 @@ func restartWebServers(signal string, newExeFilePath ...string) error {
return nil
}
// 关闭所有Web Server
// shutdownWebServers shuts down all servers.
func shutdownWebServers(signal ...string) {
serverProcessStatus.Set(gADMIN_ACTION_SHUTINGDOWN)
if len(signal) > 0 {
glog.Printf("%d: server shutting down by signal: %s", gproc.Pid(), signal[0])
// 在终端信号下,立即执行关闭操作
forceCloseWebServers()
allDoneChan <- struct{}{}
} else {
glog.Printf("%d: server shutting down by api", gproc.Pid())
// 非终端信号下异步1秒后再执行关闭
// 目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了)
gtimer.SetTimeout(time.Second, func() {
forceCloseWebServers()
allDoneChan <- struct{}{}
@ -234,8 +228,7 @@ func shutdownWebServers(signal ...string) {
}
}
// 关优雅闭进程所有端口的Web Server服务
// 注意只是关闭Web Server服务并不是退出进程
// gracefulShutdownWebServers gracefully shuts down all servers.
func gracefulShutdownWebServers() {
serverMapping.RLockFunc(func(m map[string]interface{}) {
for _, v := range m {
@ -246,8 +239,7 @@ func gracefulShutdownWebServers() {
})
}
// 强制关闭进程所有端口的Web Server服务
// 注意只是关闭Web Server服务并不是退出进程
// forceCloseWebServers forced shuts down all servers.
func forceCloseWebServers() {
serverMapping.RLockFunc(func(m map[string]interface{}) {
for _, v := range m {
@ -258,7 +250,7 @@ func forceCloseWebServers() {
})
}
// 异步监听进程间消息
// handleProcessMessage listens the signal from system.
func handleProcessMessage() {
for {
if msg := gproc.Receive(gADMIN_GPROC_COMM_GROUP); msg != nil {

View File

@ -15,10 +15,10 @@ import (
"syscall"
)
// 进程信号量监听消息队列
// procSignalChan is the channel for listening the signal.
var procSignalChan = make(chan os.Signal)
// 信号量处理
// handleProcessSignal handles all signal from system.
func handleProcessSignal() {
var sig os.Signal
signal.Notify(
@ -35,12 +35,12 @@ func handleProcessSignal() {
sig = <-procSignalChan
intlog.Printf(`signal received: %s`, sig.String())
switch sig {
// 进程终止,停止所有子进程运行
// Stop the servers.
case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGABRT:
shutdownWebServers(sig.String())
return
// 用户信号,重启服务
// Restart the servers.
case syscall.SIGUSR1:
restartWebServers(sig.String())
return

View File

@ -8,7 +8,7 @@
package ghttp
// windows不处理信号量
// handleProcessSignal does nothing on windows platform.
func handleProcessSignal() {
}

View File

@ -10,6 +10,7 @@ import (
"crypto/tls"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/util/gutil"
"net/http"
"strconv"
"time"
@ -33,55 +34,206 @@ const (
URI_TYPE_CAMEL = 3 // Method name to URI converting type, which converts name to its camel case.
)
// HTTP Server configuration.
// ServerConfig is the HTTP Server configuration manager.
type ServerConfig struct {
Address string // Basic: Server listening address like ":port", multiple addresses joined using ','.
HTTPSAddr string // Basic: HTTPS addresses, multiple addresses joined using char ','.
HTTPSCertPath string // Basic: HTTPS certification file path.
HTTPSKeyPath string // Basic: HTTPS key file path.
TLSConfig *tls.Config // Basic: TLS configuration for use by ServeTLS and ListenAndServeTLS.
Handler http.Handler // Basic: Request handler.
ReadTimeout time.Duration // Basic: Maximum duration for reading the entire request, including the body.
WriteTimeout time.Duration // Basic: Maximum duration before timing out writes of the response.
IdleTimeout time.Duration // Basic: Maximum amount of time to wait for the next request when keep-alive is enabled.
MaxHeaderBytes int // Basic: Maximum number of bytes the server will read parsing the request header's keys and values, including the request line.
KeepAlive bool // Basic: Enable HTTP keep-alive.
ServerAgent string // Basic: Server agent information.
View *gview.View // Basic: View object for the server.
Rewrites map[string]string // Static: URI rewrite rules map.
IndexFiles []string // Static: The index files for static folder.
IndexFolder bool // Static: List sub-files when requesting folder; server responses HTTP status code 403 if false.
ServerRoot string // Static: The root directory for static service.
SearchPaths []string // Static: Additional searching directories for static service.
StaticPaths []staticPathItem // Static: URI to directory mapping array.
FileServerEnabled bool // Static: Switch for static service.
CookieMaxAge time.Duration // Cookie: Max TTL for cookie items.
CookiePath string // Cookie: Cookie Path(also affects the default storage for session id).
CookieDomain string // Cookie: Cookie Domain(also affects the default storage for session id).
SessionMaxAge time.Duration // Session: Max TTL for session items.
SessionIdName string // Session: Session id name.
SessionPath string // Session: Session Storage directory path for storing session files.
SessionStorage gsession.Storage // Session: Session Storage implementer.
Logger *glog.Logger // Logging: Logger for server.
LogPath string // Logging: Directory for storing logging files.
LogStdout bool // Logging: Printing logging content to stdout.
ErrorStack bool // Logging: Logging stack information when error.
ErrorLogEnabled bool // Logging: Enable error logging files.
ErrorLogPattern string // Logging: Error log file pattern like: error-{Ymd}.log
AccessLogEnabled bool // Logging: Enable access logging files.
AccessLogPattern string // Logging: Error log file pattern like: access-{Ymd}.log
PProfEnabled bool // PProf: Enable PProf feature.
PProfPattern string // PProf: PProf service pattern for router.
FormParsingMemory int64 // Other: Max memory in bytes which can be used for parsing multimedia form.
NameToUriType int // Other: Type for converting struct method name to URI when registering routes.
RouteOverWrite bool // Other: Allow overwrite the route if duplicated.
DumpRouterMap bool // Other: Whether automatically dump router map when server starts.
Graceful bool // Other: Enable graceful reload feature for all servers of the process.
// ==================================
// Basic.
// ==================================
// Address specifies the server listening address like "port" or ":port",
// multiple addresses joined using ','.
Address string
// HTTPSAddr specifies the HTTPS addresses, multiple addresses joined using char ','.
HTTPSAddr string
// HTTPSCertPath specifies certification file path for HTTPS service.
HTTPSCertPath string
// HTTPSKeyPath specifies the key file path for HTTPS service.
HTTPSKeyPath string
// TLSConfig optionally provides a TLS configuration for use
// by ServeTLS and ListenAndServeTLS. Note that this value is
// cloned by ServeTLS and ListenAndServeTLS, so it's not
// possible to modify the configuration with methods like
// tls.Config.SetSessionTicketKeys. To use
// SetSessionTicketKeys, use Server.Serve with a TLS Listener
// instead.
TLSConfig *tls.Config
// Handler the handler for HTTP request.
Handler http.Handler
// ReadTimeout is the maximum duration for reading the entire
// request, including the body.
//
// Because ReadTimeout does not let Handlers make per-request
// decisions on each request body's acceptable deadline or
// upload rate, most users will prefer to use
// ReadHeaderTimeout. It is valid to use them both.
ReadTimeout time.Duration
// WriteTimeout is the maximum duration before timing out
// writes of the response. It is reset whenever a new
// request's header is read. Like ReadTimeout, it does not
// let Handlers make decisions on a per-request basis.
WriteTimeout time.Duration
// IdleTimeout is the maximum amount of time to wait for the
// next request when keep-alives are enabled. If IdleTimeout
// is zero, the value of ReadTimeout is used. If both are
// zero, there is no timeout.
IdleTimeout time.Duration
// MaxHeaderBytes controls the maximum number of bytes the
// server will read parsing the request header's keys and
// values, including the request line. It does not limit the
// size of the request body.
//
// It can be configured in configuration file using string like: 1m, 10m, 500kb etc.
// It's 1024 bytes in default.
MaxHeaderBytes int
// KeepAlive enables HTTP keep-alive.
KeepAlive bool
// ServerAgent specifies the server agent information, which is wrote to
// HTTP response header as "Server".
ServerAgent string
// View specifies the default template view object for the server.
View *gview.View
// ==================================
// Static.
// ==================================
// Rewrites specifies the URI rewrite rules map.
Rewrites map[string]string
// IndexFiles specifies the index files for static folder.
IndexFiles []string
// IndexFolder specifies if listing sub-files when requesting folder.
// The server responses HTTP status code 403 if it is false.
IndexFolder bool
// ServerRoot specifies the root directory for static service.
ServerRoot string
// SearchPaths specifies additional searching directories for static service.
SearchPaths []string
// StaticPaths specifies URI to directory mapping array.
StaticPaths []staticPathItem
// FileServerEnabled is the global switch for static service.
// It is automatically set enabled if any static path is set.
FileServerEnabled bool
// ==================================
// Cookie.
// ==================================
// CookieMaxAge specifies the max TTL for cookie items.
CookieMaxAge time.Duration
// CookiePath specifies cookie path.
// It also affects the default storage for session id.
CookiePath string
// CookieDomain specifies cookie domain.
// It also affects the default storage for session id.
CookieDomain string
// ==================================
// Session.
// ==================================
// SessionMaxAge specifies max TTL for session items.
SessionMaxAge time.Duration
// SessionIdName specifies the session id name.
SessionIdName string
// SessionPath specifies the session storage directory path for storing session files.
// It only makes sense if the session storage is type of file storage.
SessionPath string
// SessionStorage specifies the session storage.
SessionStorage gsession.Storage
// ==================================
// Logging.
// ==================================
// Logger specifies the logger for server.
Logger *glog.Logger
// LogPath specifies the directory for storing logging files.
LogPath string
// LogStdout specifies whether printing logging content to stdout.
LogStdout bool
// ErrorStack specifies whether logging stack information when error.
ErrorStack bool
// ErrorLogEnabled enables error logging content to files.
ErrorLogEnabled bool
// ErrorLogPattern specifies the error log file pattern like: error-{Ymd}.log
ErrorLogPattern string
// AccessLogEnabled enables access logging content to files.
AccessLogEnabled bool
// AccessLogPattern specifies the error log file pattern like: access-{Ymd}.log
AccessLogPattern string
// ==================================
// PProf.
// ==================================
// PProfEnabled enables PProf feature.
PProfEnabled bool
// PProfPattern specifies the PProf service pattern for router.
PProfPattern string
// ==================================
// Other.
// ==================================
// ClientMaxBodySize specifies the max body size limit in bytes for client request.
// It can be configured in configuration file using string like: 1m, 10m, 500kb etc.
// It's 8MB in default.
ClientMaxBodySize int64
// FormParsingMemory specifies max memory buffer size in bytes which can be used for
// parsing multimedia form.
// It can be configured in configuration file using string like: 1m, 10m, 500kb etc.
// It's 1MB in default.
FormParsingMemory int64
// NameToUriType specifies the type for converting struct method name to URI when
// registering routes.
NameToUriType int
// RouteOverWrite allows overwrite the route if duplicated.
RouteOverWrite bool
// DumpRouterMap specifies whether automatically dumps router map when server starts.
DumpRouterMap bool
// Graceful enables graceful reload feature for all servers of the process.
Graceful bool
}
// Config returns the default ServerConfig object.
// Note that, do not define this default configuration to local variable,
// as there're some pointer attributes that may be shared in different servers.
// Config creates and returns a ServerConfig object with default configurations.
// Note that, do not define this default configuration to local package variable, as there're
// some pointer attributes that may be shared in different servers.
func Config() ServerConfig {
return ServerConfig{
Address: "",
@ -112,7 +264,8 @@ func Config() ServerConfig {
AccessLogEnabled: false,
AccessLogPattern: "access-{Ymd}.log",
DumpRouterMap: true,
FormParsingMemory: 100 * 1024 * 1024, // 100MB
ClientMaxBodySize: 8 * 1024 * 1024, // 8MB
FormParsingMemory: 1024 * 1024, // 1MB
Rewrites: make(map[string]string),
Graceful: true,
}
@ -130,6 +283,21 @@ func ConfigFromMap(m map[string]interface{}) (ServerConfig, error) {
// SetConfigWithMap sets the configuration for the server using map.
func (s *Server) SetConfigWithMap(m map[string]interface{}) error {
// The m now is a shallow copy of m.
// Any changes to m does not affect the original one.
// A little tricky, isn't it?
m = gutil.MapCopy(m)
// Allow setting the size configuration items using string size like:
// 1m, 100mb, 512kb, etc.
if k, v := gutil.MapPossibleItemByKey(m, "MaxHeaderBytes"); k != "" {
m[k] = gfile.StrToSize(gconv.String(v))
}
if k, v := gutil.MapPossibleItemByKey(m, "ClientMaxBodySize"); k != "" {
m[k] = gfile.StrToSize(gconv.String(v))
}
if k, v := gutil.MapPossibleItemByKey(m, "FormParsingMemory"); k != "" {
m[k] = gfile.StrToSize(gconv.String(v))
}
// Update the current configuration object.
if err := gconv.Struct(m, &s.config); err != nil {
return err
@ -156,7 +324,6 @@ func (s *Server) SetConfig(c ServerConfig) error {
s.EnableHTTPS(c.HTTPSCertPath, c.HTTPSKeyPath)
}
SetGraceful(c.Graceful)
intlog.Printf("SetConfig: %+v", s.config)
return nil
}
@ -168,7 +335,7 @@ func (s *Server) SetAddr(address string) {
}
// SetPort sets the listening ports for the server.
// The listening ports can be multiple.
// The listening ports can be multiple like: SetPort(80, 8080).
func (s *Server) SetPort(port ...int) {
if len(port) > 0 {
s.config.Address = ""
@ -187,7 +354,7 @@ func (s *Server) SetHTTPSAddr(address string) {
}
// SetHTTPSPort sets the HTTPS listening ports for the server.
// The listening ports can be multiple.
// The listening ports can be multiple like: SetHTTPSPort(443, 500).
func (s *Server) SetHTTPSPort(port ...int) {
if len(port) > 0 {
s.config.HTTPSAddr = ""

View File

@ -10,26 +10,32 @@ import (
"time"
)
// SetCookieMaxAge sets the CookieMaxAge for server.
func (s *Server) SetCookieMaxAge(ttl time.Duration) {
s.config.CookieMaxAge = ttl
}
// SetCookiePath sets the CookiePath for server.
func (s *Server) SetCookiePath(path string) {
s.config.CookiePath = path
}
// SetCookieDomain sets the CookieDomain for server.
func (s *Server) SetCookieDomain(domain string) {
s.config.CookieDomain = domain
}
// GetCookieMaxAge returns the CookieMaxAge of server.
func (s *Server) GetCookieMaxAge() time.Duration {
return s.config.CookieMaxAge
}
// GetCookiePath returns the CookiePath of server.
func (s *Server) GetCookiePath() string {
return s.config.CookiePath
}
// GetCookieDomain returns CookieDomain of server.
func (s *Server) GetCookieDomain() string {
return s.config.CookieDomain
}

View File

@ -8,7 +8,8 @@ package ghttp
import "github.com/gogf/gf/internal/intlog"
// 设置日志目录,只有在设置了日志目录的情况下才会输出日志到日志文件中。
// SetLogPath sets the log path for server.
// It logs content to file only if the log path is set.
func (s *Server) SetLogPath(path string) {
if len(path) == 0 {
return
@ -19,38 +20,37 @@ func (s *Server) SetLogPath(path string) {
s.config.AccessLogEnabled = true
}
// 设置日志内容是否输出到终端,默认情况下只有错误日志才会自动输出到终端。
// 如果需要输出请求日志到终端默认情况下使用SetAccessLogEnabled方法开启请求日志特性即可。
// SetLogStdout sets whether output the logging content to stdout.
func (s *Server) SetLogStdout(enabled bool) {
s.config.LogStdout = enabled
}
// 设置是否开启access log日志功能
// SetAccessLogEnabled enables/disables the access log.
func (s *Server) SetAccessLogEnabled(enabled bool) {
s.config.AccessLogEnabled = enabled
}
// 设置是否开启error log日志功能
// SetErrorLogEnabled enables/disables the error log.
func (s *Server) SetErrorLogEnabled(enabled bool) {
s.config.ErrorLogEnabled = enabled
}
// 设置是否开启error stack打印功能
// SetErrorStack enables/disables the error stack feature.
func (s *Server) SetErrorStack(enabled bool) {
s.config.ErrorStack = enabled
}
// 获取日志目录
// GetLogPath returns the log path.
func (s *Server) GetLogPath() string {
return s.config.LogPath
}
// access log日志功能是否开启
// IsAccessLogEnabled checks whether the access log enabled.
func (s *Server) IsAccessLogEnabled() bool {
return s.config.AccessLogEnabled
}
// error log日志功能是否开启
// IsErrorLogEnabled checks whether the error log enabled.
func (s *Server) IsErrorLogEnabled() bool {
return s.config.ErrorLogEnabled
}

View File

@ -6,14 +6,23 @@
package ghttp
// SetNameToUriType sets the NameToUriType for server.
func (s *Server) SetNameToUriType(t int) {
s.config.NameToUriType = t
}
// SetDumpRouterMap sets the DumpRouterMap for server.
// If DumpRouterMap is enabled, it automatically dumps the route map when server starts.
func (s *Server) SetDumpRouterMap(enabled bool) {
s.config.DumpRouterMap = enabled
}
// SetClientMaxBodySize sets the ClientMaxBodySize for server.
func (s *Server) SetClientMaxBodySize(maxSize int64) {
s.config.ClientMaxBodySize = maxSize
}
// SetFormParsingMemory sets the FormParsingMemory for server.
func (s *Server) SetFormParsingMemory(maxMemory int64) {
s.config.FormParsingMemory = maxMemory
}

View File

@ -6,16 +6,19 @@
package ghttp
// SetRewrite sets rewrites for static URI for server.
func (s *Server) SetRewrite(uri string, rewrite string) {
s.config.Rewrites[uri] = rewrite
}
// SetRewriteMap sets the rewrite map for server.
func (s *Server) SetRewriteMap(rewrites map[string]string) {
for k, v := range rewrites {
s.config.Rewrites[k] = v
}
}
// SetRouteOverWrite sets the RouteOverWrite for server.
func (s *Server) SetRouteOverWrite(enabled bool) {
s.config.RouteOverWrite = enabled
}

View File

@ -12,22 +12,27 @@ import (
"github.com/gogf/gf/os/gsession"
)
// SetSessionMaxAge sets the SessionMaxAge for server.
func (s *Server) SetSessionMaxAge(ttl time.Duration) {
s.config.SessionMaxAge = ttl
}
// SetSessionIdName sets the SessionIdName for server.
func (s *Server) SetSessionIdName(name string) {
s.config.SessionIdName = name
}
// SetSessionStorage sets the SessionStorage for server.
func (s *Server) SetSessionStorage(storage gsession.Storage) {
s.config.SessionStorage = storage
}
// GetSessionMaxAge returns the SessionMaxAge of server.
func (s *Server) GetSessionMaxAge() time.Duration {
return s.config.SessionMaxAge
}
// GetSessionIdName returns the SessionIdName of server.
func (s *Server) GetSessionIdName() string {
return s.config.SessionIdName
}

View File

@ -92,9 +92,8 @@ func (s *Server) AddStaticPath(prefix string, path string) {
path: realPath,
}
if len(s.config.StaticPaths) > 0 {
// 先添加item
s.config.StaticPaths = append(s.config.StaticPaths, addItem)
// 按照prefix从长到短进行排序
// Sort the array by length of prefix from short to long.
array := garray.NewSortedArray(func(v1, v2 interface{}) int {
s1 := gconv.String(v1)
s2 := gconv.String(v2)
@ -107,7 +106,7 @@ func (s *Server) AddStaticPath(prefix string, path string) {
for _, v := range s.config.StaticPaths {
array.Add(v.prefix)
}
// 按照重新排序的顺序重新添加item
// Add the items to paths by previous sorted slice.
paths := make([]staticPathItem, 0)
for _, v := range array.Slice() {
for _, item := range s.config.StaticPaths {

View File

@ -3,9 +3,6 @@
// 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://github.com/gogf/gf.
//
// HTTP Cookie管理对象
// 由于Cookie是和HTTP请求挂钩的因此被包含到 ghttp 包中进行管理。
package ghttp

View File

@ -12,6 +12,7 @@ import (
"errors"
"fmt"
"github.com/gogf/gf/os/gproc"
"github.com/gogf/gf/text/gstr"
"log"
"net"
"net/http"
@ -22,7 +23,7 @@ import (
type gracefulServer struct {
server *Server // Belonged server.
fd uintptr // 热重启时传递的socket监听文件句柄
itemFunc string // 监听地址信息
address string // 监听地址信息
httpServer *http.Server // 底层http.Server
rawListener net.Listener // 原始listener
listener net.Listener // 接口化封装的listener
@ -31,11 +32,15 @@ type gracefulServer struct {
}
// 创建一个优雅的Http Server
func (s *Server) newGracefulServer(itemFunc string, fd ...int) *gracefulServer {
func (s *Server) newGracefulServer(address string, fd ...int) *gracefulServer {
// Change port to address like: 80 -> :80
if gstr.IsNumeric(address) {
address = ":" + address
}
gs := &gracefulServer{
server: s,
itemFunc: itemFunc,
httpServer: s.newHttpServer(itemFunc),
address: address,
httpServer: s.newHttpServer(address),
}
// 是否有继承的文件描述符
if len(fd) > 0 && fd[0] > 0 {
@ -45,9 +50,9 @@ func (s *Server) newGracefulServer(itemFunc string, fd ...int) *gracefulServer {
}
// 生成一个底层的Web Server对象
func (s *Server) newHttpServer(itemFunc string) *http.Server {
func (s *Server) newHttpServer(address string) *http.Server {
server := &http.Server{
Addr: itemFunc,
Addr: address,
Handler: s.config.Handler,
ReadTimeout: s.config.ReadTimeout,
WriteTimeout: s.config.WriteTimeout,
@ -61,8 +66,7 @@ func (s *Server) newHttpServer(itemFunc string) *http.Server {
// 执行HTTP监听
func (s *gracefulServer) ListenAndServe() error {
itemFunc := s.httpServer.Addr
ln, err := s.getNetListener(itemFunc)
ln, err := s.getNetListener()
if err != nil {
return err
}
@ -89,7 +93,6 @@ func (s *gracefulServer) setFd(fd int) {
// 执行HTTPS监听
func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string, tlsConfig ...*tls.Config) error {
itemFunc := s.httpServer.Addr
var config *tls.Config
if len(tlsConfig) > 0 && tlsConfig[0] != nil {
config = tlsConfig[0]
@ -109,7 +112,7 @@ func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string, tlsConfig .
if err != nil {
return errors.New(fmt.Sprintf(`open cert file "%s","%s" failed: %s`, certFile, keyFile, err.Error()))
}
ln, err := s.getNetListener(itemFunc)
ln, err := s.getNetListener()
if err != nil {
return err
}
@ -134,7 +137,10 @@ func (s *gracefulServer) doServe() error {
if s.fd != 0 {
action = "reloaded"
}
s.server.Logger().Printf("%d: %s server %s listening on [%s]", gproc.Pid(), s.getProto(), action, s.itemFunc)
s.server.Logger().Printf(
"%d: %s server %s listening on [%s]",
gproc.Pid(), s.getProto(), action, s.address,
)
s.status = SERVER_STATUS_RUNNING
err := s.httpServer.Serve(s.listener)
s.status = SERVER_STATUS_STOPPED
@ -142,7 +148,7 @@ func (s *gracefulServer) doServe() error {
}
// 自定义的net.Listener
func (s *gracefulServer) getNetListener(itemFunc string) (net.Listener, error) {
func (s *gracefulServer) getNetListener() (net.Listener, error) {
var ln net.Listener
var err error
if s.fd > 0 {
@ -153,7 +159,7 @@ func (s *gracefulServer) getNetListener(itemFunc string) (net.Listener, error) {
return nil, err
}
} else {
ln, err = net.Listen("tcp", itemFunc)
ln, err = net.Listen("tcp", s.httpServer.Addr)
if err != nil {
err = fmt.Errorf("%d: net.Listen error: %v", gproc.Pid(), err)
}
@ -167,7 +173,10 @@ func (s *gracefulServer) shutdown() {
return
}
if err := s.httpServer.Shutdown(context.Background()); err != nil {
s.server.Logger().Errorf("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.itemFunc, err)
s.server.Logger().Errorf(
"%d: %s server [%s] shutdown error: %v",
gproc.Pid(), s.getProto(), s.address, err,
)
}
}
@ -177,6 +186,9 @@ func (s *gracefulServer) close() {
return
}
if err := s.httpServer.Close(); err != nil {
s.server.Logger().Errorf("%d: %s server [%s] closed error: %v", gproc.Pid(), s.getProto(), s.itemFunc, err)
s.server.Logger().Errorf(
"%d: %s server [%s] closed error: %v",
gproc.Pid(), s.getProto(), s.address, err,
)
}
}

View File

@ -26,6 +26,10 @@ import (
// 默认HTTP Server处理入口http包底层默认使用了gorutine异步处理请求所以这里不再异步执行
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Max body size limit.
if s.config.ClientMaxBodySize > 0 {
r.Body = http.MaxBytesReader(w, r.Body, s.config.ClientMaxBodySize)
}
// 重写规则判断
if len(s.config.Rewrites) > 0 {
if rewrite, ok := s.config.Rewrites[r.URL.Path]; ok {

View File

@ -12,10 +12,6 @@ import (
"github.com/gogf/gf/os/glog"
)
const (
gPATH_FILTER_KEY = "github.com/gogf/gf/"
)
// Logger returns the logger of the server.
func (s *Server) Logger() *glog.Logger {
return s.config.Logger
@ -54,22 +50,22 @@ func (s *Server) handleErrorLog(err error, r *Request) {
scheme = "https"
}
content := fmt.Sprintf(
`%d, "%s %s %s %s %s" %.3f, %s, "%s", "%s"`,
`%d "%s %s %s %s %s" %.3f, %s, "%s", "%s"`,
r.Response.Status, r.Method, scheme, r.Host, r.URL.String(), r.Proto,
float64(r.LeaveTime-r.EnterTime)/1000,
r.GetClientIp(), r.Referer(), r.UserAgent(),
)
if stack := gerror.Stack(err); stack != "" {
content += "\nStack:\n" + stack
s.config.Logger.File(s.config.ErrorLogPattern).
Stack(false).
Stdout(s.config.LogStdout).
Error(content)
return
if s.config.ErrorStack {
if stack := gerror.Stack(err); stack != "" {
content += "\nStack:\n" + stack
} else {
content += ", " + err.Error()
}
} else {
content += ", " + err.Error()
}
s.Logger().File(s.config.ErrorLogPattern).
Stack(s.config.ErrorStack).
StackWithFilter(gPATH_FILTER_KEY).
s.config.Logger.
File(s.config.ErrorLogPattern).
Stdout(s.config.LogStdout).
Error(content)
Print(content)
}

View File

@ -0,0 +1,57 @@
// Copyright 2020 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package ghttp_test
import (
"fmt"
"testing"
"time"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/test/gtest"
"github.com/gogf/gf/text/gstr"
)
func Test_Client_Request_13_Dump(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/hello", func(r *ghttp.Request) {
r.Response.WriteHeader(200)
r.Response.WriteJson(g.Map{"field": "test_for_response_body"})
})
s.BindHandler("/hello2", func(r *ghttp.Request) {
r.Response.WriteHeader(200)
r.Response.Writeln(g.Map{"field": "test_for_response_body"})
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
url := fmt.Sprintf("http://127.0.0.1:%d", p)
client := ghttp.NewClient().SetPrefix(url).ContentJson()
r, err := client.Post("/hello", g.Map{"field": "test_for_request_body"})
t.Assert(err, nil)
dumpedText := r.RawRequest()
t.Assert(gstr.Contains(dumpedText, "test_for_request_body"), true)
dumpedText2 := r.RawResponse()
t.Assert(gstr.Contains(dumpedText2, "test_for_response_body"), true)
client2 := ghttp.NewClient().SetPrefix(url).ContentType("text/html")
r2, err := client2.Post("/hello2", g.Map{"field": "test_for_request_body"})
t.Assert(err, nil)
dumpedText3 := r2.RawRequest()
t.Assert(gstr.Contains(dumpedText3, "test_for_request_body"), false)
dumpedText4 := r2.RawResponse()
t.Assert(gstr.Contains(dumpedText4, "test_for_request_body"), false)
})
}

View File

@ -7,6 +7,10 @@
package ghttp_test
import (
"fmt"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/text/gstr"
"testing"
"time"
@ -58,3 +62,97 @@ func Test_SetConfigWithMap(t *testing.T) {
t.Assert(err, nil)
})
}
func Test_ClientMaxBodySize(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.Group("/", func(group *ghttp.RouterGroup) {
group.POST("/", func(r *ghttp.Request) {
r.Response.Write(r.GetBodyString())
})
})
m := g.Map{
"Address": p,
"ClientMaxBodySize": "1k",
}
gtest.Assert(s.SetConfigWithMap(m), nil)
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
c := g.Client()
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
data := make([]byte, 1056)
for i := 0; i < 1056; i++ {
data[i] = 'a'
}
t.Assert(
gstr.Trim(c.PostContent("/", data)),
data[:1024],
)
})
}
func Test_ClientMaxBodySize_File(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.Group("/", func(group *ghttp.RouterGroup) {
group.POST("/", func(r *ghttp.Request) {
r.GetUploadFile("file")
r.Response.Write("ok")
})
})
m := g.Map{
"Address": p,
"ErrorLogEnabled": false,
"ClientMaxBodySize": "1k",
}
gtest.Assert(s.SetConfigWithMap(m), nil)
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
// ok
gtest.C(t, func(t *gtest.T) {
c := g.Client()
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
path := gfile.TempDir(gtime.TimestampNanoStr())
data := make([]byte, 512)
for i := 0; i < 512; i++ {
data[i] = 'a'
}
t.Assert(gfile.PutBytes(path, data), nil)
defer gfile.Remove(path)
t.Assert(
gstr.Trim(c.PostContent("/", "name=john&file=@file:"+path)),
"ok",
)
})
// too large
gtest.C(t, func(t *gtest.T) {
c := g.Client()
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
path := gfile.TempDir(gtime.TimestampNanoStr())
data := make([]byte, 1056)
for i := 0; i < 1056; i++ {
data[i] = 'a'
}
t.Assert(gfile.PutBytes(path, data), nil)
defer gfile.Remove(path)
t.Assert(
gstr.Trim(c.PostContent("/", "name=john&file=@file:"+path)),
"http: request body too large",
)
})
}

View File

@ -42,8 +42,8 @@ func Test_HTTPS_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
c := g.Client()
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
t.Assert(gstr.Trim(c.GetContent("/")), "Client sent an HTTP request to an HTTPS server.")
t.Assert(gstr.Trim(c.GetContent("/test")), "Client sent an HTTP request to an HTTPS server.")
t.AssertIN(gstr.Trim(c.GetContent("/")), g.Slice{"", "Client sent an HTTP request to an HTTPS server."})
t.AssertIN(gstr.Trim(c.GetContent("/test")), g.Slice{"", "Client sent an HTTP request to an HTTPS server."})
})
// HTTPS
gtest.C(t, func(t *gtest.T) {

View File

@ -12,7 +12,6 @@ import (
)
var (
// 用于测试的端口数组,随机获取
ports = garray.NewIntArray(true)
)

View File

@ -52,11 +52,11 @@ func Test_Log(t *testing.T) {
t.Assert(gstr.Contains(gfile.GetContents(logPath1), "HANDLER"), true)
logPath2 := gfile.Join(logDir, "access-"+gtime.Now().Format("Ymd")+".log")
fmt.Println(gfile.GetContents(logPath2))
//fmt.Println(gfile.GetContents(logPath2))
t.Assert(gstr.Contains(gfile.GetContents(logPath2), " /hello "), true)
logPath3 := gfile.Join(logDir, "error-"+gtime.Now().Format("Ymd")+".log")
t.Assert(gstr.Contains(gfile.GetContents(logPath3), "[ERRO]"), true)
//fmt.Println(gfile.GetContents(logPath3))
t.Assert(gstr.Contains(gfile.GetContents(logPath3), "custom error"), true)
})
}

View File

@ -44,7 +44,7 @@ func Test_BindMiddleware_Basic1(t *testing.T) {
r.Response.Write("8")
})
s.SetPort(p)
//s.SetDumpRouterMap(false)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
@ -248,7 +248,7 @@ func Test_Middleware_Hook_With_Static(t *testing.T) {
})
})
s.SetPort(p)
//s.SetDumpRouterMap(false)
s.SetDumpRouterMap(false)
s.SetServerRoot(gdebug.TestDataPath("static1"))
s.Start()
defer s.Shutdown()
@ -701,7 +701,7 @@ func Test_Middleware_Panic(t *testing.T) {
})
})
s.SetPort(p)
//s.SetDumpRouterMap(false)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)

View File

@ -88,3 +88,37 @@ func Test_Params_Struct(t *testing.T) {
t.Assert(client.PostContent("/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`)
})
}
func Test_Params_Structs(t *testing.T) {
type User struct {
Id int
Name string
Time *time.Time
Pass1 string `p:"password1"`
Pass2 string `p:"password2" v:"passwd1 @required|length:2,20|password3#||密码强度不足"`
}
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/parse1", func(r *ghttp.Request) {
var users []*User
if err := r.Parse(&users); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(users[0].Id, users[1].Id)
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
t.Assert(client.PostContent(
"/parse1",
`[{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}, {"id":2,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}]`),
`12`,
)
})
}

View File

@ -469,3 +469,30 @@ func Test_Params_Priority(t *testing.T) {
t.Assert(client.GetContent("/request-map?a=1&b=2&c=3", "a=100&b=200&c=300"), `{"a":"100","b":"200"}`)
})
}
func Test_Params_GetRequestMap(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/map", func(r *ghttp.Request) {
r.Response.Write(r.GetRequestMap())
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
prefix := fmt.Sprintf("http://127.0.0.1:%d", p)
client := ghttp.NewClient()
client.SetPrefix(prefix)
t.Assert(
client.PostContent(
"/map",
"time_end2020-04-18 16:11:58&returnmsg=Success&attach=",
),
`{"attach":"","returnmsg":"Success"}`,
)
})
}

View File

@ -349,7 +349,7 @@ func Test_Router_DomainGroup(t *testing.T) {
})
})
s.SetPort(p)
//s.SetDumpRouterMap(false)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()

View File

@ -70,7 +70,7 @@ func Test_Router_Hook_Fuzzy_Router(t *testing.T) {
r.Response.Write(r.Router.Uri)
})
s.SetPort(p)
//s.SetDumpRouterMap(false)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()

View File

@ -0,0 +1,43 @@
// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
// static service testing.
package ghttp_test
import (
"fmt"
"testing"
"time"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/test/gtest"
)
func Test_StatusHandler(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.BindStatusHandlerByMap(map[int]ghttp.HandlerFunc{
404: func(r *ghttp.Request) { r.Response.WriteOver("404") },
502: func(r *ghttp.Request) { r.Response.WriteOver("502") },
})
s.BindHandler("/502", func(r *ghttp.Request) {
r.Response.WriteStatusExit(502)
})
s.SetDumpRouterMap(false)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
t.Assert(client.GetContent("/404"), "404")
t.Assert(client.GetContent("/502"), "502")
})
}

Some files were not shown because too many files have changed in this diff Show More