Compare commits

...

50 Commits

Author SHA1 Message Date
802568856c version updates 2019-03-11 16:35:44 +08:00
127fb67185 add session id check in session object initialzing 2019-03-11 16:33:51 +08:00
5db039bbce gdb.Model updates, rename Alterable function to Safe, change gdb.Model alterable in default, update Where function to support more cases when using map as its param, add more unit test cases for gdb.Model 2019-03-11 16:14:55 +08:00
8a3365d18e ghttp comment updates 2019-03-10 00:39:34 +08:00
2ae5b1a4f8 add more unit test cases for ghttp.Server 2019-03-10 00:35:03 +08:00
f9515d7126 version updates 2019-03-09 10:20:11 +08:00
b56679a97c revert g.Map to map[string]interface{}, add g.MapAnyAny 2019-03-09 10:17:21 +08:00
d1b123964a README updates 2019-03-08 21:07:18 +08:00
991f7c4958 ghttp.Response updates 2019-03-08 19:03:22 +08:00
770619c39e update unit test cases og gcron 2019-03-08 18:27:11 +08:00
4ad5450b80 gtest updates; remove one unit test case of gconv.Struct 2019-03-08 18:18:29 +08:00
49ce7fe885 fix data race issue of gqueue 2019-03-08 17:54:50 +08:00
e66f63262b add more unit test cases for ghttp.Server 2019-03-08 17:31:30 +08:00
94bd5da68a add map key operator support in Where function for gdb 2019-03-08 11:12:52 +08:00
3eee95caf2 VERSION updates 2019-03-08 10:35:35 +08:00
5c638c630a add select in support for slice type of arguments in Where function of gdb 2019-03-08 10:33:36 +08:00
a6ec9d7a1c update gdb.Model.Where, orverwrite args if multiple calls Where function 2019-03-08 10:18:38 +08:00
374c70c0e3 README updates 2019-03-08 09:03:32 +08:00
40771066d4 travis updates 2019-03-08 08:48:53 +08:00
22a7ef43ce Merge branch 'master' into develop 2019-03-08 00:21:52 +08:00
05f22d1cee README updates 2019-03-08 00:15:57 +08:00
ebf56a86ab add Alterable function for gdb.Model 2019-03-07 23:53:56 +08:00
2ba59e8943 fix issue in gtime convert in package gconv; add Structs/Scan functions for gdb.Model; add GetStructs/GetScan functions for gdb.Base and gdb.TX 2019-03-07 23:36:45 +08:00
83be1de04c remove error returns from router registry functions of WebServer; add more unit test cases for WebServer 2019-03-06 15:21:00 +08:00
c8251ed82f g.Map updates 2019-03-06 10:38:50 +08:00
2335ea0c4d merge master 2019-03-05 21:07:54 +08:00
5d874e9063 add example code for gconv.Map; comment updates of gdb 2019-03-05 17:52:34 +08:00
f2c080d25f update ghttp.Response 2019-03-05 17:16:21 +08:00
975da97b4a fix issue in empty check for nil attribute of struct in package 'empty';add set-cookie support for 302 status in ghttp.Server 2019-03-05 17:06:37 +08:00
37617589a6 add more unit test cases for ghttp.Server 2019-03-04 23:51:44 +08:00
5c9f0db903 version updates 2019-03-04 23:37:01 +08:00
28abf0c175 add ReplaceI/ReplaceIByArray/ReplaceIByMap case-insensetive replacing functions for gstr 2019-03-04 23:35:06 +08:00
13749feab4 add json features '-' and 'omitempty' for gconv.Map, like package 'json' from stdlib; add internal package 'empty', to check empty variable 2019-03-04 22:59:29 +08:00
c1e77b7e09 update example of gtcp 2019-03-03 20:40:36 +08:00
962a5e93f7 update example of gtcp 2019-03-03 20:20:31 +08:00
c3b9b8d5ae gconv updates 2019-03-03 00:53:35 +08:00
1ad076c522 travis updates 2019-03-03 00:31:16 +08:00
b01777fcd1 travis updates 2019-03-03 00:28:32 +08:00
55a5532c2e gdb updates 2019-03-03 00:14:20 +08:00
adb928941a issue template updates 2019-03-02 00:02:05 +08:00
f92c1fc527 update CORS feature of ghttp.Response; add more unit cases for ghttp.Server 2019-03-01 23:45:55 +08:00
3ae7279ebc issue template updates 2019-03-01 14:01:05 +08:00
66287c2d0e update the admin feature and unit test cases of ghttp 2019-02-28 23:57:20 +08:00
e352b07055 add issue_template 2019-02-26 22:51:30 +08:00
fdea242b50 Merge branch 'master' into develop 2019-02-26 22:23:24 +08:00
72ecf2d2af TODO++ 2019-02-25 12:39:07 +08:00
0f854e46d8 refact Merge function for garray; add more frequently-used type alias for gf 2019-02-22 09:08:46 +08:00
4564f38e1a add PopRands/Rands functions for garray 2019-02-20 19:06:08 +08:00
7e06bf6705 Merge branch 'master' into qiangg_garray2 2019-02-20 16:28:24 +08:00
d780cf64c2 garray updates 2019-02-20 14:18:11 +08:00
101 changed files with 5330 additions and 1118 deletions

View File

@ -1,17 +1,18 @@
<!-- 为更高效率地交流并解决问题请按照以下模板提交issue感谢 -->
### 1. 您当前使用的`Go`版本(将终端`go version`指令结果粘贴到下面)?
<pre>
$ go version
### 1. 您当前使用的`Go`版本,及系统版本、系统架构?
</pre>
<!-- 使用 `go version` 命令查看,期望的结果如:`go 1.12, linux/amd64` -->
### 2. 您当前使用的`GoFrame`版本(可以查看`go.mod`/`version.go`/`gf.VERSION`
### 2. 您当前使用的`GoFrame`框架版本?
<!-- 框架版本可以查看自己项目下的 `go.mod`,或者框架文件 `version.go` -->
### 3. 更新到最新的框架版本是否能够解决问题?
<!-- 务必检查是否相同问题已在新版本中已修复 -->
### 4. 问题描述?

View File

@ -1,16 +1,19 @@
<!-- Please answer these questions before submitting your issue. Thanks! -->
### 1. What version of `Go` are you using (`go version`)?
<pre>
$ go version
### 1. What version of `Go` and system type/arch are you using?
<!--
Please paste the output of command `go version` from your terminal.
What expect to see is like: `go 1.12, linux/amd64`
-->
</pre>
### 2. What version of `GoFrame` are you using?
<!-- You can find the GF version from your `go.mod`, or from the `version.go` in `GF` -->
### 3. Does this issue reproduce with the latest release?
### 3. Can this issue be reproduced with the latest release?

View File

