Merge branch 'develop' into frt/supports_for_4144
# Conflicts: # client/orm/orm_queryset.go
This commit is contained in:
15
client/cache/README.md
vendored
15
client/cache/README.md
vendored
@@ -1,37 +1,34 @@
|
||||
## cache
|
||||
cache is a Go cache manager. It can use many cache adapters. The repo is inspired by `database/sql` .
|
||||
|
||||
cache is a Go cache manager. It can use many cache adapters. The repo is inspired by `database/sql` .
|
||||
|
||||
## How to install?
|
||||
|
||||
go get github.com/astaxie/beego/cache
|
||||
|
||||
go get github.com/beego/beego/v2/cache
|
||||
|
||||
## What adapters are supported?
|
||||
|
||||
As of now this cache support memory, Memcache and Redis.
|
||||
|
||||
|
||||
## How to use it?
|
||||
|
||||
First you must import it
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/cache"
|
||||
"github.com/beego/beego/v2/cache"
|
||||
)
|
||||
|
||||
Then init a Cache (example with memory adapter)
|
||||
|
||||
bm, err := cache.NewCache("memory", `{"interval":60}`)
|
||||
|
||||
Use it like this:
|
||||
|
||||
Use it like this:
|
||||
|
||||
bm.Put("astaxie", 1, 10 * time.Second)
|
||||
bm.Get("astaxie")
|
||||
bm.IsExist("astaxie")
|
||||
bm.Delete("astaxie")
|
||||
|
||||
|
||||
## Memory adapter
|
||||
|
||||
Configure memory adapter like this:
|
||||
@@ -40,7 +37,6 @@ Configure memory adapter like this:
|
||||
|
||||
interval means the gc time. The cache will check at each time interval, whether item has expired.
|
||||
|
||||
|
||||
## Memcache adapter
|
||||
|
||||
Memcache adapter use the [gomemcache](http://github.com/bradfitz/gomemcache) client.
|
||||
@@ -49,7 +45,6 @@ Configure like this:
|
||||
|
||||
{"conn":"127.0.0.1:11211"}
|
||||
|
||||
|
||||
## Redis adapter
|
||||
|
||||
Redis adapter use the [redigo](http://github.com/gomodule/redigo) client.
|
||||
|
||||
2
client/cache/cache.go
vendored
2
client/cache/cache.go
vendored
@@ -16,7 +16,7 @@
|
||||
// Usage:
|
||||
//
|
||||
// import(
|
||||
// "github.com/astaxie/beego/cache"
|
||||
// "github.com/beego/beego/v2/cache"
|
||||
// )
|
||||
//
|
||||
// bm, err := cache.NewCache("memory", `{"interval":60}`)
|
||||
|
||||
151
client/cache/cache_test.go
vendored
151
client/cache/cache_test.go
vendored
@@ -16,6 +16,7 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -46,11 +47,11 @@ func TestCacheIncr(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
bm, err := NewCache("memory", `{"interval":20}`)
|
||||
bm, err := NewCache("memory", `{"interval":1}`)
|
||||
if err != nil {
|
||||
t.Error("init err")
|
||||
}
|
||||
timeoutDuration := 10 * time.Second
|
||||
timeoutDuration := 5 * time.Second
|
||||
if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil {
|
||||
t.Error("set Error", err)
|
||||
}
|
||||
@@ -62,7 +63,7 @@ func TestCache(t *testing.T) {
|
||||
t.Error("get err")
|
||||
}
|
||||
|
||||
time.Sleep(30 * time.Second)
|
||||
time.Sleep(7 * time.Second)
|
||||
|
||||
if res, _ := bm.IsExist(context.Background(), "astaxie"); res {
|
||||
t.Error("check err")
|
||||
@@ -72,21 +73,13 @@ func TestCache(t *testing.T) {
|
||||
t.Error("set Error", err)
|
||||
}
|
||||
|
||||
if err = bm.Incr(context.Background(), "astaxie"); err != nil {
|
||||
t.Error("Incr Error", err)
|
||||
}
|
||||
// test different integer type for incr & decr
|
||||
testMultiTypeIncrDecr(t, bm, timeoutDuration)
|
||||
|
||||
if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 2 {
|
||||
t.Error("get err")
|
||||
}
|
||||
// test overflow of incr&decr
|
||||
testIncrOverFlow(t, bm, timeoutDuration)
|
||||
testDecrOverFlow(t, bm, timeoutDuration)
|
||||
|
||||
if err = bm.Decr(context.Background(), "astaxie"); err != nil {
|
||||
t.Error("Decr Error", err)
|
||||
}
|
||||
|
||||
if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 1 {
|
||||
t.Error("get err")
|
||||
}
|
||||
bm.Delete(context.Background(), "astaxie")
|
||||
if res, _ := bm.IsExist(context.Background(), "astaxie"); res {
|
||||
t.Error("delete err")
|
||||
@@ -120,6 +113,20 @@ func TestCache(t *testing.T) {
|
||||
if vv[1].(string) != "author1" {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
|
||||
vv, err = bm.GetMulti(context.Background(), []string{"astaxie0", "astaxie1"})
|
||||
if len(vv) != 2 {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
if vv[0] != nil {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
if vv[1].(string) != "author1" {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
if err != nil && err.Error() != "key [astaxie0] error: the key isn't exist" {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileCache(t *testing.T) {
|
||||
@@ -139,21 +146,13 @@ func TestFileCache(t *testing.T) {
|
||||
t.Error("get err")
|
||||
}
|
||||
|
||||
if err = bm.Incr(context.Background(), "astaxie"); err != nil {
|
||||
t.Error("Incr Error", err)
|
||||
}
|
||||
// test different integer type for incr & decr
|
||||
testMultiTypeIncrDecr(t, bm, timeoutDuration)
|
||||
|
||||
if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 2 {
|
||||
t.Error("get err")
|
||||
}
|
||||
// test overflow of incr&decr
|
||||
testIncrOverFlow(t, bm, timeoutDuration)
|
||||
testDecrOverFlow(t, bm, timeoutDuration)
|
||||
|
||||
if err = bm.Decr(context.Background(), "astaxie"); err != nil {
|
||||
t.Error("Decr Error", err)
|
||||
}
|
||||
|
||||
if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 1 {
|
||||
t.Error("get err")
|
||||
}
|
||||
bm.Delete(context.Background(), "astaxie")
|
||||
if res, _ := bm.IsExist(context.Background(), "astaxie"); res {
|
||||
t.Error("delete err")
|
||||
@@ -189,5 +188,99 @@ func TestFileCache(t *testing.T) {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
|
||||
vv, err = bm.GetMulti(context.Background(), []string{"astaxie0", "astaxie1"})
|
||||
if len(vv) != 2 {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
if vv[0] != nil {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
if vv[1].(string) != "author1" {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
|
||||
os.RemoveAll("cache")
|
||||
}
|
||||
|
||||
func testMultiTypeIncrDecr(t *testing.T, c Cache, timeout time.Duration) {
|
||||
testIncrDecr(t, c, 1, 2, timeout)
|
||||
testIncrDecr(t, c, int32(1), int32(2), timeout)
|
||||
testIncrDecr(t, c, int64(1), int64(2), timeout)
|
||||
testIncrDecr(t, c, uint(1), uint(2), timeout)
|
||||
testIncrDecr(t, c, uint32(1), uint32(2), timeout)
|
||||
testIncrDecr(t, c, uint64(1), uint64(2), timeout)
|
||||
}
|
||||
|
||||
func testIncrDecr(t *testing.T, c Cache, beforeIncr interface{}, afterIncr interface{}, timeout time.Duration) {
|
||||
var err error
|
||||
ctx := context.Background()
|
||||
key := "incDecKey"
|
||||
if err = c.Put(ctx, key, beforeIncr, timeout); err != nil {
|
||||
t.Error("Get Error", err)
|
||||
}
|
||||
|
||||
if err = c.Incr(ctx, key); err != nil {
|
||||
t.Error("Incr Error", err)
|
||||
}
|
||||
|
||||
if v, _ := c.Get(ctx, key); v != afterIncr {
|
||||
t.Error("Get Error")
|
||||
}
|
||||
|
||||
if err = c.Decr(ctx, key); err != nil {
|
||||
t.Error("Decr Error", err)
|
||||
}
|
||||
|
||||
if v, _ := c.Get(ctx, key); v != beforeIncr {
|
||||
t.Error("Get Error")
|
||||
}
|
||||
|
||||
if err := c.Delete(ctx, key); err != nil {
|
||||
t.Error("Delete Error")
|
||||
}
|
||||
}
|
||||
|
||||
func testIncrOverFlow(t *testing.T, c Cache, timeout time.Duration) {
|
||||
var err error
|
||||
ctx := context.Background()
|
||||
key := "incKey"
|
||||
|
||||
// int64
|
||||
if err = c.Put(ctx, key, int64(math.MaxInt64), timeout); err != nil {
|
||||
t.Error("Put Error: ", err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err = c.Delete(ctx, key); err != nil {
|
||||
t.Errorf("Delete error: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
if err = c.Incr(ctx, key); err == nil {
|
||||
t.Error("Incr error")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func testDecrOverFlow(t *testing.T, c Cache, timeout time.Duration) {
|
||||
var err error
|
||||
ctx := context.Background()
|
||||
key := "decKey"
|
||||
|
||||
// int64
|
||||
if err = c.Put(ctx, key, int64(math.MinInt64), timeout); err != nil {
|
||||
t.Error("Put Error: ", err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err = c.Delete(ctx, key); err != nil {
|
||||
t.Errorf("Delete error: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
if err = c.Decr(ctx, key); err == nil {
|
||||
t.Error("Decr error")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
83
client/cache/calc_utils.go
vendored
Normal file
83
client/cache/calc_utils.go
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
func incr(originVal interface{}) (interface{}, error) {
|
||||
switch val := originVal.(type) {
|
||||
case int:
|
||||
tmp := val + 1
|
||||
if val > 0 && tmp < 0 {
|
||||
return nil, fmt.Errorf("increment would overflow")
|
||||
}
|
||||
return tmp, nil
|
||||
case int32:
|
||||
if val == math.MaxInt32 {
|
||||
return nil, fmt.Errorf("increment would overflow")
|
||||
}
|
||||
return val + 1, nil
|
||||
case int64:
|
||||
if val == math.MaxInt64 {
|
||||
return nil, fmt.Errorf("increment would overflow")
|
||||
}
|
||||
return val + 1, nil
|
||||
case uint:
|
||||
tmp := val + 1
|
||||
if tmp < val {
|
||||
return nil, fmt.Errorf("increment would overflow")
|
||||
}
|
||||
return tmp, nil
|
||||
case uint32:
|
||||
if val == math.MaxUint32 {
|
||||
return nil, fmt.Errorf("increment would overflow")
|
||||
}
|
||||
return val + 1, nil
|
||||
case uint64:
|
||||
if val == math.MaxUint64 {
|
||||
return nil, fmt.Errorf("increment would overflow")
|
||||
}
|
||||
return val + 1, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("item val is not (u)int (u)int32 (u)int64")
|
||||
}
|
||||
}
|
||||
|
||||
func decr(originVal interface{}) (interface{}, error) {
|
||||
switch val := originVal.(type) {
|
||||
case int:
|
||||
tmp := val - 1
|
||||
if val < 0 && tmp > 0 {
|
||||
return nil, fmt.Errorf("decrement would overflow")
|
||||
}
|
||||
return tmp, nil
|
||||
case int32:
|
||||
if val == math.MinInt32 {
|
||||
return nil, fmt.Errorf("decrement would overflow")
|
||||
}
|
||||
return val - 1, nil
|
||||
case int64:
|
||||
if val == math.MinInt64 {
|
||||
return nil, fmt.Errorf("decrement would overflow")
|
||||
}
|
||||
return val - 1, nil
|
||||
case uint:
|
||||
if val == 0 {
|
||||
return nil, fmt.Errorf("decrement would overflow")
|
||||
}
|
||||
return val - 1, nil
|
||||
case uint32:
|
||||
if val == 0 {
|
||||
return nil, fmt.Errorf("increment would overflow")
|
||||
}
|
||||
return val - 1, nil
|
||||
case uint64:
|
||||
if val == 0 {
|
||||
return nil, fmt.Errorf("increment would overflow")
|
||||
}
|
||||
return val - 1, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("item val is not (u)int (u)int32 (u)int64")
|
||||
}
|
||||
}
|
||||
241
client/cache/calc_utils_test.go
vendored
Normal file
241
client/cache/calc_utils_test.go
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIncr(t *testing.T) {
|
||||
// int
|
||||
var originVal interface{} = int(1)
|
||||
var updateVal interface{} = int(2)
|
||||
val, err := incr(originVal)
|
||||
if err != nil {
|
||||
t.Errorf("incr failed, err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if val != updateVal {
|
||||
t.Errorf("incr failed, expect %v, but %v actually", updateVal, val)
|
||||
return
|
||||
}
|
||||
_, err = incr(int(1 << (strconv.IntSize - 1) - 1))
|
||||
if err == nil {
|
||||
t.Error("incr failed")
|
||||
return
|
||||
}
|
||||
|
||||
// int32
|
||||
originVal = int32(1)
|
||||
updateVal = int32(2)
|
||||
val, err = incr(originVal)
|
||||
if err != nil {
|
||||
t.Errorf("incr failed, err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if val != updateVal {
|
||||
t.Errorf("incr failed, expect %v, but %v actually", updateVal, val)
|
||||
return
|
||||
}
|
||||
_, err = incr(int32(math.MaxInt32))
|
||||
if err == nil {
|
||||
t.Error("incr failed")
|
||||
return
|
||||
}
|
||||
|
||||
// int64
|
||||
originVal = int64(1)
|
||||
updateVal = int64(2)
|
||||
val, err = incr(originVal)
|
||||
if err != nil {
|
||||
t.Errorf("incr failed, err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if val != updateVal {
|
||||
t.Errorf("incr failed, expect %v, but %v actually", updateVal, val)
|
||||
return
|
||||
}
|
||||
_, err = incr(int64(math.MaxInt64))
|
||||
if err == nil {
|
||||
t.Error("incr failed")
|
||||
return
|
||||
}
|
||||
|
||||
// uint
|
||||
originVal = uint(1)
|
||||
updateVal = uint(2)
|
||||
val, err = incr(originVal)
|
||||
if err != nil {
|
||||
t.Errorf("incr failed, err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if val != updateVal {
|
||||
t.Errorf("incr failed, expect %v, but %v actually", updateVal, val)
|
||||
return
|
||||
}
|
||||
_, err = incr(uint(1 << (strconv.IntSize) - 1))
|
||||
if err == nil {
|
||||
t.Error("incr failed")
|
||||
return
|
||||
}
|
||||
|
||||
// uint32
|
||||
originVal = uint32(1)
|
||||
updateVal = uint32(2)
|
||||
val, err = incr(originVal)
|
||||
if err != nil {
|
||||
t.Errorf("incr failed, err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if val != updateVal {
|
||||
t.Errorf("incr failed, expect %v, but %v actually", updateVal, val)
|
||||
return
|
||||
}
|
||||
_, err = incr(uint32(math.MaxUint32))
|
||||
if err == nil {
|
||||
t.Error("incr failed")
|
||||
return
|
||||
}
|
||||
|
||||
// uint64
|
||||
originVal = uint64(1)
|
||||
updateVal = uint64(2)
|
||||
val, err = incr(originVal)
|
||||
if err != nil {
|
||||
t.Errorf("incr failed, err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if val != updateVal {
|
||||
t.Errorf("incr failed, expect %v, but %v actually", updateVal, val)
|
||||
return
|
||||
}
|
||||
_, err = incr(uint64(math.MaxUint64))
|
||||
if err == nil {
|
||||
t.Error("incr failed")
|
||||
return
|
||||
}
|
||||
|
||||
// other type
|
||||
_, err = incr("string")
|
||||
if err == nil {
|
||||
t.Error("incr failed")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecr(t *testing.T) {
|
||||
// int
|
||||
var originVal interface{} = int(2)
|
||||
var updateVal interface{} = int(1)
|
||||
val, err := decr(originVal)
|
||||
if err != nil {
|
||||
t.Errorf("decr failed, err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if val != updateVal {
|
||||
t.Errorf("decr failed, expect %v, but %v actually", updateVal, val)
|
||||
return
|
||||
}
|
||||
_, err = decr(int(-1 << (strconv.IntSize - 1)))
|
||||
if err == nil {
|
||||
t.Error("decr failed")
|
||||
return
|
||||
}
|
||||
|
||||
// int32
|
||||
originVal = int32(2)
|
||||
updateVal = int32(1)
|
||||
val, err = decr(originVal)
|
||||
if err != nil {
|
||||
t.Errorf("decr failed, err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if val != updateVal {
|
||||
t.Errorf("decr failed, expect %v, but %v actually", updateVal, val)
|
||||
return
|
||||
}
|
||||
_, err = decr(int32(math.MinInt32))
|
||||
if err == nil {
|
||||
t.Error("decr failed")
|
||||
return
|
||||
}
|
||||
|
||||
// int64
|
||||
originVal = int64(2)
|
||||
updateVal = int64(1)
|
||||
val, err = decr(originVal)
|
||||
if err != nil {
|
||||
t.Errorf("decr failed, err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if val != updateVal {
|
||||
t.Errorf("decr failed, expect %v, but %v actually", updateVal, val)
|
||||
return
|
||||
}
|
||||
_, err = decr(int64(math.MinInt64))
|
||||
if err == nil {
|
||||
t.Error("decr failed")
|
||||
return
|
||||
}
|
||||
|
||||
// uint
|
||||
originVal = uint(2)
|
||||
updateVal = uint(1)
|
||||
val, err = decr(originVal)
|
||||
if err != nil {
|
||||
t.Errorf("decr failed, err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if val != updateVal {
|
||||
t.Errorf("decr failed, expect %v, but %v actually", updateVal, val)
|
||||
return
|
||||
}
|
||||
_, err = decr(uint(0))
|
||||
if err == nil {
|
||||
t.Error("decr failed")
|
||||
return
|
||||
}
|
||||
|
||||
// uint32
|
||||
originVal = uint32(2)
|
||||
updateVal = uint32(1)
|
||||
val, err = decr(originVal)
|
||||
if err != nil {
|
||||
t.Errorf("decr failed, err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if val != updateVal {
|
||||
t.Errorf("decr failed, expect %v, but %v actually", updateVal, val)
|
||||
return
|
||||
}
|
||||
_, err = decr(uint32(0))
|
||||
if err == nil {
|
||||
t.Error("decr failed")
|
||||
return
|
||||
}
|
||||
|
||||
// uint64
|
||||
originVal = uint64(2)
|
||||
updateVal = uint64(1)
|
||||
val, err = decr(originVal)
|
||||
if err != nil {
|
||||
t.Errorf("decr failed, err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if val != updateVal {
|
||||
t.Errorf("decr failed, expect %v, but %v actually", updateVal, val)
|
||||
return
|
||||
}
|
||||
_, err = decr(uint64(0))
|
||||
if err == nil {
|
||||
t.Error("decr failed")
|
||||
return
|
||||
}
|
||||
|
||||
// other type
|
||||
_, err = decr("string")
|
||||
if err == nil {
|
||||
t.Error("decr failed")
|
||||
return
|
||||
}
|
||||
}
|
||||
61
client/cache/file.go
vendored
61
client/cache/file.go
vendored
@@ -26,8 +26,8 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -144,17 +144,22 @@ func (fc *FileCache) Get(ctx context.Context, key string) (interface{}, error) {
|
||||
// GetMulti gets values from file cache.
|
||||
// if nonexistent or expired return an empty string.
|
||||
func (fc *FileCache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
|
||||
var rc []interface{}
|
||||
for _, key := range keys {
|
||||
val, err := fc.Get(context.Background(), key)
|
||||
if err != nil {
|
||||
rc = append(rc, err)
|
||||
} else {
|
||||
rc = append(rc, val)
|
||||
}
|
||||
rc := make([]interface{}, len(keys))
|
||||
keysErr := make([]string, 0)
|
||||
|
||||
for i, ki := range keys {
|
||||
val, err := fc.Get(context.Background(), ki)
|
||||
if err != nil {
|
||||
keysErr = append(keysErr, fmt.Sprintf("key [%s] error: %s", ki, err.Error()))
|
||||
continue
|
||||
}
|
||||
rc[i] = val
|
||||
}
|
||||
return rc, nil
|
||||
|
||||
if len(keysErr) == 0 {
|
||||
return rc, nil
|
||||
}
|
||||
return rc, errors.New(strings.Join(keysErr, "; "))
|
||||
}
|
||||
|
||||
// Put value into file cache.
|
||||
@@ -189,28 +194,32 @@ func (fc *FileCache) Delete(ctx context.Context, key string) error {
|
||||
// Incr increases cached int value.
|
||||
// fc value is saved forever unless deleted.
|
||||
func (fc *FileCache) Incr(ctx context.Context, key string) error {
|
||||
data, _ := fc.Get(context.Background(), key)
|
||||
var incr int
|
||||
if reflect.TypeOf(data).Name() != "int" {
|
||||
incr = 0
|
||||
} else {
|
||||
incr = data.(int) + 1
|
||||
data, err := fc.Get(context.Background(), key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fc.Put(context.Background(), key, incr, time.Duration(fc.EmbedExpiry))
|
||||
return nil
|
||||
|
||||
val, err := incr(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fc.Put(context.Background(), key, val, time.Duration(fc.EmbedExpiry))
|
||||
}
|
||||
|
||||
// Decr decreases cached int value.
|
||||
func (fc *FileCache) Decr(ctx context.Context, key string) error {
|
||||
data, _ := fc.Get(context.Background(), key)
|
||||
var decr int
|
||||
if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 {
|
||||
decr = 0
|
||||
} else {
|
||||
decr = data.(int) - 1
|
||||
data, err := fc.Get(context.Background(), key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fc.Put(context.Background(), key, decr, time.Duration(fc.EmbedExpiry))
|
||||
return nil
|
||||
|
||||
val, err := decr(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fc.Put(context.Background(), key, val, time.Duration(fc.EmbedExpiry))
|
||||
}
|
||||
|
||||
// IsExist checks if value exists.
|
||||
|
||||
31
client/cache/memcache/memcache.go
vendored
31
client/cache/memcache/memcache.go
vendored
@@ -20,8 +20,8 @@
|
||||
//
|
||||
// Usage:
|
||||
// import(
|
||||
// _ "github.com/astaxie/beego/cache/memcache"
|
||||
// "github.com/astaxie/beego/cache"
|
||||
// _ "github.com/beego/beego/v2/cache/memcache"
|
||||
// "github.com/beego/beego/v2/cache"
|
||||
// )
|
||||
//
|
||||
// bm, err := cache.NewCache("memcache", `{"conn":"127.0.0.1:11211"}`)
|
||||
@@ -33,12 +33,13 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bradfitz/gomemcache/memcache"
|
||||
|
||||
"github.com/astaxie/beego/client/cache"
|
||||
"github.com/beego/beego/v2/client/cache"
|
||||
)
|
||||
|
||||
// Cache Memcache adapter.
|
||||
@@ -68,19 +69,31 @@ func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) {
|
||||
|
||||
// GetMulti gets a value from a key in memcache.
|
||||
func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
|
||||
var rv []interface{}
|
||||
rv := make([]interface{}, len(keys))
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
return rv, err
|
||||
}
|
||||
}
|
||||
|
||||
mv, err := rc.conn.GetMulti(keys)
|
||||
if err == nil {
|
||||
for _, v := range mv {
|
||||
rv = append(rv, v.Value)
|
||||
}
|
||||
if err != nil {
|
||||
return rv, err
|
||||
}
|
||||
return rv, err
|
||||
|
||||
keysErr := make([]string, 0)
|
||||
for i, ki := range keys {
|
||||
if _, ok := mv[ki]; !ok {
|
||||
keysErr = append(keysErr, fmt.Sprintf("key [%s] error: %s", ki, "the key isn't exist"))
|
||||
continue
|
||||
}
|
||||
rv[i] = mv[ki].Value
|
||||
}
|
||||
|
||||
if len(keysErr) == 0 {
|
||||
return rv, nil
|
||||
}
|
||||
return rv, fmt.Errorf(strings.Join(keysErr, "; "))
|
||||
}
|
||||
|
||||
// Put puts a value into memcache.
|
||||
|
||||
17
client/cache/memcache/memcache_test.go
vendored
17
client/cache/memcache/memcache_test.go
vendored
@@ -24,11 +24,10 @@ import (
|
||||
|
||||
_ "github.com/bradfitz/gomemcache/memcache"
|
||||
|
||||
"github.com/astaxie/beego/client/cache"
|
||||
"github.com/beego/beego/v2/client/cache"
|
||||
)
|
||||
|
||||
func TestMemcacheCache(t *testing.T) {
|
||||
|
||||
addr := os.Getenv("MEMCACHE_ADDR")
|
||||
if addr == "" {
|
||||
addr = "127.0.0.1:11211"
|
||||
@@ -114,6 +113,20 @@ func TestMemcacheCache(t *testing.T) {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
|
||||
vv, err = bm.GetMulti(context.Background(), []string{"astaxie0", "astaxie1"})
|
||||
if len(vv) != 2 {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
if vv[0] != nil {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
if string(vv[1].([]byte)) != "author1" {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
if err != nil && err.Error() == "key [astaxie0] error: key isn't exist" {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
|
||||
// test clear all
|
||||
if err = bm.ClearAll(context.Background()); err != nil {
|
||||
t.Error("clear all err")
|
||||
|
||||
76
client/cache/memory.go
vendored
76
client/cache/memory.go
vendored
@@ -18,6 +18,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -68,22 +70,28 @@ func (bc *MemoryCache) Get(ctx context.Context, key string) (interface{}, error)
|
||||
}
|
||||
return itm.val, nil
|
||||
}
|
||||
return nil, nil
|
||||
return nil, errors.New("the key isn't exist")
|
||||
}
|
||||
|
||||
// GetMulti gets caches from memory.
|
||||
// If non-existent or expired, return nil.
|
||||
func (bc *MemoryCache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
|
||||
var rc []interface{}
|
||||
for _, name := range keys {
|
||||
val, err := bc.Get(context.Background(), name)
|
||||
rc := make([]interface{}, len(keys))
|
||||
keysErr := make([]string, 0)
|
||||
|
||||
for i, ki := range keys {
|
||||
val, err := bc.Get(context.Background(), ki)
|
||||
if err != nil {
|
||||
rc = append(rc, err)
|
||||
} else {
|
||||
rc = append(rc, val)
|
||||
keysErr = append(keysErr, fmt.Sprintf("key [%s] error: %s", ki, err.Error()))
|
||||
continue
|
||||
}
|
||||
rc[i] = val
|
||||
}
|
||||
return rc, nil
|
||||
|
||||
if len(keysErr) == 0 {
|
||||
return rc, nil
|
||||
}
|
||||
return rc, errors.New(strings.Join(keysErr, "; "))
|
||||
}
|
||||
|
||||
// Put puts cache into memory.
|
||||
@@ -122,22 +130,12 @@ func (bc *MemoryCache) Incr(ctx context.Context, key string) error {
|
||||
if !ok {
|
||||
return errors.New("key not exist")
|
||||
}
|
||||
switch val := itm.val.(type) {
|
||||
case int:
|
||||
itm.val = val + 1
|
||||
case int32:
|
||||
itm.val = val + 1
|
||||
case int64:
|
||||
itm.val = val + 1
|
||||
case uint:
|
||||
itm.val = val + 1
|
||||
case uint32:
|
||||
itm.val = val + 1
|
||||
case uint64:
|
||||
itm.val = val + 1
|
||||
default:
|
||||
return errors.New("item val is not (u)int (u)int32 (u)int64")
|
||||
|
||||
val, err := incr(itm.val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itm.val = val
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -149,34 +147,12 @@ func (bc *MemoryCache) Decr(ctx context.Context, key string) error {
|
||||
if !ok {
|
||||
return errors.New("key not exist")
|
||||
}
|
||||
switch val := itm.val.(type) {
|
||||
case int:
|
||||
itm.val = val - 1
|
||||
case int64:
|
||||
itm.val = val - 1
|
||||
case int32:
|
||||
itm.val = val - 1
|
||||
case uint:
|
||||
if val > 0 {
|
||||
itm.val = val - 1
|
||||
} else {
|
||||
return errors.New("item val is less than 0")
|
||||
}
|
||||
case uint32:
|
||||
if val > 0 {
|
||||
itm.val = val - 1
|
||||
} else {
|
||||
return errors.New("item val is less than 0")
|
||||
}
|
||||
case uint64:
|
||||
if val > 0 {
|
||||
itm.val = val - 1
|
||||
} else {
|
||||
return errors.New("item val is less than 0")
|
||||
}
|
||||
default:
|
||||
return errors.New("item val is not int int64 int32")
|
||||
|
||||
val, err := decr(itm.val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itm.val = val
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
6
client/cache/redis/redis.go
vendored
6
client/cache/redis/redis.go
vendored
@@ -20,8 +20,8 @@
|
||||
//
|
||||
// Usage:
|
||||
// import(
|
||||
// _ "github.com/astaxie/beego/cache/redis"
|
||||
// "github.com/astaxie/beego/cache"
|
||||
// _ "github.com/beego/beego/v2/cache/redis"
|
||||
// "github.com/beego/beego/v2/cache"
|
||||
// )
|
||||
//
|
||||
// bm, err := cache.NewCache("redis", `{"conn":"127.0.0.1:11211"}`)
|
||||
@@ -40,7 +40,7 @@ import (
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
|
||||
"github.com/astaxie/beego/client/cache"
|
||||
"github.com/beego/beego/v2/client/cache"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
10
client/cache/redis/redis_test.go
vendored
10
client/cache/redis/redis_test.go
vendored
@@ -24,7 +24,7 @@ import (
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/astaxie/beego/client/cache"
|
||||
"github.com/beego/beego/v2/client/cache"
|
||||
)
|
||||
|
||||
func TestRedisCache(t *testing.T) {
|
||||
@@ -113,6 +113,14 @@ func TestRedisCache(t *testing.T) {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
|
||||
vv, _ = bm.GetMulti(context.Background(), []string{"astaxie0", "astaxie1"})
|
||||
if vv[0] != nil {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
if v, _ := redis.String(vv[1], nil); v != "author1" {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
|
||||
// test clear all
|
||||
if err = bm.ClearAll(context.Background()); err != nil {
|
||||
t.Error("clear all err")
|
||||
|
||||
37
client/cache/ssdb/ssdb.go
vendored
37
client/cache/ssdb/ssdb.go
vendored
@@ -4,13 +4,14 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ssdb/gossdb/ssdb"
|
||||
|
||||
"github.com/astaxie/beego/client/cache"
|
||||
"github.com/beego/beego/v2/client/cache"
|
||||
)
|
||||
|
||||
// Cache SSDB adapter
|
||||
@@ -19,7 +20,7 @@ type Cache struct {
|
||||
conninfo []string
|
||||
}
|
||||
|
||||
//NewSsdbCache creates new ssdb adapter.
|
||||
// NewSsdbCache creates new ssdb adapter.
|
||||
func NewSsdbCache() cache.Cache {
|
||||
return &Cache{}
|
||||
}
|
||||
@@ -28,36 +29,50 @@ func NewSsdbCache() cache.Cache {
|
||||
func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) {
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
return nil, nil
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
value, err := rc.conn.Get(key)
|
||||
if err == nil {
|
||||
return value, nil
|
||||
}
|
||||
return nil, nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// GetMulti gets one or keys values from ssdb.
|
||||
func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
|
||||
size := len(keys)
|
||||
var values []interface{}
|
||||
values := make([]interface{}, size)
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
return values, err
|
||||
}
|
||||
}
|
||||
|
||||
res, err := rc.conn.Do("multi_get", keys)
|
||||
if err != nil {
|
||||
return values, err
|
||||
}
|
||||
|
||||
resSize := len(res)
|
||||
if err == nil {
|
||||
for i := 1; i < resSize; i += 2 {
|
||||
values = append(values, res[i+1])
|
||||
keyIdx := make(map[string]int)
|
||||
for i := 1; i < resSize; i += 2 {
|
||||
keyIdx[res[i]] = i
|
||||
}
|
||||
|
||||
keysErr := make([]string, 0)
|
||||
for i, ki := range keys {
|
||||
if _, ok := keyIdx[ki]; !ok {
|
||||
keysErr = append(keysErr, fmt.Sprintf("key [%s] error: %s", ki, "the key isn't exist"))
|
||||
continue
|
||||
}
|
||||
return values, nil
|
||||
values[i] = res[keyIdx[ki]+1]
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
values = append(values, err)
|
||||
|
||||
if len(keysErr) != 0 {
|
||||
return values, fmt.Errorf(strings.Join(keysErr, "; "))
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
|
||||
16
client/cache/ssdb/ssdb_test.go
vendored
16
client/cache/ssdb/ssdb_test.go
vendored
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/client/cache"
|
||||
"github.com/beego/beego/v2/client/cache"
|
||||
)
|
||||
|
||||
func TestSsdbcacheCache(t *testing.T) {
|
||||
@@ -106,6 +106,20 @@ func TestSsdbcacheCache(t *testing.T) {
|
||||
t.Error("getmulti error")
|
||||
}
|
||||
|
||||
vv, err = ssdb.GetMulti(context.Background(), []string{"ssdb", "ssdb11"})
|
||||
if len(vv) != 2 {
|
||||
t.Error("getmulti error")
|
||||
}
|
||||
if vv[0].(string) != "ssdb" {
|
||||
t.Error("getmulti error")
|
||||
}
|
||||
if vv[1] != nil {
|
||||
t.Error("getmulti error")
|
||||
}
|
||||
if err != nil && err.Error() != "key [ssdb11] error: the key isn't exist" {
|
||||
t.Error("getmulti error")
|
||||
}
|
||||
|
||||
// test clear all done
|
||||
if err = ssdb.ClearAll(context.Background()); err != nil {
|
||||
t.Error("clear all err")
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
# httplib
|
||||
|
||||
httplib is an libs help you to curl remote url.
|
||||
|
||||
# How to use?
|
||||
|
||||
## GET
|
||||
|
||||
you can use Get to crawl data.
|
||||
|
||||
import "github.com/astaxie/beego/httplib"
|
||||
import "github.com/beego/beego/v2/httplib"
|
||||
|
||||
str, err := httplib.Get("http://beego.me/").String()
|
||||
if err != nil {
|
||||
// error
|
||||
}
|
||||
fmt.Println(str)
|
||||
|
||||
|
||||
## POST
|
||||
|
||||
POST data to remote url
|
||||
|
||||
req := httplib.Post("http://beego.me/")
|
||||
@@ -40,13 +43,12 @@ Example:
|
||||
// POST
|
||||
httplib.Post("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
|
||||
|
||||
|
||||
## Debug
|
||||
|
||||
If you want to debug the request info, set the debug on
|
||||
|
||||
httplib.Get("http://beego.me/").Debug(true)
|
||||
|
||||
|
||||
## Set HTTP Basic Auth
|
||||
|
||||
str, err := Get("http://beego.me/").SetBasicAuth("user", "passwd").String()
|
||||
@@ -54,21 +56,21 @@ If you want to debug the request info, set the debug on
|
||||
// error
|
||||
}
|
||||
fmt.Println(str)
|
||||
|
||||
|
||||
## Set HTTPS
|
||||
|
||||
If request url is https, You can set the client support TSL:
|
||||
|
||||
httplib.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
|
||||
More info about the `tls.Config` please visit http://golang.org/pkg/crypto/tls/#Config
|
||||
|
||||
More info about the `tls.Config` please visit http://golang.org/pkg/crypto/tls/#Config
|
||||
|
||||
## Set HTTP Version
|
||||
|
||||
some servers need to specify the protocol version of HTTP
|
||||
|
||||
httplib.Get("http://beego.me/").SetProtocolVersion("HTTP/1.1")
|
||||
|
||||
|
||||
## Set Cookie
|
||||
|
||||
some http request need setcookie. So set it like this:
|
||||
@@ -91,7 +93,6 @@ httplib support mutil file upload, use `req.PostFile()`
|
||||
}
|
||||
fmt.Println(str)
|
||||
|
||||
|
||||
See godoc for further documentation and examples.
|
||||
|
||||
* [godoc.org/github.com/astaxie/beego/httplib](https://godoc.org/github.com/astaxie/beego/httplib)
|
||||
* [godoc.org/github.com/beego/beego/v2/httplib](https://godoc.org/github.com/beego/beego/v2/httplib)
|
||||
|
||||
@@ -18,13 +18,17 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/astaxie/beego/client/httplib"
|
||||
logKit "github.com/go-kit/kit/log"
|
||||
opentracingKit "github.com/go-kit/kit/tracing/opentracing"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/log"
|
||||
|
||||
"github.com/beego/beego/v2/client/httplib"
|
||||
)
|
||||
|
||||
type FilterChainBuilder struct {
|
||||
// TagURL true will tag span with url
|
||||
TagURL bool
|
||||
// CustomSpanFunc users are able to custom their span
|
||||
CustomSpanFunc func(span opentracing.Span, ctx context.Context,
|
||||
req *httplib.BeegoHTTPRequest, resp *http.Response, err error)
|
||||
@@ -49,13 +53,19 @@ func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filt
|
||||
}
|
||||
span.SetTag("http.method", method)
|
||||
span.SetTag("peer.hostname", req.GetRequest().URL.Host)
|
||||
span.SetTag("http.url", req.GetRequest().URL.String())
|
||||
|
||||
span.SetTag("http.scheme", req.GetRequest().URL.Scheme)
|
||||
span.SetTag("span.kind", "client")
|
||||
span.SetTag("component", "beego")
|
||||
|
||||
if builder.TagURL {
|
||||
span.SetTag("http.url", req.GetRequest().URL.String())
|
||||
}
|
||||
span.LogFields(log.String("http.url", req.GetRequest().URL.String()))
|
||||
|
||||
if err != nil {
|
||||
span.SetTag("error", true)
|
||||
span.SetTag("message", err.Error())
|
||||
span.LogFields(log.String("message", err.Error()))
|
||||
} else if resp != nil && !(resp.StatusCode < 300 && resp.StatusCode >= 200) {
|
||||
span.SetTag("error", true)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/astaxie/beego/client/httplib"
|
||||
"github.com/beego/beego/v2/client/httplib"
|
||||
)
|
||||
|
||||
func TestFilterChainBuilder_FilterChain(t *testing.T) {
|
||||
@@ -33,7 +33,9 @@ func TestFilterChainBuilder_FilterChain(t *testing.T) {
|
||||
StatusCode: 404,
|
||||
}, errors.New("hello")
|
||||
}
|
||||
builder := &FilterChainBuilder{}
|
||||
builder := &FilterChainBuilder{
|
||||
TagURL: true,
|
||||
}
|
||||
filter := builder.FilterChain(next)
|
||||
req := httplib.Get("https://github.com/notifications?query=repo%3Aastaxie%2Fbeego")
|
||||
resp, err := filter(context.Background(), req)
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/astaxie/beego/client/httplib"
|
||||
"github.com/beego/beego/v2/client/httplib"
|
||||
)
|
||||
|
||||
type FilterChainBuilder struct {
|
||||
@@ -43,7 +43,7 @@ func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filt
|
||||
"appname": builder.AppName,
|
||||
},
|
||||
Help: "The statics info for remote http requests",
|
||||
}, []string{"proto", "scheme", "method", "host", "path", "status", "duration", "isError"})
|
||||
}, []string{"proto", "scheme", "method", "host", "path", "status", "isError"})
|
||||
|
||||
return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
|
||||
startTime := time.Now()
|
||||
@@ -73,5 +73,5 @@ func (builder *FilterChainBuilder) report(startTime time.Time, endTime time.Time
|
||||
dur := int(endTime.Sub(startTime) / time.Millisecond)
|
||||
|
||||
builder.summaryVec.WithLabelValues(proto, scheme, method, host, path,
|
||||
strconv.Itoa(status), strconv.Itoa(dur), strconv.FormatBool(err == nil))
|
||||
strconv.Itoa(status), strconv.FormatBool(err != nil)).Observe(float64(dur))
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/astaxie/beego/client/httplib"
|
||||
"github.com/beego/beego/v2/client/httplib"
|
||||
)
|
||||
|
||||
func TestFilterChainBuilder_FilterChain(t *testing.T) {
|
||||
|
||||
39
client/httplib/http_response.go
Normal file
39
client/httplib/http_response.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httplib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewHttpResponseWithJsonBody will try to convert the data to json format
|
||||
// usually you only use this when you want to mock http Response
|
||||
func NewHttpResponseWithJsonBody(data interface{}) *http.Response {
|
||||
var body []byte
|
||||
if str, ok := data.(string); ok {
|
||||
body = []byte(str)
|
||||
} else if bts, ok := data.([]byte); ok {
|
||||
body = bts
|
||||
} else {
|
||||
body, _ = json.Marshal(data)
|
||||
}
|
||||
return &http.Response{
|
||||
ContentLength: int64(len(body)),
|
||||
Body: ioutil.NopCloser(bytes.NewReader(body)),
|
||||
}
|
||||
}
|
||||
36
client/httplib/http_response_test.go
Normal file
36
client/httplib/http_response_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httplib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
||||
func TestNewHttpResponseWithJsonBody(t *testing.T) {
|
||||
// string
|
||||
resp := NewHttpResponseWithJsonBody("{}")
|
||||
assert.Equal(t, int64(2), resp.ContentLength)
|
||||
|
||||
resp = NewHttpResponseWithJsonBody([]byte("{}"))
|
||||
assert.Equal(t, int64(2), resp.ContentLength)
|
||||
|
||||
resp = NewHttpResponseWithJsonBody(&user{
|
||||
Name: "Tom",
|
||||
})
|
||||
assert.True(t, resp.ContentLength > 0)
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
// Package httplib is used as http.Client
|
||||
// Usage:
|
||||
//
|
||||
// import "github.com/astaxie/beego/httplib"
|
||||
// import "github.com/beego/beego/v2/httplib"
|
||||
//
|
||||
// b := httplib.Post("http://beego.me/")
|
||||
// b.Param("username","astaxie")
|
||||
@@ -44,48 +44,22 @@ import (
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var defaultSetting = BeegoHTTPSettings{
|
||||
UserAgent: "beegoServer",
|
||||
ConnectTimeout: 60 * time.Second,
|
||||
ReadWriteTimeout: 60 * time.Second,
|
||||
Gzip: true,
|
||||
DumpBody: true,
|
||||
}
|
||||
|
||||
var defaultCookieJar http.CookieJar
|
||||
var settingMutex sync.Mutex
|
||||
|
||||
// it will be the last filter and execute request.Do
|
||||
var doRequestFilter = func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) {
|
||||
return req.doRequest(ctx)
|
||||
}
|
||||
|
||||
// createDefaultCookie creates a global cookiejar to store cookies.
|
||||
func createDefaultCookie() {
|
||||
settingMutex.Lock()
|
||||
defer settingMutex.Unlock()
|
||||
defaultCookieJar, _ = cookiejar.New(nil)
|
||||
}
|
||||
|
||||
// SetDefaultSetting overwrites default settings
|
||||
func SetDefaultSetting(setting BeegoHTTPSettings) {
|
||||
settingMutex.Lock()
|
||||
defer settingMutex.Unlock()
|
||||
defaultSetting = setting
|
||||
}
|
||||
|
||||
// NewBeegoRequest returns *BeegoHttpRequest with specific method
|
||||
func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest {
|
||||
var resp http.Response
|
||||
@@ -136,23 +110,7 @@ func Head(url string) *BeegoHTTPRequest {
|
||||
return NewBeegoRequest(url, "HEAD")
|
||||
}
|
||||
|
||||
// BeegoHTTPSettings is the http.Client setting
|
||||
type BeegoHTTPSettings struct {
|
||||
ShowDebug bool
|
||||
UserAgent string
|
||||
ConnectTimeout time.Duration
|
||||
ReadWriteTimeout time.Duration
|
||||
TLSClientConfig *tls.Config
|
||||
Proxy func(*http.Request) (*url.URL, error)
|
||||
Transport http.RoundTripper
|
||||
CheckRedirect func(req *http.Request, via []*http.Request) error
|
||||
EnableCookie bool
|
||||
Gzip bool
|
||||
DumpBody bool
|
||||
Retries int // if set to -1 means will retry forever
|
||||
RetryDelay time.Duration
|
||||
FilterChains []FilterChain
|
||||
}
|
||||
|
||||
|
||||
// BeegoHTTPRequest provides more useful methods than http.Request for requesting a url.
|
||||
type BeegoHTTPRequest struct {
|
||||
@@ -338,10 +296,16 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
|
||||
case string:
|
||||
bf := bytes.NewBufferString(t)
|
||||
b.req.Body = ioutil.NopCloser(bf)
|
||||
b.req.GetBody = func() (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(bf), nil
|
||||
}
|
||||
b.req.ContentLength = int64(len(t))
|
||||
case []byte:
|
||||
bf := bytes.NewBuffer(t)
|
||||
b.req.Body = ioutil.NopCloser(bf)
|
||||
b.req.GetBody = func() (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(bf), nil
|
||||
}
|
||||
b.req.ContentLength = int64(len(t))
|
||||
}
|
||||
return b
|
||||
@@ -355,6 +319,9 @@ func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
|
||||
return b, err
|
||||
}
|
||||
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
|
||||
b.req.GetBody = func() (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(bytes.NewReader(byts)), nil
|
||||
}
|
||||
b.req.ContentLength = int64(len(byts))
|
||||
b.req.Header.Set("Content-Type", "application/xml")
|
||||
}
|
||||
@@ -553,7 +520,7 @@ func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response,
|
||||
// retries default value is 0, it will run once.
|
||||
// retries equal to -1, it will run forever until success
|
||||
// retries is setted, it will retries fixed times.
|
||||
// Sleeps for a 400ms inbetween calls to reduce spam
|
||||
// Sleeps for a 400ms between calls to reduce spam
|
||||
for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ {
|
||||
resp, err = client.Do(b.req)
|
||||
if err == nil {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package httplib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@@ -23,6 +24,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestResponse(t *testing.T) {
|
||||
@@ -98,7 +101,7 @@ func TestSimplePost(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
//func TestPostFile(t *testing.T) {
|
||||
// func TestPostFile(t *testing.T) {
|
||||
// v := "smallfish"
|
||||
// req := Post("http://httpbin.org/post")
|
||||
// req.Debug(true)
|
||||
@@ -115,7 +118,7 @@ func TestSimplePost(t *testing.T) {
|
||||
// if n == -1 {
|
||||
// t.Fatal(v + " not found in post")
|
||||
// }
|
||||
//}
|
||||
// }
|
||||
|
||||
func TestSimplePut(t *testing.T) {
|
||||
str, err := Put("http://httpbin.org/put").String()
|
||||
@@ -284,3 +287,94 @@ func TestHeader(t *testing.T) {
|
||||
}
|
||||
t.Log(str)
|
||||
}
|
||||
|
||||
// TestAddFilter make sure that AddFilters only work for the specific request
|
||||
func TestAddFilter(t *testing.T) {
|
||||
req := Get("http://beego.me")
|
||||
req.AddFilters(func(next Filter) Filter {
|
||||
return func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) {
|
||||
return next(ctx, req)
|
||||
}
|
||||
})
|
||||
|
||||
r := Get("http://beego.me")
|
||||
assert.Equal(t, 1, len(req.setting.FilterChains)-len(r.setting.FilterChains))
|
||||
}
|
||||
|
||||
func TestHead(t *testing.T) {
|
||||
req := Head("http://beego.me")
|
||||
assert.NotNil(t, req)
|
||||
assert.Equal(t, "HEAD", req.req.Method)
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
req := Delete("http://beego.me")
|
||||
assert.NotNil(t, req)
|
||||
assert.Equal(t, "DELETE", req.req.Method)
|
||||
}
|
||||
|
||||
func TestPost(t *testing.T) {
|
||||
req := Post("http://beego.me")
|
||||
assert.NotNil(t, req)
|
||||
assert.Equal(t, "POST", req.req.Method)
|
||||
}
|
||||
|
||||
func TestNewBeegoRequest(t *testing.T) {
|
||||
req := NewBeegoRequest("http://beego.me", "GET")
|
||||
assert.NotNil(t, req)
|
||||
assert.Equal(t, "GET", req.req.Method)
|
||||
}
|
||||
|
||||
func TestPut(t *testing.T) {
|
||||
req := Put("http://beego.me")
|
||||
assert.NotNil(t, req)
|
||||
assert.Equal(t, "PUT", req.req.Method)
|
||||
}
|
||||
|
||||
func TestBeegoHTTPRequest_Header(t *testing.T) {
|
||||
req := Post("http://beego.me")
|
||||
key, value := "test-header", "test-header-value"
|
||||
req.Header(key, value)
|
||||
assert.Equal(t, value, req.req.Header.Get(key))
|
||||
}
|
||||
|
||||
func TestBeegoHTTPRequest_SetHost(t *testing.T) {
|
||||
req := Post("http://beego.me")
|
||||
host := "test-hose"
|
||||
req.SetHost(host)
|
||||
assert.Equal(t, host, req.req.Host)
|
||||
}
|
||||
|
||||
func TestBeegoHTTPRequest_Param(t *testing.T) {
|
||||
req := Post("http://beego.me")
|
||||
key, value := "test-param", "test-param-value"
|
||||
req.Param(key, value)
|
||||
assert.Equal(t, value, req.params[key][0])
|
||||
|
||||
value1 := "test-param-value-1"
|
||||
req.Param(key, value1)
|
||||
assert.Equal(t, value1, req.params[key][1])
|
||||
}
|
||||
|
||||
func TestBeegoHTTPRequest_Body(t *testing.T) {
|
||||
req := Post("http://beego.me")
|
||||
body := `hello, world`
|
||||
req.Body([]byte(body))
|
||||
assert.Equal(t, int64(len(body)), req.req.ContentLength)
|
||||
assert.NotNil(t, req.req.GetBody)
|
||||
}
|
||||
|
||||
|
||||
type user struct {
|
||||
Name string `xml:"name"`
|
||||
}
|
||||
func TestBeegoHTTPRequest_XMLBody(t *testing.T) {
|
||||
req := Post("http://beego.me")
|
||||
body := &user{
|
||||
Name: "Tom",
|
||||
}
|
||||
_, err := req.XMLBody(body)
|
||||
assert.True(t, req.req.ContentLength > 0)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, req.req.GetBody)
|
||||
}
|
||||
80
client/httplib/mock/mock.go
Normal file
80
client/httplib/mock/mock.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/beego/beego/v2/client/httplib"
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
)
|
||||
|
||||
const mockCtxKey = "beego-httplib-mock"
|
||||
|
||||
func init() {
|
||||
InitMockSetting()
|
||||
}
|
||||
|
||||
type Stub interface {
|
||||
Mock(cond RequestCondition, resp *http.Response, err error)
|
||||
Clear()
|
||||
MockByPath(path string, resp *http.Response, err error)
|
||||
}
|
||||
|
||||
var mockFilter = &MockResponseFilter{}
|
||||
|
||||
func InitMockSetting() {
|
||||
httplib.AddDefaultFilter(mockFilter.FilterChain)
|
||||
}
|
||||
|
||||
func StartMock() Stub {
|
||||
return mockFilter
|
||||
}
|
||||
|
||||
func CtxWithMock(ctx context.Context, mock... *Mock) context.Context {
|
||||
return context.WithValue(ctx, mockCtxKey, mock)
|
||||
}
|
||||
|
||||
func mockFromCtx(ctx context.Context) []*Mock {
|
||||
ms := ctx.Value(mockCtxKey)
|
||||
if ms != nil {
|
||||
if res, ok := ms.([]*Mock); ok {
|
||||
return res
|
||||
}
|
||||
logs.Error("mockCtxKey found in context, but value is not type []*Mock")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Mock struct {
|
||||
cond RequestCondition
|
||||
resp *http.Response
|
||||
err error
|
||||
}
|
||||
|
||||
func NewMockByPath(path string, resp *http.Response, err error) *Mock {
|
||||
return NewMock(NewSimpleCondition(path), resp, err)
|
||||
}
|
||||
|
||||
func NewMock(con RequestCondition, resp *http.Response, err error) *Mock {
|
||||
return &Mock{
|
||||
cond: con,
|
||||
resp: resp,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
177
client/httplib/mock/mock_condition.go
Normal file
177
client/httplib/mock/mock_condition.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/textproto"
|
||||
"regexp"
|
||||
|
||||
"github.com/beego/beego/v2/client/httplib"
|
||||
)
|
||||
|
||||
type RequestCondition interface {
|
||||
Match(ctx context.Context, req *httplib.BeegoHTTPRequest) bool
|
||||
}
|
||||
|
||||
// reqCondition create condition
|
||||
// - path: same path
|
||||
// - pathReg: request path match pathReg
|
||||
// - method: same method
|
||||
// - Query parameters (key, value)
|
||||
// - header (key, value)
|
||||
// - Body json format, contains specific (key, value).
|
||||
type SimpleCondition struct {
|
||||
pathReg string
|
||||
path string
|
||||
method string
|
||||
query map[string]string
|
||||
header map[string]string
|
||||
body map[string]interface{}
|
||||
}
|
||||
|
||||
func NewSimpleCondition(path string, opts ...simpleConditionOption) *SimpleCondition {
|
||||
sc := &SimpleCondition{
|
||||
path: path,
|
||||
query: make(map[string]string),
|
||||
header: make(map[string]string),
|
||||
body: map[string]interface{}{},
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(sc)
|
||||
}
|
||||
return sc
|
||||
}
|
||||
|
||||
func (sc *SimpleCondition) Match(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
|
||||
res := true
|
||||
if len(sc.path) > 0 {
|
||||
res = sc.matchPath(ctx, req)
|
||||
} else if len(sc.pathReg) > 0 {
|
||||
res = sc.matchPathReg(ctx, req)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return res &&
|
||||
sc.matchMethod(ctx, req) &&
|
||||
sc.matchQuery(ctx, req) &&
|
||||
sc.matchHeader(ctx, req) &&
|
||||
sc.matchBodyFields(ctx, req)
|
||||
}
|
||||
|
||||
func (sc *SimpleCondition) matchPath(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
|
||||
path := req.GetRequest().URL.Path
|
||||
return path == sc.path
|
||||
}
|
||||
|
||||
func (sc *SimpleCondition) matchPathReg(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
|
||||
path := req.GetRequest().URL.Path
|
||||
if b, err := regexp.Match(sc.pathReg, []byte(path)); err == nil {
|
||||
return b
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (sc *SimpleCondition) matchQuery(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
|
||||
qs := req.GetRequest().URL.Query()
|
||||
for k, v := range sc.query {
|
||||
if uv, ok := qs[k]; !ok || uv[0] != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (sc *SimpleCondition) matchHeader(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
|
||||
headers := req.GetRequest().Header
|
||||
for k, v := range sc.header {
|
||||
if uv, ok := headers[k]; !ok || uv[0] != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (sc *SimpleCondition) matchBodyFields(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
|
||||
if len(sc.body) == 0 {
|
||||
return true
|
||||
}
|
||||
getBody := req.GetRequest().GetBody
|
||||
body, err := getBody()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
bytes := make([]byte, req.GetRequest().ContentLength)
|
||||
_, err = body.Read(bytes)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
m := make(map[string]interface{})
|
||||
|
||||
err = json.Unmarshal(bytes, &m)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for k, v := range sc.body {
|
||||
if uv, ok := m[k]; !ok || uv != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (sc *SimpleCondition) matchMethod(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
|
||||
if len(sc.method) > 0 {
|
||||
return sc.method == req.GetRequest().Method
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type simpleConditionOption func(sc *SimpleCondition)
|
||||
|
||||
func WithPathReg(pathReg string) simpleConditionOption {
|
||||
return func(sc *SimpleCondition) {
|
||||
sc.pathReg = pathReg
|
||||
}
|
||||
}
|
||||
|
||||
func WithQuery(key, value string) simpleConditionOption {
|
||||
return func(sc *SimpleCondition) {
|
||||
sc.query[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
func WithHeader(key, value string) simpleConditionOption {
|
||||
return func(sc *SimpleCondition) {
|
||||
sc.header[textproto.CanonicalMIMEHeaderKey(key)] = value
|
||||
}
|
||||
}
|
||||
|
||||
func WithJsonBodyFields(field string, value interface{}) simpleConditionOption {
|
||||
return func(sc *SimpleCondition) {
|
||||
sc.body[field] = value
|
||||
}
|
||||
}
|
||||
|
||||
func WithMethod(method string) simpleConditionOption {
|
||||
return func(sc *SimpleCondition) {
|
||||
sc.method = method
|
||||
}
|
||||
}
|
||||
124
client/httplib/mock/mock_condition_test.go
Normal file
124
client/httplib/mock/mock_condition_test.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/beego/beego/v2/client/httplib"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
}
|
||||
|
||||
func TestSimpleCondition_MatchPath(t *testing.T) {
|
||||
sc := NewSimpleCondition("/abc/s")
|
||||
res := sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s"))
|
||||
assert.True(t, res)
|
||||
}
|
||||
|
||||
func TestSimpleCondition_MatchQuery(t *testing.T) {
|
||||
k, v := "my-key", "my-value"
|
||||
sc := NewSimpleCondition("/abc/s")
|
||||
res := sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-value"))
|
||||
assert.True(t, res)
|
||||
|
||||
sc = NewSimpleCondition("/abc/s", WithQuery(k, v))
|
||||
res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-value"))
|
||||
assert.True(t, res)
|
||||
|
||||
res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-valuesss"))
|
||||
assert.False(t, res)
|
||||
|
||||
res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key-a=my-value"))
|
||||
assert.False(t, res)
|
||||
|
||||
res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-value&abc=hello"))
|
||||
assert.True(t, res)
|
||||
}
|
||||
|
||||
func TestSimpleCondition_MatchHeader(t *testing.T) {
|
||||
k, v := "my-header", "my-header-value"
|
||||
sc := NewSimpleCondition("/abc/s")
|
||||
req := httplib.Get("http://localhost:8080/abc/s")
|
||||
assert.True(t, sc.Match(context.Background(), req))
|
||||
|
||||
req = httplib.Get("http://localhost:8080/abc/s")
|
||||
req.Header(k, v)
|
||||
assert.True(t, sc.Match(context.Background(), req))
|
||||
|
||||
sc = NewSimpleCondition("/abc/s", WithHeader(k, v))
|
||||
req.Header(k, v)
|
||||
assert.True(t, sc.Match(context.Background(), req))
|
||||
|
||||
req.Header(k, "invalid")
|
||||
assert.False(t, sc.Match(context.Background(), req))
|
||||
}
|
||||
|
||||
func TestSimpleCondition_MatchBodyField(t *testing.T) {
|
||||
|
||||
sc := NewSimpleCondition("/abc/s")
|
||||
req := httplib.Post("http://localhost:8080/abc/s")
|
||||
|
||||
assert.True(t, sc.Match(context.Background(), req))
|
||||
|
||||
req.Body(`{
|
||||
"body-field": 123
|
||||
}`)
|
||||
assert.True(t, sc.Match(context.Background(), req))
|
||||
|
||||
k := "body-field"
|
||||
v := float64(123)
|
||||
sc = NewSimpleCondition("/abc/s", WithJsonBodyFields(k, v))
|
||||
assert.True(t, sc.Match(context.Background(), req))
|
||||
|
||||
sc = NewSimpleCondition("/abc/s", WithJsonBodyFields(k, v))
|
||||
req.Body(`{
|
||||
"body-field": abc
|
||||
}`)
|
||||
assert.False(t, sc.Match(context.Background(), req))
|
||||
|
||||
sc = NewSimpleCondition("/abc/s", WithJsonBodyFields("body-field", "abc"))
|
||||
req.Body(`{
|
||||
"body-field": "abc"
|
||||
}`)
|
||||
assert.True(t, sc.Match(context.Background(), req))
|
||||
}
|
||||
|
||||
func TestSimpleCondition_Match(t *testing.T) {
|
||||
sc := NewSimpleCondition("/abc/s")
|
||||
req := httplib.Post("http://localhost:8080/abc/s")
|
||||
|
||||
assert.True(t, sc.Match(context.Background(), req))
|
||||
|
||||
sc = NewSimpleCondition("/abc/s", WithMethod("POST"))
|
||||
assert.True(t, sc.Match(context.Background(), req))
|
||||
|
||||
sc = NewSimpleCondition("/abc/s", WithMethod("GET"))
|
||||
assert.False(t, sc.Match(context.Background(), req))
|
||||
}
|
||||
|
||||
func TestSimpleCondition_MatchPathReg(t *testing.T) {
|
||||
sc := NewSimpleCondition("", WithPathReg(`\/abc\/.*`))
|
||||
req := httplib.Post("http://localhost:8080/abc/s")
|
||||
assert.True(t, sc.Match(context.Background(), req))
|
||||
|
||||
req = httplib.Post("http://localhost:8080/abcd/s")
|
||||
assert.False(t, sc.Match(context.Background(), req))
|
||||
}
|
||||
61
client/httplib/mock/mock_filter.go
Normal file
61
client/httplib/mock/mock_filter.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/beego/beego/v2/client/httplib"
|
||||
)
|
||||
|
||||
// MockResponse will return mock response if find any suitable mock data
|
||||
// if you want to test your code using httplib, you need this.
|
||||
type MockResponseFilter struct {
|
||||
ms []*Mock
|
||||
}
|
||||
|
||||
func NewMockResponseFilter() *MockResponseFilter {
|
||||
return &MockResponseFilter{
|
||||
ms: make([]*Mock, 0, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockResponseFilter) FilterChain(next httplib.Filter) httplib.Filter {
|
||||
return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
|
||||
ms := mockFromCtx(ctx)
|
||||
ms = append(ms, m.ms...)
|
||||
for _, mock := range ms {
|
||||
if mock.cond.Match(ctx, req) {
|
||||
return mock.resp, mock.err
|
||||
}
|
||||
}
|
||||
return next(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockResponseFilter) MockByPath(path string, resp *http.Response, err error) {
|
||||
m.Mock(NewSimpleCondition(path), resp, err)
|
||||
}
|
||||
|
||||
func (m *MockResponseFilter) Clear() {
|
||||
m.ms = make([]*Mock, 0, 1)
|
||||
}
|
||||
|
||||
// Mock add mock data
|
||||
// If the cond.Match(...) = true, the resp and err will be returned
|
||||
func (m *MockResponseFilter) Mock(cond RequestCondition, resp *http.Response, err error) {
|
||||
m.ms = append(m.ms, NewMock(cond, resp, err))
|
||||
}
|
||||
63
client/httplib/mock/mock_filter_test.go
Normal file
63
client/httplib/mock/mock_filter_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/beego/beego/v2/client/httplib"
|
||||
)
|
||||
|
||||
func TestMockResponseFilter_FilterChain(t *testing.T) {
|
||||
req := httplib.Get("http://localhost:8080/abc/s")
|
||||
ft := NewMockResponseFilter()
|
||||
|
||||
expectedResp := httplib.NewHttpResponseWithJsonBody(`{}`)
|
||||
expectedErr := errors.New("expected error")
|
||||
ft.Mock(NewSimpleCondition("/abc/s"), expectedResp, expectedErr)
|
||||
|
||||
req.AddFilters(ft.FilterChain)
|
||||
|
||||
resp, err := req.DoRequest()
|
||||
assert.Equal(t, expectedErr, err)
|
||||
assert.Equal(t, expectedResp, resp)
|
||||
|
||||
req = httplib.Get("http://localhost:8080/abcd/s")
|
||||
req.AddFilters(ft.FilterChain)
|
||||
|
||||
resp, err = req.DoRequest()
|
||||
assert.NotEqual(t, expectedErr, err)
|
||||
assert.NotEqual(t, expectedResp, resp)
|
||||
|
||||
req = httplib.Get("http://localhost:8080/abc/s")
|
||||
req.AddFilters(ft.FilterChain)
|
||||
expectedResp1 := httplib.NewHttpResponseWithJsonBody(map[string]string{})
|
||||
expectedErr1 := errors.New("expected error")
|
||||
ft.Mock(NewSimpleCondition("/abc/abs/bbc"), expectedResp1, expectedErr1)
|
||||
|
||||
resp, err = req.DoRequest()
|
||||
assert.Equal(t, expectedErr, err)
|
||||
assert.Equal(t, expectedResp, resp)
|
||||
|
||||
req = httplib.Get("http://localhost:8080/abc/abs/bbc")
|
||||
req.AddFilters(ft.FilterChain)
|
||||
ft.Mock(NewSimpleCondition("/abc/abs/bbc"), expectedResp1, expectedErr1)
|
||||
resp, err = req.DoRequest()
|
||||
assert.Equal(t, expectedErr1, err)
|
||||
assert.Equal(t, expectedResp1, resp)
|
||||
}
|
||||
77
client/httplib/mock/mock_test.go
Normal file
77
client/httplib/mock/mock_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/beego/beego/v2/client/httplib"
|
||||
)
|
||||
|
||||
func TestStartMock(t *testing.T) {
|
||||
|
||||
// httplib.defaultSetting.FilterChains = []httplib.FilterChain{mockFilter.FilterChain}
|
||||
|
||||
stub := StartMock()
|
||||
// defer stub.Clear()
|
||||
|
||||
expectedResp := httplib.NewHttpResponseWithJsonBody([]byte(`{}`))
|
||||
expectedErr := errors.New("expected err")
|
||||
|
||||
stub.Mock(NewSimpleCondition("/abc"), expectedResp, expectedErr)
|
||||
|
||||
resp, err := OriginalCodeUsingHttplib()
|
||||
|
||||
assert.Equal(t, expectedErr, err)
|
||||
assert.Equal(t, expectedResp, resp)
|
||||
|
||||
}
|
||||
|
||||
// TestStartMock_Isolation Test StartMock that
|
||||
// mock only work for this request
|
||||
func TestStartMock_Isolation(t *testing.T) {
|
||||
// httplib.defaultSetting.FilterChains = []httplib.FilterChain{mockFilter.FilterChain}
|
||||
// setup global stub
|
||||
stub := StartMock()
|
||||
globalMockResp := httplib.NewHttpResponseWithJsonBody([]byte(`{}`))
|
||||
globalMockErr := errors.New("expected err")
|
||||
stub.Mock(NewSimpleCondition("/abc"), globalMockResp, globalMockErr)
|
||||
|
||||
expectedResp := httplib.NewHttpResponseWithJsonBody(struct {
|
||||
A string `json:"a"`
|
||||
}{
|
||||
A: "aaa",
|
||||
})
|
||||
expectedErr := errors.New("expected err aa")
|
||||
m := NewMockByPath("/abc", expectedResp, expectedErr)
|
||||
ctx := CtxWithMock(context.Background(), m)
|
||||
|
||||
resp, err := OriginnalCodeUsingHttplibPassCtx(ctx)
|
||||
assert.Equal(t, expectedErr, err)
|
||||
assert.Equal(t, expectedResp, resp)
|
||||
}
|
||||
|
||||
func OriginnalCodeUsingHttplibPassCtx(ctx context.Context) (*http.Response, error) {
|
||||
return httplib.Get("http://localhost:7777/abc").DoRequestWithCtx(ctx)
|
||||
}
|
||||
|
||||
func OriginalCodeUsingHttplib() (*http.Response, error){
|
||||
return httplib.Get("http://localhost:7777/abc").DoRequest()
|
||||
}
|
||||
81
client/httplib/setting.go
Normal file
81
client/httplib/setting.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2020 beego
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httplib
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BeegoHTTPSettings is the http.Client setting
|
||||
type BeegoHTTPSettings struct {
|
||||
ShowDebug bool
|
||||
UserAgent string
|
||||
ConnectTimeout time.Duration
|
||||
ReadWriteTimeout time.Duration
|
||||
TLSClientConfig *tls.Config
|
||||
Proxy func(*http.Request) (*url.URL, error)
|
||||
Transport http.RoundTripper
|
||||
CheckRedirect func(req *http.Request, via []*http.Request) error
|
||||
EnableCookie bool
|
||||
Gzip bool
|
||||
DumpBody bool
|
||||
Retries int // if set to -1 means will retry forever
|
||||
RetryDelay time.Duration
|
||||
FilterChains []FilterChain
|
||||
}
|
||||
|
||||
// createDefaultCookie creates a global cookiejar to store cookies.
|
||||
func createDefaultCookie() {
|
||||
settingMutex.Lock()
|
||||
defer settingMutex.Unlock()
|
||||
defaultCookieJar, _ = cookiejar.New(nil)
|
||||
}
|
||||
|
||||
// SetDefaultSetting overwrites default settings
|
||||
// Keep in mind that when you invoke the SetDefaultSetting
|
||||
// some methods invoked before SetDefaultSetting
|
||||
func SetDefaultSetting(setting BeegoHTTPSettings) {
|
||||
settingMutex.Lock()
|
||||
defer settingMutex.Unlock()
|
||||
defaultSetting = setting
|
||||
}
|
||||
|
||||
var defaultSetting = BeegoHTTPSettings{
|
||||
UserAgent: "beegoServer",
|
||||
ConnectTimeout: 60 * time.Second,
|
||||
ReadWriteTimeout: 60 * time.Second,
|
||||
Gzip: true,
|
||||
DumpBody: true,
|
||||
FilterChains: make([]FilterChain, 0, 4),
|
||||
}
|
||||
|
||||
var defaultCookieJar http.CookieJar
|
||||
var settingMutex sync.Mutex
|
||||
|
||||
// AddDefaultFilter add a new filter into defaultSetting
|
||||
// Be careful about using this method if you invoke SetDefaultSetting somewhere
|
||||
func AddDefaultFilter(fc FilterChain) {
|
||||
settingMutex.Lock()
|
||||
defer settingMutex.Unlock()
|
||||
if defaultSetting.FilterChains == nil {
|
||||
defaultSetting.FilterChains = make([]FilterChain, 0, 4)
|
||||
}
|
||||
defaultSetting.FilterChains = append(defaultSetting.FilterChains, fc)
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/client/httplib"
|
||||
"github.com/beego/beego/v2/client/httplib"
|
||||
)
|
||||
|
||||
var port = ""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# beego orm
|
||||
|
||||
[](https://drone.io/github.com/astaxie/beego/latest)
|
||||
[](https://drone.io/github.com/beego/beego/v2/latest)
|
||||
|
||||
A powerful orm framework for go.
|
||||
|
||||
@@ -27,7 +27,7 @@ more features please read the docs
|
||||
|
||||
**Install:**
|
||||
|
||||
go get github.com/astaxie/beego/orm
|
||||
go get github.com/beego/beego/v2/orm
|
||||
|
||||
## Changelog
|
||||
|
||||
@@ -45,7 +45,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/beego/beego/v2/orm"
|
||||
_ "github.com/go-sql-driver/mysql" // import your used driver
|
||||
)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/client/orm/hints"
|
||||
"github.com/beego/beego/v2/client/orm/hints"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -524,7 +524,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
|
||||
return 0, fmt.Errorf("`%s` nonsupport InsertOrUpdate in beego", a.DriverName)
|
||||
}
|
||||
|
||||
//Get on the key-value pairs
|
||||
// Get on the key-value pairs
|
||||
for _, v := range args {
|
||||
kv := strings.Split(v, "=")
|
||||
if len(kv) == 2 {
|
||||
@@ -559,7 +559,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
|
||||
updates[i] = v + "=" + valueStr
|
||||
case DRPostgres:
|
||||
if conflitValue != nil {
|
||||
//postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values
|
||||
// postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values
|
||||
updates[i] = fmt.Sprintf("%s=(select %s from %s where %s = ? )", v, valueStr, mi.table, args0)
|
||||
updateValues = append(updateValues, conflitValue)
|
||||
} else {
|
||||
@@ -584,7 +584,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
|
||||
if isMulti {
|
||||
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
|
||||
}
|
||||
//conflitValue maybe is a int,can`t use fmt.Sprintf
|
||||
// conflitValue maybe is a int,can`t use fmt.Sprintf
|
||||
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s) %s "+qupdates, Q, mi.table, Q, Q, columns, Q, qmarks, iouStr)
|
||||
|
||||
d.ins.ReplaceMarks(&query)
|
||||
|
||||
@@ -111,7 +111,7 @@ func (d *dbBaseMysql) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Val
|
||||
|
||||
iouStr = "ON DUPLICATE KEY UPDATE"
|
||||
|
||||
//Get on the key-value pairs
|
||||
// Get on the key-value pairs
|
||||
for _, v := range args {
|
||||
kv := strings.Split(v, "=")
|
||||
if len(kv) == 2 {
|
||||
@@ -155,7 +155,7 @@ func (d *dbBaseMysql) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Val
|
||||
if isMulti {
|
||||
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
|
||||
}
|
||||
//conflitValue maybe is a int,can`t use fmt.Sprintf
|
||||
// conflitValue maybe is a int,can`t use fmt.Sprintf
|
||||
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s) %s "+qupdates, Q, mi.table, Q, Q, columns, Q, qmarks, iouStr)
|
||||
|
||||
d.ins.ReplaceMarks(&query)
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/client/orm/hints"
|
||||
"github.com/beego/beego/v2/client/orm/hints"
|
||||
)
|
||||
|
||||
// oracle operators.
|
||||
@@ -77,7 +77,7 @@ func (d *dbBaseOracle) DbTypes() map[string]string {
|
||||
return oracleTypes
|
||||
}
|
||||
|
||||
//ShowTablesQuery show all the tables in database
|
||||
// ShowTablesQuery show all the tables in database
|
||||
func (d *dbBaseOracle) ShowTablesQuery() string {
|
||||
return "SELECT TABLE_NAME FROM USER_TABLES"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/client/orm/hints"
|
||||
"github.com/beego/beego/v2/client/orm/hints"
|
||||
)
|
||||
|
||||
// sqlite operators.
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/astaxie/beego/core/utils"
|
||||
"github.com/beego/beego/v2/core/utils"
|
||||
)
|
||||
|
||||
// DoNothingOrm won't do anything, usually you use this to custom your mock Ormer implementation
|
||||
|
||||
@@ -19,10 +19,10 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/core/logs"
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
|
||||
"github.com/astaxie/beego/client/orm"
|
||||
"github.com/astaxie/beego/core/bean"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
"github.com/beego/beego/v2/core/bean"
|
||||
)
|
||||
|
||||
// DefaultValueFilterChainBuilder only works for InsertXXX method,
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/astaxie/beego/client/orm"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
func TestDefaultValueFilterChainBuilder_FilterChain(t *testing.T) {
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
|
||||
"github.com/astaxie/beego/client/orm"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
// FilterChainBuilder provides an extension point
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
|
||||
"github.com/astaxie/beego/client/orm"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
func TestFilterChainBuilder_FilterChain(t *testing.T) {
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/astaxie/beego/client/orm"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
// FilterChainBuilder is an extension point,
|
||||
@@ -50,7 +50,7 @@ func (builder *FilterChainBuilder) FilterChain(next orm.Filter) orm.Filter {
|
||||
"appname": builder.AppName,
|
||||
},
|
||||
Help: "The statics info for orm operation",
|
||||
}, []string{"method", "name", "duration", "insideTx", "txName"})
|
||||
}, []string{"method", "name", "insideTx", "txName"})
|
||||
|
||||
return func(ctx context.Context, inv *orm.Invocation) []interface{} {
|
||||
startTime := time.Now()
|
||||
@@ -74,12 +74,12 @@ func (builder *FilterChainBuilder) report(ctx context.Context, inv *orm.Invocati
|
||||
builder.reportTxn(ctx, inv)
|
||||
return
|
||||
}
|
||||
builder.summaryVec.WithLabelValues(inv.Method, inv.GetTableName(), strconv.Itoa(int(dur)),
|
||||
strconv.FormatBool(inv.InsideTx), inv.TxName)
|
||||
builder.summaryVec.WithLabelValues(inv.Method, inv.GetTableName(),
|
||||
strconv.FormatBool(inv.InsideTx), inv.TxName).Observe(float64(dur))
|
||||
}
|
||||
|
||||
func (builder *FilterChainBuilder) reportTxn(ctx context.Context, inv *orm.Invocation) {
|
||||
dur := time.Now().Sub(inv.TxStartTime) / time.Millisecond
|
||||
builder.summaryVec.WithLabelValues(inv.Method, inv.TxName, strconv.Itoa(int(dur)),
|
||||
strconv.FormatBool(inv.InsideTx), inv.TxName)
|
||||
builder.summaryVec.WithLabelValues(inv.Method, inv.TxName,
|
||||
strconv.FormatBool(inv.InsideTx), inv.TxName).Observe(float64(dur))
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/astaxie/beego/client/orm"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
func TestFilterChainBuilder_FilterChain1(t *testing.T) {
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/core/utils"
|
||||
"github.com/beego/beego/v2/core/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/astaxie/beego/core/utils"
|
||||
"github.com/beego/beego/v2/core/utils"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -222,7 +222,7 @@ func TestFilterOrmDecorator_InsertMulti(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
bulk := []*FilterTestEntity{&FilterTestEntity{}, &FilterTestEntity{}}
|
||||
bulk := []*FilterTestEntity{{}, {}}
|
||||
i, err := od.InsertMulti(2, bulk)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "insert multi error", err.Error())
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
package hints
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/core/utils"
|
||||
"github.com/beego/beego/v2/core/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
//query level
|
||||
// query level
|
||||
KeyForceIndex = iota
|
||||
KeyUseIndex
|
||||
KeyIgnoreIndex
|
||||
|
||||
@@ -17,7 +17,7 @@ package migration
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego/core/logs"
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
)
|
||||
|
||||
// Index struct defines the structure of Index Columns
|
||||
|
||||
@@ -33,8 +33,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/client/orm"
|
||||
"github.com/astaxie/beego/core/logs"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
)
|
||||
|
||||
// const the data format for the bee generate migration datatype
|
||||
@@ -52,7 +52,7 @@ type Migrationer interface {
|
||||
GetCreated() int64
|
||||
}
|
||||
|
||||
//Migration defines the migrations by either SQL or DDL
|
||||
// Migration defines the migrations by either SQL or DDL
|
||||
type Migration struct {
|
||||
sqls []string
|
||||
Created string
|
||||
@@ -104,7 +104,7 @@ func (m *Migration) Down() {
|
||||
m.sqls = append(m.sqls, m.GetSQL())
|
||||
}
|
||||
|
||||
//Migrate adds the SQL to the execution list
|
||||
// Migrate adds the SQL to the execution list
|
||||
func (m *Migration) Migrate(migrationType string) {
|
||||
m.ModifyType = migrationType
|
||||
m.sqls = append(m.sqls, m.GetSQL())
|
||||
|
||||
@@ -45,7 +45,7 @@ type _modelCache struct {
|
||||
done bool
|
||||
}
|
||||
|
||||
//NewModelCacheHandler generator of _modelCache
|
||||
// NewModelCacheHandler generator of _modelCache
|
||||
func NewModelCacheHandler() *_modelCache {
|
||||
return &_modelCache{
|
||||
cache: make(map[string]*modelInfo),
|
||||
@@ -113,7 +113,7 @@ func (mc *_modelCache) clean() {
|
||||
mc.done = false
|
||||
}
|
||||
|
||||
//bootstrap bootstrap for models
|
||||
// bootstrap bootstrap for models
|
||||
func (mc *_modelCache) bootstrap() {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
@@ -407,7 +407,7 @@ func (mc *_modelCache) register(prefixOrSuffixStr string, prefixOrSuffix bool, m
|
||||
return
|
||||
}
|
||||
|
||||
//getDbDropSQL get database scheme drop sql queries
|
||||
// getDbDropSQL get database scheme drop sql queries
|
||||
func (mc *_modelCache) getDbDropSQL(al *alias) (queries []string, err error) {
|
||||
if len(mc.cache) == 0 {
|
||||
err = errors.New("no Model found, need register your model")
|
||||
@@ -422,7 +422,7 @@ func (mc *_modelCache) getDbDropSQL(al *alias) (queries []string, err error) {
|
||||
return queries, nil
|
||||
}
|
||||
|
||||
//getDbCreateSQL get database scheme creation sql queries
|
||||
// getDbCreateSQL get database scheme creation sql queries
|
||||
func (mc *_modelCache) getDbCreateSQL(al *alias) (queries []string, tableIndexes map[string][]dbIndex, err error) {
|
||||
if len(mc.cache) == 0 {
|
||||
err = errors.New("no Model found, need register your model")
|
||||
@@ -467,9 +467,9 @@ func (mc *_modelCache) getDbCreateSQL(al *alias) (queries []string, tableIndexes
|
||||
column += " " + "NOT NULL"
|
||||
}
|
||||
|
||||
//if fi.initial.String() != "" {
|
||||
// if fi.initial.String() != "" {
|
||||
// column += " DEFAULT " + fi.initial.String()
|
||||
//}
|
||||
// }
|
||||
|
||||
// Append attribute DEFAULT
|
||||
column += getColumnDefault(fi)
|
||||
|
||||
@@ -194,7 +194,7 @@ checkType:
|
||||
}
|
||||
fieldType = f.FieldType()
|
||||
if fieldType&IsRelField > 0 {
|
||||
err = fmt.Errorf("unsupport type custom field, please refer to https://github.com/astaxie/beego/blob/master/orm/models_fields.go#L24-L42")
|
||||
err = fmt.Errorf("unsupport type custom field, please refer to https://github.com/beego/beego/v2/blob/master/orm/models_fields.go#L24-L42")
|
||||
goto end
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -74,7 +74,7 @@ func addModelFields(mi *modelInfo, ind reflect.Value, mName string, index []int)
|
||||
} else if err != nil {
|
||||
break
|
||||
}
|
||||
//record current field index
|
||||
// record current field index
|
||||
fi.fieldIndex = append(fi.fieldIndex, index...)
|
||||
fi.fieldIndex = append(fi.fieldIndex, i)
|
||||
fi.mi = mi
|
||||
|
||||
@@ -318,7 +318,7 @@ type Post struct {
|
||||
Created time.Time `orm:"auto_now_add"`
|
||||
Updated time.Time `orm:"auto_now"`
|
||||
UpdatedPrecision time.Time `orm:"auto_now;type(datetime);precision(4)"`
|
||||
Tags []*Tag `orm:"rel(m2m);rel_through(github.com/astaxie/beego/client/orm.PostTags)"`
|
||||
Tags []*Tag `orm:"rel(m2m);rel_through(github.com/beego/beego/v2/client/orm.PostTags)"`
|
||||
}
|
||||
|
||||
func (u *Post) TableIndex() [][]string {
|
||||
@@ -376,7 +376,7 @@ type Group struct {
|
||||
type Permission struct {
|
||||
ID int `orm:"column(id)"`
|
||||
Name string
|
||||
Groups []*Group `orm:"rel(m2m);rel_through(github.com/astaxie/beego/client/orm.GroupPermissions)"`
|
||||
Groups []*Group `orm:"rel(m2m);rel_through(github.com/beego/beego/v2/client/orm.GroupPermissions)"`
|
||||
}
|
||||
|
||||
type GroupPermissions struct {
|
||||
@@ -485,7 +485,7 @@ var (
|
||||
|
||||
usage:
|
||||
|
||||
go get -u github.com/astaxie/beego/client/orm
|
||||
go get -u github.com/beego/beego/v2/client/orm
|
||||
go get -u github.com/go-sql-driver/mysql
|
||||
go get -u github.com/mattn/go-sqlite3
|
||||
go get -u github.com/lib/pq
|
||||
@@ -495,25 +495,25 @@ var (
|
||||
mysql -u root -e 'create database orm_test;'
|
||||
export ORM_DRIVER=mysql
|
||||
export ORM_SOURCE="root:@/orm_test?charset=utf8"
|
||||
go test -v github.com/astaxie/beego/client/orm
|
||||
go test -v github.com/beego/beego/v2/client/orm
|
||||
|
||||
|
||||
#### Sqlite3
|
||||
export ORM_DRIVER=sqlite3
|
||||
export ORM_SOURCE='file:memory_test?mode=memory'
|
||||
go test -v github.com/astaxie/beego/client/orm
|
||||
go test -v github.com/beego/beego/v2/client/orm
|
||||
|
||||
|
||||
#### PostgreSQL
|
||||
psql -c 'create database orm_test;' -U postgres
|
||||
export ORM_DRIVER=postgres
|
||||
export ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
|
||||
go test -v github.com/astaxie/beego/client/orm
|
||||
go test -v github.com/beego/beego/v2/client/orm
|
||||
|
||||
#### TiDB
|
||||
export ORM_DRIVER=tidb
|
||||
export ORM_SOURCE='memory://test/test'
|
||||
go test -v github.com/astaxie/beego/pgk/orm
|
||||
go test -v github.com/beego/beego/v2/pgk/orm
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "github.com/astaxie/beego/client/orm"
|
||||
// "github.com/beego/beego/v2/client/orm"
|
||||
// _ "github.com/go-sql-driver/mysql" // import your used driver
|
||||
// )
|
||||
//
|
||||
@@ -63,10 +63,10 @@ import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/client/orm/hints"
|
||||
"github.com/astaxie/beego/core/utils"
|
||||
"github.com/beego/beego/v2/client/orm/hints"
|
||||
"github.com/beego/beego/v2/core/utils"
|
||||
|
||||
"github.com/astaxie/beego/core/logs"
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
)
|
||||
|
||||
// DebugQueries define the debug
|
||||
|
||||
@@ -77,10 +77,13 @@ func (c Condition) AndNot(expr string, args ...interface{}) *Condition {
|
||||
|
||||
// AndCond combine a condition to current condition
|
||||
func (c *Condition) AndCond(cond *Condition) *Condition {
|
||||
c = c.clone()
|
||||
|
||||
if c == cond {
|
||||
panic(fmt.Errorf("<Condition.AndCond> cannot use self as sub cond"))
|
||||
}
|
||||
|
||||
c = c.clone()
|
||||
|
||||
if cond != nil {
|
||||
c.params = append(c.params, condValue{cond: cond, isCond: true})
|
||||
}
|
||||
@@ -150,5 +153,8 @@ func (c *Condition) IsEmpty() bool {
|
||||
|
||||
// clone clone a condition
|
||||
func (c Condition) clone() *Condition {
|
||||
params := make([]condValue, len(c.params))
|
||||
copy(params, c.params)
|
||||
c.params = params
|
||||
return &c
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ type Log struct {
|
||||
*log.Logger
|
||||
}
|
||||
|
||||
//costomer log func
|
||||
// costomer log func
|
||||
var LogFunc func(query map[string]interface{})
|
||||
|
||||
// NewLog set io.Writer to create a Logger.
|
||||
|
||||
@@ -17,8 +17,8 @@ package orm
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/client/orm/hints"
|
||||
"github.com/astaxie/beego/client/orm/clauses/order_clause"
|
||||
"github.com/astaxie/beego/v2/client/orm/hints"
|
||||
"github.com/astaxie/beego/v2/client/orm/clauses/order_clause"
|
||||
)
|
||||
|
||||
type colValue struct {
|
||||
|
||||
@@ -181,6 +181,12 @@ func (o *rawSet) setFieldValue(ind reflect.Value, value interface{}) {
|
||||
if err == nil {
|
||||
ind.Set(reflect.ValueOf(t))
|
||||
}
|
||||
} else if len(str) >= 8 {
|
||||
str = str[:8]
|
||||
t, err := time.ParseInLocation(formatTime, str, DefaultTimeLoc)
|
||||
if err == nil {
|
||||
ind.Set(reflect.ValueOf(t))
|
||||
}
|
||||
}
|
||||
}
|
||||
case sql.NullString, sql.NullInt64, sql.NullFloat64, sql.NullBool:
|
||||
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/client/orm/hints"
|
||||
"github.com/beego/beego/v2/client/orm/hints"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -1780,6 +1780,10 @@ func TestRawQueryRow(t *testing.T) {
|
||||
throwFail(t, AssertIs(id, 1))
|
||||
break
|
||||
case "time":
|
||||
v = v.(time.Time).In(DefaultTimeLoc)
|
||||
value := dataValues[col].(time.Time).In(DefaultTimeLoc)
|
||||
assert.True(t, v.(time.Time).Sub(value) <= time.Second)
|
||||
break
|
||||
case "date":
|
||||
case "datetime":
|
||||
v = v.(time.Time).In(DefaultTimeLoc)
|
||||
@@ -1807,12 +1811,12 @@ func TestRawQueryRow(t *testing.T) {
|
||||
throwFail(t, AssertIs(*status, 3))
|
||||
throwFail(t, AssertIs(pid, nil))
|
||||
|
||||
type Embeded struct {
|
||||
type Embedded struct {
|
||||
Email string
|
||||
}
|
||||
type queryRowNoModelTest struct {
|
||||
Id int
|
||||
EmbedField Embeded
|
||||
EmbedField Embedded
|
||||
}
|
||||
|
||||
cols = []string{
|
||||
@@ -2702,3 +2706,48 @@ func TestPSQueryBuilder(t *testing.T) {
|
||||
throwFailNow(t, AssertIs(l[0].UserName, "astaxie"))
|
||||
throwFailNow(t, AssertIs(l[0].Age, 30))
|
||||
}
|
||||
|
||||
func TestCondition(t *testing.T) {
|
||||
// test Condition whether to include yourself
|
||||
cond := NewCondition()
|
||||
cond = cond.AndCond(cond.Or("ID", 1))
|
||||
cond = cond.AndCond(cond.Or("ID", 2))
|
||||
cond = cond.AndCond(cond.Or("ID", 3))
|
||||
cond = cond.AndCond(cond.Or("ID", 4))
|
||||
|
||||
cycleFlag := false
|
||||
var hasCycle func(*Condition)
|
||||
hasCycle = func(c *Condition) {
|
||||
if nil == c || cycleFlag {
|
||||
return
|
||||
}
|
||||
condPointMap := make(map[string]bool)
|
||||
condPointMap[fmt.Sprintf("%p", c)] = true
|
||||
for _, p := range c.params {
|
||||
if p.isCond {
|
||||
adr := fmt.Sprintf("%p", p.cond)
|
||||
if condPointMap[adr] {
|
||||
// self as sub cond was cycle
|
||||
cycleFlag = true
|
||||
break
|
||||
}
|
||||
condPointMap[adr] = true
|
||||
|
||||
}
|
||||
}
|
||||
if cycleFlag {
|
||||
return
|
||||
}
|
||||
for _, p := range c.params {
|
||||
if p.isCond {
|
||||
// check next cond
|
||||
hasCycle(p.cond)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
hasCycle(cond)
|
||||
// cycleFlag was true,meaning use self as sub cond
|
||||
throwFail(t, AssertIs(!cycleFlag, true))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/core/utils"
|
||||
"github.com/beego/beego/v2/core/utils"
|
||||
)
|
||||
|
||||
// TableNaming is usually used by model
|
||||
@@ -96,13 +96,13 @@ type Fielder interface {
|
||||
}
|
||||
|
||||
type TxBeginner interface {
|
||||
//self control transaction
|
||||
// self control transaction
|
||||
Begin() (TxOrmer, error)
|
||||
BeginWithCtx(ctx context.Context) (TxOrmer, error)
|
||||
BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error)
|
||||
BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error)
|
||||
|
||||
//closure control transaction
|
||||
// closure control transaction
|
||||
DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error
|
||||
DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error
|
||||
DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error
|
||||
@@ -114,7 +114,7 @@ type TxCommitter interface {
|
||||
Rollback() error
|
||||
}
|
||||
|
||||
//Data Manipulation Language
|
||||
// Data Manipulation Language
|
||||
type DML interface {
|
||||
// insert model data to database
|
||||
// for example:
|
||||
|
||||
@@ -49,12 +49,12 @@ func (f *StrTo) Set(v string) {
|
||||
|
||||
// Clear string
|
||||
func (f *StrTo) Clear() {
|
||||
*f = StrTo(0x1E)
|
||||
*f = StrTo(rune(0x1E))
|
||||
}
|
||||
|
||||
// Exist check string exist
|
||||
func (f StrTo) Exist() bool {
|
||||
return string(f) != string(0x1E)
|
||||
return string(f) != string(rune(0x1E))
|
||||
}
|
||||
|
||||
// Bool string to bool
|
||||
|
||||
Reference in New Issue
Block a user