@ -1,7 +1,8 @@
language: go
go:
- "1.11.x"
- "1.11.x"
- "1.12.x"
branches:
only:
@ -9,25 +10,28 @@ branches:
- develop
env:
- GO111MODULE=on
- GO111MODULE=on
services:
- mysql
- mysql
addons:
hosts:
- local
before_install:
- pwd
- pwd
install:
- pwd
- cat /etc/hosts
script:
- cd g
- GOARCH=386 go test -v ./...
- GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
- cd g
- GOARCH=386 go test -v ./...
- GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)
- bash <(curl -s https://codecov.io/bash)

View File

@ -4,17 +4,16 @@
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf)
[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf)](https://goreportcard.com/report/github.com/gogf/gf)
[![Documents](https://img.shields.io/badge/docs-100%25-green.svg)](https://goframe.org)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
[![Language](https://img.shields.io/badge/language-go-blue.svg)](https://github.com/gogf/gf)
[![Release](https://img.shields.io/github/release/gogf/gf.svg?style=flat)](https://github.com/gogf/gf/releases)
<!--
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf)
[![Code Helper](https://www.codetriage.com/gogf/gf/badges/users.svg)](https://www.codetriage.com/gogf/gf)
-->
GoFrame is a modular, loose-coupled, production-ready and most-powerful application development framework of golang. Providing a series of core components and dozens of practical modules, such as: cache, logging, containers, timer, validator, database orm, etc. Supporting web server integrated with router, cookie, session, logger, configure, template, https, hooks, rewrites and many more features.
-->
`GF(GoFrame)` is a modular, loose-coupled and production-ready application development framework written in Go. Providing a series of core components and dozens of practical modules, such as: cache, logging, array/queue/set/map, timer/timing tasks, file/memory lock, object pool, validator, database ORM, etc. Supporting web server with graceful server, hot updates, multi-domain, multi-port, multi-service, HTTP/HTTPS, dynamic/hook routing, rewrite rules and many more features.
`GF(GoFrame)` is a modular, loose-coupled, production-ready and most-powerful application development framework of golang. Providing a series of core components and dozens of practical modules, such as: memcache, configure, validator, logging, array/queue/set/map containers, timer/timing tasks, file/memory lock, object pool, database ORM, etc. Supporting web server integrated with router, cookie, session, logger, template, https, hooks, rewrites and many more features.
# Installation
```

View File

@ -4,16 +4,11 @@
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf)
[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf)](https://goreportcard.com/report/github.com/gogf/gf)
[![Documents](https://img.shields.io/badge/docs-100%25-green.svg)](https://goframe.org)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
[![Language](https://img.shields.io/badge/language-go-blue.svg)](https://github.com/gogf/gf)
[![Release](https://img.shields.io/github/release/gogf/gf.svg?style=flat)](https://github.com/gogf/gf/releases)
<!--
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf)
[![Code Helper](https://www.codetriage.com/gogf/gf/badges/users.svg)](https://www.codetriage.com/gogf/gf)
-->
`GF(Go Frame)`是一款模块化、松耦合、生产级Go应用开发框架。提供了常用的核心开发组件缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、数据校验、数据编码、文件监控、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、
并发安全容器等等。并提供了Web服务开发的系列核心组件Router、Cookie、Session、服务注册、配置管理、模板引擎等等支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。

View File

@ -53,6 +53,10 @@
1. 改进gproc进程间通信处理逻辑提高稳定性以应对进程间大批量的数据发送/接收;
1. gdb的Data方法支持struct参数传入
1. gfcache依旧使用gcache作为缓存控制对象不要使用gmap
1. 增加对ghttp路由注册的{.struct}/{.method}单元测试;
1. 更新跨域请求CORS相关功能文档
1. ghttp的热重启的本地进程端口监听在不使用该特性时默认关闭掉
1. gcfg包目前允许添加重复的目录路径需要在SetPath/AddPath时判断重复性不能添加重复的路径

View File

@ -6,3 +6,15 @@
package garray
type apiSliceInterface interface {
Slice() []interface{}
}
type apiSliceInt interface {
Slice() []int
}
type apiSliceString interface {
Slice() []string
}

View File

@ -7,12 +7,12 @@
package garray
import (
"bytes"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
"strings"
)
type IntArray struct {
@ -53,6 +53,20 @@ func NewIntArrayFrom(array []int, unsafe...bool) *IntArray {
}
}
// Create an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice拷贝创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewIntArrayFromCopy(array []int, unsafe...bool) *IntArray {
newArray := make([]int, len(array))
copy(newArray, array)
return &IntArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Get value by index.
//
// 获取指定索引的数据项, 调用方注意判断数组边界。
@ -233,13 +247,31 @@ func (a *IntArray) PopRight() int {
return value
}
// Pop an random item from array.
// PopRand picks an random item out of array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *IntArray) PopRand() int {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands picks <size> items out of array.
//
// 随机将size个数据项移出数组并返回该数据项。
func (a *IntArray) PopRands(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]int, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项。
@ -425,17 +457,22 @@ func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
return a
}
// Merge two arrays.
// Merge two arrays. The parameter <array> can be any garray type or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more variable types.
//
// 合并两个数组.
func (a *IntArray) Merge(array *IntArray) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
// 合并两个数组, 支持任意的garray数组类型及slice类型.
func (a *IntArray) Merge(array interface{}) *IntArray {
switch v := array.(type) {
case *Array: a.Append(gconv.Ints(v.Slice())...)
case *IntArray: a.Append(gconv.Ints(v.Slice())...)
case *StringArray: a.Append(gconv.Ints(v.Slice())...)
case *SortedArray: a.Append(gconv.Ints(v.Slice())...)
case *SortedIntArray: a.Append(gconv.Ints(v.Slice())...)
case *SortedStringArray: a.Append(gconv.Ints(v.Slice())...)
default:
a.Append(gconv.Ints(array)...)
}
a.array = append(a.array, array.array...)
return a
}
@ -538,11 +575,19 @@ func (a *IntArray) SubSlice(offset, size int) []int {
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
// Rand gets one random entry from array.
//
// 从数组中随机取出size个元素项构成slice返回
func (a *IntArray) Rand(size int) []int {
// 从数组中随机获得1个元素项(不删除)
func (a *IntArray) Rand() int {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands gets one or more random entries from array(a copy).
//
// 从数组中随机拷贝size个元素项构成slice返回。
func (a *IntArray) Rands(size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
@ -588,5 +633,12 @@ func (a *IntArray) Reverse() *IntArray {
func (a *IntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(gconv.Strings(a.array), glue)
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}

View File

@ -7,12 +7,13 @@
package garray
import (
"bytes"
"fmt"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
"strings"
)
type Array struct {
@ -30,8 +31,6 @@ func New(unsafe...bool) *Array {
}
// See New.
//
// 同New方法。
func NewArray(unsafe...bool) *Array {
return NewArraySize(0, 0, unsafe...)
}
@ -48,6 +47,16 @@ func NewArraySize(size int, cap int, unsafe...bool) *Array {
}
}
// See NewArrayFrom.
func NewFrom(array []interface{}, unsafe...bool) *Array {
return NewArrayFrom(array, unsafe...)
}
// See NewArrayFromCopy.
func NewFromCopy(array []interface{}, unsafe...bool) *Array {
return NewArrayFromCopy(array, unsafe...)
}
// Create an array with given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
@ -60,6 +69,20 @@ func NewArrayFrom(array []interface{}, unsafe...bool) *Array {
}
}
// Create an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice拷贝创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewArrayFromCopy(array []interface{}, unsafe...bool) *Array {
newArray := make([]interface{}, len(array))
copy(newArray, array)
return &Array{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Get value by index.
//
// 获取指定索引的数据项, 调用方注意判断数组边界
@ -196,13 +219,31 @@ func (a *Array) PushRight(value...interface{}) *Array {
return a
}
// Pop an random item from array.
// PopRand picks an random item out of array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *Array) PopRand() interface{} {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands picks <size> items out of array.
//
// 随机将size个数据项移出数组并返回该数据项。
func (a *Array) PopRands(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]interface{}, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// Pop an item from the beginning of array.
//
// 将最左端(索引为0)的数据项移出数组,并返回该数据项。
@ -408,17 +449,22 @@ func (a *Array) RLockFunc(f func(array []interface{})) *Array {
return a
}
// Merge two arrays.
// Merge two arrays. The parameter <array> can be any garray type or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more variable types.
//
// 合并两个数组.
func (a *Array) Merge(array *Array) *Array {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
// 合并两个数组, 支持任意的garray数组类型及slice类型.
func (a *Array) Merge(array interface{}) *Array {
switch v := array.(type) {
case *Array: a.Append(gconv.Interfaces(v.Slice())...)
case *IntArray: a.Append(gconv.Interfaces(v.Slice())...)
case *StringArray: a.Append(gconv.Interfaces(v.Slice())...)
case *SortedArray: a.Append(gconv.Interfaces(v.Slice())...)
case *SortedIntArray: a.Append(gconv.Interfaces(v.Slice())...)
case *SortedStringArray: a.Append(gconv.Interfaces(v.Slice())...)
default:
a.Append(gconv.Interfaces(array)...)
}
a.array = append(a.array, array.array...)
return a
}
@ -520,11 +566,19 @@ func (a *Array) SubSlice(offset, size int) []interface{} {
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
// Rand gets one random entry from array.
//
// 从数组中随机取出size个元素项构成slice返回
func (a *Array) Rand(size int) []interface{} {
// 从数组中随机获得1个元素项(不删除)
func (a *Array) Rand() interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands gets one or more random entries from array(a copy).
//
// 从数组中随机拷贝size个元素项构成slice返回。
func (a *Array) Rands(size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
@ -570,7 +624,14 @@ func (a *Array) Reverse() *Array {
func (a *Array) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(gconv.Strings(a.array), glue)
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// Counts all the values of an array.
@ -584,4 +645,13 @@ func (a *Array) CountValues() map[interface{}]int {
m[v]++
}
return m
}
// String returns current array as a string.
//
// 将当前数组转换为字符串返回。
func (a *Array) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -7,6 +7,7 @@
package garray
import (
"bytes"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
@ -53,6 +54,20 @@ func NewStringArrayFrom(array []string, unsafe...bool) *StringArray {
}
}
// Create an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice拷贝创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewStringArrayFromCopy(array []string, unsafe...bool) *StringArray {
newArray := make([]string, len(array))
copy(newArray, array)
return &StringArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Get value by index.
//
// 获取指定索引的数据项, 调用方注意判断数组边界。
@ -233,13 +248,31 @@ func (a *StringArray) PopRight() string {
return value
}
// Pop an random item from array.
// PopRand picks an random item out of array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *StringArray) PopRand() string {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands picks <size> items out of array.
//
// 随机将size个数据项移出数组并返回该数据项。
func (a *StringArray) PopRands(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]string, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项
@ -423,17 +456,22 @@ func (a *StringArray) RLockFunc(f func(array []string)) *StringArray {
return a
}
// Merge two arrays.
// Merge two arrays. The parameter <array> can be any garray type or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more variable types.
//
// 合并两个数组.
func (a *StringArray) Merge(array *StringArray) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
// 合并两个数组, 支持任意的garray数组类型及slice类型.
func (a *StringArray) Merge(array interface{}) *StringArray {
switch v := array.(type) {
case *Array: a.Append(gconv.Strings(v.Slice())...)
case *IntArray: a.Append(gconv.Strings(v.Slice())...)
case *StringArray: a.Append(gconv.Strings(v.Slice())...)
case *SortedArray: a.Append(gconv.Strings(v.Slice())...)
case *SortedIntArray: a.Append(gconv.Strings(v.Slice())...)
case *SortedStringArray: a.Append(gconv.Strings(v.Slice())...)
default:
a.Append(gconv.Strings(array)...)
}
a.array = append(a.array, array.array...)
return a
}
@ -537,11 +575,19 @@ func (a *StringArray) SubSlice(offset, size int) []string {
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
// Rand gets one random entry from array.
//
// 从数组中随机取出size个元素项构成slice返回
func (a *StringArray) Rand(size int) []string {
// 从数组中随机获得1个元素项(不删除)
func (a *StringArray) Rand() string {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands gets one or more random entries from array(a copy).
//
// 从数组中随机拷贝size个元素项构成slice返回。
func (a *StringArray) Rands(size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
@ -587,6 +633,13 @@ func (a *StringArray) Reverse() *StringArray {
func (a *StringArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(a.array, glue)
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}

View File

@ -7,13 +7,13 @@
package garray
import (
"bytes"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
"strings"
)
// 默认按照从小到大进行排序
@ -67,6 +67,20 @@ func NewSortedIntArrayFrom(array []int, unsafe...bool) *SortedIntArray {
return a
}
// Create an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice拷贝创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewSortedIntArrayFromCopy(array []int, unsafe...bool) *SortedIntArray {
newArray := make([]int, len(array))
copy(newArray, array)
return &SortedIntArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Set the underlying slice array with the given <array> param.
//
// 设置底层数组变量.
@ -172,13 +186,31 @@ func (a *SortedIntArray) PopRight() int {
return value
}
// Pop an random item from array.
// PopRand picks an random item out of array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *SortedIntArray) PopRand() int {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands picks <size> items out of array.
//
// 随机将size个数据项移出数组并返回该数据项。
func (a *SortedIntArray) PopRands(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]int, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项
@ -399,18 +431,22 @@ func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
return a
}
// Merge two arrays.
// Merge two arrays. The parameter <array> can be any garray type or slice type.
// The difference between Merge and Add is Add supports only specified slice type,
// but Merge supports more variable types.
//
// 合并两个数组.
func (a *SortedIntArray) Merge(array *SortedIntArray) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
// 合并两个数组, 支持任意的garray数组类型及slice类型.
func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray {
switch v := array.(type) {
case *Array: a.Add(gconv.Ints(v.Slice())...)
case *IntArray: a.Add(gconv.Ints(v.Slice())...)
case *StringArray: a.Add(gconv.Ints(v.Slice())...)
case *SortedArray: a.Add(gconv.Ints(v.Slice())...)
case *SortedIntArray: a.Add(gconv.Ints(v.Slice())...)
case *SortedStringArray: a.Add(gconv.Ints(v.Slice())...)
default:
a.Add(gconv.Ints(array)...)
}
a.array = append(a.array, array.array...)
sort.Ints(a.array)
return a
}
@ -462,11 +498,19 @@ func (a *SortedIntArray) SubSlice(offset, size int) []int {
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
// Rand gets one random entry from array.
//
// 从数组中随机取出size个元素项构成slice返回
func (a *SortedIntArray) Rand(size int) []int {
// 从数组中随机获得1个元素项(不删除)
func (a *SortedIntArray) Rand() int {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands gets one or more random entries from array(a copy).
//
// 从数组中随机拷贝size个元素项构成slice返回。
func (a *SortedIntArray) Rands(size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
@ -488,5 +532,12 @@ func (a *SortedIntArray) Rand(size int) []int {
func (a *SortedIntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(gconv.Strings(a.array), glue)
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}

View File

@ -7,13 +7,13 @@
package garray
import (
"bytes"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
"strings"
)
// 默认按照从小到大进行排序
@ -69,6 +69,20 @@ func NewSortedArrayFrom(array []interface{}, compareFunc func(v1, v2 interface{}
return a
}
// Create an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice拷贝创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewSortedArrayFromCopy(array []interface{}, unsafe...bool) *SortedArray {
newArray := make([]interface{}, len(array))
copy(newArray, array)
return &SortedArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Set the underlying slice array with the given <array> param.
//
// 设置底层数组变量.
@ -178,13 +192,31 @@ func (a *SortedArray) PopRight() interface{} {
return value
}
// Pop an random item from array.
// PopRand picks an random item out of array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *SortedArray) PopRand() interface{} {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands picks <size> items out of array.
//
// 随机将size个数据项移出数组并返回该数据项。
func (a *SortedArray) PopRands(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]interface{}, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项
@ -406,20 +438,22 @@ func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
return a
}
// Merge two arrays.
// Merge two arrays. The parameter <array> can be any garray type or slice type.
// The difference between Merge and Add is Add supports only specified slice type,
// but Merge supports more variable types.
//
// 合并两个数组.
func (a *SortedArray) Merge(array *SortedArray) *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
// 合并两个数组, 支持任意的garray数组类型及slice类型.
func (a *SortedArray) Merge(array interface{}) *SortedArray {
switch v := array.(type) {
case *Array: a.Add(gconv.Interfaces(v.Slice())...)
case *IntArray: a.Add(gconv.Interfaces(v.Slice())...)
case *StringArray: a.Add(gconv.Interfaces(v.Slice())...)
case *SortedArray: a.Add(gconv.Interfaces(v.Slice())...)
case *SortedIntArray: a.Add(gconv.Interfaces(v.Slice())...)
case *SortedStringArray: a.Add(gconv.Interfaces(v.Slice())...)
default:
a.Add(gconv.Interfaces(array)...)
}
a.array = append(a.array, array.array...)
sort.Slice(a.array, func(i, j int) bool {
return a.compareFunc(a.array[i], a.array[j]) < 0
})
return a
}
@ -471,11 +505,19 @@ func (a *SortedArray) SubSlice(offset, size int) []interface{} {
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
// Rand gets one random entry from array.
//
// 从数组中随机取出size个元素项构成slice返回
func (a *SortedArray) Rand(size int) []interface{} {
// 从数组中随机获得1个元素项(不删除)
func (a *SortedArray) Rand() interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands gets one or more random entries from array(a copy).
//
// 从数组中随机拷贝size个元素项构成slice返回。
func (a *SortedArray) Rands(size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
@ -497,5 +539,12 @@ func (a *SortedArray) Rand(size int) []interface{} {
func (a *SortedArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(gconv.Strings(a.array), glue)
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}

View File

@ -7,6 +7,7 @@
package garray
import (
"bytes"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
@ -61,6 +62,20 @@ func NewSortedStringArrayFrom(array []string, unsafe...bool) *SortedStringArray
return a
}
// Create an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice拷贝创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewSortedStringArrayFromCopy(array []string, unsafe...bool) *SortedStringArray {
newArray := make([]string, len(array))
copy(newArray, array)
return &SortedStringArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Set the underlying slice array with the given <array> param.
//
// 设置底层数组变量.
@ -166,13 +181,31 @@ func (a *SortedStringArray) PopRight() string {
return value
}
// Pop an random item from array.
// PopRand picks an random item out of array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *SortedStringArray) PopRand() string {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands picks <size> items out of array.
//
// 随机将size个数据项移出数组并返回该数据项。
func (a *SortedStringArray) PopRands(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]string, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项
@ -393,18 +426,22 @@ func (a *SortedStringArray) RLockFunc(f func(array []string)) *SortedStringArray
return a
}
// Merge two arrays.
// Merge two arrays. The parameter <array> can be any garray type or slice type.
// The difference between Merge and Add is Add supports only specified slice type,
// but Merge supports more variable types.
//
// 合并两个数组.
func (a *SortedStringArray) Merge(array *SortedStringArray) *SortedStringArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
// 合并两个数组, 支持任意的garray数组类型及slice类型.
func (a *SortedStringArray) Merge(array interface{}) *SortedStringArray {
switch v := array.(type) {
case *Array: a.Add(gconv.Strings(v.Slice())...)
case *IntArray: a.Add(gconv.Strings(v.Slice())...)
case *StringArray: a.Add(gconv.Strings(v.Slice())...)
case *SortedArray: a.Add(gconv.Strings(v.Slice())...)
case *SortedIntArray: a.Add(gconv.Strings(v.Slice())...)
case *SortedStringArray: a.Add(gconv.Strings(v.Slice())...)
default:
a.Add(gconv.Strings(array)...)
}
a.array = append(a.array, array.array...)
sort.Strings(a.array)
return a
}
@ -456,11 +493,19 @@ func (a *SortedStringArray) SubSlice(offset, size int) []string {
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
// Rand gets one random entry from array.
//
// 从数组中随机取出size个元素项构成slice返回
func (a *SortedStringArray) Rand(size int) []string {
// 从数组中随机获得1个元素项(不删除)
func (a *SortedStringArray) Rand() string {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands gets one or more random entries from array(a copy).
//
// 从数组中随机拷贝size个元素项构成slice返回。
func (a *SortedStringArray) Rands(size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
@ -482,5 +527,12 @@ func (a *SortedStringArray) Rand(size int) []string {
func (a *SortedStringArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(a.array, glue)
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}

View File

@ -0,0 +1,111 @@
// 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 garray_test
import (
"fmt"
"github.com/gogf/gf/g/container/garray"
)
func Example_basic() {
// 创建普通的数组,默认并发安全(带锁)
a := garray.New()
// 添加数据项
for i := 0; i < 10; i++ {
a.Append(i)
}
// 获取当前数组长度
fmt.Println(a.Len())
// 获取当前数据项列表
fmt.Println(a.Slice())
// 获取指定索引项
fmt.Println(a.Get(6))
// 查找指定数据项是否存在
fmt.Println(a.Contains(6))
fmt.Println(a.Contains(100))
// 在指定索引前插入数据项
a.InsertAfter(9, 11)
// 在指定索引后插入数据项
a.InsertBefore(10, 10)
fmt.Println(a.Slice())
// 修改指定索引的数据项
a.Set(0, 100)
fmt.Println(a.Slice())
// 搜索数据项,返回搜索到的索引位置
fmt.Println(a.Search(5))
// 删除指定索引的数据项
a.Remove(0)
fmt.Println(a.Slice())
// 清空数组
fmt.Println(a.Slice())
a.Clear()
fmt.Println(a.Slice())
// Output:
// 10
// [0 1 2 3 4 5 6 7 8 9]
// 6
// true
// false
// [0 1 2 3 4 5 6 7 8 9 10 11]
// [100 1 2 3 4 5 6 7 8 9 10 11]
// 5
// [1 2 3 4 5 6 7 8 9 10 11]
// [1 2 3 4 5 6 7 8 9 10 11]
// []
}
func Example_rand() {
array := garray.NewFrom([]interface{}{1,2,3,4,5,6,7,8,9})
// 随机返回两个数据项(不删除)
fmt.Println(array.Rands(2))
fmt.Println(array.PopRand())
}
func Example_pop() {
array := garray.NewFrom([]interface{}{1,2,3,4,5,6,7,8,9})
fmt.Println(array.PopLeft())
fmt.Println(array.PopLefts(2))
fmt.Println(array.PopRight())
fmt.Println(array.PopRights(2))
// Output:
// 1
// [2 3]
// 9
// [7 8]
}
func Example_merge() {
array1 := garray.NewFrom([]interface{}{1,2})
array2 := garray.NewFrom([]interface{}{3,4})
slice1 := []interface{}{5,6}
slice2 := []int{7,8}
slice3 := []string{"9","0"}
fmt.Println(array1.Slice())
array1.Merge(array1)
array1.Merge(array2)
array1.Merge(slice1)
array1.Merge(slice2)
array1.Merge(slice3)
fmt.Println(array1.Slice())
// Output:
// [1 2]
// [1 2 1 2 3 4 5 6 7 8 9 0]
}

View File

@ -161,9 +161,18 @@ func TestIntArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0,1,2,3,4,5,6}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(len(array1.Rand(2)), 2)
gtest.Assert(len(array1.Rand(10)), 7)
gtest.AssertIN(array1.Rand(1)[0], a1)
gtest.Assert(len(array1.Rands(2)), 2)
gtest.Assert(len(array1.Rands(10)), 7)
gtest.AssertIN(array1.Rands(1)[0], a1)
gtest.AssertIN(array1.Rand(), a1)
})
}
func TestIntArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{100, 200, 300, 400, 500, 600}
array := garray.NewFromCopy(a1)
gtest.AssertIN(array.PopRands(2), a1)
})
}

View File

@ -80,6 +80,14 @@ func TestArray_PushAndPop(t *testing.T) {
})
}
func TestArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{100, 200, 300, 400, 500, 600}
array := garray.NewFromCopy(a1)
gtest.AssertIN(array.PopRands(2), a1)
})
}
func TestArray_PopLeftsAndPopRights(t *testing.T) {
gtest.Case(t, func() {
value1 := []interface{}{0,1,2,3,4,5,6}
@ -165,9 +173,9 @@ func TestArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0,1,2,3,4,5,6}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(len(array1.Rand(2)), 2)
gtest.Assert(len(array1.Rand(10)), 7)
gtest.AssertIN(array1.Rand(1)[0], a1)
gtest.Assert(len(array1.Rands(2)), 2)
gtest.Assert(len(array1.Rands(10)), 7)
gtest.AssertIN(array1.Rands(1)[0], a1)
})
}

View File

@ -162,9 +162,17 @@ func TestStringArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0","1","2","3","4","5","6"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(len(array1.Rand(2)), "2")
gtest.Assert(len(array1.Rand(10)), "7")
gtest.AssertIN(array1.Rand(1)[0], a1)
gtest.Assert(len(array1.Rands(2)), "2")
gtest.Assert(len(array1.Rands(10)), "7")
gtest.AssertIN(array1.Rands(1)[0], a1)
})
}
func TestStringArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"100", "200", "300", "400", "500", "600"}
array := garray.NewFromCopy(a1)
gtest.AssertIN(array.PopRands(2), a1)
})
}

View File

@ -15,9 +15,8 @@
package gqueue
import (
"container/list"
"github.com/gogf/gf/g/container/glist"
"math"
"sync"
)
// 1、这是一个先进先出的队列(chan <-- list)
@ -28,9 +27,8 @@ import (
//
// 4、由于功能主体是chan那么操作仍然像chan那样具有阻塞效果
type Queue struct {
mu sync.Mutex // 底层链表写锁
limit int // 队列限制大小
list *list.List // 底层数据链表
list *glist.List // 底层数据链表
events chan struct{} // 写入事件通知
closed chan struct{} // 队列关闭通知
C chan interface{} // 队列数据读取
@ -50,7 +48,7 @@ func New(limit...int) *Queue {
q.limit = limit[0]
q.C = make(chan interface{}, limit[0])
} else {
q.list = list.New()
q.list = glist.New()
q.events = make(chan struct{}, math.MaxInt32)
q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE)
go q.startAsyncLoop()
@ -68,7 +66,6 @@ func (q *Queue) startAsyncLoop() {
for {
if length := q.list.Len(); length > 0 {
array := make([]interface{}, length)
q.mu.Lock()
for i := 0; i < length; i++ {
if e := q.list.Front(); e != nil {
array[i] = q.list.Remove(e)
@ -76,7 +73,6 @@ func (q *Queue) startAsyncLoop() {
break
}
}
q.mu.Unlock()
for _, v := range array {
q.C <- v
}
@ -93,9 +89,7 @@ func (q *Queue) Push(v interface{}) {
if q.limit > 0 {
q.C <- v
} else {
q.mu.Lock()
q.list.PushBack(v)
q.mu.Unlock()
q.events <- struct{}{}
}
}

View File

@ -47,7 +47,9 @@ type DB interface {
GetOne(query string, args ...interface{}) (Record, error)
GetValue(query string, args ...interface{}) (Value, error)
GetCount(query string, args ...interface{}) (int, error)
GetStruct(obj interface{}, query string, args ...interface{}) error
GetStruct(objPointer interface{}, query string, args ...interface{}) error
GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error
GetScan(objPointer interface{}, query string, args ...interface{}) error
// 创建底层数据库master/slave链接对象
Master() (*sql.DB, error)

View File

@ -165,13 +165,44 @@ func (bs *dbBase) GetOne(query string, args ...interface{}) (Record, error) {
return nil, nil
}
// 数据库查询,获取查询结果记录自动映射数据到给定的struct对象中
func (bs *dbBase) GetStruct(obj interface{}, query string, args ...interface{}) error {
// 数据库查询,查询单条记录自动映射数据到给定的struct对象中
func (bs *dbBase) GetStruct(objPointer interface{}, query string, args ...interface{}) error {
one, err := bs.GetOne(query, args...)
if err != nil {
return err
}
return one.ToStruct(obj)
return one.ToStruct(objPointer)
}
// 数据库查询查询多条记录并自动转换为指定的slice对象, 如: []struct/[]*struct。
func (bs *dbBase) GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error {
all, err := bs.GetAll(query, args...)
if err != nil {
return err
}
return all.ToStructs(objPointerSlice)
}
// 将结果转换为指定的struct/*struct/[]struct/[]*struct,
// 参数应该为指针类型,否则返回失败。
// 该方法自动识别参数类型调用Struct/Structs方法。
func (bs *dbBase) GetScan(objPointer interface{}, query string, args ...interface{}) error {
t := reflect.TypeOf(objPointer)
k := t.Kind()
if k != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", k)
}
k = t.Elem().Kind()
switch k {
case reflect.Array:
case reflect.Slice:
return bs.db.GetStructs(objPointer, query, args ...)
case reflect.Struct:
return bs.db.GetStruct(objPointer, query, args ...)
default:
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
}
return nil
}
// 数据库查询,获取查询字段值

View File

@ -12,16 +12,16 @@ import (
"fmt"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"reflect"
"strings"
)
// 格式化SQL查询条件
func formatCondition(where interface{}, args []interface{}) (string, []interface{}) {
func formatCondition(where interface{}, args []interface{}) (newWhere string, newArgs []interface{}) {
// 条件字符串处理
buffer := bytes.NewBuffer(nil)
// 使用反射进行类型判断
@ -31,32 +31,61 @@ func formatCondition(where interface{}, args []interface{}) (string, []interface
rv = rv.Elem()
kind = rv.Kind()
}
tmpArgs := []interface{}(nil)
switch kind {
// map/struct类型
case reflect.Map: fallthrough
case reflect.Struct:
for k, v := range gconv.Map(where) {
key := gconv.String(k)
value := gconv.String(v)
for key, value := range gconv.Map(where) {
if buffer.Len() > 0 {
buffer.WriteString(" AND ")
}
if gstr.IsNumeric(value) || value == "?" {
buffer.WriteString(key + "=" + value)
} else {
buffer.WriteString(key + "='" + value + "'")
// 支持slice键值/属性,如果只有一个?占位符号那么作为IN查询否则打散作为多个查询参数
rv := reflect.ValueOf(value)
switch rv.Kind() {
case reflect.Slice: fallthrough
case reflect.Array:
count := gstr.Count(key, "?")
if count == 0 {
buffer.WriteString(key + " IN(?)")
tmpArgs = append(tmpArgs, value)
} else if count != rv.Len() {
buffer.WriteString(key)
tmpArgs = append(tmpArgs, value)
} else {
buffer.WriteString(key)
// 如果键名/属性名称中带有多个?占位符号,那么将参数打散
tmpArgs = append(tmpArgs, gconv.Interfaces(value)...)
}
default:
if value == nil {
buffer.WriteString(key)
} else {
if gstr.Pos(key, "?") == -1 {
if gstr.Pos(key, "<") == -1 && gstr.Pos(key, ">") == -1 && gstr.Pos(key, "=") == -1 {
buffer.WriteString(key + "=?")
} else {
buffer.WriteString(key + "?")
}
} else {
buffer.WriteString(key)
}
tmpArgs = append(tmpArgs, value)
}
}
}
default:
buffer.WriteString(gconv.String(where))
}
if buffer.Len() == 0 {
buffer.WriteString("1=1")
}
// 查询条件处理
newWhere := buffer.String()
newArgs := make([]interface{}, 0)
if len(args) > 0 {
for index, arg := range args {
newWhere = buffer.String()
tmpArgs = append(tmpArgs, args...)
// 查询条件参数处理主要处理slice参数类型
if len(tmpArgs) > 0 {
for index, arg := range tmpArgs {
rv := reflect.ValueOf(arg)
kind := rv.Kind()
if kind == reflect.Ptr {
@ -64,12 +93,14 @@ func formatCondition(where interface{}, args []interface{}) (string, []interface
kind = rv.Kind()
}
switch kind {
// Where条件参数支持slice类型
// '?'占位符支持slice类型,
// 这里会将slice参数拆散并更新原有占位符'?'为多个'?',使用','符号连接。
case reflect.Slice: fallthrough
case reflect.Array:
for i := 0; i < rv.Len(); i++ {
newArgs = append(newArgs, rv.Index(i).Interface())
}
// counter用于匹配该参数的位置(与index对应)
counter := 0
newWhere, _ = gregex.ReplaceStringFunc(`\?`, newWhere, func(s string) string {
counter++
@ -79,11 +110,19 @@ func formatCondition(where interface{}, args []interface{}) (string, []interface
return s
})
default:
// 支持例如 Where/And/Or("uid", 1) 这种格式
if gstr.Pos(newWhere, "?") == -1 {
if gstr.Pos(newWhere, "<") == -1 && gstr.Pos(newWhere, ">") == -1 && gstr.Pos(newWhere, "=") == -1 {
newWhere += "=?"
} else {
newWhere += "?"
}
}
newArgs = append(newArgs, arg)
}
}
}
return newWhere, newArgs
return
}
// 打印SQL对象(仅在debug=true时有效)

View File

@ -3,17 +3,18 @@
// 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.
//
// @author john, ymrjqyy
package gdb
import (
"fmt"
"errors"
"database/sql"
"github.com/gogf/gf/g/util/gconv"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/g/util/gconv"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"reflect"
"strings"
)
// 数据库链式操作模型对象
@ -35,6 +36,7 @@ type Model struct {
cacheEnabled bool // 当前SQL操作是否开启查询缓存功能
cacheTime int // 查询缓存时间
cacheName string // 查询缓存名称
safe bool // 当前模型是否运行安全模式(可修改当前模型,否则每一次链式操作都是返回新的模型对象)
}
// 链式操作,数据表字段,可支持多个表,以半角逗号连接
@ -44,6 +46,7 @@ func (bs *dbBase) Table(tables string) (*Model) {
tablesInit : tables,
tables : tables,
fields : "*",
safe : false,
}
}
@ -59,6 +62,7 @@ func (tx *TX) Table(tables string) (*Model) {
tx : tx,
tablesInit : tables,
tables : tables,
safe : false,
}
}
@ -79,98 +83,130 @@ func (md *Model) Clone() *Model {
return newModel
}
// 标识当前对象运行安全模式(可被修改)。
// 1. 默认情况下,模型对象的对象属性无法被修改,
// 每一次链式操作都是克隆一个新的模型对象,这样所有的操作都不会污染模型对象。
// 但是链式操作如果需要分开执行,那么需要将新的克隆对象赋值给旧的模型对象继续操作。
// 2. 当标识模型对象为可修改,那么在当前模型对象的所有链式操作均会影响下一次的链式操作,
// 即使是链式操作分开执行。
// 3. 大部分ORM框架默认模型对象是可修改的但是GF框架的ORM提供给开发者更灵活更安全的链式操作选项。
func (md *Model) Safe(safe...bool) *Model {
if len(safe) > 0 {
md.safe = safe[0]
} else {
md.safe = true
}
return md
}
// 返回操作的模型对象可能是当前对象也可能是新的克隆对象根据alterable决定。
func (md *Model) getModel() *Model {
if !md.safe {
return md
} else {
return md.Clone()
}
}
// 链式操作,左联表
func (md *Model) LeftJoin(joinTable string, on string) (*Model) {
model := md.Clone()
model := md.getModel()
model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", joinTable, on)
return model
}
// 链式操作,右联表
func (md *Model) RightJoin(joinTable string, on string) (*Model) {
model := md.Clone()
model := md.getModel()
model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", joinTable, on)
return model
}
// 链式操作,内联表
func (md *Model) InnerJoin(joinTable string, on string) (*Model) {
model := md.Clone()
model := md.getModel()
model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", joinTable, on)
return model
}
// 链式操作,查询字段
func (md *Model) Fields(fields string) (*Model) {
model := md.Clone()
model := md.getModel()
model.fields = fields
return model
}
// 链式操作,过滤字段
func (md *Model) Filter() (*Model) {
model := md.Clone()
model := md.getModel()
model.filter = true
return model
}
// 链式操作condition支持string & gdb.Map
// 链式操作condition支持string & gdb.Map.
// 注意多个Where调用时会自动转换为And条件调用。
func (md *Model) Where(where interface{}, args ...interface{}) (*Model) {
model := md.Clone()
model := md.getModel()
if model.where != "" {
return md.And(where, args...)
}
newWhere, newArgs := formatCondition(where, args)
model.where = newWhere
model.whereArgs = append(model.whereArgs, newArgs...)
// 支持 Where("uid", 1)这种格式
if len(args) == 1 && strings.Index(model.where , "?") < 0 {
model.where += "=?"
}
model.whereArgs = newArgs
return model
}
// 链式操作添加AND条件到Where中
func (md *Model) And(where interface{}, args ...interface{}) (*Model) {
model := md.Clone()
model := md.getModel()
newWhere, newArgs := formatCondition(where, args)
model.where += " AND " + newWhere
model.whereArgs = append(model.whereArgs, newArgs...)
if len(model.where) > 0 && model.where[0] == '(' {
model.where = fmt.Sprintf(`%s AND (%s)`, model.where, newWhere)
} else {
model.where = fmt.Sprintf(`(%s) AND (%s)`, model.where, newWhere)
}
model.whereArgs = append(model.whereArgs, newArgs...)
return model
}
// 链式操作添加OR条件到Where中
func (md *Model) Or(where interface{}, args ...interface{}) (*Model) {
model := md.Clone()
model := md.getModel()
newWhere, newArgs := formatCondition(where, args)
model.where += " OR " + newWhere
model.whereArgs = append(model.whereArgs, newArgs...)
if len(model.where) > 0 && model.where[0] == '(' {
model.where = fmt.Sprintf(`%s OR (%s)`, model.where, newWhere)
} else {
model.where = fmt.Sprintf(`(%s) OR (%s)`, model.where, newWhere)
}
model.whereArgs = append(model.whereArgs, newArgs...)
return model
}
// 链式操作group by
func (md *Model) GroupBy(groupBy string) (*Model) {
model := md.Clone()
model := md.getModel()
model.groupBy = groupBy
return model
}
// 链式操作order by
func (md *Model) OrderBy(orderBy string) (*Model) {
model := md.Clone()
model := md.getModel()
model.orderBy = orderBy
return model
}
// 链式操作limit
func (md *Model) Limit(start int, limit int) (*Model) {
model := md.Clone()
model := md.getModel()
model.start = start
model.limit = limit
return model
}
// 链式操作,翻页
// @author ymrjqyy
// 链式操作,翻页注意分页页码从1开始而Limit方法从0开始。
func (md *Model) ForPage(page, limit int) (*Model) {
model := md.Clone()
model := md.getModel()
model.start = (page - 1) * limit
model.limit = limit
return model
@ -178,7 +214,7 @@ func (md *Model) ForPage(page, limit int) (*Model) {
// 设置批处理的大小
func (md *Model) Batch(batch int) *Model {
model := md.Clone()
model := md.getModel()
model.batch = batch
return model
}
@ -188,7 +224,7 @@ func (md *Model) Batch(batch int) *Model {
// name表示自定义的缓存名称便于业务层精准定位缓存项(如果业务层需要手动清理时,必须指定缓存名称)
// 例如:查询缓存时设置名称,清理缓存时可以给定清理的缓存名称进行精准清理。
func (md *Model) Cache(time int, name ... string) *Model {
model := md.Clone()
model := md.getModel()
model.cacheTime = time
if len(name) > 0 {
model.cacheName = name[0]
@ -203,7 +239,7 @@ func (md *Model) Cache(time int, name ... string) *Model {
// 链式操作操作数据项参数data类型支持 string/map/slice/struct/*struct ,
// 也可以是key,value,key,value,...。
func (md *Model) Data(data ...interface{}) *Model {
model := md.Clone()
model := md.getModel()
if len(data) > 1 {
m := make(map[string]interface{})
for i := 0; i < len(data); i += 2 {
@ -438,13 +474,44 @@ func (md *Model) Value() (Value, error) {
return nil, nil
}
// 链式操作查询单条记录并自动转换为struct对象
func (md *Model) Struct(obj interface{}) error {
// 链式操作查询单条记录并自动转换为struct对象, 参数必须为对象的指针,不能为空指针。
func (md *Model) Struct(objPointer interface{}) error {
one, err := md.One()
if err != nil {
return err
}
return one.ToStruct(obj)
return one.ToStruct(objPointer)
}
// 链式操作查询多条记录并自动转换为指定的slice对象, 如: []struct/[]*struct。
func (md *Model) Structs(objPointerSlice interface{}) error {
r, err := md.All()
if err != nil {
return err
}
return r.ToStructs(objPointerSlice)
}
// 链式操作将结果转换为指定的struct/*struct/[]struct/[]*struct,
// 参数应该为指针类型,否则返回失败。
// 该方法自动识别参数类型调用Struct/Structs方法。
func (md *Model) Scan(objPointer interface{}) error {
t := reflect.TypeOf(objPointer)
k := t.Kind()
if k != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", k)
}
k = t.Elem().Kind()
switch k {
case reflect.Array:
case reflect.Slice:
return md.Structs(objPointer)
case reflect.Struct:
return md.Struct(objPointer)
default:
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
}
return nil
}
// 链式操作查询数量fields可以为空也可以自定义查询字段
@ -532,14 +599,13 @@ func (md *Model) getFormattedSql() string {
return s
}
// 组块结果集
// @author ymrjqyy
// @author 2018-08-15
// 组块结果集
func (md *Model) Chunk(limit int, callback func(result Result, err error) bool) {
page := 1
page := 1
model := md
for {
md.ForPage(page, limit)
data, err := md.getAll(md.getFormattedSql(), md.whereArgs...)
model = model.ForPage(page, limit)
data, err := model.All()
if err != nil {
callback(nil, err)
break

View File

@ -11,6 +11,7 @@
2.不支持save/replace方法
3.不支持LastInsertId方法
*/
package gdb
import (

View File

@ -11,6 +11,7 @@
2.不支持save/replace方法可以调用这2个方法估计会报错还没测试过,(应该是可以通过oracle的merge来实现这2个功能的还没仔细研究)
3.不支持LastInsertId方法
*/
package gdb
import (

View File

@ -4,7 +4,6 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb
import (

View File

@ -8,8 +8,10 @@ package gdb
import (
"database/sql"
"fmt"
"github.com/gogf/gf/g/text/gregex"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"reflect"
)
// 数据库事务对象
@ -75,6 +77,37 @@ func (tx *TX) GetStruct(obj interface{}, query string, args ...interface{}) erro
return one.ToStruct(obj)
}
// 数据库查询查询多条记录并自动转换为指定的slice对象, 如: []struct/[]*struct。
func (tx *TX) GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error {
all, err := tx.GetAll(query, args...)
if err != nil {
return err
}
return all.ToStructs(objPointerSlice)
}
// 将结果转换为指定的struct/*struct/[]struct/[]*struct,
// 参数应该为指针类型,否则返回失败。
// 该方法自动识别参数类型调用Struct/Structs方法。
func (tx *TX) GetScan(objPointer interface{}, query string, args ...interface{}) error {
t := reflect.TypeOf(objPointer)
k := t.Kind()
if k != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", k)
}
k = t.Elem().Kind()
switch k {
case reflect.Array:
case reflect.Slice:
return tx.db.GetStructs(objPointer, query, args ...)
case reflect.Struct:
return tx.db.GetStruct(objPointer, query, args ...)
default:
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
}
return nil
}
// 数据库查询,获取查询字段值
func (tx *TX) GetValue(query string, args ...interface{}) (Value, error) {
one, err := tx.GetOne(query, args ...)

View File

@ -33,10 +33,6 @@ func (r Record) ToMap() Map {
}
// 将Map变量映射到指定的struct对象中注意参数应当是一个对象的指针
func (r Record) ToStruct(obj interface{}) error {
m := make(map[string]interface{})
for k, v := range r {
m[k] = v.Val()
}
return gconv.Struct(m, obj)
func (r Record) ToStruct(objPointer interface{}) error {
return gconv.Struct(r.ToMap(), objPointer)
}

View File

@ -7,7 +7,9 @@
package gdb
import (
"fmt"
"github.com/gogf/gf/g/encoding/gparser"
"reflect"
)
// 将结果集转换为JSON字符串
@ -96,3 +98,30 @@ func (r Result) ToUintRecord(key string) map[uint]Record {
}
return m
}
// 将结果列表转换为指定对象的slice。
func (r Result) ToStructs(objPointerSlice interface{}) error {
l := len(r)
if l == 0 {
return nil
}
t := reflect.TypeOf(objPointerSlice)
if t.Kind() != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", t.Kind())
}
a := reflect.MakeSlice(t.Elem(), l, l)
itemType := a.Index(0).Type()
for i := 0; i < l; i++ {
if itemType.Kind() == reflect.Ptr {
e := reflect.New(itemType.Elem()).Elem()
r[i].ToStruct(e)
a.Index(i).Set(e.Addr())
} else {
e := reflect.New(itemType).Elem()
r[i].ToStruct(e)
a.Index(i).Set(e)
}
}
reflect.ValueOf(objPointerSlice).Elem().Set(a)
return nil
}

View File

@ -3,6 +3,7 @@ package gdb_test
import (
"github.com/gogf/gf/g/database/gdb"
"github.com/gogf/gf/g/test/gtest"
"os"
)
var (
@ -13,7 +14,7 @@ var (
// 初始化连接参数。
// 测试前需要修改连接参数。
func init() {
gdb.AddDefaultConfigNode(gdb.ConfigNode{
node := gdb.ConfigNode{
Host: "127.0.0.1",
Port: "3306",
User: "root",
@ -22,8 +23,13 @@ func init() {
Type: "mysql",
Role: "master",
Charset: "utf8",
Priority: 1,
})
Priority: 1,
}
hostname, _ := os.Hostname()
if hostname == "ijohn" {
node.Pass = "12345678"
}
gdb.AddDefaultConfigNode(node)
if r, err := gdb.New(); err != nil {
gtest.Fatal(err)
} else {

View File

@ -266,19 +266,159 @@ func TestDbBase_GetCount(t *testing.T) {
}
func TestDbBase_GetStruct(t *testing.T) {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
if err := db.GetStruct(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
}
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
if err := db.GetStruct(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
}
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := new(User)
if err := db.GetStruct(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
}
})
}
func TestDbBase_GetStructs(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
var users []User
if err := db.GetStructs(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []User
if err := db.GetStructs(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
})
}
func TestDbBase_GetScan(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
if err := db.GetScan(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
}
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := new(User)
if err := db.GetScan(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
}
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
var users []User
if err := db.GetScan(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []User
if err := db.GetScan(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
})
}
func TestDbBase_Delete(t *testing.T) {

View File

@ -121,7 +121,7 @@ func TestModel_Replace(t *testing.T) {
"passport" : "t11",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T11",
"create_time" : gtime.Now().String(),
"create_time" : "2018-10-10 00:01:10",
}).Replace()
if err != nil {
gtest.Fatal(err)
@ -136,7 +136,7 @@ func TestModel_Save(t *testing.T) {
"passport" : "t111",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T111",
"create_time" : gtime.Now().String(),
"create_time" : "2018-10-10 00:01:10",
}).Save()
if err != nil {
gtest.Fatal(err)
@ -168,13 +168,44 @@ func TestModel_Clone(t *testing.T) {
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 2)
gtest.Assert(record["id"].Int(), 3)
gtest.Assert(len(result), 2)
gtest.Assert(count, 2)
gtest.Assert(record["id"].Int(), 3)
gtest.Assert(len(result), 2)
gtest.Assert(result[0]["id"].Int(), 1)
gtest.Assert(result[1]["id"].Int(), 3)
}
func TestModel_Safe(t *testing.T) {
gtest.Case(t, func() {
md := db.Table("user").Safe(false).Where("id IN(?)", g.Slice{1,3})
count, err := md.Count()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 2)
md.And("id = ?", 1)
count, err = md.Count()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 1)
})
gtest.Case(t, func() {
md := db.Table("user").Safe(true).Where("id IN(?)", g.Slice{1,3})
count, err := md.Count()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 2)
md.And("id = ?", 1)
count, err = md.Count()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 2)
})
}
func TestModel_All(t *testing.T) {
result, err := db.Table("user").All()
if err != nil {
@ -222,19 +253,164 @@ func TestModel_Select(t *testing.T) {
}
func TestModel_Struct(t *testing.T) {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
err := db.Table("user").Where("id=1").Struct(user)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T111")
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
err := db.Table("user").Where("id=1").Struct(user)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T111")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := new(User)
err := db.Table("user").Where("id=1").Struct(user)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T111")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
}
func TestModel_Structs(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
var users []User
err := db.Table("user").OrderBy("id asc").Structs(&users)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []*User
err := db.Table("user").OrderBy("id asc").Structs(&users)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
}
func TestModel_Scan(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
err := db.Table("user").Where("id=1").Scan(user)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T111")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := new(User)
err := db.Table("user").Where("id=1").Scan(user)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T111")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
var users []User
err := db.Table("user").OrderBy("id asc").Scan(&users)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []*User
err := db.Table("user").OrderBy("id asc").Scan(&users)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
}
func TestModel_OrderBy(t *testing.T) {
@ -255,25 +431,164 @@ func TestModel_GroupBy(t *testing.T) {
gtest.Assert(result[0]["nickname"].String(), "T111")
}
// slice
func TestModel_Where1(t *testing.T) {
result, err := db.Table("user").Where("id IN(?)", g.Slice{1,3}).OrderBy("id ASC").All()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(result), 2)
gtest.Assert(result[0]["id"].Int(), 1)
gtest.Assert(result[1]["id"].Int(), 3)
}
func TestModel_Where(t *testing.T) {
// string
gtest.Case(t, func() {
result, err := db.Table("user").Where("id=? and nickname=?", 3, "T3").One()
if err != nil {
gtest.Fatal(err)
}
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 3).One()
if err != nil {
gtest.Fatal(err)
}
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 3).Where("nickname", "T3").One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 3).And("nickname", "T3").One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 30).Or("nickname", "T3").One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 30).Or("nickname", "T3").And("id>?", 1).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 30).Or("nickname", "T3").And("id>", 1).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// map
gtest.Case(t, func() {
result, err := db.Table("user").Where(g.Map{"id" : 3, "nickname" : "T3"}).One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
// map key operator
gtest.Case(t, func() {
result, err := db.Table("user").Where(g.Map{"id>" : 1, "id<" : 3}).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 2)
})
// complicated where 1
gtest.Case(t, func() {
//db.SetDebug(true)
conditions := g.Map{
"nickname like ?" : "%T%",
"id between ? and ?" : g.Slice{1,3},
"id > 0" : nil,
"create_time > 0" : nil,
"id" : g.Slice{1,2,3},
}
result, err := db.Table("user").Where(conditions).OrderBy("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["id"].Int(), 1)
})
// complicated where 2
gtest.Case(t, func() {
//db.SetDebug(true)
conditions := g.Map{
"nickname like ?" : "%T%",
"id between ? and ?" : g.Slice{1,3},
"id >= ?" : 1,
"create_time > ?" : 0,
"id in(?)" : g.Slice{1,2,3},
}
result, err := db.Table("user").Where(conditions).OrderBy("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["id"].Int(), 1)
})
// struct
gtest.Case(t, func() {
type User struct {
Id int `json:"id"`
Nickname string `gconv:"nickname"`
}
result, err := db.Table("user").Where(User{3, "T3"}).One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
// slice
func TestModel_Where2(t *testing.T) {
result, err := db.Table("user").Where("nickname=? AND id IN(?)", "T3", g.Slice{1,3}).OrderBy("id ASC").All()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 3)
result, err = db.Table("user").Where(&User{3, "T3"}).One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
// slice single
gtest.Case(t, func() {
result, err := db.Table("user").Where("id IN(?)", g.Slice{1, 3}).OrderBy("id ASC").All()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(result), 2)
gtest.Assert(result[0]["id"].Int(), 1)
gtest.Assert(result[1]["id"].Int(), 3)
})
// slice + string
gtest.Case(t, func() {
result, err := db.Table("user").Where("nickname=? AND id IN(?)", "T3", g.Slice{1,3}).OrderBy("id ASC").All()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 3)
})
// slice + map
gtest.Case(t, func() {
result, err := db.Table("user").Where(g.Map{
"id" : g.Slice{1,3},
"nickname" : "T3",
}).OrderBy("id ASC").All()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 3)
})
// slice + struct
gtest.Case(t, func() {
type User struct {
Ids []int `json:"id"`
Nickname string `gconv:"nickname"`
}
result, err := db.Table("user").Where(User{
Ids : []int{1, 3},
Nickname : "T3",
}).OrderBy("id ASC").All()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 3)
})
}
func TestModel_Delete(t *testing.T) {

View File

@ -267,6 +267,29 @@ func TestTX_Save(t *testing.T) {
}
}
func TestTX_Update(t *testing.T) {
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if result, err := db.Update("user", "create_time='2010-10-10 00:00:01'", "id=3"); err != nil {
gtest.Fatal(err)
} else {
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
if value, err := db.Table("user").Fields("create_time").Where("id", 3).Value(); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(value.String(), "2010-10-10 00:00:01")
}
})
}
func TestTX_GetAll(t *testing.T) {
tx, err := db.Begin()
if err != nil {
@ -331,26 +354,215 @@ func TestTX_GetCount(t *testing.T) {
}
func TestTX_GetStruct(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
if err := tx.GetStruct(user, "SELECT * FROM user WHERE id=?", 1); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(user.NickName, "T11")
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
if err := tx.GetStruct(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T3")
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := new(User)
if err := tx.GetStruct(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T3")
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
}
func TestTX_GetStructs(t *testing.T) {
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
var users []User
if err := tx.GetStructs(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 4)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T11")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []User
if err := tx.GetStructs(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 4)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T11")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
}
func TestTX_GetScan(t *testing.T) {
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
if err := tx.GetScan(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T3")
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := new(User)
if err := tx.GetScan(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T3")
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
var users []User
if err := tx.GetScan(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 4)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T11")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []User
if err := tx.GetScan(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 4)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T11")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
}
func TestTX_Delete(t *testing.T) {

31
g/g.go
View File

@ -3,34 +3,51 @@
// 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 g
import "github.com/gogf/gf/g/container/gvar"
// 框架动态变量可以用该类型替代interface{}类型
type Var = gvar.Var
// Universal variable type, like generics.
//
// 动态变量类型可以用该类型替代interface{}类型
type Var = gvar.Var
// Frequently-used map type alias.
//
// 常用map数据结构(使用别名)
type Map = map[string]interface{}
type MapAnyAny = map[interface{}]interface{}
type MapAnyStr = map[interface{}]string
type MapAnyInt = map[interface{}]int
type MapStrAny = map[string]interface{}
type MapStrStr = map[string]string
type MapStrInt = map[string]int
type MapIntAny = map[int]interface{}
type MapIntStr = map[int]string
type MapIntInt = map[int]int
// Frequently-used slice type alias.
//
// 常用list数据结构(使用别名)
type List = []Map
type ListAnyStr = []map[interface{}]string
type ListAnyInt = []map[interface{}]int
type ListStrAny = []map[string]interface{}
type ListStrStr = []map[string]string
type ListStrInt = []map[string]int
type ListIntAny = []map[int]interface{}
type ListIntStr = []map[int]string
type ListIntInt = []map[int]int
// Frequently-used slice type alias.
//
// 常用slice数据结构(使用别名)
type Slice = []interface{}
type SliceAny = []interface{}
type SliceStr = []string
type SliceInt = []int
type Array = Slice
type ArrayStr = SliceStr
type ArrayInt = SliceInt
type Array = []interface{}
type ArrayAny = []interface{}
type ArrayStr = []string
type ArrayInt = []int

52
g/internal/empty/empty.go Normal file
View File

@ -0,0 +1,52 @@
// Copyright 2019 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 empty
import (
"reflect"
)
// 判断给定的变量是否为空。
// 整型为0, 布尔为false, slice/map长度为0, 其他为nil的情况都为空。
// 为空时返回true否则返回false。
func IsEmpty(value interface{}) bool {
if value == nil {
return true
}
// 优先通过断言来进行常用类型判断
switch value := value.(type) {
case int: return value == 0
case int8: return value == 0
case int16: return value == 0
case int32: return value == 0
case int64: return value == 0
case uint: return value == 0
case uint8: return value == 0
case uint16: return value == 0
case uint32: return value == 0
case uint64: return value == 0
case float32: return value == 0
case float64: return value == 0
case bool: return value == false
case string: return value == ""
case []byte: return len(value) == 0
default:
// 最后通过反射来判断
rv := reflect.ValueOf(value)
if rv.IsNil() {
return true
}
kind := rv.Kind()
switch kind {
case reflect.Map: fallthrough
case reflect.Slice: fallthrough
case reflect.Array:
return rv.Len() == 0
}
}
return false
}

View File

@ -4,5 +4,7 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package ghttp provides quite powerful HTTP server and simple client implementations.
// Package ghttp provides a powerful http server and a simple client.
//
// ghttp是GF框架的核心模块实现了一个强大的Web Server并提供了一个简便的HTTP客户端。
package ghttp

View File

@ -45,13 +45,10 @@ func (r *Response) Write(content ... interface{}) {
return
}
for _, v := range content {
switch v.(type) {
case []byte:
// 如果是二进制数据,那么返回二进制数据
r.buffer.Write(gconv.Bytes(v))
switch value := v.(type) {
case []byte: r.buffer.Write(value)
case string: r.buffer.WriteString(value)
default:
// 否则一律按照可显示的字符串进行转换
r.buffer.WriteString(gconv.String(v))
}
}
@ -118,15 +115,17 @@ func (r *Response) WriteXml(content interface{}, rootTag...string) error {
return nil
}
// 允许AJAX跨域访问
// Deprecated, please use CORSDefault instead.
//
// (已废弃请使用CORSDefault)允许AJAX跨域访问.
func (r *Response) SetAllowCrossDomainRequest(allowOrigin string, allowMethods string, maxAge...int) {
age := 3628800
if len(maxAge) > 0 {
age = maxAge[0]
}
r.Header().Set("Access-Control-Allow-Origin", allowOrigin);
r.Header().Set("Access-Control-Allow-Methods", allowMethods);
r.Header().Set("Access-Control-Max-Age", strconv.Itoa(age));
r.Header().Set("Access-Control-Allow-Origin", allowOrigin)
r.Header().Set("Access-Control-Allow-Methods", allowMethods)
r.Header().Set("Access-Control-Max-Age", strconv.Itoa(age))
}
// 返回HTTP Code状态码
@ -185,7 +184,8 @@ func (r *Response) ServeFileDownload(path string, name...string) {
r.Server.serveFile(r.request, path)
}
// 返回location标识引导客户端跳转
// 返回location标识引导客户端跳转
// 注意这里要先把设置的cookie输出否则会被忽略。
func (r *Response) RedirectTo(location string) {
r.Header().Set("Location", location)
r.WriteHeader(http.StatusFound)
@ -218,10 +218,19 @@ func (r *Response) ClearBuffer() {
r.buffer.Reset()
}
// 输出缓冲区数据到客户端
// Deprecated.
//
// 输出缓冲区数据到客户端.
func (r *Response) OutputBuffer() {
r.Header().Set("Server", r.Server.config.ServerAgent)
//r.handleGzip()
r.Writer.OutputBuffer()
}
// 输出缓冲区数据到客户端.
func (r *Response) Output() {
r.Header().Set("Server", r.Server.config.ServerAgent)
//r.handleGzip()
r.Writer.OutputBuffer()
}

View File

@ -0,0 +1,62 @@
// Copyright 2019 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 (
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
)
// See https://www.w3.org/TR/cors/ .
// 服务端允许跨域请求选项
type CORSOptions struct {
AllowOrigin string // Access-Control-Allow-Origin
AllowCredentials string // Access-Control-Allow-Credentials
ExposeHeaders string // Access-Control-Expose-Headers
MaxAge int // Access-Control-Max-Age
AllowMethods string // Access-Control-Allow-Methods
AllowHeaders string // Access-Control-Allow-Headers
}
// 默认的CORS配置
func (r *Response) DefaultCORSOptions() CORSOptions {
return CORSOptions {
AllowOrigin : gstr.TrimRight(r.request.Referer(), "/"),
AllowMethods : HTTP_METHODS,
AllowCredentials : "true",
MaxAge : 3628800,
}
}
// See https://www.w3.org/TR/cors/ .
// 允许请求跨域访问.
func (r *Response) CORS(options CORSOptions) {
if options.AllowOrigin != "" {
r.Header().Set("Access-Control-Allow-Origin", options.AllowOrigin)
}
if options.AllowCredentials != "" {
r.Header().Set("Access-Control-Allow-Credentials", options.AllowCredentials)
}
if options.ExposeHeaders != "" {
r.Header().Set("Access-Control-Expose-Headers", options.ExposeHeaders)
}
if options.MaxAge != 0 {
r.Header().Set("Access-Control-Max-Age", gconv.String(options.MaxAge))
}
if options.AllowMethods != "" {
r.Header().Set("Access-Control-Allow-Methods", options.AllowMethods)
}
if options.AllowHeaders != "" {
r.Header().Set("Access-Control-Allow-Headers", options.AllowHeaders)
}
}
// 允许请求跨域访问(使用more配置).
func (r *Response) CORSDefault() {
r.CORS(r.DefaultCORSOptions())
}

View File

@ -13,10 +13,10 @@ import (
)
// 展示模板,可以给定模板参数,及临时的自定义模板函数
func (r *Response) WriteTpl(tpl string, params map[string]interface{}, funcmap...map[string]interface{}) error {
func (r *Response) WriteTpl(tpl string, params map[string]interface{}, funcMap...map[string]interface{}) error {
fmap := make(gview.FuncMap)
if len(funcmap) > 0 {
fmap = funcmap[0]
if len(funcMap) > 0 {
fmap = funcMap[0]
}
if b, err := r.ParseTpl(tpl, params, fmap); err != nil {
r.Write("Tpl Parsing Error: " + err.Error())
@ -28,10 +28,10 @@ func (r *Response) WriteTpl(tpl string, params map[string]interface{}, funcmap..
}
// 展示模板内容,可以给定模板参数,及临时的自定义模板函数
func (r *Response) WriteTplContent(content string, params map[string]interface{}, funcmap...map[string]interface{}) error {
func (r *Response) WriteTplContent(content string, params map[string]interface{}, funcMap...map[string]interface{}) error {
fmap := make(gview.FuncMap)
if len(funcmap) > 0 {
fmap = funcmap[0]
if len(funcMap) > 0 {
fmap = funcMap[0]
}
if b, err := r.ParseTplContent(content, params, fmap); err != nil {
r.Write("Tpl Parsing Error: " + err.Error())
@ -43,19 +43,19 @@ func (r *Response) WriteTplContent(content string, params map[string]interface{}
}
// 解析模板文件,并返回模板内容
func (r *Response) ParseTpl(tpl string, params gview.Params, funcmap...map[string]interface{}) ([]byte, error) {
func (r *Response) ParseTpl(tpl string, params gview.Params, funcMap...map[string]interface{}) ([]byte, error) {
fmap := make(gview.FuncMap)
if len(funcmap) > 0 {
fmap = funcmap[0]
if len(funcMap) > 0 {
fmap = funcMap[0]
}
return gins.View().Parse(tpl, r.buildInVars(params), r.buildInFuncs(fmap))
}
// 解析并返回模板内容
func (r *Response) ParseTplContent(content string, params gview.Params, funcmap...map[string]interface{}) ([]byte, error) {
func (r *Response) ParseTplContent(content string, params gview.Params, funcMap...map[string]interface{}) ([]byte, error) {
fmap := make(gview.FuncMap)
if len(funcmap) > 0 {
fmap = funcmap[0]
if len(funcMap) > 0 {
fmap = funcMap[0]
}
return gins.View().ParseContent(content, r.buildInVars(params), r.buildInFuncs(fmap))
}
@ -77,14 +77,14 @@ func (r *Response) buildInVars(params map[string]interface{}) map[string]interfa
}
// 内置函数
func (r *Response) buildInFuncs(funcmap map[string]interface{}) map[string]interface{} {
if funcmap == nil {
funcmap = make(map[string]interface{})
func (r *Response) buildInFuncs(funcMap map[string]interface{}) map[string]interface{} {
if funcMap == nil {
funcMap = make(map[string]interface{})
}
funcmap["get"] = r.funcGet
funcmap["post"] = r.funcPost
funcmap["request"] = r.funcRequest
return funcmap
funcMap["get"] = r.funcGet
funcMap["post"] = r.funcPost
funcMap["request"] = r.funcRequest
return funcMap
}
// 模板内置函数: get

View File

@ -25,16 +25,19 @@ func (w *ResponseWriter) Write(data []byte) (int, error) {
return len(data), nil
}
// 覆盖父级的WriteHeader方法
func (w *ResponseWriter) WriteHeader(code int) {
w.Status = code
w.ResponseWriter.WriteHeader(code)
// 覆盖父级的WriteHeader方法, 这里只会记录Status做缓冲处理, 并不会立即输出到HEADER。
func (w *ResponseWriter) WriteHeader(status int) {
w.Status = status
}
// 输出buffer数据到客户端
// 输出buffer数据到客户端.
func (w *ResponseWriter) OutputBuffer() {
if w.Status != 0 {
w.ResponseWriter.WriteHeader(w.Status)
}
if w.buffer.Len() > 0 {
w.ResponseWriter.Write(w.buffer.Bytes())
w.buffer.Reset()
}
}

View File

@ -19,8 +19,8 @@ import (
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gproc"
"github.com/gogf/gf/g/os/gtimer"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/third/github.com/gorilla/websocket"
"github.com/gogf/gf/third/github.com/olekukonko/tablewriter"
"net/http"
@ -39,7 +39,8 @@ type (
name string // 服务名称,方便识别
config ServerConfig // 配置对象
servers []*gracefulServer // 底层http.Server列表
methodsMap map[string]struct{} // 所有支持的HTTP Method(初始化时自动填充)
serverCount *gtype.Int // 底层http.Server数量
closeChan chan struct{} // 用以关闭事件通知的通道
servedCount *gtype.Int // 已经服务的请求数(4-8字节不考虑溢出情况)同时作为请求ID
// 服务注册相关
serveTree map[string]interface{} // 所有注册的服务回调函数(路由表,树型结构,哈希表+链表优先级匹配)
@ -101,8 +102,8 @@ type (
)
const (
SERVER_STATUS_STOPPED = 0 // Server状态停止
SERVER_STATUS_RUNNING = 1 // Server状态运行
SERVER_STATUS_STOPPED = 0 // Server状态停止
SERVER_STATUS_RUNNING = 1 // Server状态运行
HOOK_BEFORE_SERVE = "BeforeServe"
HOOK_AFTER_SERVE = "AfterServe"
HOOK_BEFORE_OUTPUT = "BeforeOutput"
@ -110,7 +111,7 @@ const (
HOOK_BEFORE_CLOSE = "BeforeClose"
HOOK_AFTER_CLOSE = "AfterClose"
gHTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
HTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
gDEFAULT_SERVER = "default"
gDEFAULT_DOMAIN = "default"
gDEFAULT_METHOD = "ALL"
@ -123,29 +124,39 @@ const (
)
var (
// Server表用以存储和检索名称与Server对象之间的关联关系
// 所有支持的HTTP Method Map(初始化时自动填充),
// 用于快速检索需要
methodsMap = make(map[string]struct{})
// WebServer表用以存储和检索名称与Server对象之间的关联关系
serverMapping = gmap.NewStringInterfaceMap()
// 正常运行的Server数量如果没有运行、失败或者全部退出那么该值为0
// 正常运行的WebServer数量如果没有运行、失败或者全部退出那么该值为0
serverRunning = gtype.NewInt()
// Web Socket默认配置
// WebSocket默认配置
wsUpgrader = websocket.Upgrader {
// 默认允许WebSocket请求跨域权限控制可以由业务层自己负责灵活度更高
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// Web Server已完成服务事件通道当有事件时表示服务完成当前进程退出
doneChan = make(chan struct{}, 1000)
// WebServer已完成服务事件通道当有事件时表示服务完成当前进程退出
allDoneChan = make(chan struct{}, 1000)
// 用于服务进程初始化,只能初始化一次,采用“懒初始化”(在server运行时才初始化)
serverProcessInited = gtype.NewBool()
// 是否开启WebServer平滑重启特性, 会开启额外的本地端口监听,用于进程管理通信
// 是否开启WebServer平滑重启特性, 会开启额外的本地端口监听,用于进程管理通信(默认开启)
gracefulEnabled = true
)
func init() {
for _, v := range strings.Split(HTTP_METHODS, ",") {
methodsMap[v] = struct{}{}
}
}
// 是否开启平滑重启特性
func SetGraceful(enabled bool) {
gracefulEnabled = enabled
@ -174,6 +185,11 @@ func serverProcessInit() {
if gracefulEnabled {
go handleProcessMessage()
}
// 是否处于开发环境
if gfile.MainPkgPath() != "" {
glog.Debug("GF notices that you're in develop environment, so error logs are auto enabled to stdout.")
}
}
// 获取/创建一个默认配置的HTTP Server(默认监听端口是80)
@ -189,7 +205,8 @@ func GetServer(name...interface{}) (*Server) {
s := &Server {
name : sname,
servers : make([]*gracefulServer, 0),
methodsMap : make(map[string]struct{}),
closeChan : make(chan struct{}, 100),
serverCount : gtype.NewInt(),
statusHandlerMap : make(map[string]HandlerFunc),
serveTree : make(map[string]interface{}),
hooksTree : make(map[string]interface{}),
@ -202,9 +219,6 @@ func GetServer(name...interface{}) (*Server) {
}
// 日志的标准输出默认关闭,但是错误信息会特殊处理
s.logger.SetStdPrint(false)
for _, v := range strings.Split(gHTTP_METHODS, ",") {
s.methodsMap[v] = struct{}{}
}
// 初始化时使用默认配置
s.SetConfig(defaultServerConfig)
// 记录到全局ServerMap中
@ -212,8 +226,8 @@ func GetServer(name...interface{}) (*Server) {
return s
}
// 作为守护协程异步执行(当同一进程中存在多个Web Server时需要采用这种方式执行)
// 需要结合Wait方式一起使用
// 作为守护协程异步执行(当同一进程中存在多个Web Server时需要采用这种方式执行),
// 需要结合Wait方式一起使用.
func (s *Server) Start() error {
// 服务进程初始化,只会初始化一次
serverProcessInit()
@ -228,11 +242,12 @@ func (s *Server) Start() error {
s.config.Handler = http.HandlerFunc(s.defaultHttpHandle)
}
// 不允许访问的路由注册(使用HOOK实现)
// @TODO 去掉HOOK的实现方式
if s.config.DenyRoutes != nil {
for _, v := range s.config.DenyRoutes {
s.BindHookHandler(v, HOOK_BEFORE_SERVE, func(r *Request) {
r.Response.WriteStatus(403)
r.Exit()
r.ExitAll()
})
}
}
@ -266,10 +281,6 @@ func (s *Server) Start() error {
}
})
}
// 是否处于开发环境
if gfile.MainPkgPath() != "" {
glog.Debug("GF notices that you're in develop environment, so error logs are auto enabled to stdout.")
}
// 打印展示路由表
s.DumpRoutesMap()
@ -367,7 +378,7 @@ func (s *Server) Run() error {
return err
}
// 阻塞等待服务执行完成
<- doneChan
<- s.closeChan
glog.Printfln("%d: all servers shutdown", gproc.Pid())
return nil
@ -378,7 +389,7 @@ func (s *Server) Run() error {
// 这是一个与进程相关的方法
func Wait() {
// 阻塞等待服务执行完成
<- doneChan
<- allDoneChan
glog.Printfln("%d: all servers shutdown", gproc.Pid())
}
@ -462,23 +473,28 @@ func (s *Server) startServer(fdMap listenerFdMap) {
}
}
// 开始执行异步监听
serverRunning.Add(1)
for _, v := range s.servers {
go func(server *gracefulServer) {
serverRunning.Add(1)
s.serverCount.Add(1)
err := (error)(nil)
if server.isHttps {
err = server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath)
} else {
err = server.ListenAndServe()
}
serverRunning.Add(-1)
// 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作
if err != nil && !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) {
glog.Fatal(err)
}
// 如果所有异步的Server都已经停止并且没有在管理操作(重启/关闭)进行中,那么主Server就可以退出了
if serverRunning.Val() < 1 && serverProcessStatus.Val() == 0 {
doneChan <- struct{}{}
// 如果所有异步的http.Server都已经停止那么WebServer就可以退出了
if s.serverCount.Add(-1) < 1 {
s.closeChan <- struct{}{}
// 如果所有WebServer都退出那么退出Wait等待
if serverRunning.Add(-1) < 1 {
serverMapping.Remove(s.name)
allDoneChan <- struct{}{}
}
}
}(v)
}

View File

@ -8,46 +8,14 @@
package ghttp
import (
"github.com/gogf/gf/g/os/gtimer"
"strings"
"github.com/gogf/gf/g/os/gview"
"github.com/gogf/gf/g/os/gproc"
"sync"
"github.com/gogf/gf/g/os/gtime"
"errors"
"fmt"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gtimer"
"github.com/gogf/gf/g/os/gview"
"os"
"github.com/gogf/gf/g/encoding/gjson"
"github.com/gogf/gf/g/util/gconv"
"strings"
"time"
"runtime"
"bytes"
)
const (
gADMIN_ACTION_INTERVAL_LIMIT = 2000 // (毫秒)服务开启后允许执行管理操作的间隔限制
gADMIN_ACTION_NONE = 0
gADMIN_ACTION_RESTARTING = 1
gADMIN_ACTION_SHUTINGDOWN = 2
gADMIN_ACTION_RELOAD_ENVKEY = "GF_SERVER_RELOAD"
gADMIN_ACTION_RESTART_ENVKEY = "GF_SERVER_RESTART"
gADMIN_GPROC_COMM_GROUP = "GF_GPROC_HTTP_SERVER"
)
// 用于服务管理的对象
type utilAdmin struct {}
// (进程级别)用于Web Server管理操作的互斥锁保证管理操作的原子性
var serverActionLocker sync.Mutex
// (进程级别)用于记录上一次操作的时间(毫秒)
var serverActionLastTime = gtype.NewInt64(gtime.Millisecond())
// 当前服务进程所处的互斥管理操作状态
var serverProcessStatus = gtype.NewInt()
// 服务管理首页
func (p *utilAdmin) Index(r *Request) {
data := map[string]interface{}{
@ -79,9 +47,9 @@ func (p *utilAdmin) Restart(r *Request) {
}
// 执行重启操作
if len(path) > 0 {
err = r.Server.Restart(path)
err = RestartAllServer(path)
} else {
err = r.Server.Restart()
err = RestartAllServer()
}
if err == nil {
r.Response.Write("server restarted")
@ -93,7 +61,7 @@ func (p *utilAdmin) Restart(r *Request) {
// 服务关闭
func (p *utilAdmin) Shutdown(r *Request) {
r.Server.Shutdown()
if err := r.Server.Shutdown(); err == nil {
if err := ShutdownAllServer(); err == nil {
r.Response.Write("server shutdown")
} else {
r.Response.Write(err.Error())
@ -109,222 +77,15 @@ func (s *Server) EnableAdmin(pattern...string) {
s.BindObject(p, &utilAdmin{})
}
// 重启Web Server,参数支持自定义重启的可执行文件路径,不传递时默认和原有可执行文件路径一致。
// 针对*niux系统: 平滑重启
// 针对windows : 完整重启
func (s *Server) Restart(newExeFilePath...string) error {
serverActionLocker.Lock()
defer serverActionLocker.Unlock()
if err := s.checkActionStatus(); err != nil {
return err
}
if err := s.checkActionFrequence(); err != nil {
return err
}
return restartWebServers("", newExeFilePath...)
}
// 关闭Web Server
// 关闭当前Web Server
func (s *Server) Shutdown() error {
serverActionLocker.Lock()
defer serverActionLocker.Unlock()
if err := s.checkActionStatus(); err != nil {
return err
}
if err := s.checkActionFrequence(); err != nil {
return err
}
shutdownWebServers("")
return nil
}
// 检测当前操作的频繁度
func (s *Server) checkActionFrequence() error {
interval := gtime.Millisecond() - 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))
}
serverActionLastTime.Set(gtime.Millisecond())
return nil
}
// 检查当前服务进程的状态
func (s *Server) checkActionStatus() error {
status := serverProcessStatus.Val()
if status > 0 {
switch status {
case gADMIN_ACTION_RESTARTING: return errors.New("server is restarting")
case gADMIN_ACTION_SHUTINGDOWN: return errors.New("server is shutting down")
}
}
return nil
}
// 平滑重启:创建一个子进程,通过环境变量传参
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
for name, m := range sfm {
for fdk, fdv := range m {
if len(fdv) > 0 {
s := ""
for _, item := range strings.Split(fdv, ",") {
array := strings.Split(item, "#")
fd := uintptr(gconv.Uint(array[1]))
if fd > 0 {
s += fmt.Sprintf("%s#%d,", array[0], 3 + len(p.ExtraFiles))
p.ExtraFiles = append(p.ExtraFiles, os.NewFile(fd, ""))
} else {
s += fmt.Sprintf("%s#%d,", array[0], 0)
}
}
sfm[name][fdk] = strings.TrimRight(s, ",")
}
}
}
buffer, _ := gjson.Encode(sfm)
p.Env = append(p.Env, gADMIN_ACTION_RELOAD_ENVKEY + "=" + string(buffer))
if _, err := p.Start(); err != nil {
glog.Errorfln("%d: fork process failed, error:%s, %s", gproc.Pid(), err.Error(), string(buffer))
return err
}
return nil
}
// 完整重启:创建一个新的子进程
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")
p := gproc.NewProcess(path, os.Args, env)
if _, err := p.Start(); err != nil {
glog.Errorfln("%d: fork process failed, error:%s", gproc.Pid(), err.Error())
return err
}
return nil
}
// 获取所有Web Server的文件描述符map
func getServerFdMap() map[string]listenerFdMap {
sfm := make(map[string]listenerFdMap)
serverMapping.RLockFunc(func(m map[string]interface{}) {
for k, v := range m {
sfm[k] = v.(*Server).getListenerFdMap()
// 非终端信号下异步1秒后再执行关闭
// 目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了)
gtimer.SetTimeout(time.Second, func() {
// 只关闭当前的Web Server
for _, v := range s.servers {
v.close()
}
})
return sfm
}
// 二进制转换为FdMap
func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap {
sfm := make(map[string]listenerFdMap)
if len(buffer) > 0 {
j, _ := gjson.LoadContent(buffer, "json")
for k, _ := range j.ToMap() {
m := make(map[string]string)
for k, v := range j.GetMap(k) {
m[k] = gconv.String(v)
}
sfm[k] = m
}
}
return sfm
}
// Web Server重启
func restartWebServers(signal string, newExeFilePath...string) error {
serverProcessStatus.Set(gADMIN_ACTION_RESTARTING)
if runtime.GOOS == "windows" {
if len(signal) > 0 {
// 在终端信号下,立即执行重启操作
forcedlyCloseWebServers()
forkRestartProcess(newExeFilePath...)
} else {
// 非终端信号下异步1秒后再执行重启目的是让接口能够正确返回结果否则接口会报错(因为web server关闭了)
gtimer.SetTimeout(time.Second, func() {
forcedlyCloseWebServers()
forkRestartProcess(newExeFilePath...)
})
}
} else {
if err := forkReloadProcess(newExeFilePath...); err != nil {
glog.Printfln("%d: server restarts failed", gproc.Pid())
serverProcessStatus.Set(gADMIN_ACTION_NONE)
return err
} else {
if len(signal) > 0 {
glog.Printfln("%d: server restarting by signal: %s", gproc.Pid(), signal)
} else {
glog.Printfln("%d: server restarting by web admin", gproc.Pid())
}
}
}
return nil
}
// Web Server关闭服务
func shutdownWebServers(signal string) {
serverProcessStatus.Set(gADMIN_ACTION_SHUTINGDOWN)
if len(signal) > 0 {
glog.Printfln("%d: server shutting down by signal: %s", gproc.Pid(), signal)
// 在终端信号下,立即执行关闭操作
forcedlyCloseWebServers()
doneChan <- struct{}{}
} else {
glog.Printfln("%d: server shutting down by web admin", gproc.Pid())
// 非终端信号下异步1秒后再执行关闭目的是让接口能够正确返回结果否则接口会报错(因为web server关闭了)
gtimer.SetTimeout(time.Second, func() {
forcedlyCloseWebServers()
doneChan <- struct{}{}
})
}
}
// 关优雅闭进程所有端口的Web Server服务
// 注意只是关闭Web Server服务并不是退出进程
func gracefulShutdownWebServers() {
serverMapping.RLockFunc(func(m map[string]interface{}) {
for _, v := range m {
for _, s := range v.(*Server).servers {
s.shutdown()
}
}
})
}
// 强制关闭进程所有端口的Web Server服务
// 注意只是关闭Web Server服务并不是退出进程
func forcedlyCloseWebServers() {
serverMapping.RLockFunc(func(m map[string]interface{}) {
for _, v := range m {
for _, s := range v.(*Server).servers {
s.close()
}
}
})
}
// 异步监听进程间消息
func handleProcessMessage() {
for {
if msg := gproc.Receive(gADMIN_GPROC_COMM_GROUP); msg != nil {
if bytes.EqualFold(msg.Data, []byte("exit")) {
gracefulShutdownWebServers()
doneChan <- struct{}{}
return
}
}
}
}

View File

@ -0,0 +1,269 @@
// 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.
// pprof封装.
package ghttp
import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/encoding/gjson"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gproc"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/os/gtimer"
"github.com/gogf/gf/g/util/gconv"
"os"
"runtime"
"strings"
"sync"
"time"
)
const (
gADMIN_ACTION_INTERVAL_LIMIT = 2000 // (毫秒)服务开启后允许执行管理操作的间隔限制
gADMIN_ACTION_NONE = 0
gADMIN_ACTION_RESTARTING = 1
gADMIN_ACTION_SHUTINGDOWN = 2
gADMIN_ACTION_RELOAD_ENVKEY = "GF_SERVER_RELOAD"
gADMIN_ACTION_RESTART_ENVKEY = "GF_SERVER_RESTART"
gADMIN_GPROC_COMM_GROUP = "GF_GPROC_HTTP_SERVER"
)
// 用于服务管理的对象
type utilAdmin struct {}
// (进程级别)用于Web Server管理操作的互斥锁保证管理操作的原子性
var serverActionLocker sync.Mutex
// (进程级别)用于记录上一次操作的时间(毫秒)
var serverActionLastTime = gtype.NewInt64(gtime.Millisecond())
// 当前服务进程所处的互斥管理操作状态
var serverProcessStatus = gtype.NewInt()
// 重启Web Server参数支持自定义重启的可执行文件路径不传递时默认和原有可执行文件路径一致。
// 针对*niux系统: 平滑重启
// 针对windows : 完整重启
func RestartAllServer(newExeFilePath...string) error {
serverActionLocker.Lock()
defer serverActionLocker.Unlock()
if err := checkProcessStatus(); err != nil {
return err
}
if err := checkActionFrequence(); err != nil {
return err
}
return restartWebServers("", newExeFilePath...)
}
// 关闭所有的WebServer
func ShutdownAllServer() error {
serverActionLocker.Lock()
defer serverActionLocker.Unlock()
if err := checkProcessStatus(); err != nil {
return err
}
if err := checkActionFrequence(); err != nil {
return err
}
shutdownWebServers()
return nil
}
// 检查当前服务进程的状态
func checkProcessStatus() error {
status := serverProcessStatus.Val()
if status > 0 {
switch status {
case gADMIN_ACTION_RESTARTING: return errors.New("server is restarting")
case gADMIN_ACTION_SHUTINGDOWN: return errors.New("server is shutting down")
}
}
return nil
}
// 检测当前操作的频繁度
func checkActionFrequence() error {
interval := gtime.Millisecond() - 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))
}
serverActionLastTime.Set(gtime.Millisecond())
return nil
}
// 平滑重启:创建一个子进程,通过环境变量传参
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
for name, m := range sfm {
for fdk, fdv := range m {
if len(fdv) > 0 {
s := ""
for _, item := range strings.Split(fdv, ",") {
array := strings.Split(item, "#")
fd := uintptr(gconv.Uint(array[1]))
if fd > 0 {
s += fmt.Sprintf("%s#%d,", array[0], 3 + len(p.ExtraFiles))
p.ExtraFiles = append(p.ExtraFiles, os.NewFile(fd, ""))
} else {
s += fmt.Sprintf("%s#%d,", array[0], 0)
}
}
sfm[name][fdk] = strings.TrimRight(s, ",")
}
}
}
buffer, _ := gjson.Encode(sfm)
p.Env = append(p.Env, gADMIN_ACTION_RELOAD_ENVKEY + "=" + string(buffer))
if _, err := p.Start(); err != nil {
glog.Errorfln("%d: fork process failed, error:%s, %s", gproc.Pid(), err.Error(), string(buffer))
return err
}
return nil
}
// 完整重启:创建一个新的子进程
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")
p := gproc.NewProcess(path, os.Args, env)
if _, err := p.Start(); err != nil {
glog.Errorfln("%d: fork process failed, error:%s", gproc.Pid(), err.Error())
return err
}
return nil
}
// 获取所有Web Server的文件描述符map
func getServerFdMap() map[string]listenerFdMap {
sfm := make(map[string]listenerFdMap)
serverMapping.RLockFunc(func(m map[string]interface{}) {
for k, v := range m {
sfm[k] = v.(*Server).getListenerFdMap()
}
})
return sfm
}
// 二进制转换为FdMap
func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap {
sfm := make(map[string]listenerFdMap)
if len(buffer) > 0 {
j, _ := gjson.LoadContent(buffer, "json")
for k, _ := range j.ToMap() {
m := make(map[string]string)
for k, v := range j.GetMap(k) {
m[k] = gconv.String(v)
}
sfm[k] = m
}
}
return sfm
}
// Web Server重启
func restartWebServers(signal string, newExeFilePath...string) error {
serverProcessStatus.Set(gADMIN_ACTION_RESTARTING)
if runtime.GOOS == "windows" {
if len(signal) > 0 {
// 在终端信号下,立即执行重启操作
forceCloseWebServers()
forkRestartProcess(newExeFilePath...)
} else {
// 非终端信号下异步1秒后再执行重启目的是让接口能够正确返回结果否则接口会报错(因为web server关闭了)
gtimer.SetTimeout(time.Second, func() {
forceCloseWebServers()
forkRestartProcess(newExeFilePath...)
})
}
} else {
if err := forkReloadProcess(newExeFilePath...); err != nil {
glog.Printfln("%d: server restarts failed", gproc.Pid())
serverProcessStatus.Set(gADMIN_ACTION_NONE)
return err
} else {
if len(signal) > 0 {
glog.Printfln("%d: server restarting by signal: %s", gproc.Pid(), signal)
} else {
glog.Printfln("%d: server restarting by web admin", gproc.Pid())
}
}
}
return nil
}
// 关闭所有Web Server
func shutdownWebServers(signal...string) {
serverProcessStatus.Set(gADMIN_ACTION_SHUTINGDOWN)
if len(signal) > 0 {
glog.Printfln("%d: server shutting down by signal: %s", gproc.Pid(), signal[0])
// 在终端信号下,立即执行关闭操作
forceCloseWebServers()
allDoneChan <- struct{}{}
} else {
glog.Printfln("%d: server shutting down by api", gproc.Pid())
// 非终端信号下异步1秒后再执行关闭
// 目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了)
gtimer.SetTimeout(time.Second, func() {
forceCloseWebServers()
allDoneChan <- struct{}{}
})
}
}
// 关优雅闭进程所有端口的Web Server服务
// 注意只是关闭Web Server服务并不是退出进程
func gracefulShutdownWebServers() {
serverMapping.RLockFunc(func(m map[string]interface{}) {
for _, v := range m {
for _, s := range v.(*Server).servers {
s.shutdown()
}
}
})
}
// 强制关闭进程所有端口的Web Server服务
// 注意只是关闭Web Server服务并不是退出进程
func forceCloseWebServers() {
serverMapping.RLockFunc(func(m map[string]interface{}) {
for _, v := range m {
for _, s := range v.(*Server).servers {
s.close()
}
}
})
}
// 异步监听进程间消息
func handleProcessMessage() {
for {
if msg := gproc.Receive(gADMIN_GPROC_COMM_GROUP); msg != nil {
if bytes.EqualFold(msg.Data, []byte("exit")) {
gracefulShutdownWebServers()
allDoneChan <- struct{}{}
return
}
}
}
}

View File

@ -24,7 +24,7 @@ const (
NAME_TO_URI_TYPE_CAMEL = 3 // 采用驼峰命名方式
gDEFAULT_COOKIE_PATH = "/" // 默认path
gDEFAULT_COOKIE_MAX_AGE = 86400*365 // 默认cookie有效期(一年)
gDEFAULT_SESSION_MAX_AGE = 600 // 默认session有效期(600秒)
gDEFAULT_SESSION_MAX_AGE = 600000 // 默认session有效期(600秒)
gDEFAULT_SESSION_ID_NAME = "gfsessionid" // 默认存放Cookie中的SessionId名称
gCHANGE_CONFIG_WHILE_RUNNING_ERROR = "cannot be changed while running"
)

View File

@ -64,6 +64,7 @@ func (s *Server)SetServerRoot(root string) {
if path == "" {
glog.Fatal(fmt.Sprintf(`[ghttp] SetServerRoot failed: path "%s" does not exist`, root))
}
glog.Debug("[ghttp] SetServerRoot path:", path)
s.config.SearchPaths = []string{strings.TrimRight(path, gfile.Separator)}
s.config.FileServerEnabled = true
}

View File

@ -5,7 +5,7 @@
// You can obtain one at https://github.com/gogf/gf.
//
// HTTP Cookie管理对象
// 由于Cookie是和HTTP请求挂钩的因此被包含到 ghttp 包中进行管理
// 由于Cookie是和HTTP请求挂钩的因此被包含到 ghttp 包中进行管理
package ghttp
@ -87,6 +87,14 @@ func (c *Cookie) SessionId() string {
return id
}
// 获取SessionId不存在时则创建
func (c *Cookie) MakeSessionId() string {
c.init()
id := makeSessionId()
c.SetSessionId(id)
return id
}
// 判断Cookie中是否存在制定键名(并且没有过期)
func (c *Cookie) Contains(key string) bool {
c.init()

View File

@ -9,7 +9,6 @@ package ghttp
import (
"strings"
"github.com/gogf/gf/g/container/gmap"
)
// 域名管理器对象
@ -18,121 +17,80 @@ type Domain struct {
m map[string]bool // 多域名
}
// 域名对象表,用以存储和检索域名(支持多域名)与域名对象之间的关联关系
var domainMap = gmap.NewStringInterfaceMap()
// 生成一个域名对象
// 生成一个域名对象, 参数 domains 支持给定多个域名。
func (s *Server) Domain(domains string) *Domain {
if r := domainMap.Get(domains); r != nil {
return r.(*Domain)
}
d := &Domain{
s : s,
m : make(map[string]bool),
}
result := strings.Split(domains, ",")
for _, v := range result {
for _, v := range strings.Split(domains, ",") {
d.m[strings.TrimSpace(v)] = true
}
domainMap.Set(domains, d)
return d
}
// 注意该方法是直接绑定方法的内存地址,执行的时候直接执行该方法,不会存在初始化新的控制器逻辑
func (d *Domain) BindHandler(pattern string, handler HandlerFunc) error {
func (d *Domain) BindHandler(pattern string, handler HandlerFunc) {
for domain, _ := range d.m {
if err := d.s.BindHandler(pattern + "@" + domain, handler); err != nil {
return err
}
d.s.BindHandler(pattern + "@" + domain, handler)
}
return nil
}
// 执行对象方法
func (d *Domain) BindObject(pattern string, obj interface{}, methods...string) error {
if len(methods) > 0 {
return d.BindObjectMethod(pattern, obj, strings.Join(methods, ","))
}
func (d *Domain) BindObject(pattern string, obj interface{}, methods...string) {
for domain, _ := range d.m {
if err := d.s.BindObject(pattern + "@" + domain, obj); err != nil {
return err
}
d.s.BindObject(pattern + "@" + domain, obj, methods...)
}
return nil
}
// 执行对象方法注册methods参数不区分大小写
func (d *Domain) BindObjectMethod(pattern string, obj interface{}, method string) error {
func (d *Domain) BindObjectMethod(pattern string, obj interface{}, method string) {
for domain, _ := range d.m {
if err := d.s.BindObjectMethod(pattern + "@" + domain, obj, method); err != nil {
return err
}
d.s.BindObjectMethod(pattern + "@" + domain, obj, method)
}
return nil
}
// RESTful执行对象注册
func (d *Domain) BindObjectRest(pattern string, obj interface{}) error {
func (d *Domain) BindObjectRest(pattern string, obj interface{}) {
for domain, _ := range d.m {
if err := d.s.BindObjectRest(pattern + "@" + domain, obj); err != nil {
return err
}
d.s.BindObjectRest(pattern + "@" + domain, obj)
}
return nil
}
// 控制器注册
func (d *Domain) BindController(pattern string, c Controller, methods...string) error {
if len(methods) > 0 {
return d.BindControllerMethod(pattern, c, strings.Join(methods, ","))
}
func (d *Domain) BindController(pattern string, c Controller, methods...string) {
for domain, _ := range d.m {
if err := d.s.BindController(pattern + "@" + domain, c); err != nil {
return err
}
d.s.BindController(pattern + "@" + domain, c, methods...)
}
return nil
}
// 控制器方法注册methods参数区分大小写
func (d *Domain) BindControllerMethod(pattern string, c Controller, method string) error {
func (d *Domain) BindControllerMethod(pattern string, c Controller, method string) {
for domain, _ := range d.m {
if err := d.s.BindControllerMethod(pattern + "@" + domain, c, method); err != nil {
return err
}
d.s.BindControllerMethod(pattern + "@" + domain, c, method)
}
return nil
}
// RESTful控制器注册
func (d *Domain) BindControllerRest(pattern string, c Controller) error {
func (d *Domain) BindControllerRest(pattern string, c Controller) {
for domain, _ := range d.m {
if err := d.s.BindControllerRest(pattern + "@" + domain, c); err != nil {
return err
}
d.s.BindControllerRest(pattern + "@" + domain, c)
}
return nil
}
// 绑定指定的hook回调函数, hook参数的值由ghttp server设定参数不区分大小写
// 目前hook支持Init/Shut
func (d *Domain)BindHookHandler(pattern string, hook string, handler HandlerFunc) error {
func (d *Domain)BindHookHandler(pattern string, hook string, handler HandlerFunc) {
for domain, _ := range d.m {
if err := d.s.BindHookHandler(pattern + "@" + domain, hook, handler); err != nil {
return err
}
d.s.BindHookHandler(pattern + "@" + domain, hook, handler)
}
return nil
}
// 通过map批量绑定回调函数
func (d *Domain)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error {
func (d *Domain)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) {
for domain, _ := range d.m {
if err := d.s.BindHookHandlerByMap(pattern + "@" + domain, hookmap); err != nil {
return err
}
d.s.BindHookHandlerByMap(pattern + "@" + domain, hookmap)
}
return nil
}
// 绑定指定的状态码回调函数

View File

@ -24,7 +24,7 @@ func (s *Server)defaultHttpHandle(w http.ResponseWriter, r *http.Request) {
s.handleRequest(w, r)
}
// 执行处理HTTP请求
// 执行处理HTTP请求
// 首先,查找是否有对应域名的处理接口配置;
// 其次,如果没有对应的自定义处理接口配置,那么走默认的域名处理接口配置;
// 最后,如果以上都没有找到处理接口,那么进行文件处理;
@ -55,12 +55,11 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
// 输出Cookie
request.Cookie.Output()
// 输出缓冲区
request.Response.OutputBuffer()
request.Response.Output()
// 事件 - AfterOutput
if !request.IsExited() {
s.callHookHandler(HOOK_AFTER_OUTPUT, request)
}
// 事件 - BeforeClose
s.callHookHandler(HOOK_BEFORE_CLOSE, request)
// access log
@ -92,7 +91,7 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
// 动态服务检索
handler := (*handlerItem)(nil)
if !request.IsFileRequest() || isStaticDir {
if !request.isFileRequest || isStaticDir {
if parsedItem := s.getServeHandlerWithCache(request); parsedItem != nil {
handler = parsedItem.handler
for k, v := range parsedItem.values {
@ -112,9 +111,9 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
// 执行静态文件服务/回调控制器/执行对象/方法
if !request.IsExited() {
// 需要再次判断文件是否真实存在,因为文件检索可能使用了缓存,从健壮性考虑这里需要二次判断
// 需要再次判断文件是否真实存在,
// 因为文件检索可能使用了缓存,从健壮性考虑这里需要二次判断
if request.isFileRequest /* && gfile.Exists(staticFile) */{
// 静态文件
s.serveFile(request, staticFile)
} else {
if handler != nil {

View File

@ -60,10 +60,11 @@ func (s *Server) getHandlerRegisterCallerLine(handler *handlerItem) string {
// 路由注册处理方法。
// 如果带有hook参数表示是回调注册方法; 否则为普通路由执行方法。
func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... string) (resultErr error) {
func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... string) {
// Web Server正常运行时无法动态注册路由方法
if s.Status() == SERVER_STATUS_RUNNING {
return errors.New("cannot bind handler while server running")
glog.Error("cannot bind handler while server running")
return
}
var hookName string
if len(hook) > 0 {
@ -71,29 +72,22 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin
}
domain, method, uri, err := s.parsePattern(pattern)
if err != nil {
return errors.New("invalid pattern")
glog.Error("invalid pattern:", pattern)
return
}
if len(uri) == 0 || uri[0] != '/' {
glog.Error("invalid pattern:", pattern)
return
}
// 注册地址记录及重复注册判断
regkey := s.handlerKey(hookName, method, uri, domain)
caller := s.getHandlerRegisterCallerLine(handler)
if len(hook) == 0 {
if item, ok := s.routesMap[regkey]; ok {
s := fmt.Sprintf(`duplicated route registry "%s", already registered in %s`, pattern, item[0].file)
glog.Errorfln(s)
return errors.New(s)
glog.Errorfln(`duplicated route registry "%s", already registered in %s`, pattern, item[0].file)
return
}
}
defer func() {
if resultErr == nil {
if _, ok := s.routesMap[regkey]; !ok {
s.routesMap[regkey] = make([]registeredRouteItem, 0)
}
s.routesMap[regkey] = append(s.routesMap[regkey], registeredRouteItem {
file : caller,
handler : handler,
})
}
}()
// 路由对象
handler.router = &Router {
@ -193,9 +187,15 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin
l.PushBack(handler)
}
}
//gutil.Dump(s.serveTree)
//gutil.Dump(s.hooksTree)
return nil
// gutil.Dump(s.serveTree)
// gutil.Dump(s.hooksTree)
if _, ok := s.routesMap[regkey]; !ok {
s.routesMap[regkey] = make([]registeredRouteItem, 0)
}
s.routesMap[regkey] = append(s.routesMap[regkey], registeredRouteItem {
file : caller,
handler : handler,
})
}
// 对比两个handlerItem的优先级需要非常注意的是注意新老对比项的参数先后顺序。

View File

@ -18,8 +18,8 @@ import (
)
// 绑定指定的hook回调函数, pattern参数同BindHandler支持命名路由hook参数的值由ghttp server设定参数不区分大小写
func (s *Server)BindHookHandler(pattern string, hook string, handler HandlerFunc) error {
return s.setHandler(pattern, &handlerItem {
func (s *Server)BindHookHandler(pattern string, hook string, handler HandlerFunc) {
s.setHandler(pattern, &handlerItem {
name : runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name(),
ctype : nil,
fname : "",
@ -28,13 +28,10 @@ func (s *Server)BindHookHandler(pattern string, hook string, handler HandlerFunc
}
// 通过map批量绑定回调函数
func (s *Server)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error {
func (s *Server)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) {
for k, v := range hookmap {
if err := s.BindHookHandler(pattern, k, v); err != nil {
return err
}
s.BindHookHandler(pattern, k, v)
}
return nil
}
// 事件回调处理,内部使用了缓存处理.

View File

@ -8,19 +8,19 @@
package ghttp
import (
"errors"
"github.com/gogf/gf/g/os/glog"
"strings"
"reflect"
"fmt"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/text/gstr"
"reflect"
"strings"
)
// 绑定控制器控制器需要实现gmvc.Controller接口
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话
// 第三个参数methods用以指定需要注册的方法支持多个方法名称多个方法以英文“,”号分隔,区分大小写
func (s *Server)BindController(pattern string, c Controller, methods...string) error {
// 绑定控制器,控制器需要实现 gmvc.Controller 接口,
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话,
// 第三个参数methods用以指定需要注册的方法支持多个方法名称多个方法以英文“,”号分隔,区分大小写.
func (s *Server)BindController(pattern string, c Controller, methods...string) {
methodMap := (map[string]bool)(nil)
if len(methods) > 0 {
methodMap = make(map[string]bool)
@ -44,11 +44,7 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) e
continue
}
if _, ok := v.Method(i).Interface().(func()); !ok {
if methodMap != nil {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
}
glog.Errorfln(`invalid method definition "%s", while "func()" is required`, v.Method(i).Type().String())
continue
}
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
@ -63,14 +59,15 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) e
fname : mname,
faddr : nil,
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
// 例如: pattern为/user, 那么会同时注册/user及/user/index,
// 这里处理新增/user路由绑定
if strings.EqualFold(mname, "Index") {
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
// 例如: pattern为/user, 那么会同时注册/user及/user/index
// 这里处理新增/user路由绑定
// 注意当pattern带有内置变量时不会自动加该路由。
if strings.EqualFold(mname, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
p := gstr.PosR(key, "/index")
k := key[0 : p] + key[p + 6 : ]
if len(k) == 0 {
k = "/"
if len(k) == 0 || k[0] == '@' {
k = "/" + k
}
m[k] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, mname),
@ -81,11 +78,11 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) e
}
}
}
return s.bindHandlerByMap(m)
s.bindHandlerByMap(m)
}
// 绑定路由到指定的方法执行
func (s *Server)BindControllerMethod(pattern string, c Controller, method string) error {
// 绑定路由到指定的方法执行, 第三个参数method仅支持一个方法注册不支持多个并且区分大小写。
func (s *Server)BindControllerMethod(pattern string, c Controller, method string) {
m := make(handlerMap)
v := reflect.ValueOf(c)
t := v.Type()
@ -93,12 +90,12 @@ func (s *Server)BindControllerMethod(pattern string, c Controller, method string
mname := strings.TrimSpace(method)
fval := v.MethodByName(mname)
if !fval.IsValid() {
return errors.New("invalid method name:" + mname)
glog.Error("invalid method name:" + mname)
return
}
if _, ok := fval.Interface().(func()); !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, fval.Type().String())
glog.Error(s)
return errors.New(s)
glog.Errorfln(`invalid method definition "%s", while "func()" is required`, fval.Type().String())
return
}
pkgPath := t.Elem().PkgPath()
pkgName := gfile.Basename(pkgPath)
@ -114,37 +111,37 @@ func (s *Server)BindControllerMethod(pattern string, c Controller, method string
fname : mname,
faddr : nil,
}
return s.bindHandlerByMap(m)
s.bindHandlerByMap(m)
}
// 绑定控制器(RESTFul)控制器需要实现gmvc.Controller接口
// 方法会识别HTTP方法并做REST绑定处理例如Post方法会绑定到HTTP POST的方法请求处理Delete方法会绑定到HTTP DELETE的方法请求处理
// 因此只会绑定HTTP Method对应的方法其他方法不会自动注册绑定
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话
func (s *Server)BindControllerRest(pattern string, c Controller) error {
func (s *Server)BindControllerRest(pattern string, c Controller) {
// 遍历控制器获取方法列表并构造成uri
m := make(handlerMap)
v := reflect.ValueOf(c)
t := v.Type()
sname := t.Elem().Name()
pkgPath := t.Elem().PkgPath()
// 如果存在与HttpMethod对应名字的方法那么绑定这些方法
for i := 0; i < v.NumMethod(); i++ {
mname := t.Method(i).Name
method := strings.ToUpper(mname)
if _, ok := s.methodsMap[method]; !ok {
if _, ok := methodsMap[method]; !ok {
continue
}
if _, ok := v.Method(i).Interface().(func()); !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
glog.Errorfln(`invalid method definition "%s", while "func()" is required`, v.Method(i).Type().String())
return
}
pkgName := gfile.Basename(pkgPath)
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if ctlName[0] == '*' {
ctlName = fmt.Sprintf(`(%s)`, ctlName)
}
key := mname + ":" + pattern
key := s.mergeBuildInNameToPattern(mname + ":" + pattern, sname, mname, false)
m[key] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, mname),
rtype : gROUTE_REGISTER_CONTROLLER,
@ -153,5 +150,5 @@ func (s *Server)BindControllerRest(pattern string, c Controller) error {
faddr : nil,
}
}
return s.bindHandlerByMap(m)
s.bindHandlerByMap(m)
}

View File

@ -8,17 +8,17 @@
package ghttp
import (
"errors"
"strings"
"github.com/gogf/gf/g/text/gstr"
"bytes"
"runtime"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/text/gstr"
"reflect"
"runtime"
"strings"
)
// 注意该方法是直接绑定函数的内存地址,执行的时候直接执行该方法,不会存在初始化新的控制器逻辑
func (s *Server) BindHandler(pattern string, handler HandlerFunc) error {
return s.bindHandlerItem(pattern, &handlerItem {
func (s *Server) BindHandler(pattern string, handler HandlerFunc) {
s.bindHandlerItem(pattern, &handlerItem {
name : runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name(),
rtype : gROUTE_REGISTER_HANDLER,
ctype : nil,
@ -30,26 +30,24 @@ func (s *Server) BindHandler(pattern string, handler HandlerFunc) error {
// 绑定URI到操作函数/方法
// pattern的格式形如/user/list, put:/user, delete:/user, post:/user@johng.cn
// 支持RESTful的请求格式具体业务逻辑由绑定的处理方法来执行
func (s *Server) bindHandlerItem(pattern string, item *handlerItem) error {
func (s *Server) bindHandlerItem(pattern string, item *handlerItem) {
if s.Status() == SERVER_STATUS_RUNNING {
return errors.New("server handlers cannot be changed while running")
glog.Error("server handlers cannot be changed while running")
return
}
return s.setHandler(pattern, item)
s.setHandler(pattern, item)
}
// 通过映射数组绑定URI到操作函数/方法
func (s *Server) bindHandlerByMap(m handlerMap) error {
func (s *Server) bindHandlerByMap(m handlerMap) {
for p, h := range m {
if err := s.bindHandlerItem(p, h); err != nil {
return err
}
s.bindHandlerItem(p, h)
}
return nil
}
// 将内置的名称按照设定的规则合并到pattern中内置名称按照{.xxx}规则命名。
// 规则1pattern中的URI包含{.struct}关键字,则替换该关键字为结构体名称;
// 规则1pattern中的URI包含{.method}关键字,则替换该关键字为方法名称;
// 规则2pattern中的URI包含{.method}关键字,则替换该关键字为方法名称;
// 规则2如果不满足规则1那么直接将防发明附加到pattern中的URI后面
func (s *Server) mergeBuildInNameToPattern(pattern string, structName, methodName string, allowAppend bool) string {
structName = s.nameToUrlPart(structName)

View File

@ -8,18 +8,18 @@
package ghttp
import (
"errors"
"github.com/gogf/gf/g/os/glog"
"strings"
"reflect"
"fmt"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/text/gstr"
"reflect"
"strings"
)
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面
// 第三个参数methods用以指定需要注册的方法支持多个方法名称多个方法以英文“,”号分隔,区分大小写
func (s *Server)BindObject(pattern string, obj interface{}, methods...string) error {
func (s *Server)BindObject(pattern string, obj interface{}, methods...string) {
methodMap := (map[string]bool)(nil)
if len(methods) > 0 {
methodMap = make(map[string]bool)
@ -51,11 +51,7 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
}
faddr, ok := v.Method(i).Interface().(func(*Request))
if !ok {
if methodMap != nil {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func(*Request))" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
}
glog.Errorfln(`invalid method definition "%s", while "func(*Request))" is required`, v.Method(i).Type().String())
continue
}
objName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
@ -72,12 +68,13 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
finit : finit,
fshut : fshut,
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
if strings.EqualFold(mname, "Index") {
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
// 注意当pattern带有内置变量时不会自动加该路由。
if strings.EqualFold(mname, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
p := gstr.PosR(key, "/index")
k := key[0 : p] + key[p + 6 : ]
if len(k) == 0 {
k = "/"
if len(k) == 0 || k[0] == '@' {
k = "/" + k
}
m[k] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, mname),
@ -90,12 +87,12 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
}
}
}
return s.bindHandlerByMap(m)
s.bindHandlerByMap(m)
}
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面
// 第三个参数methods支持个方法注册,多个方法以英文“,”号分隔,区分大小写
func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string) error {
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面
// 第三个参数method支持个方法注册,不支持多个,并且区分大小写
func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string) {
m := make(handlerMap)
v := reflect.ValueOf(obj)
t := v.Type()
@ -103,13 +100,13 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string)
mname := strings.TrimSpace(method)
fval := v.MethodByName(mname)
if !fval.IsValid() {
return errors.New("invalid method name:" + mname)
glog.Error("invalid method name:" + mname)
return
}
faddr, ok := fval.Interface().(func(*Request))
if !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func(*Request)" is required`, fval.Type().String())
glog.Error(s)
return errors.New(s)
glog.Errorfln(`invalid method definition "%s", while "func(*Request)" is required`, fval.Type().String())
return
}
finit := (func(*Request))(nil)
fshut := (func(*Request))(nil)
@ -136,15 +133,16 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string)
fshut : fshut,
}
return s.bindHandlerByMap(m)
s.bindHandlerByMap(m)
}
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面
// 需要注意对象方法的定义必须按照ghttp.HandlerFunc来定义
func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面,
// 需要注意对象方法的定义必须按照 ghttp.HandlerFunc 来定义
func (s *Server)BindObjectRest(pattern string, obj interface{}) {
m := make(handlerMap)
v := reflect.ValueOf(obj)
t := v.Type()
sname := t.Elem().Name()
finit := (func(*Request))(nil)
fshut := (func(*Request))(nil)
if v.MethodByName("Init").IsValid() {
@ -157,21 +155,20 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
for i := 0; i < v.NumMethod(); i++ {
mname := t.Method(i).Name
method := strings.ToUpper(mname)
if _, ok := s.methodsMap[method]; !ok {
if _, ok := methodsMap[method]; !ok {
continue
}
faddr, ok := v.Method(i).Interface().(func(*Request))
if !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
glog.Errorfln(`invalid method definition "%s", while "func(*Request)" is required`, v.Method(i).Type().String())
continue
}
pkgName := gfile.Basename(pkgPath)
objName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if objName[0] == '*' {
objName = fmt.Sprintf(`(%s)`, objName)
}
key := mname + ":" + pattern
key := s.mergeBuildInNameToPattern(mname + ":" + pattern, sname, mname, false)
m[key] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, mname),
rtype : gROUTE_REGISTER_OBJECT,
@ -182,5 +179,5 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
fshut : fshut,
}
}
return s.bindHandlerByMap(m)
s.bindHandlerByMap(m)
}

View File

@ -26,9 +26,9 @@ type Session struct {
request *Request // 关联的请求
}
// 生成一个唯一的SessionId字符串长度16位
// 生成一个唯一的SessionId字符串长度18位。
func makeSessionId() string {
return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 32) + grand.RandStr(3))
return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 36) + grand.RandStr(6))
}
// 获取或者生成一个session对象(延迟初始化)
@ -41,14 +41,24 @@ func GetSession(r *Request) *Session {
}
}
// 执行初始化(用于延迟初始化)
// 执行初始化(用于延迟初始化).
func (s *Session) init() {
if len(s.id) == 0 {
s.id = s.request.Cookie.SessionId()
s.server = s.request.Server
s.data = s.server.sessions.GetOrSetFuncLock(s.id, func() interface{} {
return gmap.NewStringInterfaceMap()
}, s.server.GetSessionMaxAge()).(*gmap.StringInterfaceMap)
// 根据提交的SESSION ID获取已存在SESSION
id := s.request.Cookie.GetSessionId()
if id != "" {
data := s.server.sessions.Get(id)
if data != nil {
s.id = id
s.data = data.(*gmap.StringInterfaceMap)
return
}
}
// 否则执行初始化创建
s.id = s.request.Cookie.MakeSessionId()
s.data = gmap.NewStringInterfaceMap()
s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge())
}
}

View File

@ -8,16 +8,17 @@
package ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Cookie(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/set", func(r *ghttp.Request){
r.Cookie.Set(r.Get("k"), r.Get("v"))
})
@ -28,19 +29,17 @@ func Test_Cookie(t *testing.T) {
s.BindHandler("/remove", func(r *ghttp.Request){
r.Cookie.Remove(r.Get("k"))
})
s.SetPort(8500)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetBrowserMode(true)
client.SetPrefix("http://127.0.0.1:8500")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
r1, e1 := client.Get("/set?k=key1&v=100")
if r1 != nil {
defer r1.Close()

View File

@ -0,0 +1,25 @@
// 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 ghttp_test
import (
"github.com/gogf/gf/g/container/garray"
)
var (
// 用于测试的端口数组,随机获取
ports = garray.NewIntArray()
)
func init() {
for i := 8000; i <= 9000; i++ {
ports.Append(i)
}
}

View File

@ -0,0 +1,79 @@
// 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 ghttp_test
import (
"encoding/json"
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Params_Json(t *testing.T) {
type User struct {
Uid int
Name string
SiteUrl string `gconv:"-"`
NickName string `gconv:"nickname, omitempty"`
Pass1 string `gconv:"password1"`
Pass2 string `gconv:"password2"`
}
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/json1", func(r *ghttp.Request){
r.Response.WriteJson(User{
Uid : 100,
Name : "john",
SiteUrl : "https://goframe.org",
Pass1 : "123",
Pass2 : "456",
})
})
s.BindHandler("/json2", func(r *ghttp.Request){
r.Response.WriteJson(&User{
Uid : 100,
Name : "john",
SiteUrl : "https://goframe.org",
Pass1 : "123",
Pass2 : "456",
})
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
map1 := make(map[string]interface{})
err1 := json.Unmarshal([]byte(client.GetContent("/json1")), &map1)
gtest.Assert(err1, nil)
gtest.Assert(len(map1), 4)
gtest.Assert(map1["Name"], "john")
gtest.Assert(map1["Uid"], 100)
gtest.Assert(map1["password1"], "123")
gtest.Assert(map1["password2"], "456")
map2 := make(map[string]interface{})
err2 := json.Unmarshal([]byte(client.GetContent("/json1")), &map2)
gtest.Assert(err2, nil)
gtest.Assert(len(map2), 4)
gtest.Assert(map2["Name"], "john")
gtest.Assert(map2["Uid"], 100)
gtest.Assert(map2["password1"], "123")
gtest.Assert(map2["password2"], "456")
})
}

View File

@ -8,9 +8,9 @@
package ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
@ -23,7 +23,8 @@ func Test_Params_Basic(t *testing.T) {
Pass1 string `params:"password1"`
Pass2 string `params:"password2"`
}
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/get", func(r *ghttp.Request){
if r.GetQuery("slice") != nil {
r.Response.Write(r.GetQuery("slice"))
@ -96,18 +97,16 @@ func Test_Params_Basic(t *testing.T) {
r.Response.Write(user.Id, user.Name, user.Pass1, user.Pass2)
}
})
s.SetPort(8400)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8400")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
// GET
gtest.Assert(client.GetContent("/get", "slice=1&slice=2"), `["1","2"]`)
gtest.Assert(client.GetContent("/get", "bool=1"), `true`)

View File

@ -8,18 +8,18 @@
package ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
// 基本路由功能测试
func Test_Router_Basic(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/:name", func(r *ghttp.Request){
r.Response.Write("/:name")
})
@ -35,19 +35,16 @@ func Test_Router_Basic(t *testing.T) {
s.BindHandler("/user/list/{field}.html", func(r *ghttp.Request){
r.Response.Write(r.Get("field"))
})
s.SetPort(8100)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8100")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/john"), "")
gtest.Assert(client.GetContent("/john/update"), "john")
gtest.Assert(client.GetContent("/john/edit"), "edit")
@ -57,25 +54,24 @@ func Test_Router_Basic(t *testing.T) {
// 测试HTTP Method注册.
func Test_Router_Method(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("GET:/get", func(r *ghttp.Request){
})
s.BindHandler("POST:/post", func(r *ghttp.Request){
})
s.SetPort(8105)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8105")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
resp1, err := client.Get("/get")
defer resp1.Close()
@ -101,7 +97,8 @@ func Test_Router_Method(t *testing.T) {
// 测试状态返回.
func Test_Router_Status(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/200", func(r *ghttp.Request){
r.Response.WriteStatus(200)
})
@ -114,18 +111,16 @@ func Test_Router_Status(t *testing.T) {
s.BindHandler("/500", func(r *ghttp.Request){
r.Response.WriteStatus(500)
})
s.SetPort(8110)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8110")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
resp1, err := client.Get("/200")
defer resp1.Close()
@ -151,25 +146,24 @@ func Test_Router_Status(t *testing.T) {
// 自定义状态码处理.
func Test_Router_CustomStatusHandler(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Write("hello")
})
s.BindStatusHandler(404, func(r *ghttp.Request){
r.Response.Write("404 page")
})
s.SetPort(8120)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8120")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "hello")
resp, err := client.Get("/ThisDoesNotExist")
@ -182,22 +176,21 @@ func Test_Router_CustomStatusHandler(t *testing.T) {
// 测试不存在的路由.
func Test_Router_404(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Write("hello")
})
s.SetPort(8130)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8130")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "hello")
resp, err := client.Get("/ThisDoesNotExist")

View File

@ -0,0 +1,106 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/frame/gmvc"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type ControllerRest struct {
gmvc.Controller
}
func (c *ControllerRest) Init(r *ghttp.Request) {
c.Controller.Init(r)
c.Response.Write("1")
}
func (c *ControllerRest) Shut() {
c.Response.Write("2")
}
func (c *ControllerRest) Get() {
c.Response.Write("Controller Get")
}
func (c *ControllerRest) Put() {
c.Response.Write("Controller Put")
}
func (c *ControllerRest) Post() {
c.Response.Write("Controller Post")
}
func (c *ControllerRest) Delete() {
c.Response.Write("Controller Delete")
}
func (c *ControllerRest) Patch() {
c.Response.Write("Controller Patch")
}
func (c *ControllerRest) Options() {
c.Response.Write("Controller Options")
}
func (c *ControllerRest) Head() {
c.Response.Header().Set("head-ok", "1")
}
// 控制器注册测试
func Test_Router_ControllerRest(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindControllerRest("/", new(ControllerRest))
s.BindControllerRest("/{.struct}/{.method}", new(ControllerRest))
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "1Controller Get2")
gtest.Assert(client.PutContent("/"), "1Controller Put2")
gtest.Assert(client.PostContent("/"), "1Controller Post2")
gtest.Assert(client.DeleteContent("/"), "1Controller Delete2")
gtest.Assert(client.PatchContent("/"), "1Controller Patch2")
gtest.Assert(client.OptionsContent("/"), "1Controller Options2")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/controller-rest/get"), "1Controller Get2")
gtest.Assert(client.PutContent("/controller-rest/put"), "1Controller Put2")
gtest.Assert(client.PostContent("/controller-rest/post"), "1Controller Post2")
gtest.Assert(client.DeleteContent("/controller-rest/delete"), "1Controller Delete2")
gtest.Assert(client.PatchContent("/controller-rest/patch"), "1Controller Patch2")
gtest.Assert(client.OptionsContent("/controller-rest/options"), "1Controller Options2")
resp2, err := client.Head("/controller-rest/head")
if err == nil {
defer resp2.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp2.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,130 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/frame/gmvc"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
// 控制器
type Controller struct {
gmvc.Controller
}
func (c *Controller) Init(r *ghttp.Request) {
c.Controller.Init(r)
c.Response.Write("1")
}
func (c *Controller) Shut() {
c.Response.Write("2")
}
func (c *Controller) Index() {
c.Response.Write("Controller Index")
}
func (c *Controller) Show() {
c.Response.Write("Controller Show")
}
func (c *Controller) Info() {
c.Response.Write("Controller Info")
}
func Test_Router_Controller1(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindController("/", new(Controller))
s.BindController("/{.struct}/{.method}", new(Controller))
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "1Controller Index2")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "1Controller Index2")
gtest.Assert(client.GetContent("/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "1Controller Index2")
gtest.Assert(client.GetContent("/controller/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_Controller2(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindController("/controller", new(Controller), "Show, Info")
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/controller/info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_ControllerMethod(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindControllerMethod("/controller-info", new(Controller), "Info")
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "Not Found")
gtest.Assert(client.GetContent("/controller/info"), "Not Found")
gtest.Assert(client.GetContent("/controller-info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,338 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
// 基本路由功能测试
func Test_Router_DomainBasic(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindHandler("/:name", func(r *ghttp.Request){
r.Response.Write("/:name")
})
d.BindHandler("/:name/update", func(r *ghttp.Request){
r.Response.Write(r.Get("name"))
})
d.BindHandler("/:name/:action", func(r *ghttp.Request){
r.Response.Write(r.Get("action"))
})
d.BindHandler("/:name/*any", func(r *ghttp.Request){
r.Response.Write(r.Get("any"))
})
d.BindHandler("/user/list/{field}.html", func(r *ghttp.Request){
r.Response.Write(r.Get("field"))
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/john"), "Not Found")
gtest.Assert(client.GetContent("/john/update"), "Not Found")
gtest.Assert(client.GetContent("/john/edit"), "Not Found")
gtest.Assert(client.GetContent("/user/list/100.html"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/john"), "")
gtest.Assert(client.GetContent("/john/update"), "john")
gtest.Assert(client.GetContent("/john/edit"), "edit")
gtest.Assert(client.GetContent("/user/list/100.html"), "100")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/john"), "")
gtest.Assert(client.GetContent("/john/update"), "john")
gtest.Assert(client.GetContent("/john/edit"), "edit")
gtest.Assert(client.GetContent("/user/list/100.html"), "100")
})
}
// 测试HTTP Method注册.
func Test_Router_DomainMethod(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindHandler("GET:/get", func(r *ghttp.Request){
})
d.BindHandler("POST:/post", func(r *ghttp.Request){
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
resp1, err := client.Get("/get")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 404)
resp2, err := client.Post("/get")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 404)
resp3, err := client.Get("/post")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 404)
resp4, err := client.Post("/post")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 404)
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
resp1, err := client.Get("/get")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 200)
resp2, err := client.Post("/get")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 404)
resp3, err := client.Get("/post")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 404)
resp4, err := client.Post("/post")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 200)
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
resp1, err := client.Get("/get")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 200)
resp2, err := client.Post("/get")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 404)
resp3, err := client.Get("/post")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 404)
resp4, err := client.Post("/post")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 200)
})
}
// 测试状态返回.
func Test_Router_DomainStatus(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindHandler("/200", func(r *ghttp.Request){
r.Response.WriteStatus(200)
})
d.BindHandler("/300", func(r *ghttp.Request){
r.Response.WriteStatus(300)
})
d.BindHandler("/400", func(r *ghttp.Request){
r.Response.WriteStatus(400)
})
d.BindHandler("/500", func(r *ghttp.Request){
r.Response.WriteStatus(500)
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
resp1, err := client.Get("/200")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 404)
resp2, err := client.Get("/300")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 404)
resp3, err := client.Get("/400")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 404)
resp4, err := client.Get("/500")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 404)
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
resp1, err := client.Get("/200")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 200)
resp2, err := client.Get("/300")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 300)
resp3, err := client.Get("/400")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 400)
resp4, err := client.Get("/500")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 500)
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
resp1, err := client.Get("/200")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 200)
resp2, err := client.Get("/300")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 300)
resp3, err := client.Get("/400")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 400)
resp4, err := client.Get("/500")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 500)
})
}
// 自定义状态码处理.
func Test_Router_DomainCustomStatusHandler(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindHandler("/", func(r *ghttp.Request){
r.Response.Write("hello")
})
d.BindStatusHandler(404, func(r *ghttp.Request){
r.Response.Write("404 page")
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/ThisDoesNotExist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "hello")
gtest.Assert(client.GetContent("/ThisDoesNotExist"), "404 page")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "hello")
gtest.Assert(client.GetContent("/ThisDoesNotExist"), "404 page")
})
}
// 测试不存在的路由.
func Test_Router_Domain404(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindHandler("/", func(r *ghttp.Request){
r.Response.Write("hello")
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "hello")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "hello")
})
}

View File

@ -0,0 +1,127 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/frame/gmvc"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type DomainControllerRest struct {
gmvc.Controller
}
func (c *DomainControllerRest) Init(r *ghttp.Request) {
c.Controller.Init(r)
c.Response.Write("1")
}
func (c *DomainControllerRest) Shut() {
c.Response.Write("2")
}
func (c *DomainControllerRest) Get() {
c.Response.Write("Controller Get")
}
func (c *DomainControllerRest) Put() {
c.Response.Write("Controller Put")
}
func (c *DomainControllerRest) Post() {
c.Response.Write("Controller Post")
}
func (c *DomainControllerRest) Delete() {
c.Response.Write("Controller Delete")
}
func (c *DomainControllerRest) Patch() {
c.Response.Write("Controller Patch")
}
func (c *DomainControllerRest) Options() {
c.Response.Write("Controller Options")
}
func (c *DomainControllerRest) Head() {
c.Response.Header().Set("head-ok", "1")
}
// 控制器注册测试
func Test_Router_DomainControllerRest(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindControllerRest("/", new(DomainControllerRest))
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.PutContent("/"), "Not Found")
gtest.Assert(client.PostContent("/"), "Not Found")
gtest.Assert(client.DeleteContent("/"), "Not Found")
gtest.Assert(client.PatchContent("/"), "Not Found")
gtest.Assert(client.OptionsContent("/"), "Not Found")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "1Controller Get2")
gtest.Assert(client.PutContent("/"), "1Controller Put2")
gtest.Assert(client.PostContent("/"), "1Controller Post2")
gtest.Assert(client.DeleteContent("/"), "1Controller Delete2")
gtest.Assert(client.PatchContent("/"), "1Controller Patch2")
gtest.Assert(client.OptionsContent("/"), "1Controller Options2")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "1Controller Get2")
gtest.Assert(client.PutContent("/"), "1Controller Put2")
gtest.Assert(client.PostContent("/"), "1Controller Post2")
gtest.Assert(client.DeleteContent("/"), "1Controller Delete2")
gtest.Assert(client.PatchContent("/"), "1Controller Patch2")
gtest.Assert(client.OptionsContent("/"), "1Controller Options2")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,202 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/frame/gmvc"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type DomainController struct {
gmvc.Controller
}
func (c *DomainController) Init(r *ghttp.Request) {
c.Controller.Init(r)
c.Response.Write("1")
}
func (c *DomainController) Shut() {
c.Response.Write("2")
}
func (c *DomainController) Index() {
c.Response.Write("Controller Index")
}
func (c *DomainController) Show() {
c.Response.Write("Controller Show")
}
func (c *DomainController) Info() {
c.Response.Write("Controller Info")
}
func Test_Router_DomainController1(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Domain("localhost, local").BindController("/", new(DomainController))
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "Not Found")
gtest.Assert(client.GetContent("/show"), "Not Found")
gtest.Assert(client.GetContent("/info"), "Not Found")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "1Controller Index2")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "1Controller Index2")
gtest.Assert(client.GetContent("/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "1Controller Index2")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "1Controller Index2")
gtest.Assert(client.GetContent("/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_DomainController2(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Domain("localhost, local").BindController("/controller", new(DomainController), "Show, Info")
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "Not Found")
gtest.Assert(client.GetContent("/controller/info"), "Not Found")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/controller/info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/controller/info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_DomainControllerMethod(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Domain("localhost, local").BindControllerMethod("/controller-info", new(DomainController), "Info")
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "Not Found")
gtest.Assert(client.GetContent("/controller/info"), "Not Found")
gtest.Assert(client.GetContent("/controller-info"), "Not Found")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "Not Found")
gtest.Assert(client.GetContent("/controller/info"), "Not Found")
gtest.Assert(client.GetContent("/controller-info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "Not Found")
gtest.Assert(client.GetContent("/controller/info"), "Not Found")
gtest.Assert(client.GetContent("/controller-info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,122 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type DomainObjectRest struct {}
func (o *DomainObjectRest) Init(r *ghttp.Request) {
r.Response.Write("1")
}
func (o *DomainObjectRest) Shut(r *ghttp.Request) {
r.Response.Write("2")
}
func (o *DomainObjectRest) Get(r *ghttp.Request) {
r.Response.Write("Object Get")
}
func (o *DomainObjectRest) Put(r *ghttp.Request) {
r.Response.Write("Object Put")
}
func (o *DomainObjectRest) Post(r *ghttp.Request) {
r.Response.Write("Object Post")
}
func (o *DomainObjectRest) Delete(r *ghttp.Request) {
r.Response.Write("Object Delete")
}
func (o *DomainObjectRest) Patch(r *ghttp.Request) {
r.Response.Write("Object Patch")
}
func (o *DomainObjectRest) Options(r *ghttp.Request) {
r.Response.Write("Object Options")
}
func (o *DomainObjectRest) Head(r *ghttp.Request) {
r.Response.Header().Set("head-ok", "1")
}
func Test_Router_DomainObjectRest(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindObjectRest("/", new(DomainObjectRest))
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.PutContent("/"), "Not Found")
gtest.Assert(client.PostContent("/"), "Not Found")
gtest.Assert(client.DeleteContent("/"), "Not Found")
gtest.Assert(client.PatchContent("/"), "Not Found")
gtest.Assert(client.OptionsContent("/"), "Not Found")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "1Object Get2")
gtest.Assert(client.PutContent("/"), "1Object Put2")
gtest.Assert(client.PostContent("/"), "1Object Post2")
gtest.Assert(client.DeleteContent("/"), "1Object Delete2")
gtest.Assert(client.PatchContent("/"), "1Object Patch2")
gtest.Assert(client.OptionsContent("/"), "1Object Options2")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "1Object Get2")
gtest.Assert(client.PutContent("/"), "1Object Put2")
gtest.Assert(client.PostContent("/"), "1Object Post2")
gtest.Assert(client.DeleteContent("/"), "1Object Delete2")
gtest.Assert(client.PatchContent("/"), "1Object Patch2")
gtest.Assert(client.OptionsContent("/"), "1Object Options2")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,196 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type DomainObject struct {}
func (o *DomainObject) Init(r *ghttp.Request) {
r.Response.Write("1")
}
func (o *DomainObject) Shut(r *ghttp.Request) {
r.Response.Write("2")
}
func (o *DomainObject) Index(r *ghttp.Request) {
r.Response.Write("Object Index")
}
func (o *DomainObject) Show(r *ghttp.Request) {
r.Response.Write("Object Show")
}
func (o *DomainObject) Info(r *ghttp.Request) {
r.Response.Write("Object Info")
}
func Test_Router_DomainObject1(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Domain("localhost, local").BindObject("/", new(DomainObject))
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "Not Found")
gtest.Assert(client.GetContent("/show"), "Not Found")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "1Object Index2")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "1Object Index2")
gtest.Assert(client.GetContent("/show"), "1Object Show2")
gtest.Assert(client.GetContent("/info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "1Object Index2")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "1Object Index2")
gtest.Assert(client.GetContent("/show"), "1Object Show2")
gtest.Assert(client.GetContent("/info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_DomainObject2(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Domain("localhost, local").BindObject("/object", new(DomainObject), "Show, Info")
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "Not Found")
gtest.Assert(client.GetContent("/object/info"), "Not Found")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "1Object Show2")
gtest.Assert(client.GetContent("/object/info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "1Object Show2")
gtest.Assert(client.GetContent("/object/info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_DomainObjectMethod(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Domain("localhost, local").BindObjectMethod("/object-info", new(DomainObject), "Info")
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "Not Found")
gtest.Assert(client.GetContent("/object/info"), "Not Found")
gtest.Assert(client.GetContent("/object-info"), "Not Found")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "Not Found")
gtest.Assert(client.GetContent("/object/info"), "Not Found")
gtest.Assert(client.GetContent("/object-info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "Not Found")
gtest.Assert(client.GetContent("/object/info"), "Not Found")
gtest.Assert(client.GetContent("/object-info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,126 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Router_Exit(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindHookHandlerByMap("/*", map[string]ghttp.HandlerFunc{
"BeforeServe" : func(r *ghttp.Request){ r.Response.Write("1") },
"AfterServe" : func(r *ghttp.Request){ r.Response.Write("2") },
"BeforeOutput" : func(r *ghttp.Request){ r.Response.Write("3") },
"AfterOutput" : func(r *ghttp.Request){ r.Response.Write("4") },
"BeforeClose" : func(r *ghttp.Request){ r.Response.Write("5") },
"AfterClose" : func(r *ghttp.Request){ r.Response.Write("6") },
})
s.BindHandler("/test/test", func(r *ghttp.Request) {
r.Response.Write("test-start")
r.Exit()
r.Response.Write("test-end")
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "123")
gtest.Assert(client.GetContent("/test/test"), "1test-start23")
})
}
func Test_Router_ExitHook(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/priority/show", func(r *ghttp.Request) {
r.Response.Write("show")
})
s.BindHookHandlerByMap("/priority/:name", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("1")
},
})
s.BindHookHandlerByMap("/priority/*any", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("2")
},
})
s.BindHookHandlerByMap("/priority/show", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("3")
r.ExitHook()
},
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/priority/show"), "3show")
})
}
func Test_Router_ExitAll(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/priority/show", func(r *ghttp.Request) {
r.Response.Write("show")
})
s.BindHookHandlerByMap("/priority/:name", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("1")
},
})
s.BindHookHandlerByMap("/priority/*any", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("2")
},
})
s.BindHookHandlerByMap("/priority/show", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("3")
r.ExitAll()
},
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/priority/show"), "3")
})
}

View File

@ -0,0 +1,173 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/frame/gmvc"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type GroupCtlRest struct {
gmvc.Controller
}
func (c *GroupCtlRest) Init(r *ghttp.Request) {
c.Controller.Init(r)
c.Response.Write("1")
}
func (c *GroupCtlRest) Shut() {
c.Response.Write("2")
}
func (c *GroupCtlRest) Get() {
c.Response.Write("Controller Get")
}
func (c *GroupCtlRest) Put() {
c.Response.Write("Controller Put")
}
func (c *GroupCtlRest) Post() {
c.Response.Write("Controller Post")
}
func (c *GroupCtlRest) Delete() {
c.Response.Write("Controller Delete")
}
func (c *GroupCtlRest) Patch() {
c.Response.Write("Controller Patch")
}
func (c *GroupCtlRest) Options() {
c.Response.Write("Controller Options")
}
func (c *GroupCtlRest) Head() {
c.Response.Header().Set("head-ok", "1")
}
type GroupObjRest struct {}
func (o *GroupObjRest) Init(r *ghttp.Request) {
r.Response.Write("1")
}
func (o *GroupObjRest) Shut(r *ghttp.Request) {
r.Response.Write("2")
}
func (o *GroupObjRest) Get(r *ghttp.Request) {
r.Response.Write("Object Get")
}
func (o *GroupObjRest) Put(r *ghttp.Request) {
r.Response.Write("Object Put")
}
func (o *GroupObjRest) Post(r *ghttp.Request) {
r.Response.Write("Object Post")
}
func (o *GroupObjRest) Delete(r *ghttp.Request) {
r.Response.Write("Object Delete")
}
func (o *GroupObjRest) Patch(r *ghttp.Request) {
r.Response.Write("Object Patch")
}
func (o *GroupObjRest) Options(r *ghttp.Request) {
r.Response.Write("Object Options")
}
func (o *GroupObjRest) Head(r *ghttp.Request) {
r.Response.Header().Set("head-ok", "1")
}
func Test_Router_GroupRest(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
g := s.Group("/api")
ctl := new(GroupCtlRest)
obj := new(GroupObjRest)
g.REST("/ctl", ctl)
g.REST("/obj", obj)
g.REST("/{.struct}/{.method}", ctl)
g.REST("/{.struct}/{.method}", obj)
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/api/ctl"), "1Controller Get2")
gtest.Assert(client.PutContent("/api/ctl"), "1Controller Put2")
gtest.Assert(client.PostContent("/api/ctl"), "1Controller Post2")
gtest.Assert(client.DeleteContent("/api/ctl"), "1Controller Delete2")
gtest.Assert(client.PatchContent("/api/ctl"), "1Controller Patch2")
gtest.Assert(client.OptionsContent("/api/ctl"), "1Controller Options2")
resp1, err := client.Head("/api/ctl")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/api/obj"), "1Object Get2")
gtest.Assert(client.PutContent("/api/obj"), "1Object Put2")
gtest.Assert(client.PostContent("/api/obj"), "1Object Post2")
gtest.Assert(client.DeleteContent("/api/obj"), "1Object Delete2")
gtest.Assert(client.PatchContent("/api/obj"), "1Object Patch2")
gtest.Assert(client.OptionsContent("/api/obj"), "1Object Options2")
resp2, err := client.Head("/api/obj")
if err == nil {
defer resp2.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp2.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/api/group-ctl-rest"), "Not Found")
gtest.Assert(client.GetContent("/api/group-ctl-rest/get"), "1Controller Get2")
gtest.Assert(client.PutContent("/api/group-ctl-rest/put"), "1Controller Put2")
gtest.Assert(client.PostContent("/api/group-ctl-rest/post"), "1Controller Post2")
gtest.Assert(client.DeleteContent("/api/group-ctl-rest/delete"), "1Controller Delete2")
gtest.Assert(client.PatchContent("/api/group-ctl-rest/patch"), "1Controller Patch2")
gtest.Assert(client.OptionsContent("/api/group-ctl-rest/options"), "1Controller Options2")
resp3, err := client.Head("/api/group-ctl-rest/head")
if err == nil {
defer resp3.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp3.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/api/group-obj-rest"), "Not Found")
gtest.Assert(client.GetContent("/api/group-obj-rest/get"), "1Object Get2")
gtest.Assert(client.PutContent("/api/group-obj-rest/put"), "1Object Put2")
gtest.Assert(client.PostContent("/api/group-obj-rest/post"), "1Object Post2")
gtest.Assert(client.DeleteContent("/api/group-obj-rest/delete"), "1Object Delete2")
gtest.Assert(client.PatchContent("/api/group-obj-rest/patch"), "1Object Patch2")
gtest.Assert(client.OptionsContent("/api/group-obj-rest/options"), "1Object Options2")
resp4, err := client.Head("/api/group-obj-rest/head")
if err == nil {
defer resp4.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp4.Header.Get("head-ok"), "1")
})
}

View File

@ -8,55 +8,73 @@
package ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/frame/gmvc"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
// 执行对象
type Object struct {}
type GroupObject struct {}
func (o *Object) Index(r *ghttp.Request) {
func (o *GroupObject) Init(r *ghttp.Request) {
r.Response.Write("1")
}
func (o *GroupObject) Shut(r *ghttp.Request) {
r.Response.Write("2")
}
func (o *GroupObject) Index(r *ghttp.Request) {
r.Response.Write("Object Index")
}
func (o *Object) Show(r *ghttp.Request) {
func (o *GroupObject) Show(r *ghttp.Request) {
r.Response.Write("Object Show")
}
func (o *Object) Delete(r *ghttp.Request) {
r.Response.Write("Object REST Delete")
func (o *GroupObject) Delete(r *ghttp.Request) {
r.Response.Write("Object Delete")
}
// 控制器
type Controller struct {
type GroupController struct {
gmvc.Controller
}
func (c *Controller) Index() {
func (c *GroupController) Init(r *ghttp.Request) {
c.Controller.Init(r)
c.Response.Write("1")
}
func (c *GroupController) Shut() {
c.Response.Write("2")
}
func (c *GroupController) Index() {
c.Response.Write("Controller Index")
}
func (c *Controller) Show() {
func (c *GroupController) Show() {
c.Response.Write("Controller Show")
}
func (c *Controller) Post() {
c.Response.Write("Controller REST Post")
func (c *GroupController) Post() {
c.Response.Write("Controller Post")
}
func Handler(r *ghttp.Request) {
r.Response.Write("Handler")
}
func Test_Router_Group1(t *testing.T) {
s := g.Server(gtime.Nanosecond())
obj := new(Object)
ctl := new(Controller)
func Test_Router_GroupBasic1(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
obj := new(GroupObject)
ctl := new(GroupController)
// 分组路由方法注册
g := s.Group("/api")
g.ALL ("/handler", Handler)
@ -66,48 +84,44 @@ func Test_Router_Group1(t *testing.T) {
g.ALL ("/obj", obj)
g.GET ("/obj/my-show", obj, "Show")
g.REST("/obj/rest", obj)
s.SetPort(8200)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8200")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent ("/api/handler"), "Handler")
gtest.Assert(client.GetContent ("/api/ctl"), "Controller Index")
gtest.Assert(client.GetContent ("/api/ctl/"), "Controller Index")
gtest.Assert(client.GetContent ("/api/ctl/index"), "Controller Index")
gtest.Assert(client.GetContent ("/api/ctl/my-show"), "Controller Show")
gtest.Assert(client.GetContent ("/api/ctl/post"), "Controller REST Post")
gtest.Assert(client.GetContent ("/api/ctl/show"), "Controller Show")
gtest.Assert(client.PostContent("/api/ctl/rest"), "Controller REST Post")
gtest.Assert(client.GetContent ("/api/ctl"), "1Controller Index2")
gtest.Assert(client.GetContent ("/api/ctl/"), "1Controller Index2")
gtest.Assert(client.GetContent ("/api/ctl/index"), "1Controller Index2")
gtest.Assert(client.GetContent ("/api/ctl/my-show"), "1Controller Show2")
gtest.Assert(client.GetContent ("/api/ctl/post"), "1Controller Post2")
gtest.Assert(client.GetContent ("/api/ctl/show"), "1Controller Show2")
gtest.Assert(client.PostContent("/api/ctl/rest"), "1Controller Post2")
gtest.Assert(client.GetContent ("/api/obj"), "Object Index")
gtest.Assert(client.GetContent ("/api/obj/"), "Object Index")
gtest.Assert(client.GetContent ("/api/obj/index"), "Object Index")
gtest.Assert(client.GetContent ("/api/obj/delete"), "Object REST Delete")
gtest.Assert(client.GetContent ("/api/obj/my-show"), "Object Show")
gtest.Assert(client.GetContent ("/api/obj/show"), "Object Show")
gtest.Assert(client.DeleteContent("/api/obj/rest"), "Object REST Delete")
gtest.Assert(client.GetContent ("/api/obj"), "1Object Index2")
gtest.Assert(client.GetContent ("/api/obj/"), "1Object Index2")
gtest.Assert(client.GetContent ("/api/obj/index"), "1Object Index2")
gtest.Assert(client.GetContent ("/api/obj/delete"), "1Object Delete2")
gtest.Assert(client.GetContent ("/api/obj/my-show"), "1Object Show2")
gtest.Assert(client.GetContent ("/api/obj/show"), "1Object Show2")
gtest.Assert(client.DeleteContent("/api/obj/rest"), "1Object Delete2")
// 测试404
resp, err := client.Get("/ThisDoesNotExist")
defer resp.Close()
gtest.Assert(err, nil)
gtest.Assert(resp.StatusCode, 404)
gtest.Assert(client.DeleteContent("/ThisDoesNotExist"), "Not Found")
gtest.Assert(client.DeleteContent("/api/ThisDoesNotExist"), "Not Found")
})
}
func Test_Router_Group2(t *testing.T) {
s := g.Server(gtime.Nanosecond())
obj := new(Object)
ctl := new(Controller)
func Test_Router_Basic2(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
obj := new(GroupObject)
ctl := new(GroupController)
// 分组路由批量注册
s.Group("/api").Bind("/api", []ghttp.GroupItem{
{"ALL", "/handler", Handler},
@ -118,34 +132,61 @@ func Test_Router_Group2(t *testing.T) {
{"GET", "/obj/my-show", obj, "Show"},
{"REST", "/obj/rest", obj},
})
s.SetPort(8300)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8300")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent ("/api/handler"), "Handler")
gtest.Assert(client.GetContent ("/api/ctl/my-show"), "Controller Show")
gtest.Assert(client.GetContent ("/api/ctl/post"), "Controller REST Post")
gtest.Assert(client.GetContent ("/api/ctl/show"), "Controller Show")
gtest.Assert(client.PostContent("/api/ctl/rest"), "Controller REST Post")
gtest.Assert(client.GetContent ("/api/ctl/my-show"), "1Controller Show2")
gtest.Assert(client.GetContent ("/api/ctl/post"), "1Controller Post2")
gtest.Assert(client.GetContent ("/api/ctl/show"), "1Controller Show2")
gtest.Assert(client.PostContent("/api/ctl/rest"), "1Controller Post2")
gtest.Assert(client.GetContent ("/api/obj/delete"), "Object REST Delete")
gtest.Assert(client.GetContent ("/api/obj/my-show"), "Object Show")
gtest.Assert(client.GetContent ("/api/obj/show"), "Object Show")
gtest.Assert(client.DeleteContent("/api/obj/rest"), "Object REST Delete")
gtest.Assert(client.GetContent ("/api/obj/delete"), "1Object Delete2")
gtest.Assert(client.GetContent ("/api/obj/my-show"), "1Object Show2")
gtest.Assert(client.GetContent ("/api/obj/show"), "1Object Show2")
gtest.Assert(client.DeleteContent("/api/obj/rest"), "1Object Delete2")
// 测试404
resp, err := client.Get("/ThisDoesNotExist")
defer resp.Close()
gtest.Assert(err, nil)
gtest.Assert(resp.StatusCode, 404)
gtest.Assert(client.DeleteContent("/ThisDoesNotExist"), "Not Found")
gtest.Assert(client.DeleteContent("/api/ThisDoesNotExist"), "Not Found")
})
}
func Test_Router_GroupBuildInVar(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
obj := new(GroupObject)
ctl := new(GroupController)
// 分组路由方法注册
g := s.Group("/api")
g.ALL ("/{.struct}/{.method}", ctl)
g.ALL ("/{.struct}/{.method}", obj)
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent ("/api/group-controller/index"), "1Controller Index2")
gtest.Assert(client.GetContent ("/api/group-controller/post"), "1Controller Post2")
gtest.Assert(client.GetContent ("/api/group-controller/show"), "1Controller Show2")
gtest.Assert(client.GetContent ("/api/group-object/index"), "1Object Index2")
gtest.Assert(client.GetContent ("/api/group-object/delete"), "1Object Delete2")
gtest.Assert(client.GetContent ("/api/group-object/show"), "1Object Show2")
gtest.Assert(client.DeleteContent("/ThisDoesNotExist"), "Not Found")
gtest.Assert(client.DeleteContent("/api/ThisDoesNotExist"), "Not Found")
})
}

View File

@ -0,0 +1,87 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Router_Hook_Basic(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindHookHandlerByMap("/*", map[string]ghttp.HandlerFunc{
"BeforeServe" : func(r *ghttp.Request){ r.Response.Write("1") },
"AfterServe" : func(r *ghttp.Request){ r.Response.Write("2") },
"BeforeOutput" : func(r *ghttp.Request){ r.Response.Write("3") },
"AfterOutput" : func(r *ghttp.Request){ r.Response.Write("4") },
"BeforeClose" : func(r *ghttp.Request){ r.Response.Write("5") },
"AfterClose" : func(r *ghttp.Request){ r.Response.Write("6") },
})
s.BindHandler("/test/test", func(r *ghttp.Request) {
r.Response.Write("test")
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "123")
gtest.Assert(client.GetContent("/test/test"), "1test23")
})
}
func Test_Router_Hook_Priority(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/priority/show", func(r *ghttp.Request) {
r.Response.Write("show")
})
s.BindHookHandlerByMap("/priority/:name", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("1")
},
})
s.BindHookHandlerByMap("/priority/*any", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("2")
},
})
s.BindHookHandlerByMap("/priority/show", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("3")
},
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/priority/show"), "312show")
gtest.Assert(client.GetContent("/priority/any/any"), "2")
gtest.Assert(client.GetContent("/priority/name"), "12")
})
}

View File

@ -0,0 +1,112 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type NamesObject struct {}
func (o *NamesObject) ShowName(r *ghttp.Request) {
r.Response.Write("Object Show Name")
}
func Test_NameToUri_FullName(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.SetNameToUriType(ghttp.NAME_TO_URI_TYPE_FULLNAME)
s.BindObject("/{.struct}/{.method}", new(NamesObject))
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetBrowserMode(true)
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/NamesObject"), "Not Found")
gtest.Assert(client.GetContent("/NamesObject/ShowName"), "Object Show Name")
})
}
func Test_NameToUri_AllLower(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.SetNameToUriType(ghttp.NAME_TO_URI_TYPE_ALLLOWER)
s.BindObject("/{.struct}/{.method}", new(NamesObject))
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetBrowserMode(true)
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/NamesObject"), "Not Found")
gtest.Assert(client.GetContent("/namesobject/showname"), "Object Show Name")
})
}
func Test_NameToUri_Camel(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.SetNameToUriType(ghttp.NAME_TO_URI_TYPE_CAMEL)
s.BindObject("/{.struct}/{.method}", new(NamesObject))
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetBrowserMode(true)
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/NamesObject"), "Not Found")
gtest.Assert(client.GetContent("/namesObject/showName"), "Object Show Name")
})
}
func Test_NameToUri_Default(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.SetNameToUriType(ghttp.NAME_TO_URI_TYPE_DEFAULT)
s.BindObject("/{.struct}/{.method}", new(NamesObject))
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetBrowserMode(true)
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/NamesObject"), "Not Found")
gtest.Assert(client.GetContent("/names-object/show-name"), "Object Show Name")
})
}

View File

@ -0,0 +1,100 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type ObjectRest struct {}
func (o *ObjectRest) Init(r *ghttp.Request) {
r.Response.Write("1")
}
func (o *ObjectRest) Shut(r *ghttp.Request) {
r.Response.Write("2")
}
func (o *ObjectRest) Get(r *ghttp.Request) {
r.Response.Write("Object Get")
}
func (o *ObjectRest) Put(r *ghttp.Request) {
r.Response.Write("Object Put")
}
func (o *ObjectRest) Post(r *ghttp.Request) {
r.Response.Write("Object Post")
}
func (o *ObjectRest) Delete(r *ghttp.Request) {
r.Response.Write("Object Delete")
}
func (o *ObjectRest) Patch(r *ghttp.Request) {
r.Response.Write("Object Patch")
}
func (o *ObjectRest) Options(r *ghttp.Request) {
r.Response.Write("Object Options")
}
func (o *ObjectRest) Head(r *ghttp.Request) {
r.Response.Header().Set("head-ok", "1")
}
func Test_Router_ObjectRest(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindObjectRest("/", new(ObjectRest))
s.BindObjectRest("/{.struct}/{.method}", new(ObjectRest))
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "1Object Get2")
gtest.Assert(client.PutContent("/"), "1Object Put2")
gtest.Assert(client.PostContent("/"), "1Object Post2")
gtest.Assert(client.DeleteContent("/"), "1Object Delete2")
gtest.Assert(client.PatchContent("/"), "1Object Patch2")
gtest.Assert(client.OptionsContent("/"), "1Object Options2")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/object-rest/get"), "1Object Get2")
gtest.Assert(client.PutContent("/object-rest/put"), "1Object Put2")
gtest.Assert(client.PostContent("/object-rest/post"), "1Object Post2")
gtest.Assert(client.DeleteContent("/object-rest/delete"), "1Object Delete2")
gtest.Assert(client.PatchContent("/object-rest/patch"), "1Object Patch2")
gtest.Assert(client.OptionsContent("/object-rest/options"), "1Object Options2")
resp2, err := client.Head("/object-rest/head")
if err == nil {
defer resp2.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp2.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,126 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type Object struct {}
func (o *Object) Init(r *ghttp.Request) {
r.Response.Write("1")
}
func (o *Object) Shut(r *ghttp.Request) {
r.Response.Write("2")
}
func (o *Object) Index(r *ghttp.Request) {
r.Response.Write("Object Index")
}
func (o *Object) Show(r *ghttp.Request) {
r.Response.Write("Object Show")
}
func (o *Object) Info(r *ghttp.Request) {
r.Response.Write("Object Info")
}
func Test_Router_Object1(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindObject("/", new(Object))
s.BindObject("/{.struct}/{.method}", new(Object))
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "1Object Index2")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "1Object Index2")
gtest.Assert(client.GetContent("/show"), "1Object Show2")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "1Object Index2")
gtest.Assert(client.GetContent("/object/show"), "1Object Show2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_Object2(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindObject("/object", new(Object), "Show, Info")
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "1Object Show2")
gtest.Assert(client.GetContent("/object/info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_ObjectMethod(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindObjectMethod("/object-info", new(Object), "Info")
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "Not Found")
gtest.Assert(client.GetContent("/object/info"), "Not Found")
gtest.Assert(client.GetContent("/object-info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -8,16 +8,17 @@
package ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Session(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/set", func(r *ghttp.Request){
r.Session.Set(r.Get("k"), r.Get("v"))
})
@ -30,19 +31,17 @@ func Test_Session(t *testing.T) {
s.BindHandler("/clear", func(r *ghttp.Request){
r.Session.Clear()
})
s.SetPort(8600)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetBrowserMode(true)
client.SetPrefix("http://127.0.0.1:8600")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
r1, e1 := client.Get("/set?k=key1&v=100")
if r1 != nil {
defer r1.Close()

View File

@ -0,0 +1,272 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Static_ServerRoot(t *testing.T) {
// SetServerRoot with absolute path
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
defer gfile.Remove(path)
gfile.PutContents(path + "/index.htm", "index")
s.SetServerRoot(path)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "index")
gtest.Assert(client.GetContent("/index.htm"), "index")
})
// SetServerRoot with relative path
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`static/test/%d`, p)
defer gfile.Remove(path)
gfile.PutContents(path + "/index.htm", "index")
s.SetServerRoot(path)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "index")
gtest.Assert(client.GetContent("/index.htm"), "index")
})
}
func Test_Static_Folder_Forbidden(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
defer gfile.Remove(path)
gfile.PutContents(path + "/test.html", "test")
s.SetServerRoot(path)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/index.html"), "Not Found")
gtest.Assert(client.GetContent("/test.html"), "test")
})
}
func Test_Static_IndexFolder(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
defer gfile.Remove(path)
gfile.PutContents(path + "/test.html", "test")
s.SetIndexFolder(true)
s.SetServerRoot(path)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.AssertNE(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/index.html"), "Not Found")
gtest.Assert(client.GetContent("/test.html"), "test")
})
}
func Test_Static_IndexFiles1(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
defer gfile.Remove(path)
gfile.PutContents(path + "/index.html", "index")
gfile.PutContents(path + "/test.html", "test")
s.SetServerRoot(path)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "index")
gtest.Assert(client.GetContent("/index.html"), "index")
gtest.Assert(client.GetContent("/test.html"), "test")
})
}
func Test_Static_IndexFiles2(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
defer gfile.Remove(path)
gfile.PutContents(path + "/test.html", "test")
s.SetIndexFiles([]string{"index.html", "test.html"})
s.SetServerRoot(path)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "test")
gtest.Assert(client.GetContent("/index.html"), "Not Found")
gtest.Assert(client.GetContent("/test.html"), "test")
})
}
func Test_Static_AddSearchPath1(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path1 := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
path2 := fmt.Sprintf(`%s/ghttp/static/test/%d/%d`, gfile.TempDir(), p, p)
defer gfile.Remove(path1)
defer gfile.Remove(path2)
gfile.PutContents(path2 + "/test.html", "test")
s.SetServerRoot(path1)
s.AddSearchPath(path2)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/test.html"), "test")
})
}
func Test_Static_AddSearchPath2(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path1 := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
path2 := fmt.Sprintf(`%s/ghttp/static/test/%d/%d`, gfile.TempDir(), p, p)
defer gfile.Remove(path1)
defer gfile.Remove(path2)
gfile.PutContents(path1 + "/test.html", "test1")
gfile.PutContents(path2 + "/test.html", "test2")
s.SetServerRoot(path1)
s.AddSearchPath(path2)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/test.html"), "test1")
})
}
func Test_Static_AddStaticPath(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path1 := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
path2 := fmt.Sprintf(`%s/ghttp/static/test/%d/%d`, gfile.TempDir(), p, p)
defer gfile.Remove(path1)
defer gfile.Remove(path2)
gfile.PutContents(path1 + "/test.html", "test1")
gfile.PutContents(path2 + "/test.html", "test2")
s.SetServerRoot(path1)
s.AddStaticPath("/my-test", path2)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/test.html"), "test1")
gtest.Assert(client.GetContent("/my-test/test.html"), "test2")
})
}
func Test_Static_AddStaticPath_Priority(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path1 := fmt.Sprintf(`%s/ghttp/static/test/%d/test`, gfile.TempDir(), p)
path2 := fmt.Sprintf(`%s/ghttp/static/test/%d/%d/test`, gfile.TempDir(), p, p)
defer gfile.Remove(path1)
defer gfile.Remove(path2)
gfile.PutContents(path1 + "/test.html", "test1")
gfile.PutContents(path2 + "/test.html", "test2")
s.SetServerRoot(path1)
s.AddStaticPath("/test", path2)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/test.html"), "test1")
gtest.Assert(client.GetContent("/test/test.html"), "test2")
})
}
func Test_Static_Rewrite(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
defer gfile.Remove(path)
gfile.PutContents(path + "/test1.html", "test1")
gfile.PutContents(path + "/test2.html", "test2")
s.SetServerRoot(path)
s.SetRewrite("/test.html", "/test1.html")
s.SetRewriteMap(g.MapStrStr{
"/my-test1" : "/test1.html",
"/my-test2" : "/test2.html",
})
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/test.html"), "test1")
gtest.Assert(client.GetContent("/test1.html"), "test1")
gtest.Assert(client.GetContent("/test2.html"), "test2")
gtest.Assert(client.GetContent("/my-test1"), "test1")
gtest.Assert(client.GetContent("/my-test2"), "test2")
})
}

View File

@ -26,7 +26,8 @@ import (
)
const (
DEFAULT_CONFIG_FILE = "config.toml" // 默认的配置管理文件名称
// 默认的配置管理文件名称
DEFAULT_CONFIG_FILE = "config.toml"
)
// 配置管理对象
@ -37,6 +38,8 @@ type Config struct {
vc *gtype.Bool // 层级检索是否执行分隔符冲突检测(默认为false检测会比较影响检索效率)
}
// New returns a new configuration management object.
//
// 生成一个配置管理对象
func New(path string, file...string) *Config {
name := DEFAULT_CONFIG_FILE
@ -55,6 +58,8 @@ func New(path string, file...string) *Config {
return c
}
// filePath returns the absolute configuration file path for the given filename by <file>.
//
// 判断从哪个配置文件中获取内容,返回配置文件的绝对路径
func (c *Config) filePath(file...string) (path string) {
name := c.name.Val()
@ -70,12 +75,16 @@ func (c *Config) filePath(file...string) (path string) {
})
if path == "" {
buffer := bytes.NewBuffer(nil)
buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" in following paths:", name))
c.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
if c.paths.Len() > 0 {
buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" in following paths:", name))
c.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
} else {
buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path set/add", name))
}
glog.Error(buffer.String())
}
return path
@ -96,7 +105,7 @@ func (c *Config) SetPath(path string) error {
c.jsons.Clear()
c.paths.Clear()
c.paths.Append(realPath)
glog.Debug("[gcfg] SetPath:", realPath)
//glog.Debug("[gcfg] SetPath:", realPath)
return nil
}
@ -120,7 +129,7 @@ func (c *Config) AddPath(path string) error {
return nil
}
c.paths.Append(realPath)
glog.Debug("[gcfg] AddPath:", realPath)
//glog.Debug("[gcfg] AddPath:", realPath)
return nil
}
@ -142,7 +151,7 @@ func (c *Config) GetFilePath(file...string) (path string) {
// 设置配置管理对象的默认文件名称
func (c *Config) SetFileName(name string) {
glog.Debug("[gcfg] SetFileName:", name)
//glog.Debug("[gcfg] SetFileName:", name)
c.name.Set(name)
}

View File

@ -39,14 +39,14 @@ func TestCron_Add_Close(t *testing.T) {
gtest.AssertNE(err3, nil)
gtest.Assert(err4, nil)
gtest.Assert(cron.Size(), 3)
time.Sleep(1100*time.Millisecond)
time.Sleep(1200*time.Millisecond)
gtest.Assert(array.Len(), 2)
time.Sleep(1100*time.Millisecond)
time.Sleep(1200*time.Millisecond)
gtest.Assert(array.Len(), 5)
cron.Close()
time.Sleep(1100*time.Millisecond)
time.Sleep(1200*time.Millisecond)
fixedLength := array.Len()
time.Sleep(1100*time.Millisecond)
time.Sleep(1200*time.Millisecond)
gtest.Assert(array.Len(), fixedLength)
})
}

View File

@ -18,7 +18,7 @@ func (w *Watcher) startWatchLoop() {
// 关闭事件
case <- w.closeChan: return
// 监听事件
// 监听事件
case ev := <- w.watcher.Events:
//fmt.Println("ev:", ev.String())
w.cache.SetIfNotExist(ev.String(), func() interface{} {

View File

@ -235,7 +235,7 @@ func TestTimer_AddLeveledEntry1(t *testing.T) {
})
time.Sleep(1500*time.Millisecond)
gtest.Assert(array.Len(), 0)
time.Sleep(1200*time.Millisecond)
time.Sleep(1300*time.Millisecond)
//glog.Println("check")
gtest.Assert(array.Len(), 1)
})

View File

@ -121,7 +121,7 @@ func (view *View) SetPath(path string) error {
}
view.paths.Clear()
view.paths.Append(realPath)
glog.Debug("[gview] SetPath:", realPath)
//glog.Debug("[gview] SetPath:", realPath)
return nil
}
@ -141,7 +141,7 @@ func (view *View) AddPath(path string) error {
return nil
}
view.paths.Append(realPath)
glog.Debug("[gview] AddPath:", realPath)
//glog.Debug("[gview] AddPath:", realPath)
return nil
}
@ -173,12 +173,16 @@ func (view *View) Parse(file string, params Params, funcmap...map[string]interfa
})
if path == "" {
buffer := bytes.NewBuffer(nil)
buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" in following paths:", file))
view.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
if view.paths.Len() > 0 {
buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" in following paths:", file))
view.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
} else {
buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" with no path set/add", file))
}
glog.Error(buffer.String())
return nil, errors.New(fmt.Sprintf(`tpl "%s" not found`, file))
}

View File

@ -181,14 +181,22 @@ func AssertLTE(value, expect interface{}) {
}
// 断言判断, value IN expect; 注意: expect必须为slice类型
// 断言判断, value IN expect; 注意: expect必须为slice类型
// 注意value参数可以为普通变量也可以为slice类型。
func AssertIN(value, expect interface{}) {
passed := false
passed := true
switch reflect.ValueOf(expect).Kind() {
case reflect.Slice, reflect.Array:
for _, v := range gconv.Interfaces(expect) {
if v == value {
passed = true
for _, v1 := range gconv.Interfaces(value) {
result := false
for _, v2 := range gconv.Interfaces(expect) {
if v1 == v2 {
result = true
break
}
}
if !result {
passed = false
break
}
}
@ -200,24 +208,31 @@ func AssertIN(value, expect interface{}) {
// 断言判断, value NOT IN expect; 注意: expect必须为slice类型
func AssertNI(value, expect interface{}) {
passed := false
passed := true
switch reflect.ValueOf(expect).Kind() {
case reflect.Slice, reflect.Array:
for _, v := range gconv.Interfaces(expect) {
if v == value {
passed = true
for _, v1 := range gconv.Interfaces(value) {
result := true
for _, v2 := range gconv.Interfaces(expect) {
if v1 == v2 {
result = false
break
}
}
if !result {
passed = false
break
}
}
}
if passed {
if !passed {
panic(fmt.Sprintf(`[ASSERT] EXPECT %v NOT IN %v`, value, expect))
}
}
// 提示错误不退出进程执行
func Error(message...interface{}) {
fmt.Fprintf(os.Stderr, "[ERROR] %s\n%s", fmt.Sprint(message...), getBacktrace())
panic(fmt.Sprintf("[ERROR] %s", fmt.Sprint(message...)))
}
// 提示错误并退出进程执行
@ -238,14 +253,21 @@ func compareMap(value, expect interface{}) error {
if rvExpect.Kind() == reflect.Map {
if rvValue.Kind() == reflect.Map {
if rvExpect.Len() == rvValue.Len() {
// 将两个map类型转换为同一个map类型, 才能执行比较,
// 直接使用 rvValue.MapIndex(key).Interface() 当key类型不一致时会报错。
mValue := make(map[string]string)
mExpect := make(map[string]string)
ksValue := rvValue.MapKeys()
ksExpect := rvExpect.MapKeys()
for _, key := range ksValue {
mValue[gconv.String(key.Interface())] = gconv.String(rvValue.MapIndex(key).Interface())
}
for _, key := range ksExpect {
if fmt.Sprintf("%v", rvValue.MapIndex(key).Interface()) != fmt.Sprintf("%v", rvExpect.MapIndex(key).Interface()) {
return fmt.Errorf(`[ASSERT] EXPECT VALUE map["%v"]:%v == %v`,
key,
rvValue.MapIndex(key).Interface(),
rvExpect.MapIndex(key).Interface(),
)
mExpect[gconv.String(key.Interface())] = gconv.String(rvExpect.MapIndex(key).Interface())
}
for k, v := range mExpect {
if v != mValue[k] {
return fmt.Errorf(`[ASSERT] EXPECT VALUE map["%v"]:%v == %v`, k, mValue[k], v)
}
}
} else {

View File

@ -31,15 +31,92 @@ func Replace(origin, search, replace string, count...int) string {
return strings.Replace(origin, search, replace, n)
}
// Replace returns a copy of the string <origin> with string <search> replaced by <replace>
// with case-insensitive.
//
// 字符串替换(大小写不敏感)
func ReplaceI(origin, search, replace string, count...int) string {
n := -1
if len(count) > 0 {
n = count[0]
}
if n == 0 {
return origin
}
length := len(search)
searchLower := strings.ToLower(search)
for {
originLower := strings.ToLower(origin)
if pos := strings.Index(originLower, searchLower); pos != -1 {
origin = origin[ : pos] + replace + origin[pos + length : ]
if n -= 1; n == 0 {
break
}
} else {
break
}
}
return origin
}
// Count counts the number of <substr> appears in <s>. It returns 0 if no <substr> found in <s>.
//
// 计算字符串substr在字符串s中出现的次数如果没有在s中找到substr那么返回0。
func Count(s, substr string) int {
return strings.Count(s, substr)
}
// Count counts the number of <substr> appears in <s>, case-insensitive. It returns 0 if no <substr> found in <s>.
//
// (非大小写敏感)计算字符串substr在字符串s中出现的次数如果没有在s中找到substr那么返回0。
func CountI(s, substr string) int {
return strings.Count(ToLower(s), ToLower(substr))
}
// Replace string by array/slice.
//
// 使用map进行字符串替换(大小写敏感)
func ReplaceByArray(origin string, array []string) string {
for i := 0; i < len(array); i += 2 {
if i + 1 >= len(array) {
break
}
origin = Replace(origin, array[i], array[i + 1])
}
return origin
}
// Replace string by array/slice with case-insensitive.
//
// 使用map进行字符串替换(大小写不敏感)
func ReplaceIByArray(origin string, array []string) string {
for i := 0; i < len(array); i += 2 {
if i + 1 >= len(array) {
break
}
origin = ReplaceI(origin, array[i], array[i + 1])
}
return origin
}
// Replace string by map.
//
// 使用map进行字符串替换(大小写敏感)
func ReplaceByMap(origin string, replaces map[string]string) string {
result := origin
for k, v := range replaces {
result = strings.Replace(result, k, v, -1)
origin = Replace(origin, k, v)
}
return result
return origin
}
// Replace string by map with case-insensitive.
//
// 使用map进行字符串替换(大小写不敏感)
func ReplaceIByMap(origin string, replaces map[string]string) string {
for k, v := range replaces {
origin = ReplaceI(origin, k, v)
}
return origin
}
// ToLower returns a copy of the string s with all Unicode letters mapped to their lower case.

View File

@ -21,6 +21,13 @@ func Test_Replace(t *testing.T) {
gtest.Assert(gstr.Replace(s1, "ab", "AB"), "ABcdEFG乱入的中文ABcdefg")
gtest.Assert(gstr.Replace(s1, "EF", "ef"), "abcdefG乱入的中文abcdefg")
gtest.Assert(gstr.Replace(s1, "MN", "mn"), s1)
gtest.Assert(gstr.ReplaceByArray(s1, g.ArrayStr {
"a" , "A",
"A" , "-",
"a",
}), "-bcdEFG乱入的中文-bcdefg")
gtest.Assert(gstr.ReplaceByMap(s1, g.MapStrStr{
"a" : "A",
"G" : "g",
@ -28,6 +35,36 @@ func Test_Replace(t *testing.T) {
})
}
func Test_ReplaceI_1(t *testing.T) {
gtest.Case(t, func() {
s1 := "abcd乱入的中文ABCD"
s2 := "a"
gtest.Assert(gstr.ReplaceI(s1, "ab", "aa"), "aacd乱入的中文aaCD")
gtest.Assert(gstr.ReplaceI(s1, "ab", "aa", 0), "abcd乱入的中文ABCD")
gtest.Assert(gstr.ReplaceI(s1, "ab", "aa", 1), "aacd乱入的中文ABCD")
gtest.Assert(gstr.ReplaceI(s1, "abcd", "-"), "-乱入的中文-")
gtest.Assert(gstr.ReplaceI(s1, "abcd", "-", 1), "-乱入的中文ABCD")
gtest.Assert(gstr.ReplaceI(s1, "abcd乱入的", ""), "中文ABCD")
gtest.Assert(gstr.ReplaceI(s1, "ABCD乱入的", ""), "中文ABCD")
gtest.Assert(gstr.ReplaceI(s2, "A", "-"), "-")
gtest.Assert(gstr.ReplaceI(s2, "a", "-"), "-")
gtest.Assert(gstr.ReplaceIByArray(s1, g.ArrayStr {
"abcd乱入的" , "-",
"-" , "=",
"a",
}), "=中文ABCD")
gtest.Assert(gstr.ReplaceIByMap(s1, g.MapStrStr {
"ab" : "-",
"CD" : "=",
}), "-=乱入的中文-=")
})
}
func Test_ToLower(t *testing.T) {
gtest.Case(t, func() {
s1 := "abcdEFG乱入的中文abcdefg"

View File

@ -47,7 +47,16 @@ func Convert(i interface{}, t string, extraParams...interface{}) interface{} {
return Time(i, String(extraParams[0]))
}
return Time(i)
case "gtime.Time":
if len(extraParams) > 0 {
return GTime(i, String(extraParams[0]))
}
return *GTime(i)
case "*gtime.Time":
if len(extraParams) > 0 {
return GTime(i, String(extraParams[0]))
}
return GTime(i)
case "time.Duration": return TimeDuration(i)
default:
return i

View File

@ -7,111 +7,135 @@
package gconv
import (
"github.com/gogf/gf/g/internal/empty"
"reflect"
"strings"
)
// 任意类型转换为 map[string]interface{} 类型,
// 如果给定的输入参数i不是map类型那么转换会失败返回nil.
// 当i为struct对象时第二个参数noTagCheck表示不检测json标签否则将会使用json tag作为map的键名。
func Map(i interface{}, noTagCheck...bool) map[string]interface{} {
if i == nil {
func Map(value interface{}, noTagCheck...bool) map[string]interface{} {
if value == nil {
return nil
}
if r, ok := i.(map[string]interface{}); ok {
if r, ok := value.(map[string]interface{}); ok {
return r
} else {
// 仅对常见的几种map组合进行断言最后才会使用反射
m := make(map[string]interface{})
switch i.(type) {
switch value.(type) {
case map[interface{}]interface{}:
for k, v := range i.(map[interface{}]interface{}) {
for k, v := range value.(map[interface{}]interface{}) {
m[String(k)] = v
}
case map[interface{}]string:
for k, v := range i.(map[interface{}]string) {
for k, v := range value.(map[interface{}]string) {
m[String(k)] = v
}
case map[interface{}]int:
for k, v := range i.(map[interface{}]int) {
for k, v := range value.(map[interface{}]int) {
m[String(k)] = v
}
case map[interface{}]uint:
for k, v := range i.(map[interface{}]uint) {
for k, v := range value.(map[interface{}]uint) {
m[String(k)] = v
}
case map[interface{}]float32:
for k, v := range i.(map[interface{}]float32) {
for k, v := range value.(map[interface{}]float32) {
m[String(k)] = v
}
case map[interface{}]float64:
for k, v := range i.(map[interface{}]float64) {
for k, v := range value.(map[interface{}]float64) {
m[String(k)] = v
}
case map[string]bool:
for k, v := range i.(map[string]bool) {
for k, v := range value.(map[string]bool) {
m[k] = v
}
case map[string]int:
for k, v := range i.(map[string]int) {
for k, v := range value.(map[string]int) {
m[k] = v
}
case map[string]uint:
for k, v := range i.(map[string]uint) {
for k, v := range value.(map[string]uint) {
m[k] = v
}
case map[string]float32:
for k, v := range i.(map[string]float32) {
for k, v := range value.(map[string]float32) {
m[k] = v
}
case map[string]float64:
for k, v := range i.(map[string]float64) {
for k, v := range value.(map[string]float64) {
m[k] = v
}
case map[int]interface{}:
for k, v := range i.(map[int]interface{}) {
for k, v := range value.(map[int]interface{}) {
m[String(k)] = v
}
case map[int]string:
for k, v := range i.(map[int]string) {
for k, v := range value.(map[int]string) {
m[String(k)] = v
}
case map[uint]string:
for k, v := range i.(map[uint]string) {
for k, v := range value.(map[uint]string) {
m[String(k)] = v
}
// 不是常见类型,则使用反射
default:
rv := reflect.ValueOf(i)
rv := reflect.ValueOf(value)
kind := rv.Kind()
// 如果是指针,那么需要转换到指针对应的数据项,以便识别真实的类型
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
if kind == reflect.Map {
ks := rv.MapKeys()
for _, k := range ks {
m[String(k.Interface())] = rv.MapIndex(k).Interface()
}
} else if kind == reflect.Struct {
rt := rv.Type()
name := ""
for i := 0; i < rv.NumField(); i++ {
// 检查tag, 支持gconv, json标签, 优先使用gconv
if len(noTagCheck) == 0 || !noTagCheck[0] {
tag := rt.Field(i).Tag
if name = tag.Get("gconv"); name == "" {
if name = tag.Get("json"); name == "" {
name = rt.Field(i).Name
switch kind {
case reflect.Map:
ks := rv.MapKeys()
for _, k := range ks {
m[String(k.Interface())] = rv.MapIndex(k).Interface()
}
case reflect.Struct:
rt := rv.Type()
name := ""
for i := 0; i < rv.NumField(); i++ {
name = ""
// 检查tag, 支持gconv, json标签, 优先使用gconv
if len(noTagCheck) == 0 || !noTagCheck[0] {
tag := rt.Field(i).Tag
if name = tag.Get("gconv"); name == "" {
name = tag.Get("json")
}
}
if name == "" {
name = strings.TrimSpace(rt.Field(i).Name)
} else {
// 支持标准库json特性: -, omitempty
name = strings.TrimSpace(name)
if name == "-" {
continue
}
array := strings.Split(name, ",")
if len(array) > 1 {
switch strings.TrimSpace(array[1]) {
case "omitempty":
if empty.IsEmpty(rv.Field(i).Interface()) {
continue
} else {
name = strings.TrimSpace(array[0])
}
default:
name = strings.TrimSpace(array[0])
}
}
}
m[name] = rv.Field(i).Interface()
}
m[name] = rv.Field(i).Interface()
}
} else {
return nil
default:
return nil
}
}
return m

View File

@ -15,68 +15,60 @@ import (
"strings"
)
// 将params键值对参数映射到对应的struct对象属性上第三个参数mapping为非必需表示自定义名称与属性名称的映射关系。
// 将params键值对参数映射到对应的struct对象属性上
// 第三个参数mapping为非必需表示自定义名称与属性名称的映射关系。
// 需要注意:
// 1、第二个参数应当为struct对象指针
// 2、struct对象的**公开属性(首字母大写)**才能被映射赋值;
// 3、map中的键名可以为小写映射转换时会自动将键名首字母转为大写做匹配映射如果无法匹配则忽略
func Struct(params interface{}, objPointer interface{}, attrMapping...map[string]string) error {
if params == nil {
return nil
return errors.New("params cannot be nil")
}
isParamMap := true
paramsMap := (map[string]interface{})(nil)
// 先将参数转为 map[string]interface{} 类型
if m, ok := params.(map[string]interface{}); ok {
paramsMap = m
} else {
paramsMap = make(map[string]interface{})
if reflect.ValueOf(params).Kind() == reflect.Map {
ks := reflect.ValueOf(params).MapKeys()
vs := reflect.ValueOf(params)
for _, k := range ks {
paramsMap[String(k.Interface())] = vs.MapIndex(k).Interface()
}
} else {
isParamMap = false
}
if objPointer == nil {
return errors.New("object pointer cannot be nil")
}
paramsMap := Map(params)
if paramsMap == nil {
return fmt.Errorf("invalid params: %v", params)
}
// struct的反射对象
elem := reflect.Value{}
if v, ok := objPointer.(reflect.Value); ok {
elem = v
} else {
elem = reflect.ValueOf(objPointer).Elem()
}
// 如果给定的参数不是map类型那么直接将参数值映射到第一个属性上
if !isParamMap {
if err := bindVarToStructByIndex(elem, 0, params); err != nil {
return err
rv := reflect.ValueOf(objPointer)
if kind := rv.Kind(); kind != reflect.Ptr {
return fmt.Errorf("object pointer should be type of: %v", kind)
}
return nil
if !rv.IsValid() || rv.IsNil() {
return errors.New("object pointer cannot be nil")
}
elem = rv.Elem()
}
// 已执行过转换的属性,只执行一次转换
dmap := make(map[string]bool)
// 已执行过转换的属性,只执行一次转换
// 或者是已经执行过转换检查的属性(即使不进行转换), 以便重复判断。
doneMap := make(map[string]bool)
// 首先按照传递的映射关系进行匹配
if len(attrMapping) > 0 && len(attrMapping[0]) > 0 {
for mappingk, mappingv := range attrMapping[0] {
if v, ok := paramsMap[mappingk]; ok {
dmap[mappingv] = true
if err := bindVarToStructAttr(elem, mappingv, v); err != nil {
for mapK, mapV := range attrMapping[0] {
if v, ok := paramsMap[mapK]; ok {
doneMap[mapV] = true
if err := bindVarToStructAttr(elem, mapV, v); err != nil {
return err
}
}
}
}
// 其次匹配对象定义时绑定的属性名称
// 其次匹配对象定义时绑定的属性名称,
// 标签映射关系map如果有的话
tagmap := getTagMapOfStruct(objPointer)
for tagk, tagv := range tagmap {
if _, ok := dmap[tagv]; ok {
tagMap := getTagMapOfStruct(objPointer)
for tagk, tagv := range tagMap {
if _, ok := doneMap[tagv]; ok {
continue
}
if v, ok := paramsMap[tagk]; ok {
dmap[tagv] = true
doneMap[tagv] = true
if err := bindVarToStructAttr(elem, tagv, v); err != nil {
return err
}
@ -88,19 +80,19 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
for i := 0; i < elem.NumField(); i++ {
attrMap[elemType.Field(i).Name] = struct{}{}
}
for mapk, mapv := range paramsMap {
for mapK, mapV := range paramsMap {
name := ""
for _, checkName := range []string {
gstr.UcFirst(mapk),
gstr.ReplaceByMap(mapk, map[string]string{
gstr.UcFirst(mapK),
gstr.ReplaceByMap(mapK, map[string]string{
"_" : "",
"-" : "",
" " : "",
})} {
if _, ok := dmap[checkName]; ok {
})} {
if _, ok := doneMap[checkName]; ok {
continue
}
if _, ok := tagmap[checkName]; ok {
if _, ok := tagMap[checkName]; ok {
continue
}
// 循环查找属性名称进行匹配
@ -114,6 +106,7 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
break
}
}
doneMap[checkName] = true
if name != "" {
break
}
@ -122,7 +115,7 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
if name == "" {
continue
}
if err := bindVarToStructAttr(elem, name, mapv); err != nil {
if err := bindVarToStructAttr(elem, name, mapV); err != nil {
return err
}
}

View File

@ -22,7 +22,7 @@ func TimeDuration(i interface{}) time.Duration {
return time.Duration(Int64(i))
}
// 将变量i转换为time.Time类型
// 将变量i转换为time.Time类型, 自动识别i为时间戳或者标准化的时间字符串。
func GTime(i interface{}, format...string) *gtime.Time {
s := String(i)
if len(s) == 0 {

View File

@ -14,7 +14,7 @@ import (
)
func Test_Map(t *testing.T) {
func Test_Map_Basic(t *testing.T) {
gtest.Case(t, func() {
m1 := map[string]string{
"k" : "v",
@ -36,3 +36,79 @@ func Test_Map(t *testing.T) {
})
})
}
func Test_Map_StructWithGconvTag(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Uid int
Name string
SiteUrl string `gconv:"-"`
NickName string `gconv:"nickname, omitempty"`
Pass1 string `gconv:"password1"`
Pass2 string `gconv:"password2"`
}
user1 := User{
Uid : 100,
Name : "john",
SiteUrl : "https://goframe.org",
Pass1 : "123",
Pass2 : "456",
}
user2 := &user1
map1 := gconv.Map(user1)
map2 := gconv.Map(user2)
gtest.Assert(map1["Uid"], 100)
gtest.Assert(map1["Name"], "john")
gtest.Assert(map1["SiteUrl"], nil)
gtest.Assert(map1["NickName"], nil)
gtest.Assert(map1["nickname"], nil)
gtest.Assert(map1["password1"], "123")
gtest.Assert(map1["password2"], "456")
gtest.Assert(map2["Uid"], 100)
gtest.Assert(map2["Name"], "john")
gtest.Assert(map2["SiteUrl"], nil)
gtest.Assert(map2["NickName"], nil)
gtest.Assert(map2["nickname"], nil)
gtest.Assert(map2["password1"], "123")
gtest.Assert(map2["password2"], "456")
})
}
func Test_Map_StructWithJsonTag(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Uid int
Name string
SiteUrl string `json:"-"`
NickName string `json:"nickname, omitempty"`
Pass1 string `json:"password1"`
Pass2 string `json:"password2"`
}
user1 := User{
Uid : 100,
Name : "john",
SiteUrl : "https://goframe.org",
Pass1 : "123",
Pass2 : "456",
}
user2 := &user1
map1 := gconv.Map(user1)
map2 := gconv.Map(user2)
gtest.Assert(map1["Uid"], 100)
gtest.Assert(map1["Name"], "john")
gtest.Assert(map1["SiteUrl"], nil)
gtest.Assert(map1["NickName"], nil)
gtest.Assert(map1["nickname"], nil)
gtest.Assert(map1["password1"], "123")
gtest.Assert(map1["password2"], "456")
gtest.Assert(map2["Uid"], 100)
gtest.Assert(map2["Name"], "john")
gtest.Assert(map2["SiteUrl"], nil)
gtest.Assert(map2["NickName"], nil)
gtest.Assert(map2["nickname"], nil)
gtest.Assert(map2["password1"], "123")
gtest.Assert(map2["password2"], "456")
})
}

View File

@ -109,11 +109,8 @@ func Test_Struct_Attr_Slice(t *testing.T) {
type User struct {
Scores []int
}
user := new(User)
scores := []interface{}{99, 100, 60, 140}
// 通过map映射转换
user := new(User)
if err := gconv.Struct(g.Map{"Scores" : scores}, user); err != nil {
gtest.Error(err)
} else {
@ -121,15 +118,6 @@ func Test_Struct_Attr_Slice(t *testing.T) {
Scores : []int{99, 100, 60, 140},
})
}
// 通过变量映射转换直接slice赋值
if err := gconv.Struct(scores, user); err != nil {
gtest.Error(err)
} else {
gtest.Assert(user, &User{
Scores : []int{99, 100, 60, 140},
})
}
})
}

View File

@ -105,16 +105,20 @@ var (
)
// 检测单条数据的规则:
// value为需要校验的数据可以为任意基本数据类型
// msgs为自定义错误信息由于同一条数据的校验规则可能存在多条为方便调用参数类型支持 string/map[string]string 允许传递多个自定义的错误信息如果类型为string那么中间使用"|"符号分隔多个自定义错误
// params参数为联合校验参数对于需要联合校验的规则有效required-*、same、different
func Check(value interface{}, rules string, msgs interface{}, params...map[string]interface{}) *Error {
//
// 1. value为需要校验的数据可以为任意基本数据类型
//
// 2. msgs为自定义错误信息由于同一条数据的校验规则可能存在多条为方便调用参数类型支持 string/map/struct/*struct,
// 允许传递多个自定义的错误信息如果类型为string那么中间使用"|"符号分隔多个自定义错误;
//
// 3. params参数为联合校验参数支持任意的map/struct/*struct类型对于需要联合校验的规则有效required-*、same、different
func Check(value interface{}, rules string, msgs interface{}, params...interface{}) *Error {
// 内部会将参数全部转换为字符串类型进行校验
val := strings.TrimSpace(gconv.String(value))
data := make(map[string]string)
errorMsgs := make(map[string]string)
if len(params) > 0 {
for k, v := range params[0] {
for k, v := range gconv.Map(params[0]) {
data[k] = gconv.String(v)
}
}
@ -122,11 +126,12 @@ func Check(value interface{}, rules string, msgs interface{}, params...map[strin
msgArray := make([]string, 0)
customMsgMap := make(map[string]string)
switch v := msgs.(type) {
case map[string]string:
customMsgMap = v
case string:
msgArray = strings.Split(v, "|")
default:
for k, v := range gconv.Map(msgs) {
data[k] = gconv.String(v)
}
}
ruleItems := strings.Split(strings.TrimSpace(rules), "|")
// 规则项预处理, 主要解决规则中存在的"|"关键字符号

View File

@ -13,7 +13,7 @@ func Hello2(r *ghttp.Request) {
func main() {
s := ghttp.GetServer()
s.Domain("127.0.0.1").BindHandler("/", Hello1)
s.Domain("localhost").BindHandler("/", Hello2)
s.Domain("localhost, local").BindHandler("/", Hello2)
s.SetPort(8199)
s.Run()
}

View File

@ -0,0 +1,27 @@
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/frame/gmvc"
"github.com/gogf/gf/g/net/ghttp"
)
type Order2 struct {
gmvc.Controller
}
func (o *Order2) Get() {
o.Response.Write("GET")
}
func main() {
s := g.Server()
s.BindHookHandlerByMap("/api.v2/*any", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.CORSDefault()
},
})
s.BindControllerRest("/api.v2/{.struct}", new(Order2))
s.SetPort(8199)
s.Run()
}

View File

@ -1,23 +0,0 @@
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Writeln("hello")
})
s.BindHandler("/restart", func(r *ghttp.Request){
r.Response.Writeln("restart server")
r.Server.Restart()
})
s.BindHandler("/shutdown", func(r *ghttp.Request){
r.Response.Writeln("shutdown server")
r.Server.Shutdown()
})
s.SetPort(8199, 8200)
s.Run()
}

View File

@ -0,0 +1,33 @@
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/frame/gmvc"
)
type Controller struct {
gmvc.Controller
}
func (c *Controller) Login() {
c.Session.Id()
c.Response.Write("这个页面用户填写信息执行登录")
}
func (c *Controller) DoLogin() {
c.Session.Set("key", "value")
//c.Response.Header().Set("Set-Cookie", "myid=1B27UGQGCIBP0P70; Path=/; Domain=127.0.0.1; Expires=Wed, 04 Mar 2020 07:12:05 GMT")
c.Response.RedirectTo("/main")
}
func (c *Controller) Main() {
c.Response.WriteJson(c.Session.Data())
}
func main() {
s := g.Server()
s.BindController("/", new(Controller))
s.SetPort(8199)
s.Run()
}

View File

@ -15,9 +15,11 @@ func main() {
for {
data, err := conn.Recv(-1)
if len(data) > 0 {
fmt.Println(string(data))
fmt.Println(string(data))
}
if err != nil {
// client closed, err will be: EOF
fmt.Println(err)
break
}
}
@ -36,5 +38,12 @@ func main() {
glog.Error(err)
}
time.Sleep(time.Second)
if i == 5 {
conn.Close()
break
}
}
// exit after 5 seconds
time.Sleep(5*time.Second)
}

View File

@ -0,0 +1,23 @@
package main
import (
"github.com/gogf/gf/g/net/gtcp"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/util/gconv"
"time"
)
func main() {
// Client
conn, err := gtcp.NewConn("127.0.0.1:8999")
if err != nil {
panic(err)
}
defer conn.Close()
for i := 0; i < 3; i++ {
if err := conn.Send([]byte(gconv.String(i))); err != nil {
glog.Error(err)
}
time.Sleep(time.Second)
}
}

View File

@ -0,0 +1,24 @@
package main
import (
"fmt"
"github.com/gogf/gf/g/net/gtcp"
)
func main() {
// Server
gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) {
defer conn.Close()
for {
data, err := conn.Recv(-1)
if len(data) > 0 {
fmt.Println(string(data))
}
if err != nil {
// client closed, err will be: EOF
fmt.Println(err)
break
}
}
}).Run()
}

View File

@ -2,13 +2,12 @@ package main
import (
"fmt"
"github.com/gogf/gf/g/os/gtime"
"strconv"
"strings"
)
func main() {
s := "abc我是中国人é"
fmt.Println(len(s))
for i := 0; i < len(s); i++ {
fmt.Println(s[i])
}
//t := gconv.GTime("2010-10-10 00:00:01")
fmt.Println(strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 36)))
}

View File

@ -0,0 +1,23 @@
package main
import (
"fmt"
"github.com/gogf/gf/g/util/gconv"
)
func main() {
type User struct {
Uid int
Name string `gconv:"-"`
NickName string `gconv:"nickname, omitempty"`
Pass1 string `gconv:"password1"`
Pass2 string `gconv:"password2"`
}
user := User{
Uid : 100,
Name : "john",
Pass1 : "123",
Pass2 : "456",
}
fmt.Println(gconv.Map(user))
}

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