Merge master and develop

This commit is contained in:
Ming Deng 2021-04-05 20:55:30 +08:00
commit 3acc858909
227 changed files with 8831 additions and 3174 deletions

12
.deepsource.toml Normal file
View File

@ -0,0 +1,12 @@
version = 1
test_patterns = ["**/*_test.go"]
exclude_patterns = ["scripts/**"]
[[analyzers]]
name = "go"
enabled = true
[analyzers.meta]
import_paths = ["github.com/beego/beego"]

34
.github/workflows/changelog.yml vendored Normal file
View File

@ -0,0 +1,34 @@
# This action requires that any PR targeting the master branch should touch at
# least one CHANGELOG file. If a CHANGELOG entry is not required, add the "Skip
# Changelog" label to disable this action.
name: changelog
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
branches:
- develop
jobs:
changelog:
runs-on: ubuntu-latest
if: "!contains(github.event.pull_request.labels.*.name, 'Skip Changelog')"
steps:
- uses: actions/checkout@v2
- name: Check for CHANGELOG changes
run: |
# Only the latest commit of the feature branch is available
# automatically. To diff with the base branch, we need to
# fetch that too (and we only need its latest commit).
git fetch origin ${{ github.base_ref }} --depth=1
if [[ $(git diff --name-only FETCH_HEAD | grep CHANGELOG) ]]
then
echo "A CHANGELOG was modified. Looks good!"
else
echo "No CHANGELOG was modified."
echo "Please add a CHANGELOG entry, or add the \"Skip Changelog\" label if not required."
false
fi

32
.github/workflows/golangci-lint.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: golangci-lint
on:
push:
tags:
- v*
branches:
- master
- main
pull_request:
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.29
# Optional: working directory, useful for monorepos
# working-directory: ./
# Optional: golangci-lint command line arguments.
args: --timeout=5m --print-issued-lines=true --print-linter-name=true --uniq-by-line=true
# Optional: show only new issues if it's a pull request. The default value is `false`.
only-new-issues: true
# Optional: if set to true then the action will use pre-installed Go
# skip-go-installation: true

2
.gitignore vendored
View File

@ -10,3 +10,5 @@ _beeTmp2/
pkg/_beeTmp/
pkg/_beeTmp2/
test/tmp/
profile.out

View File

@ -16,6 +16,7 @@ env:
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
- ORM_DRIVER=mysql export ORM_SOURCE="root:@/orm_test?charset=utf8"
before_install:
- export CODECOV_TOKEN="4f4bc484-32a8-43b7-9f48-20966bd48ceb"
# link the local repo with ${GOPATH}/src/<namespace>/<repo>
- GO_REPO_NAMESPACE=${GO_REPO_FULLNAME%/*}
# relies on GOPATH to contain only one directory...
@ -55,30 +56,9 @@ before_install:
- docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put current.serialize.name test"
- docker exec etcd-gcr-v3.3.25 /bin/sh -c "ETCDCTL_API=3 /usr/local/bin/etcdctl put sub.sub.key1 sub.sub.key"
install:
- go get github.com/lib/pq
- go get github.com/go-sql-driver/mysql
- go get github.com/mattn/go-sqlite3
- go get github.com/bradfitz/gomemcache/memcache
- go get github.com/gomodule/redigo/redis
- go get github.com/beego/x2j
- go get github.com/couchbase/go-couchbase
- go get github.com/beego/goyaml2
- go get gopkg.in/yaml.v2
- go get github.com/belogik/goes
- go get github.com/ledisdb/ledisdb
- go get github.com/ssdb/gossdb/ssdb
- go get github.com/cloudflare/golz4
- go get github.com/gogo/protobuf/proto
- go get github.com/Knetic/govaluate
- go get github.com/casbin/casbin
- go get github.com/elazarl/go-bindata-assetfs
- go get github.com/OwnLocal/goes
- go get github.com/shiena/ansicolor
- go get -u honnef.co/go/tools/cmd/staticcheck
- go get -u github.com/mdempsky/unconvert
- go get -u github.com/gordonklaus/ineffassign
- go get -u golang.org/x/lint/golint
- go get -u github.com/go-redis/redis
before_script:
# -
@ -87,19 +67,19 @@ before_script:
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"
- sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi"
- sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi"
- sh -c "go get github.com/golang/lint/golint; golint ./...;"
- sh -c "go list ./... | grep -v vendor | xargs go vet -v"
- mkdir -p res/var
- ./ssdb/ssdb-server ./ssdb/ssdb.conf -d
after_script:
- killall -w ssdb-server
- rm -rf ./res/var/*
after_success:
- bash <(curl -s https://codecov.io/bash)
script:
- go test ./...
- GO111MODULE=on go test -coverprofile=coverage.txt -covermode=atomic ./...
- staticcheck -show-ignored -checks "-ST1017,-U1000,-ST1005,-S1034,-S1012,-SA4006,-SA6005,-SA1019,-SA1024" ./
- unconvert $(go list ./... | grep -v /vendor/)
- ineffassign .
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
- golint ./...
addons:
postgresql: "9.6"

View File

@ -1,9 +1,40 @@
# developing
- Error codes definition of cache module. [4493](https://github.com/beego/beego/pull/4493)
- Remove generateCommentRoute http hook. Using `bee generate routers` commands instead.[4486](https://github.com/beego/beego/pull/4486) [bee PR 762](https://github.com/beego/bee/pull/762)
- Fix: /abc.html/aaa match /abc/aaa. [4459](https://github.com/beego/beego/pull/4459)
- ORM mock. [4407](https://github.com/beego/beego/pull/4407)
- Add sonar check and ignore test. [4432](https://github.com/beego/beego/pull/4432) [4433](https://github.com/beego/beego/pull/4433)
- Update changlog.yml to check every PR to develop branch.[4427](https://github.com/beego/beego/pull/4427)
- Fix 4396: Add context.param module into adapter. [4398](https://github.com/beego/beego/pull/4398)
- Support `RollbackUnlessCommit` API. [4542](https://github.com/beego/beego/pull/4542)
- Fix 4503 and 4504: Add `when` to `Write([]byte)` method and add `prefix` to `writeMsg`. [4507](https://github.com/beego/beego/pull/4507)
- Fix 4480: log format incorrect. [4482](https://github.com/beego/beego/pull/4482)
- Remove `duration` from prometheus labels. [4391](https://github.com/beego/beego/pull/4391)
- Fix `unknown escape sequence` in generated code. [4385](https://github.com/beego/beego/pull/4385)
- Using fixed name `commentRouter.go` as generated file name. [4385](https://github.com/beego/beego/pull/4385)
- Fix 4383: ORM Adapter produces panic when using orm.RegisterModelWithPrefix. [4386](https://github.com/beego/beego/pull/4386)
- Support 4144: Add new api for order by for supporting multiple way to query [4294](https://github.com/beego/beego/pull/4294)
- Support session Filter chain. [4404](https://github.com/beego/beego/pull/4404)
- Feature issue #4402 finish router get example. [4416](https://github.com/beego/beego/pull/4416)
- Implement context.Context support and deprecate `QueryM2MWithCtx` and `QueryTableWithCtx` [4424](https://github.com/beego/beego/pull/4424)
- Finish timeout option for tasks #4441 [4441](https://github.com/beego/beego/pull/4441)
- Error Module brief design & using httplib module to validate this design. [4453](https://github.com/beego/beego/pull/4453)
- Fix 4444: panic when 404 not found. [4446](https://github.com/beego/beego/pull/4446)
- Fix 4435: fix panic when controller dir not found. [4452](https://github.com/beego/beego/pull/4452)
- Fix 4435: fix panic when controller dir not found. [4452](https://github.com/beego/beego/pull/4452)
- Fix 4456: Fix router method expression [4456](https://github.com/beego/beego/pull/4456)
- Remove some `go get` lines in `.travis.yml` file [4469](https://github.com/beego/beego/pull/4469)
- Fix 4451: support QueryExecutor interface. [4461](https://github.com/beego/beego/pull/4461)
- Add some testing scripts [4461](https://github.com/beego/beego/pull/4461)
- Refactor httplib: Move debug code to a filter [4440](https://github.com/beego/beego/issues/4440)
- fix: code quality issues [4513](https://github.com/beego/beego/pull/4513)
- Optimize maligned structs to reduce memory foot-print [4525](https://github.com/beego/beego/pull/4525)
- Feat: add token bucket ratelimit filter [4508](https://github.com/beego/beego/pull/4508)
- Improve: Avoid ignoring mistakes that need attention [4548](https://github.com/beego/beego/pull/4548)
- Integration: DeepSource [4560](https://github.com/beego/beego/pull/4560)
## Fix Sonar
- [4473](https://github.com/beego/beego/pull/4473)
- [4474](https://github.com/beego/beego/pull/4474)
- [4479](https://github.com/beego/beego/pull/4479)

View File

@ -36,7 +36,7 @@ We provide docker compose file to start all middlewares.
You can run:
```shell script
docker-compose -f scripts/test_docker_compose.yml up -d
docker-compose -f scripts/test_docker_compose.yaml up -d
```
Unit tests read addresses from environment, here is an example:
@ -53,7 +53,7 @@ export SSDB_ADDR="192.168.0.105:8888"
### Pull requests
First of all. beego follow the gitflow. So please send you pull request to **develop-2** branch. We will close the pull
First of all. beego follow the gitflow. So please send you pull request to **develop** branch. We will close the pull
request to master branch.
We are always happy to receive pull requests, and do our best to review them as fast as possible. Not sure if that typo

5
ERROR_SPECIFICATION.md Normal file
View File

@ -0,0 +1,5 @@
# Error Module
## Module code
- httplib 1
- cache 2

View File

@ -1,4 +1,4 @@
# Beego [![Build Status](https://travis-ci.org/astaxie/beego.svg?branch=master)](https://travis-ci.org/astaxie/beego) [![GoDoc](http://godoc.org/github.com/beego/beego/v2?status.svg)](http://godoc.org/github.com/beego/beego/v2) [![Foundation](https://img.shields.io/badge/Golang-Foundation-green.svg)](http://golangfoundation.org) [![Go Report Card](https://goreportcard.com/badge/github.com/beego/beego/v2)](https://goreportcard.com/report/github.com/beego/beego/v2)
# Beego [![Build Status](https://travis-ci.org/beego/beego.svg?branch=master)](https://travis-ci.org/beego/beego) [![GoDoc](http://godoc.org/github.com/beego/beego?status.svg)](http://godoc.org/github.com/beego/beego) [![Foundation](https://img.shields.io/badge/Golang-Foundation-green.svg)](http://golangfoundation.org) [![Go Report Card](https://goreportcard.com/badge/github.com/beego/beego)](https://goreportcard.com/report/github.com/beego/beego)
Beego is used for rapid development of enterprise application in Go, including RESTful APIs, web apps and backend
services.
@ -19,7 +19,7 @@ Beego is compos of four parts:
## Quick Start
[Officail website](http://beego.me)
[Official website](http://beego.me)
[Example](https://github.com/beego/beego-example)
@ -40,7 +40,7 @@ Beego is compos of four parts:
#### Download and install
go get github.com/beego/beego/v2@v2.0.0
go get github.com/beego/beego/v2@latest
#### Create file `hello.go`
@ -90,8 +90,7 @@ Congratulations! You've just built your first **beego** app.
## Community
* [http://beego.me/community](http://beego.me/community)
* Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited
from [here](https://github.com/beego/beedoc/issues/232)
* Welcome to join us in Slack: [https://beego.slack.com invite](https://join.slack.com/t/beego/shared_invite/zt-fqlfjaxs-_CRmiITCSbEqQG9NeBqXKA),
* QQ Group Group ID:523992905
* [Contribution Guide](https://github.com/beego/beedoc/blob/master/en-US/intro/contributing.md).

View File

@ -16,7 +16,7 @@
// Usage:
//
// import(
// "github.com/beego/beego/v2/cache"
// "github.com/beego/beego/v2/client/cache"
// )
//
// bm, err := cache.NewCache("memory", `{"interval":60}`)

View File

@ -19,12 +19,22 @@ import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
const (
initError = "init err"
setError = "set Error"
checkError = "check err"
getError = "get err"
getMultiError = "GetMulti Error"
)
func TestCacheIncr(t *testing.T) {
bm, err := NewCache("memory", `{"interval":20}`)
if err != nil {
t.Error("init err")
t.Error(initError)
}
// timeoutDuration := 10 * time.Second
@ -45,147 +55,95 @@ func TestCacheIncr(t *testing.T) {
func TestCache(t *testing.T) {
bm, err := NewCache("memory", `{"interval":20}`)
if err != nil {
t.Error("init err")
}
timeoutDuration := 10 * time.Second
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
if v := bm.Get("astaxie"); v.(int) != 1 {
t.Error("get err")
}
assert.Nil(t, err)
time.Sleep(30 * time.Second)
timeoutDuration := 5 * time.Second
err = bm.Put("astaxie", 1, timeoutDuration)
assert.Nil(t, err)
if bm.IsExist("astaxie") {
t.Error("check err")
}
assert.True(t, bm.IsExist("astaxie"))
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err)
}
assert.Equal(t, 1, bm.Get("astaxie"))
if err = bm.Incr("astaxie"); err != nil {
t.Error("Incr Error", err)
}
time.Sleep(10 * time.Second)
if v := bm.Get("astaxie"); v.(int) != 2 {
t.Error("get err")
}
assert.False(t, bm.IsExist("astaxie"))
if err = bm.Decr("astaxie"); err != nil {
t.Error("Decr Error", err)
}
err = bm.Put("astaxie", 1, timeoutDuration)
assert.Nil(t, err)
if v := bm.Get("astaxie"); v.(int) != 1 {
t.Error("get err")
}
bm.Delete("astaxie")
if bm.IsExist("astaxie") {
t.Error("delete err")
}
err = bm.Incr("astaxie")
assert.Nil(t, err)
// test GetMulti
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
if v := bm.Get("astaxie"); v.(string) != "author" {
t.Error("get err")
}
assert.Equal(t, 2, bm.Get("astaxie"))
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie1") {
t.Error("check err")
}
assert.Nil(t, bm.Decr("astaxie"))
assert.Equal(t, 1, bm.Get("astaxie"))
assert.Nil(t, bm.Delete("astaxie"))
assert.False(t, bm.IsExist("astaxie"))
assert.Nil(t, bm.Put("astaxie", "author", timeoutDuration))
assert.True(t, bm.IsExist("astaxie"))
assert.Equal(t, "author", bm.Get("astaxie"))
assert.Nil(t, bm.Put("astaxie1", "author1", timeoutDuration))
assert.True(t, bm.IsExist("astaxie1"))
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if vv[0].(string) != "author" {
t.Error("GetMulti ERROR")
}
if vv[1].(string) != "author1" {
t.Error("GetMulti ERROR")
}
assert.Equal(t, 2, len(vv))
assert.Equal(t, "author", vv[0])
assert.Equal(t, "author1", vv[1])
}
func TestFileCache(t *testing.T) {
bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`)
if err != nil {
t.Error("init err")
}
timeoutDuration := 10 * time.Second
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
if v := bm.Get("astaxie"); v.(int) != 1 {
t.Error("get err")
}
assert.Nil(t, err)
timeoutDuration := 5 * time.Second
if err = bm.Incr("astaxie"); err != nil {
t.Error("Incr Error", err)
}
assert.Nil(t, bm.Put("astaxie", 1, timeoutDuration))
if v := bm.Get("astaxie"); v.(int) != 2 {
t.Error("get err")
}
assert.True(t, bm.IsExist("astaxie"))
if err = bm.Decr("astaxie"); err != nil {
t.Error("Decr Error", err)
}
assert.Equal(t, 1, bm.Get("astaxie"))
if v := bm.Get("astaxie"); v.(int) != 1 {
t.Error("get err")
}
bm.Delete("astaxie")
if bm.IsExist("astaxie") {
t.Error("delete err")
}
assert.Nil(t, bm.Incr("astaxie"))
// test string
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
if v := bm.Get("astaxie"); v.(string) != "author" {
t.Error("get err")
}
assert.Equal(t, 2, bm.Get("astaxie"))
// test GetMulti
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie1") {
t.Error("check err")
}
assert.Nil(t, bm.Decr("astaxie"))
assert.Equal(t, 1, bm.Get("astaxie"))
assert.Nil(t, bm.Delete("astaxie"))
assert.False(t, bm.IsExist("astaxie"))
assert.Nil(t, bm.Put("astaxie", "author", timeoutDuration))
assert.True(t, bm.IsExist("astaxie"))
assert.Equal(t, "author", bm.Get("astaxie"))
assert.Nil(t, bm.Put("astaxie1", "author1", timeoutDuration))
assert.True(t, bm.IsExist("astaxie1"))
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if vv[0].(string) != "author" {
t.Error("GetMulti ERROR")
}
if vv[1].(string) != "author1" {
t.Error("GetMulti ERROR")
}
os.RemoveAll("cache")
assert.Equal(t, 2, len(vv))
assert.Equal(t, "author", vv[0])
assert.Equal(t, "author1", vv[1])
assert.Nil(t, os.RemoveAll("cache"))
}

View File

@ -20,8 +20,8 @@
//
// Usage:
// import(
// _ "github.com/beego/beego/v2/cache/memcache"
// "github.com/beego/beego/v2/cache"
// _ "github.com/beego/beego/v2/client/cache/memcache"
// "github.com/beego/beego/v2/client/cache"
// )
//
// bm, err := cache.NewCache("memcache", `{"conn":"127.0.0.1:11211"}`)

View File

@ -21,9 +21,19 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/adapter/cache"
)
const (
initError = "init err"
setError = "set Error"
checkError = "check err"
getError = "get err"
getMultiError = "GetMulti Error"
)
func TestMemcacheCache(t *testing.T) {
addr := os.Getenv("MEMCACHE_ADDR")
@ -32,83 +42,52 @@ func TestMemcacheCache(t *testing.T) {
}
bm, err := cache.NewCache("memcache", fmt.Sprintf(`{"conn": "%s"}`, addr))
if err != nil {
t.Error("init err")
}
timeoutDuration := 10 * time.Second
if err = bm.Put("astaxie", "1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
assert.Nil(t, err)
timeoutDuration := 5 * time.Second
assert.Nil(t, bm.Put("astaxie", "1", timeoutDuration))
assert.True(t, bm.IsExist("astaxie"))
time.Sleep(11 * time.Second)
if bm.IsExist("astaxie") {
t.Error("check err")
}
if err = bm.Put("astaxie", "1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
assert.False(t, bm.IsExist("astaxie"))
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
t.Error("get err")
}
assert.Nil(t, bm.Put("astaxie", "1", timeoutDuration))
v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte)))
assert.Nil(t, err)
assert.Equal(t, 1, v)
if err = bm.Incr("astaxie"); err != nil {
t.Error("Incr Error", err)
}
assert.Nil(t, bm.Incr("astaxie"))
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 2 {
t.Error("get err")
}
v, err = strconv.Atoi(string(bm.Get("astaxie").([]byte)))
assert.Nil(t, err)
assert.Equal(t, 2, v)
if err = bm.Decr("astaxie"); err != nil {
t.Error("Decr Error", err)
}
assert.Nil(t, bm.Decr("astaxie"))
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
t.Error("get err")
}
bm.Delete("astaxie")
if bm.IsExist("astaxie") {
t.Error("delete err")
}
v, err = strconv.Atoi(string(bm.Get("astaxie").([]byte)))
assert.Nil(t, err)
assert.Equal(t, 1, v)
// test string
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
assert.Nil(t, bm.Delete("astaxie"))
if v := bm.Get("astaxie").([]byte); string(v) != "author" {
t.Error("get err")
}
assert.False(t, bm.IsExist("astaxie"))
// test GetMulti
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie1") {
t.Error("check err")
}
assert.Nil(t, bm.Put("astaxie", "author", timeoutDuration))
assert.True(t, bm.IsExist("astaxie"))
assert.Equal(t, []byte("author"), bm.Get("astaxie"))
assert.Nil(t, bm.Put("astaxie1", "author1", timeoutDuration))
assert.True(t, bm.IsExist("astaxie1"))
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if string(vv[0].([]byte)) != "author" && string(vv[0].([]byte)) != "author1" {
t.Error("GetMulti ERROR")
}
if string(vv[1].([]byte)) != "author1" && string(vv[1].([]byte)) != "author" {
t.Error("GetMulti ERROR")
}
assert.Equal(t, 2, len(vv))
assert.Equal(t, []byte("author"), vv[0])
assert.Equal(t, []byte("author1"), vv[1])
// test clear all
if err = bm.ClearAll(); err != nil {
t.Error("clear all err")
}
assert.Nil(t, bm.ClearAll())
}

View File

@ -20,8 +20,8 @@
//
// Usage:
// import(
// _ "github.com/beego/beego/v2/cache/redis"
// "github.com/beego/beego/v2/cache"
// _ "github.com/beego/beego/v2/client/cache/redis"
// "github.com/beego/beego/v2/client/cache"
// )
//
// bm, err := cache.NewCache("redis", `{"conn":"127.0.0.1:11211"}`)

View File

@ -21,10 +21,19 @@ import (
"time"
"github.com/gomodule/redigo/redis"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/adapter/cache"
)
const (
initError = "init err"
setError = "set Error"
checkError = "check err"
getError = "get err"
getMultiError = "GetMulti Error"
)
func TestRedisCache(t *testing.T) {
redisAddr := os.Getenv("REDIS_ADDR")
if redisAddr == "" {
@ -32,98 +41,79 @@ func TestRedisCache(t *testing.T) {
}
bm, err := cache.NewCache("redis", fmt.Sprintf(`{"conn": "%s"}`, redisAddr))
if err != nil {
t.Error("init err")
}
timeoutDuration := 10 * time.Second
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
assert.Nil(t, err)
timeoutDuration := 5 * time.Second
time.Sleep(11 * time.Second)
assert.Nil(t, bm.Put("astaxie", 1, timeoutDuration))
if bm.IsExist("astaxie") {
t.Error("check err")
}
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err)
}
assert.True(t, bm.IsExist("astaxie"))
if v, _ := redis.Int(bm.Get("astaxie"), err); v != 1 {
t.Error("get err")
}
time.Sleep(7 * time.Second)
if err = bm.Incr("astaxie"); err != nil {
t.Error("Incr Error", err)
}
assert.False(t, bm.IsExist("astaxie"))
if v, _ := redis.Int(bm.Get("astaxie"), err); v != 2 {
t.Error("get err")
}
assert.Nil(t, bm.Put("astaxie", 1, timeoutDuration))
if err = bm.Decr("astaxie"); err != nil {
t.Error("Decr Error", err)
}
v, err := redis.Int(bm.Get("astaxie"), err)
assert.Nil(t, err)
assert.Equal(t, 1, v)
if v, _ := redis.Int(bm.Get("astaxie"), err); v != 1 {
t.Error("get err")
}
bm.Delete("astaxie")
if bm.IsExist("astaxie") {
t.Error("delete err")
}
assert.Nil(t, bm.Incr("astaxie"))
// test string
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
v, err = redis.Int(bm.Get("astaxie"), err)
assert.Nil(t, err)
assert.Equal(t, 2, v)
if v, _ := redis.String(bm.Get("astaxie"), err); v != "author" {
t.Error("get err")
}
assert.Nil(t, bm.Decr("astaxie"))
// test GetMulti
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie1") {
t.Error("check err")
}
v, err = redis.Int(bm.Get("astaxie"), err)
assert.Nil(t, err)
assert.Equal(t, 1, v)
assert.Nil(t, bm.Delete("astaxie"))
assert.False(t, bm.IsExist("astaxie"))
assert.Nil(t, bm.Put("astaxie", "author", timeoutDuration))
assert.True(t, bm.IsExist("astaxie"))
vs, err := redis.String(bm.Get("astaxie"), err)
assert.Nil(t, err)
assert.Equal(t, "author", vs)
assert.Nil(t, bm.Put("astaxie1", "author1", timeoutDuration))
assert.True(t, bm.IsExist("astaxie1"))
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if v, _ := redis.String(vv[0], nil); v != "author" {
t.Error("GetMulti ERROR")
}
if v, _ := redis.String(vv[1], nil); v != "author1" {
t.Error("GetMulti ERROR")
}
assert.Equal(t, 2, len(vv))
vs, err = redis.String(vv[0], nil)
assert.Nil(t, err)
assert.Equal(t, "author", vs)
vs, err = redis.String(vv[1], nil)
assert.Nil(t, err)
assert.Equal(t, "author1", vs)
assert.Nil(t, bm.ClearAll())
// test clear all
if err = bm.ClearAll(); err != nil {
t.Error("clear all err")
}
}
func TestCache_Scan(t *testing.T) {
func TestCacheScan(t *testing.T) {
timeoutDuration := 10 * time.Second
// init
bm, err := cache.NewCache("redis", `{"conn": "127.0.0.1:6379"}`)
if err != nil {
t.Error("init err")
t.Error(initError)
}
// insert all
for i := 0; i < 10000; i++ {
if err = bm.Put(fmt.Sprintf("astaxie%d", i), fmt.Sprintf("author%d", i), timeoutDuration); err != nil {
t.Error("set Error", err)
t.Error(setError, err)
}
}

View File

@ -7,9 +7,19 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/adapter/cache"
)
const (
initError = "init err"
setError = "set Error"
checkError = "check err"
getError = "get err"
getMultiError = "GetMulti Error"
)
func TestSsdbcacheCache(t *testing.T) {
ssdbAddr := os.Getenv("SSDB_ADDR")
if ssdbAddr == "" {
@ -17,95 +27,59 @@ func TestSsdbcacheCache(t *testing.T) {
}
ssdb, err := cache.NewCache("ssdb", fmt.Sprintf(`{"conn": "%s"}`, ssdbAddr))
if err != nil {
t.Error("init err")
}
assert.Nil(t, err)
assert.False(t, ssdb.IsExist("ssdb"))
// test put and exist
if ssdb.IsExist("ssdb") {
t.Error("check err")
}
timeoutDuration := 10 * time.Second
timeoutDuration := 3 * time.Second
// timeoutDuration := -10*time.Second if timeoutDuration is negtive,it means permanent
if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !ssdb.IsExist("ssdb") {
t.Error("check err")
}
assert.Nil(t, ssdb.Put("ssdb", "ssdb", timeoutDuration))
assert.True(t, ssdb.IsExist("ssdb"))
// Get test done
if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil {
t.Error("set Error", err)
}
assert.Nil(t, ssdb.Put("ssdb", "ssdb", timeoutDuration))
if v := ssdb.Get("ssdb"); v != "ssdb" {
t.Error("get Error")
}
assert.Equal(t, "ssdb", ssdb.Get("ssdb"))
// inc/dec test done
if err = ssdb.Put("ssdb", "2", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if err = ssdb.Incr("ssdb"); err != nil {
t.Error("incr Error", err)
}
assert.Nil(t, ssdb.Put("ssdb", "2", timeoutDuration))
if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 {
t.Error("get err")
}
assert.Nil(t, ssdb.Incr("ssdb"))
if err = ssdb.Decr("ssdb"); err != nil {
t.Error("decr error")
}
v, err := strconv.Atoi(ssdb.Get("ssdb").(string))
assert.Nil(t, err)
assert.Equal(t, 3, v)
assert.Nil(t, ssdb.Decr("ssdb"))
assert.Nil(t, ssdb.Put("ssdb", "3", timeoutDuration))
// test del
if err = ssdb.Put("ssdb", "3", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 {
t.Error("get err")
}
if err := ssdb.Delete("ssdb"); err == nil {
if ssdb.IsExist("ssdb") {
t.Error("delete err")
}
}
v, err = strconv.Atoi(ssdb.Get("ssdb").(string))
assert.Nil(t, err)
assert.Equal(t, 3, v)
assert.Nil(t, ssdb.Delete("ssdb"))
assert.False(t, ssdb.IsExist("ssdb"))
// test string
if err = ssdb.Put("ssdb", "ssdb", -10*time.Second); err != nil {
t.Error("set Error", err)
}
if !ssdb.IsExist("ssdb") {
t.Error("check err")
}
if v := ssdb.Get("ssdb").(string); v != "ssdb" {
t.Error("get err")
}
assert.Nil(t, ssdb.Put("ssdb", "ssdb", -10*time.Second))
assert.True(t, ssdb.IsExist("ssdb"))
assert.Equal(t, "ssdb", ssdb.Get("ssdb"))
// test GetMulti done
if err = ssdb.Put("ssdb1", "ssdb1", -10*time.Second); err != nil {
t.Error("set Error", err)
}
if !ssdb.IsExist("ssdb1") {
t.Error("check err")
}
vv := ssdb.GetMulti([]string{"ssdb", "ssdb1"})
if len(vv) != 2 {
t.Error("getmulti error")
}
if vv[0].(string) != "ssdb" {
t.Error("getmulti error")
}
if vv[1].(string) != "ssdb1" {
t.Error("getmulti error")
}
assert.Nil(t, ssdb.Put("ssdb1", "ssdb1", -10*time.Second))
assert.True(t, ssdb.IsExist("ssdb1") )
vv := ssdb.GetMulti([]string{"ssdb", "ssdb1"})
assert.Equal(t, 2, len(vv))
assert.Equal(t, "ssdb", vv[0])
assert.Equal(t, "ssdb1", vv[1])
assert.Nil(t, ssdb.ClearAll())
assert.False(t, ssdb.IsExist("ssdb"))
assert.False(t, ssdb.IsExist("ssdb1"))
// test clear all done
if err = ssdb.ClearAll(); err != nil {
t.Error("clear all err")
}
if ssdb.IsExist("ssdb") || ssdb.IsExist("ssdb1") {
t.Error("check err")
}
}

View File

@ -14,7 +14,7 @@
// Package config is used to parse config.
// Usage:
// import "github.com/beego/beego/v2/config"
// import "github.com/beego/beego/v2/core/config"
// Examples.
//
// cnf, err := config.NewConfig("ini", "config.conf")

View File

@ -81,7 +81,8 @@ password = ${GOPATH}
}
)
f, err := os.Create("testini.conf")
cfgFile := "testini.conf"
f, err := os.Create(cfgFile)
if err != nil {
t.Fatal(err)
}
@ -91,8 +92,8 @@ password = ${GOPATH}
t.Fatal(err)
}
f.Close()
defer os.Remove("testini.conf")
iniconf, err := NewConfig("ini", "testini.conf")
defer os.Remove(cfgFile)
iniconf, err := NewConfig("ini", cfgFile)
if err != nil {
t.Fatal(err)
}

View File

@ -18,6 +18,8 @@ import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestJsonStartsWithArray(t *testing.T) {
@ -32,7 +34,8 @@ func TestJsonStartsWithArray(t *testing.T) {
"serviceAPI": "http://www.test.com/employee"
}
]`
f, err := os.Create("testjsonWithArray.conf")
cfgFileName := "testjsonWithArray.conf"
f, err := os.Create(cfgFileName)
if err != nil {
t.Fatal(err)
}
@ -42,8 +45,8 @@ func TestJsonStartsWithArray(t *testing.T) {
t.Fatal(err)
}
f.Close()
defer os.Remove("testjsonWithArray.conf")
jsonconf, err := NewConfig("json", "testjsonWithArray.conf")
defer os.Remove(cfgFileName)
jsonconf, err := NewConfig("json", cfgFileName)
if err != nil {
t.Fatal(err)
}
@ -132,7 +135,8 @@ func TestJson(t *testing.T) {
}
)
f, err := os.Create("testjson.conf")
cfgFileName := "testjson.conf"
f, err := os.Create(cfgFileName)
if err != nil {
t.Fatal(err)
}
@ -142,8 +146,8 @@ func TestJson(t *testing.T) {
t.Fatal(err)
}
f.Close()
defer os.Remove("testjson.conf")
jsonconf, err := NewConfig("json", "testjson.conf")
defer os.Remove(cfgFileName)
jsonconf, err := NewConfig("json", cfgFileName)
if err != nil {
t.Fatal(err)
}
@ -167,56 +171,39 @@ func TestJson(t *testing.T) {
default:
value, err = jsonconf.DIY(k)
}
if err != nil {
t.Fatalf("get key %q value fatal,%v err %s", k, v, err)
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
}
}
if err = jsonconf.Set("name", "astaxie"); err != nil {
t.Fatal(err)
}
if jsonconf.String("name") != "astaxie" {
t.Fatal("get name error")
assert.Nil(t, err)
assert.Equal(t, fmt.Sprintf("%v", v), fmt.Sprintf("%v", value))
}
if db, err := jsonconf.DIY("database"); err != nil {
t.Fatal(err)
} else if m, ok := db.(map[string]interface{}); !ok {
t.Log(db)
t.Fatal("db not map[string]interface{}")
} else {
if m["host"].(string) != "host" {
t.Fatal("get host err")
}
}
assert.Nil(t, jsonconf.Set("name", "astaxie"))
if _, err := jsonconf.Int("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting an Int")
}
assert.Equal(t, "astaxie", jsonconf.String("name"))
if _, err := jsonconf.Int64("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting an Int64")
}
db, err := jsonconf.DIY("database")
assert.Nil(t, err)
if _, err := jsonconf.Float("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting a Float")
}
m, ok := db.(map[string]interface{})
assert.True(t, ok)
assert.Equal(t,"host" , m["host"])
if _, err := jsonconf.DIY("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting an interface{}")
}
_, err = jsonconf.Int("unknown")
assert.NotNil(t, err)
if val := jsonconf.String("unknown"); val != "" {
t.Error("unknown keys should return an empty string when expecting a String")
}
_, err = jsonconf.Int64("unknown")
assert.NotNil(t, err)
if _, err := jsonconf.Bool("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting a Bool")
}
_, err = jsonconf.Float("unknown")
assert.NotNil(t, err)
if !jsonconf.DefaultBool("unknown", true) {
t.Error("unknown keys with default value wrong")
}
_, err = jsonconf.DIY("unknown")
assert.NotNil(t, err)
val := jsonconf.String("unknown")
assert.Equal(t, "", val)
_, err = jsonconf.Bool("unknown")
assert.NotNil(t, err)
assert.True(t, jsonconf.DefaultBool("unknown", true))
}

View File

@ -20,8 +20,8 @@
//
// Usage:
// import(
// _ "github.com/beego/beego/v2/config/xml"
// "github.com/beego/beego/v2/config"
// _ "github.com/beego/beego/v2/core/config/xml"
// "github.com/beego/beego/v2/core/config"
// )
//
// cnf, err := config.NewConfig("xml", "config.xml")

View File

@ -58,7 +58,8 @@ func TestXML(t *testing.T) {
}
)
f, err := os.Create("testxml.conf")
cfgFileName := "testxml.conf"
f, err := os.Create(cfgFileName)
if err != nil {
t.Fatal(err)
}
@ -68,9 +69,9 @@ func TestXML(t *testing.T) {
t.Fatal(err)
}
f.Close()
defer os.Remove("testxml.conf")
defer os.Remove(cfgFileName)
xmlconf, err := config.NewConfig("xml", "testxml.conf")
xmlconf, err := config.NewConfig("xml", cfgFileName)
if err != nil {
t.Fatal(err)
}

View File

@ -20,8 +20,8 @@
//
// Usage:
// import(
// _ "github.com/beego/beego/v2/config/yaml"
// "github.com/beego/beego/v2/config"
// _ "github.com/beego/beego/v2/core/config/yaml"
// "github.com/beego/beego/v2/core/config"
// )
//
// cnf, err := config.NewConfig("yaml", "config.yaml")

View File

@ -54,7 +54,8 @@ func TestYaml(t *testing.T) {
"emptystrings": []string{},
}
)
f, err := os.Create("testyaml.conf")
cfgFileName := "testyaml.conf"
f, err := os.Create(cfgFileName)
if err != nil {
t.Fatal(err)
}
@ -64,8 +65,8 @@ func TestYaml(t *testing.T) {
t.Fatal(err)
}
f.Close()
defer os.Remove("testyaml.conf")
yamlconf, err := config.NewConfig("yaml", "testyaml.conf")
defer os.Remove(cfgFileName)
yamlconf, err := config.NewConfig("yaml", cfgFileName)
if err != nil {
t.Fatal(err)
}

View File

@ -15,7 +15,7 @@
// Package context provide the context utils
// Usage:
//
// import "github.com/beego/beego/v2/context"
// import "github.com/beego/beego/v2/server/web/context"
//
// ctx := context.Context{Request:req,ResponseWriter:rw}
//

View File

@ -0,0 +1,18 @@
package param
import (
"reflect"
beecontext "github.com/beego/beego/v2/adapter/context"
"github.com/beego/beego/v2/server/web/context"
"github.com/beego/beego/v2/server/web/context/param"
)
// ConvertParams converts http method params to values that will be passed to the method controller as arguments
func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) {
nps := make([]*param.MethodParam, 0, len(methodParams))
for _, mp := range methodParams {
nps = append(nps, (*param.MethodParam)(mp))
}
return param.ConvertParams(nps, methodType, (*context.Context)(ctx))
}

View File

@ -0,0 +1,41 @@
// 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 param
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/adapter/context"
)
// Demo is used to test, it's empty
func Demo(i int) {
}
func TestConvertParams(t *testing.T) {
res := ConvertParams(nil, reflect.TypeOf(Demo), context.NewContext())
assert.Equal(t, 0, len(res))
ctx := context.NewContext()
ctx.Input.RequestBody = []byte("11")
res = ConvertParams([]*MethodParam{
New("A", InBody),
}, reflect.TypeOf(Demo), ctx)
assert.Equal(t, int64(11), res[0].Int())
}

View File

@ -0,0 +1,29 @@
package param
import (
"github.com/beego/beego/v2/server/web/context/param"
)
// MethodParam keeps param information to be auto passed to controller methods
type MethodParam param.MethodParam
// New creates a new MethodParam with name and specific options
func New(name string, opts ...MethodParamOption) *MethodParam {
newOps := make([]param.MethodParamOption, 0, len(opts))
for _, o := range opts {
newOps = append(newOps, oldMpoToNew(o))
}
return (*MethodParam)(param.New(name, newOps...))
}
// Make creates an array of MethodParmas or an empty array
func Make(list ...*MethodParam) []*MethodParam {
if len(list) > 0 {
return list
}
return nil
}
func (mp *MethodParam) String() string {
return (*param.MethodParam)(mp).String()
}

View File

@ -0,0 +1,34 @@
// 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 param
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMethodParamString(t *testing.T) {
method := New("myName", IsRequired, InHeader, Default("abc"))
s := method.String()
assert.Equal(t, `param.New("myName", param.IsRequired, param.InHeader, param.Default("abc"))`, s)
}
func TestMake(t *testing.T) {
res := Make()
assert.Equal(t, 0, len(res))
res = Make(New("myName", InBody))
assert.Equal(t, 1, len(res))
}

View File

@ -0,0 +1,45 @@
package param
import (
"github.com/beego/beego/v2/server/web/context/param"
)
// MethodParamOption defines a func which apply options on a MethodParam
type MethodParamOption func(*MethodParam)
// IsRequired indicates that this param is required and can not be omitted from the http request
var IsRequired MethodParamOption = func(p *MethodParam) {
param.IsRequired((*param.MethodParam)(p))
}
// InHeader indicates that this param is passed via an http header
var InHeader MethodParamOption = func(p *MethodParam) {
param.InHeader((*param.MethodParam)(p))
}
// InPath indicates that this param is part of the URL path
var InPath MethodParamOption = func(p *MethodParam) {
param.InPath((*param.MethodParam)(p))
}
// InBody indicates that this param is passed as an http request body
var InBody MethodParamOption = func(p *MethodParam) {
param.InBody((*param.MethodParam)(p))
}
// Default provides a default value for the http param
func Default(defaultValue interface{}) MethodParamOption {
return newMpoToOld(param.Default(defaultValue))
}
func newMpoToOld(n param.MethodParamOption) MethodParamOption {
return func(methodParam *MethodParam) {
n((*param.MethodParam)(methodParam))
}
}
func oldMpoToNew(old MethodParamOption) param.MethodParamOption {
return func(methodParam *param.MethodParam) {
old((*MethodParam)(methodParam))
}
}

View File

@ -22,7 +22,7 @@
// "net/http"
// "os"
//
// "github.com/beego/beego/v2/grace"
// "github.com/beego/beego/v2/server/web/grace"
// )
//
// func handler(w http.ResponseWriter, r *http.Request) {

View File

@ -15,7 +15,7 @@
// Package httplib is used as http.Client
// Usage:
//
// import "github.com/beego/beego/v2/httplib"
// import "github.com/beego/beego/v2/client/httplib"
//
// b := httplib.Post("http://beego.me/")
// b.Param("username","astaxie")
@ -115,12 +115,6 @@ func (b *BeegoHTTPRequest) SetUserAgent(useragent string) *BeegoHTTPRequest {
return b
}
// Debug sets show debug or not when executing request.
func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest {
b.delegate.Debug(isdebug)
return b
}
// Retries sets Retries times.
// default is 0 means no retried.
// -1 means retried forever.
@ -135,17 +129,6 @@ func (b *BeegoHTTPRequest) RetryDelay(delay time.Duration) *BeegoHTTPRequest {
return b
}
// DumpBody setting whether need to Dump the Body.
func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest {
b.delegate.DumpBody(isdump)
return b
}
// DumpRequest return the DumpRequest
func (b *BeegoHTTPRequest) DumpRequest() []byte {
return b.delegate.DumpRequest()
}
// SetTimeout sets connect time out and read-write time out for BeegoRequest.
func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHTTPRequest {
b.delegate.SetTimeout(connectTimeout, readWriteTimeout)

View File

@ -15,6 +15,7 @@
package httplib
import (
"bytes"
"errors"
"io/ioutil"
"net"
@ -25,8 +26,11 @@ import (
"time"
)
const getUrl = "http://httpbin.org/get"
const ipUrl = "http://httpbin.org/ip"
func TestResponse(t *testing.T) {
req := Get("http://httpbin.org/get")
req := Get(getUrl)
resp, err := req.Response()
if err != nil {
t.Fatal(err)
@ -63,7 +67,8 @@ func TestDoRequest(t *testing.T) {
}
func TestGet(t *testing.T) {
req := Get("http://httpbin.org/get")
req := Get(getUrl)
b, err := req.Bytes()
if err != nil {
t.Fatal(err)
@ -205,7 +210,7 @@ func TestWithSetting(t *testing.T) {
setting.ReadWriteTimeout = 5 * time.Second
SetDefaultSetting(setting)
str, err := Get("http://httpbin.org/get").String()
str, err := Get(getUrl).String()
if err != nil {
t.Fatal(err)
}
@ -218,7 +223,8 @@ func TestWithSetting(t *testing.T) {
}
func TestToJson(t *testing.T) {
req := Get("http://httpbin.org/ip")
req := Get(ipUrl)
resp, err := req.Response()
if err != nil {
t.Fatal(err)
@ -249,28 +255,28 @@ func TestToJson(t *testing.T) {
func TestToFile(t *testing.T) {
f := "beego_testfile"
req := Get("http://httpbin.org/ip")
req := Get(ipUrl)
err := req.ToFile(f)
if err != nil {
t.Fatal(err)
}
defer os.Remove(f)
b, err := ioutil.ReadFile(f)
if n := strings.Index(string(b), "origin"); n == -1 {
if n := bytes.Index(b, []byte("origin")); n == -1 {
t.Fatal(err)
}
}
func TestToFileDir(t *testing.T) {
f := "./files/beego_testfile"
req := Get("http://httpbin.org/ip")
req := Get(ipUrl)
err := req.ToFile(f)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll("./files")
b, err := ioutil.ReadFile(f)
if n := strings.Index(string(b), "origin"); n == -1 {
if n := bytes.Index(b, []byte("origin")); n == -1 {
t.Fatal(err)
}
}

View File

@ -23,7 +23,7 @@ import (
)
// Log levels to control the logging output.
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
const (
LevelEmergency = webLog.LevelEmergency
LevelAlert = webLog.LevelAlert
@ -36,90 +36,90 @@ const (
)
// BeeLogger references the used application logger.
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
var BeeLogger = logs.GetBeeLogger()
// SetLevel sets the global log level used by the simple logger.
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func SetLevel(l int) {
logs.SetLevel(l)
}
// SetLogFuncCall set the CallDepth, default is 3
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func SetLogFuncCall(b bool) {
logs.SetLogFuncCall(b)
}
// SetLogger sets a new logger.
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func SetLogger(adaptername string, config string) error {
return logs.SetLogger(adaptername, config)
}
// Emergency logs a message at emergency level.
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func Emergency(v ...interface{}) {
logs.Emergency(generateFmtStr(len(v)), v...)
}
// Alert logs a message at alert level.
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func Alert(v ...interface{}) {
logs.Alert(generateFmtStr(len(v)), v...)
}
// Critical logs a message at critical level.
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func Critical(v ...interface{}) {
logs.Critical(generateFmtStr(len(v)), v...)
}
// Error logs a message at error level.
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func Error(v ...interface{}) {
logs.Error(generateFmtStr(len(v)), v...)
}
// Warning logs a message at warning level.
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func Warning(v ...interface{}) {
logs.Warning(generateFmtStr(len(v)), v...)
}
// Warn compatibility alias for Warning()
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func Warn(v ...interface{}) {
logs.Warn(generateFmtStr(len(v)), v...)
}
// Notice logs a message at notice level.
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func Notice(v ...interface{}) {
logs.Notice(generateFmtStr(len(v)), v...)
}
// Informational logs a message at info level.
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func Informational(v ...interface{}) {
logs.Informational(generateFmtStr(len(v)), v...)
}
// Info compatibility alias for Warning()
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func Info(v ...interface{}) {
logs.Info(generateFmtStr(len(v)), v...)
}
// Debug logs a message at debug level.
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func Debug(v ...interface{}) {
logs.Debug(generateFmtStr(len(v)), v...)
}
// Trace logs a message at trace level.
// compatibility alias for Warning()
// Deprecated: use github.com/beego/beego/v2/logs instead.
// Deprecated: use github.com/beego/beego/v2/core/logs instead.
func Trace(v ...interface{}) {
logs.Trace(generateFmtStr(len(v)), v...)
}

View File

@ -15,7 +15,7 @@
// Package logs provide a general log interface
// Usage:
//
// import "github.com/beego/beego/v2/logs"
// import "github.com/beego/beego/v2/core/logs"
//
// log := NewLogger(10000)
// log.SetLogger("console", "")

View File

@ -18,7 +18,7 @@ import (
"testing"
)
func TestBeeLogger_Info(t *testing.T) {
func TestBeeLoggerInfo(t *testing.T) {
log := NewLogger(1000)
log.SetLogger("file", `{"net":"tcp","addr":":7020"}`)
}

View File

@ -15,6 +15,7 @@
package metric
import (
"fmt"
"net/http"
"net/url"
"testing"
@ -26,7 +27,9 @@ import (
)
func TestPrometheusMiddleWare(t *testing.T) {
middleware := PrometheusMiddleWare(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
middleware := PrometheusMiddleWare(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
fmt.Print("you are coming")
}))
writer := &context.Response{}
request := &http.Request{
URL: &url.URL{

View File

@ -238,141 +238,158 @@ func AddNamespace(nl ...*Namespace) {
// NSCond is Namespace Condition
func NSCond(cond namespaceCond) LinkNamespace {
wc := web.NSCond(func(b *context.Context) bool {
return cond((*adtContext.Context)(b))
})
return func(namespace *Namespace) {
web.NSCond(func(b *context.Context) bool {
return cond((*adtContext.Context)(b))
})
wc((*web.Namespace)(namespace))
}
}
// NSBefore Namespace BeforeRouter filter
func NSBefore(filterList ...FilterFunc) LinkNamespace {
nfs := oldToNewFilter(filterList)
wf := web.NSBefore(nfs...)
return func(namespace *Namespace) {
nfs := oldToNewFilter(filterList)
web.NSBefore(nfs...)
wf((*web.Namespace)(namespace))
}
}
// NSAfter add Namespace FinishRouter filter
func NSAfter(filterList ...FilterFunc) LinkNamespace {
nfs := oldToNewFilter(filterList)
wf := web.NSAfter(nfs...)
return func(namespace *Namespace) {
nfs := oldToNewFilter(filterList)
web.NSAfter(nfs...)
wf((*web.Namespace)(namespace))
}
}
// NSInclude Namespace Include ControllerInterface
func NSInclude(cList ...ControllerInterface) LinkNamespace {
nfs := oldToNewCtrlIntfs(cList)
wi := web.NSInclude(nfs...)
return func(namespace *Namespace) {
nfs := oldToNewCtrlIntfs(cList)
web.NSInclude(nfs...)
wi((*web.Namespace)(namespace))
}
}
// NSRouter call Namespace Router
func NSRouter(rootpath string, c ControllerInterface, mappingMethods ...string) LinkNamespace {
wn := web.NSRouter(rootpath, c, mappingMethods...)
return func(namespace *Namespace) {
web.Router(rootpath, c, mappingMethods...)
wn((*web.Namespace)(namespace))
}
}
// NSGet call Namespace Get
func NSGet(rootpath string, f FilterFunc) LinkNamespace {
ln := web.NSGet(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
return func(ns *Namespace) {
web.NSGet(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
ln((*web.Namespace)(ns))
}
}
// NSPost call Namespace Post
func NSPost(rootpath string, f FilterFunc) LinkNamespace {
wp := web.NSPost(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
return func(ns *Namespace) {
web.Post(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
wp((*web.Namespace)(ns))
}
}
// NSHead call Namespace Head
func NSHead(rootpath string, f FilterFunc) LinkNamespace {
wb := web.NSHead(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
return func(ns *Namespace) {
web.NSHead(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
wb((*web.Namespace)(ns))
}
}
// NSPut call Namespace Put
func NSPut(rootpath string, f FilterFunc) LinkNamespace {
wn := web.NSPut(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
return func(ns *Namespace) {
web.NSPut(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
wn((*web.Namespace)(ns))
}
}
// NSDelete call Namespace Delete
func NSDelete(rootpath string, f FilterFunc) LinkNamespace {
wn := web.NSDelete(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
return func(ns *Namespace) {
web.NSDelete(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
wn((*web.Namespace)(ns))
}
}
// NSAny call Namespace Any
func NSAny(rootpath string, f FilterFunc) LinkNamespace {
wn := web.NSAny(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
return func(ns *Namespace) {
web.NSAny(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
wn((*web.Namespace)(ns))
}
}
// NSOptions call Namespace Options
func NSOptions(rootpath string, f FilterFunc) LinkNamespace {
wo := web.NSOptions(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
return func(ns *Namespace) {
web.NSOptions(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
wo((*web.Namespace)(ns))
}
}
// NSPatch call Namespace Patch
func NSPatch(rootpath string, f FilterFunc) LinkNamespace {
wn := web.NSPatch(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
return func(ns *Namespace) {
web.NSPatch(rootpath, func(ctx *context.Context) {
f((*adtContext.Context)(ctx))
})
wn((*web.Namespace)(ns))
}
}
// NSAutoRouter call Namespace AutoRouter
func NSAutoRouter(c ControllerInterface) LinkNamespace {
wn := web.NSAutoRouter(c)
return func(ns *Namespace) {
web.NSAutoRouter(c)
wn((*web.Namespace)(ns))
}
}
// NSAutoPrefix call Namespace AutoPrefix
func NSAutoPrefix(prefix string, c ControllerInterface) LinkNamespace {
wn := web.NSAutoPrefix(prefix, c)
return func(ns *Namespace) {
web.NSAutoPrefix(prefix, c)
wn((*web.Namespace)(ns))
}
}
// NSNamespace add sub Namespace
func NSNamespace(prefix string, params ...LinkNamespace) LinkNamespace {
nps := oldToNewLinkNs(params)
wn := web.NSNamespace(prefix, nps...)
return func(ns *Namespace) {
nps := oldToNewLinkNs(params)
web.NSNamespace(prefix, nps...)
wn((*web.Namespace)(ns))
}
}
// NSHandler add handler
func NSHandler(rootpath string, h http.Handler) LinkNamespace {
wn := web.NSHandler(rootpath, h)
return func(ns *Namespace) {
web.NSHandler(rootpath, h)
wn((*web.Namespace)(ns))
}
}

View File

@ -25,7 +25,7 @@ func RegisterModel(models ...interface{}) {
// RegisterModelWithPrefix register models with a prefix
func RegisterModelWithPrefix(prefix string, models ...interface{}) {
orm.RegisterModelWithPrefix(prefix, models)
orm.RegisterModelWithPrefix(prefix, models...)
}
// RegisterModelWithSuffix register models with a suffix

View File

@ -0,0 +1,31 @@
// 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 orm
import (
"testing"
)
type User struct {
Id int
}
type Seller struct {
Id int
}
func TestRegisterModelWithPrefix(t *testing.T) {
RegisterModelWithPrefix("test", &User{}, &Seller{})
}

View File

@ -21,7 +21,7 @@
//
// import (
// "fmt"
// "github.com/beego/beego/v2/orm"
// "github.com/beego/beego/v2/client/orm"
// _ "github.com/go-sql-driver/mysql" // import your used driver
// )
//

View File

@ -21,14 +21,16 @@ import (
type baseQuerySetter struct {
}
const shouldNotInvoke = "you should not invoke this method."
func (b *baseQuerySetter) ForceIndex(indexes ...string) orm.QuerySeter {
panic("you should not invoke this method.")
panic(shouldNotInvoke)
}
func (b *baseQuerySetter) UseIndex(indexes ...string) orm.QuerySeter {
panic("you should not invoke this method.")
panic(shouldNotInvoke)
}
func (b *baseQuerySetter) IgnoreIndex(indexes ...string) orm.QuerySeter {
panic("you should not invoke this method.")
panic(shouldNotInvoke)
}

View File

@ -195,7 +195,7 @@ func snakeStringWithAcronym(s string) string {
}
data = append(data, d)
}
return strings.ToLower(string(data[:]))
return strings.ToLower(string(data))
}
// snake string, XxYy to xx_yy , XxYY to xx_y_y
@ -213,7 +213,7 @@ func snakeString(s string) string {
}
data = append(data, d)
}
return strings.ToLower(string(data[:]))
return strings.ToLower(string(data))
}
// SetNameStrategy set different name strategy
@ -241,7 +241,7 @@ func camelString(s string) string {
}
data = append(data, d)
}
return string(data[:])
return string(data)
}
type argString []string

View File

@ -16,6 +16,8 @@ package orm
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCamelString(t *testing.T) {
@ -29,9 +31,7 @@ func TestCamelString(t *testing.T) {
for _, v := range snake {
res := camelString(v)
if res != answer[v] {
t.Error("Unit Test Fail:", v, res, answer[v])
}
assert.Equal(t, answer[v], res)
}
}
@ -46,9 +46,7 @@ func TestSnakeString(t *testing.T) {
for _, v := range camel {
res := snakeString(v)
if res != answer[v] {
t.Error("Unit Test Fail:", v, res, answer[v])
}
assert.Equal(t, answer[v], res)
}
}
@ -63,8 +61,6 @@ func TestSnakeStringWithAcronym(t *testing.T) {
for _, v := range camel {
res := snakeStringWithAcronym(v)
if res != answer[v] {
t.Error("Unit Test Fail:", v, res, answer[v])
}
assert.Equal(t, answer[v], res)
}
}

View File

@ -17,7 +17,7 @@
// Simple Usage:
// import(
// "github.com/beego/beego/v2"
// "github.com/beego/beego/v2/plugins/apiauth"
// "github.com/beego/beego/v2/server/web/filter/apiauth"
// )
//
// func main(){

View File

@ -16,7 +16,7 @@
// Simple Usage:
// import(
// "github.com/beego/beego/v2"
// "github.com/beego/beego/v2/plugins/auth"
// "github.com/beego/beego/v2/server/web/filter/auth"
// )
//
// func main(){

View File

@ -16,7 +16,7 @@
// Simple Usage:
// import(
// "github.com/beego/beego/v2"
// "github.com/beego/beego/v2/plugins/authz"
// "github.com/beego/beego/v2/server/web/filter/authz"
// "github.com/casbin/casbin"
// )
//

View File

@ -26,6 +26,11 @@ import (
"github.com/beego/beego/v2/adapter/plugins/auth"
)
const (
authCfg = "authz_model.conf"
authCsv = "authz_policy.csv"
)
func testRequest(t *testing.T, handler *beego.ControllerRegister, user string, path string, method string, code int) {
r, _ := http.NewRequest(method, path, nil)
r.SetBasicAuth(user, "123")
@ -40,70 +45,79 @@ func testRequest(t *testing.T, handler *beego.ControllerRegister, user string, p
func TestBasic(t *testing.T) {
handler := beego.NewControllerRegister()
handler.InsertFilter("*", beego.BeforeRouter, auth.Basic("alice", "123"))
handler.InsertFilter("*", beego.BeforeRouter, NewAuthorizer(casbin.NewEnforcer("authz_model.conf", "authz_policy.csv")))
_ = handler.InsertFilter("*", beego.BeforeRouter, auth.Basic("alice", "123"))
_ = handler.InsertFilter("*", beego.BeforeRouter, NewAuthorizer(casbin.NewEnforcer(authCfg, authCsv)))
handler.Any("*", func(ctx *context.Context) {
ctx.Output.SetStatus(200)
})
testRequest(t, handler, "alice", "/dataset1/resource1", "GET", 200)
testRequest(t, handler, "alice", "/dataset1/resource1", "POST", 200)
testRequest(t, handler, "alice", "/dataset1/resource2", "GET", 200)
testRequest(t, handler, "alice", "/dataset1/resource2", "POST", 403)
const d1r1 = "/dataset1/resource1"
testRequest(t, handler, "alice", d1r1, "GET", 200)
testRequest(t, handler, "alice", d1r1, "POST", 200)
const d1r2 = "/dataset1/resource2"
testRequest(t, handler, "alice", d1r2, "GET", 200)
testRequest(t, handler, "alice", d1r2, "POST", 403)
}
func TestPathWildcard(t *testing.T) {
handler := beego.NewControllerRegister()
handler.InsertFilter("*", beego.BeforeRouter, auth.Basic("bob", "123"))
handler.InsertFilter("*", beego.BeforeRouter, NewAuthorizer(casbin.NewEnforcer("authz_model.conf", "authz_policy.csv")))
_ = handler.InsertFilter("*", beego.BeforeRouter, auth.Basic("bob", "123"))
_ = handler.InsertFilter("*", beego.BeforeRouter, NewAuthorizer(casbin.NewEnforcer(authCfg, authCsv)))
handler.Any("*", func(ctx *context.Context) {
ctx.Output.SetStatus(200)
})
testRequest(t, handler, "bob", "/dataset2/resource1", "GET", 200)
testRequest(t, handler, "bob", "/dataset2/resource1", "POST", 200)
testRequest(t, handler, "bob", "/dataset2/resource1", "DELETE", 200)
testRequest(t, handler, "bob", "/dataset2/resource2", "GET", 200)
testRequest(t, handler, "bob", "/dataset2/resource2", "POST", 403)
testRequest(t, handler, "bob", "/dataset2/resource2", "DELETE", 403)
const d2r1 = "/dataset2/resource1"
testRequest(t, handler, "bob", d2r1, "GET", 200)
testRequest(t, handler, "bob", d2r1, "POST", 200)
testRequest(t, handler, "bob", d2r1, "DELETE", 200)
const d2r2 = "/dataset2/resource2"
testRequest(t, handler, "bob", d2r2, "GET", 200)
testRequest(t, handler, "bob", d2r2, "POST", 403)
testRequest(t, handler, "bob", d2r2, "DELETE", 403)
testRequest(t, handler, "bob", "/dataset2/folder1/item1", "GET", 403)
testRequest(t, handler, "bob", "/dataset2/folder1/item1", "POST", 200)
testRequest(t, handler, "bob", "/dataset2/folder1/item1", "DELETE", 403)
testRequest(t, handler, "bob", "/dataset2/folder1/item2", "GET", 403)
testRequest(t, handler, "bob", "/dataset2/folder1/item2", "POST", 200)
testRequest(t, handler, "bob", "/dataset2/folder1/item2", "DELETE", 403)
const item1 = "/dataset2/folder1/item1"
testRequest(t, handler, "bob", item1, "GET", 403)
testRequest(t, handler, "bob", item1, "POST", 200)
testRequest(t, handler, "bob", item1, "DELETE", 403)
const item2 = "/dataset2/folder1/item2"
testRequest(t, handler, "bob", item2, "GET", 403)
testRequest(t, handler, "bob", item2, "POST", 200)
testRequest(t, handler, "bob", item2, "DELETE", 403)
}
func TestRBAC(t *testing.T) {
handler := beego.NewControllerRegister()
handler.InsertFilter("*", beego.BeforeRouter, auth.Basic("cathy", "123"))
e := casbin.NewEnforcer("authz_model.conf", "authz_policy.csv")
handler.InsertFilter("*", beego.BeforeRouter, NewAuthorizer(e))
_ = handler.InsertFilter("*", beego.BeforeRouter, auth.Basic("cathy", "123"))
e := casbin.NewEnforcer(authCfg, authCsv)
_ = handler.InsertFilter("*", beego.BeforeRouter, NewAuthorizer(e))
handler.Any("*", func(ctx *context.Context) {
ctx.Output.SetStatus(200)
})
// cathy can access all /dataset1/* resources via all methods because it has the dataset1_admin role.
testRequest(t, handler, "cathy", "/dataset1/item", "GET", 200)
testRequest(t, handler, "cathy", "/dataset1/item", "POST", 200)
testRequest(t, handler, "cathy", "/dataset1/item", "DELETE", 200)
testRequest(t, handler, "cathy", "/dataset2/item", "GET", 403)
testRequest(t, handler, "cathy", "/dataset2/item", "POST", 403)
testRequest(t, handler, "cathy", "/dataset2/item", "DELETE", 403)
const dataSet1 = "/dataset1/item"
testRequest(t, handler, "cathy", dataSet1, "GET", 200)
testRequest(t, handler, "cathy", dataSet1, "POST", 200)
testRequest(t, handler, "cathy", dataSet1, "DELETE", 200)
const dataSet2 = "/dataset2/item"
testRequest(t, handler, "cathy", dataSet2, "GET", 403)
testRequest(t, handler, "cathy", dataSet2, "POST", 403)
testRequest(t, handler, "cathy", dataSet2, "DELETE", 403)
// delete all roles on user cathy, so cathy cannot access any resources now.
e.DeleteRolesForUser("cathy")
testRequest(t, handler, "cathy", "/dataset1/item", "GET", 403)
testRequest(t, handler, "cathy", "/dataset1/item", "POST", 403)
testRequest(t, handler, "cathy", "/dataset1/item", "DELETE", 403)
testRequest(t, handler, "cathy", "/dataset2/item", "GET", 403)
testRequest(t, handler, "cathy", "/dataset2/item", "POST", 403)
testRequest(t, handler, "cathy", "/dataset2/item", "DELETE", 403)
testRequest(t, handler, "cathy", dataSet1, "GET", 403)
testRequest(t, handler, "cathy", dataSet1, "POST", 403)
testRequest(t, handler, "cathy", dataSet1, "DELETE", 403)
testRequest(t, handler, "cathy", dataSet2, "GET", 403)
testRequest(t, handler, "cathy", dataSet2, "POST", 403)
testRequest(t, handler, "cathy", dataSet2, "DELETE", 403)
}

View File

@ -16,7 +16,7 @@
// Usage
// import (
// "github.com/beego/beego/v2"
// "github.com/beego/beego/v2/plugins/cors"
// "github.com/beego/beego/v2/server/web/filter/cors"
// )
//
// func main() {

View File

@ -87,7 +87,7 @@ func NewControllerRegister() *ControllerRegister {
// Add("/api",&RestController{},"get,post:ApiFunc"
// Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc")
func (p *ControllerRegister) Add(pattern string, c ControllerInterface, mappingMethods ...string) {
(*web.ControllerRegister)(p).Add(pattern, c, mappingMethods...)
(*web.ControllerRegister)(p).Add(pattern, c, web.WithRouterMethods(c, mappingMethods...))
}
// Include only when the Runmode is dev will generate router file in the router/auto.go from the controller

View File

@ -20,8 +20,8 @@
//
// Usage:
// import(
// _ "github.com/beego/beego/v2/session/couchbase"
// "github.com/beego/beego/v2/session"
// _ "github.com/beego/beego/v2/server/web/session/couchbase"
// "github.com/beego/beego/v2/server/web/session"
// )
//
// func init() {

View File

@ -20,8 +20,8 @@
//
// Usage:
// import(
// _ "github.com/beego/beego/v2/session/memcache"
// "github.com/beego/beego/v2/session"
// _ "github.com/beego/beego/v2/server/web/session/memcache"
// "github.com/beego/beego/v2/server/web/session"
// )
//
// func init() {

View File

@ -28,8 +28,8 @@
//
// Usage:
// import(
// _ "github.com/beego/beego/v2/session/mysql"
// "github.com/beego/beego/v2/session"
// _ "github.com/beego/beego/v2/server/web/session/mysql"
// "github.com/beego/beego/v2/server/web/session"
// )
//
// func init() {

View File

@ -38,8 +38,8 @@
//
// Usage:
// import(
// _ "github.com/beego/beego/v2/session/postgresql"
// "github.com/beego/beego/v2/session"
// _ "github.com/beego/beego/v2/server/web/session/postgresql"
// "github.com/beego/beego/v2/server/web/session"
// )
//
// func init() {

View File

@ -20,8 +20,8 @@
//
// Usage:
// import(
// _ "github.com/beego/beego/v2/session/redis"
// "github.com/beego/beego/v2/session"
// _ "github.com/beego/beego/v2/server/web/session/redis"
// "github.com/beego/beego/v2/server/web/session"
// )
//
// func init() {

View File

@ -20,8 +20,8 @@
//
// Usage:
// import(
// _ "github.com/beego/beego/v2/session/redis_cluster"
// "github.com/beego/beego/v2/session"
// _ "github.com/beego/beego/v2/server/web/session/redis_cluster"
// "github.com/beego/beego/v2/server/web/session"
// )
//
// func init() {

View File

@ -20,8 +20,8 @@
//
// Usage:
// import(
// _ "github.com/beego/beego/v2/session/redis_sentinel"
// "github.com/beego/beego/v2/session"
// _ "github.com/beego/beego/v2/server/web/session/redis_sentinel"
// "github.com/beego/beego/v2/server/web/session"
// )
//
// func init() {

View File

@ -5,6 +5,8 @@ import (
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/adapter/session"
)
@ -19,71 +21,55 @@ func TestRedisSentinel(t *testing.T) {
ProviderConfig: "127.0.0.1:6379,100,,0,master",
}
globalSessions, e := session.NewManager("redis_sentinel", sessionConfig)
if e != nil {
t.Log(e)
return
}
// todo test if e==nil
go globalSessions.GC()
r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
sess, err := globalSessions.SessionStart(w, r)
if err != nil {
t.Fatal("session start failed:", err)
}
assert.Nil(t, err)
defer sess.SessionRelease(w)
// SET AND GET
err = sess.Set("username", "astaxie")
if err != nil {
t.Fatal("set username failed:", err)
}
assert.Nil(t, err)
username := sess.Get("username")
if username != "astaxie" {
t.Fatal("get username failed")
}
assert.Equal(t, "astaxie", username)
// DELETE
err = sess.Delete("username")
if err != nil {
t.Fatal("delete username failed:", err)
}
assert.Nil(t, err)
username = sess.Get("username")
if username != nil {
t.Fatal("delete username failed")
}
assert.Nil(t, username)
// FLUSH
err = sess.Set("username", "astaxie")
if err != nil {
t.Fatal("set failed:", err)
}
assert.Nil(t, err)
err = sess.Set("password", "1qaz2wsx")
if err != nil {
t.Fatal("set failed:", err)
}
assert.Nil(t, err)
username = sess.Get("username")
if username != "astaxie" {
t.Fatal("get username failed")
}
assert.Equal(t, "astaxie", username)
password := sess.Get("password")
if password != "1qaz2wsx" {
t.Fatal("get password failed")
}
assert.Equal(t, "1qaz2wsx", password)
err = sess.Flush()
if err != nil {
t.Fatal("flush failed:", err)
}
assert.Nil(t, err)
username = sess.Get("username")
if username != nil {
t.Fatal("flush failed")
}
assert.Nil(t, username)
password = sess.Get("password")
if password != nil {
t.Fatal("flush failed")
}
assert.Nil(t, password)
sess.SessionRelease(w)

View File

@ -22,6 +22,8 @@ import (
"testing"
)
const setCookieKey = "Set-Cookie"
func TestCookie(t *testing.T) {
config := `{"cookieName":"gosessionid","enableSetCookie":false,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}`
conf := new(ManagerConfig)
@ -46,7 +48,8 @@ func TestCookie(t *testing.T) {
t.Fatal("get username error")
}
sess.SessionRelease(w)
if cookiestr := w.Header().Get("Set-Cookie"); cookiestr == "" {
if cookiestr := w.Header().Get(setCookieKey); cookiestr == "" {
t.Fatal("setcookie error")
} else {
parts := strings.Split(strings.TrimSpace(cookiestr), ";")
@ -79,7 +82,7 @@ func TestDestorySessionCookie(t *testing.T) {
// request again ,will get same sesssion id .
r1, _ := http.NewRequest("GET", "/", nil)
r1.Header.Set("Cookie", w.Header().Get("Set-Cookie"))
r1.Header.Set("Cookie", w.Header().Get(setCookieKey))
w = httptest.NewRecorder()
newSession, err := globalSessions.SessionStart(w, r1)
if err != nil {
@ -92,7 +95,7 @@ func TestDestorySessionCookie(t *testing.T) {
// After destroy session , will get a new session id .
globalSessions.SessionDestroy(w, r1)
r2, _ := http.NewRequest("GET", "/", nil)
r2.Header.Set("Cookie", w.Header().Get("Set-Cookie"))
r2.Header.Set("Cookie", w.Header().Get(setCookieKey))
w = httptest.NewRecorder()
newSession, err = globalSessions.SessionStart(w, r2)

View File

@ -16,7 +16,7 @@
//
// Usage:
// import(
// "github.com/beego/beego/v2/session"
// "github.com/beego/beego/v2/server/web/session"
// )
//
// func init() {

View File

@ -19,19 +19,15 @@ import (
"net/url"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestSubstr(t *testing.T) {
s := `012345`
if Substr(s, 0, 2) != "01" {
t.Error("should be equal")
}
if Substr(s, 0, 100) != "012345" {
t.Error("should be equal")
}
if Substr(s, 12, 100) != "012345" {
t.Error("should be equal")
}
assert.Equal(t, "01", Substr(s, 0, 2))
assert.Equal(t, "012345", Substr(s, 0, 100))
assert.Equal(t, "012345", Substr(s, 12, 100))
}
func TestHtml2str(t *testing.T) {
@ -39,73 +35,51 @@ func TestHtml2str(t *testing.T) {
\n`
if HTML2str(h) != "123\\n\n\\n" {
t.Error("should be equal")
}
assert.Equal(t, "123\\n\n\\n", HTML2str(h))
}
func TestDateFormat(t *testing.T) {
ts := "Mon, 01 Jul 2013 13:27:42 CST"
tt, _ := time.Parse(time.RFC1123, ts)
if ss := DateFormat(tt, "2006-01-02 15:04:05"); ss != "2013-07-01 13:27:42" {
t.Errorf("2013-07-01 13:27:42 does not equal %v", ss)
}
assert.Equal(t, "2013-07-01 13:27:42", DateFormat(tt, "2006-01-02 15:04:05"))
}
func TestDate(t *testing.T) {
ts := "Mon, 01 Jul 2013 13:27:42 CST"
tt, _ := time.Parse(time.RFC1123, ts)
if ss := Date(tt, "Y-m-d H:i:s"); ss != "2013-07-01 13:27:42" {
t.Errorf("2013-07-01 13:27:42 does not equal %v", ss)
}
if ss := Date(tt, "y-n-j h:i:s A"); ss != "13-7-1 01:27:42 PM" {
t.Errorf("13-7-1 01:27:42 PM does not equal %v", ss)
}
if ss := Date(tt, "D, d M Y g:i:s a"); ss != "Mon, 01 Jul 2013 1:27:42 pm" {
t.Errorf("Mon, 01 Jul 2013 1:27:42 pm does not equal %v", ss)
}
if ss := Date(tt, "l, d F Y G:i:s"); ss != "Monday, 01 July 2013 13:27:42" {
t.Errorf("Monday, 01 July 2013 13:27:42 does not equal %v", ss)
}
assert.Equal(t, "2013-07-01 13:27:42", Date(tt, "Y-m-d H:i:s"))
assert.Equal(t, "13-7-1 01:27:42 PM", Date(tt, "y-n-j h:i:s A"))
assert.Equal(t, "Mon, 01 Jul 2013 1:27:42 pm", Date(tt, "D, d M Y g:i:s a"))
assert.Equal(t, "Monday, 01 July 2013 13:27:42", Date(tt, "l, d F Y G:i:s"))
}
func TestCompareRelated(t *testing.T) {
if !Compare("abc", "abc") {
t.Error("should be equal")
}
if Compare("abc", "aBc") {
t.Error("should be not equal")
}
if !Compare("1", 1) {
t.Error("should be equal")
}
if CompareNot("abc", "abc") {
t.Error("should be equal")
}
if !CompareNot("abc", "aBc") {
t.Error("should be not equal")
}
if !NotNil("a string") {
t.Error("should not be nil")
}
assert.True(t, Compare("abc", "abc"))
assert.False(t, Compare("abc", "aBc"))
assert.True(t, Compare("1", 1))
assert.False(t, CompareNot("abc", "abc"))
assert.True(t, CompareNot("abc", "aBc"))
assert.True(t, NotNil("a string"))
}
func TestHtmlquote(t *testing.T) {
h := `&lt;&#39;&nbsp;&rdquo;&ldquo;&amp;&#34;&gt;`
s := `<' ”“&">`
if Htmlquote(s) != h {
t.Error("should be equal")
}
assert.Equal(t, h, Htmlquote(s))
}
func TestHtmlunquote(t *testing.T) {
h := `&lt;&#39;&nbsp;&rdquo;&ldquo;&amp;&#34;&gt;`
s := `<' ”“&">`
if Htmlunquote(h) != s {
t.Error("should be equal")
}
assert.Equal(t, s, Htmlunquote(h))
}
func TestParseForm(t *testing.T) {
@ -148,55 +122,42 @@ func TestParseForm(t *testing.T) {
"hobby": []string{"", "Basketball", "Football"},
"memo": []string{"nothing"},
}
if err := ParseForm(form, u); err == nil {
t.Fatal("nothing will be changed")
}
if err := ParseForm(form, &u); err != nil {
t.Fatal(err)
}
if u.ID != 0 {
t.Errorf("ID should equal 0 but got %v", u.ID)
}
if len(u.tag) != 0 {
t.Errorf("tag's length should equal 0 but got %v", len(u.tag))
}
if u.Name.(string) != "test" {
t.Errorf("Name should equal `test` but got `%v`", u.Name.(string))
}
if u.Age != 40 {
t.Errorf("Age should equal 40 but got %v", u.Age)
}
if u.Email != "test@gmail.com" {
t.Errorf("Email should equal `test@gmail.com` but got `%v`", u.Email)
}
if u.Intro != "I am an engineer!" {
t.Errorf("Intro should equal `I am an engineer!` but got `%v`", u.Intro)
}
if !u.StrBool {
t.Errorf("strboll should equal `true`, but got `%v`", u.StrBool)
}
assert.NotNil(t, ParseForm(form, u))
assert.Nil(t, ParseForm(form, &u))
assert.Equal(t, 0, u.ID)
assert.Equal(t, 0, len(u.tag))
assert.Equal(t, "test", u.Name)
assert.Equal(t, 40, u.Age)
assert.Equal(t, "test@gmail.com", u.Email)
assert.Equal(t, "I am an engineer!", u.Intro)
assert.True(t, u.StrBool)
y, m, d := u.Date.Date()
if y != 2014 || m.String() != "November" || d != 12 {
t.Errorf("Date should equal `2014-11-12`, but got `%v`", u.Date.String())
}
if u.Organization != "beego" {
t.Errorf("Organization should equal `beego`, but got `%v`", u.Organization)
}
if u.Title != "CXO" {
t.Errorf("Title should equal `CXO`, but got `%v`", u.Title)
}
if u.Hobby[0] != "" {
t.Errorf("Hobby should equal ``, but got `%v`", u.Hobby[0])
}
if u.Hobby[1] != "Basketball" {
t.Errorf("Hobby should equal `Basketball`, but got `%v`", u.Hobby[1])
}
if u.Hobby[2] != "Football" {
t.Errorf("Hobby should equal `Football`, but got `%v`", u.Hobby[2])
}
if len(u.Memo) != 0 {
t.Errorf("Memo's length should equal 0 but got %v", len(u.Memo))
}
assert.Equal(t, 2014, y)
assert.Equal(t, "November", m.String())
assert.Equal(t, 12, d)
assert.Equal(t, "beego", u.Organization)
assert.Equal(t, "CXO", u.Title)
assert.Equal(t, "", u.Hobby[0])
assert.Equal(t, "Basketball", u.Hobby[1])
assert.Equal(t, "Football", u.Hobby[2])
assert.Equal(t, 0, len(u.Memo))
}
func TestRenderForm(t *testing.T) {
@ -212,18 +173,14 @@ func TestRenderForm(t *testing.T) {
u := user{Name: "test", Intro: "Some Text"}
output := RenderForm(u)
if output != template.HTML("") {
t.Errorf("output should be empty but got %v", output)
}
assert.Equal(t, template.HTML(""), output)
output = RenderForm(&u)
result := template.HTML(
`Name: <input name="username" type="text" value="test"></br>` +
`年龄:<input name="age" type="text" value="0"></br>` +
`Sex: <input name="Sex" type="text" value=""></br>` +
`Intro: <textarea name="Intro">Some Text</textarea>`)
if output != result {
t.Errorf("output should equal `%v` but got `%v`", result, output)
}
assert.Equal(t, result, output)
}
func TestMapGet(t *testing.T) {
@ -233,29 +190,18 @@ func TestMapGet(t *testing.T) {
"1": 2,
}
if res, err := MapGet(m1, "a"); err == nil {
if res.(int64) != 1 {
t.Errorf("Should return 1, but return %v", res)
}
} else {
t.Errorf("Error happens %v", err)
}
res, err := MapGet(m1, "a")
assert.Nil(t, err)
assert.Equal(t, int64(1), res)
if res, err := MapGet(m1, "1"); err == nil {
if res.(int64) != 2 {
t.Errorf("Should return 2, but return %v", res)
}
} else {
t.Errorf("Error happens %v", err)
}
res, err = MapGet(m1, "1")
assert.Nil(t, err)
assert.Equal(t, int64(2), res)
if res, err := MapGet(m1, 1); err == nil {
if res.(int64) != 2 {
t.Errorf("Should return 2, but return %v", res)
}
} else {
t.Errorf("Error happens %v", err)
}
res, err = MapGet(m1, 1)
assert.Nil(t, err)
assert.Equal(t, int64(2), res)
// test 2 level map
m2 := M{
@ -264,13 +210,9 @@ func TestMapGet(t *testing.T) {
},
}
if res, err := MapGet(m2, 1, 2); err == nil {
if res.(float64) != 3.5 {
t.Errorf("Should return 3.5, but return %v", res)
}
} else {
t.Errorf("Error happens %v", err)
}
res, err = MapGet(m2, 1, 2)
assert.Nil(t, err)
assert.Equal(t, 3.5, res)
// test 5 level map
m5 := M{
@ -285,20 +227,13 @@ func TestMapGet(t *testing.T) {
},
}
if res, err := MapGet(m5, 1, 2, 3, 4, 5); err == nil {
if res.(float64) != 1.2 {
t.Errorf("Should return 1.2, but return %v", res)
}
} else {
t.Errorf("Error happens %v", err)
}
res, err = MapGet(m5, 1, 2, 3, 4, 5)
assert.Nil(t, err)
assert.Equal(t, 1.2, res)
// check whether element not exists in map
if res, err := MapGet(m5, 5, 4, 3, 2, 1); err == nil {
if res != nil {
t.Errorf("Should return nil, but return %v", res)
}
} else {
t.Errorf("Error happens %v", err)
}
res, err = MapGet(m5, 5, 4, 3, 2, 1)
assert.Nil(t, err)
assert.Nil(t, res)
}

View File

@ -21,13 +21,16 @@ import (
)
func TestStatics(t *testing.T) {
StatisticsMap.AddStatistics("POST", "/api/user", "&admin.user", time.Duration(2000))
StatisticsMap.AddStatistics("POST", "/api/user", "&admin.user", time.Duration(120000))
StatisticsMap.AddStatistics("GET", "/api/user", "&admin.user", time.Duration(13000))
StatisticsMap.AddStatistics("POST", "/api/admin", "&admin.user", time.Duration(14000))
StatisticsMap.AddStatistics("POST", "/api/user/astaxie", "&admin.user", time.Duration(12000))
StatisticsMap.AddStatistics("POST", "/api/user/xiemengjun", "&admin.user", time.Duration(13000))
StatisticsMap.AddStatistics("DELETE", "/api/user", "&admin.user", time.Duration(1400))
userApi := "/api/user"
post := "POST"
adminUser := "&admin.user"
StatisticsMap.AddStatistics(post, userApi, adminUser, time.Duration(2000))
StatisticsMap.AddStatistics(post, userApi, adminUser, time.Duration(120000))
StatisticsMap.AddStatistics("GET", userApi, adminUser, time.Duration(13000))
StatisticsMap.AddStatistics(post, "/api/admin", adminUser, time.Duration(14000))
StatisticsMap.AddStatistics(post, "/api/user/astaxie", adminUser, time.Duration(12000))
StatisticsMap.AddStatistics(post, "/api/user/xiemengjun", adminUser, time.Duration(13000))
StatisticsMap.AddStatistics("DELETE", userApi, adminUser, time.Duration(1400))
t.Log(StatisticsMap.GetMap())
data := StatisticsMap.GetMapData()

View File

@ -289,3 +289,7 @@ func (o *oldToNewAdapter) SetPrev(ctx context.Context, t time.Time) {
func (o *oldToNewAdapter) GetPrev(ctx context.Context) time.Time {
return o.delegate.GetPrev()
}
func (o *oldToNewAdapter) GetTimeout(ctx context.Context) time.Duration {
return 0
}

View File

@ -7,8 +7,8 @@ package controllers
import (
"github.com/beego/beego/v2"
"github.com/beego/beego/v2/cache"
"github.com/beego/beego/v2/utils/captcha"
"github.com/beego/beego/v2/client/cache"
"github.com/beego/beego/v2/server/web/captcha"
)
var cpt *captcha.Captcha

View File

@ -20,8 +20,8 @@
//
// import (
// "github.com/beego/beego/v2"
// "github.com/beego/beego/v2/cache"
// "github.com/beego/beego/v2/utils/captcha"
// "github.com/beego/beego/v2/client/cache"
// "github.com/beego/beego/v2/server/web/captcha"
// )
//
// var cpt *captcha.Captcha

View File

@ -8,7 +8,7 @@ In your beego.Controller:
package controllers
import "github.com/beego/beego/v2/utils/pagination"
import "github.com/beego/beego/v2/server/web/pagination"
type PostsController struct {
beego.Controller

View File

@ -16,7 +16,7 @@ package utils
import "testing"
func TestRand_01(t *testing.T) {
func TestRand01(t *testing.T) {
bs0 := RandomCreateBytes(16)
bs1 := RandomCreateBytes(16)

View File

@ -15,7 +15,7 @@
// Package validation for validations
//
// import (
// "github.com/beego/beego/v2/validation"
// "github.com/beego/beego/v2/core/validation"
// "log"
// )
//

View File

@ -18,131 +18,83 @@ import (
"regexp"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestRequired(t *testing.T) {
valid := Validation{}
if valid.Required(nil, "nil").Ok {
t.Error("nil object should be false")
}
if !valid.Required(true, "bool").Ok {
t.Error("Bool value should always return true")
}
if !valid.Required(false, "bool").Ok {
t.Error("Bool value should always return true")
}
if valid.Required("", "string").Ok {
t.Error("\"'\" string should be false")
}
if valid.Required(" ", "string").Ok {
t.Error("\" \" string should be false") // For #2361
}
if valid.Required("\n", "string").Ok {
t.Error("new line string should be false") // For #2361
}
if !valid.Required("astaxie", "string").Ok {
t.Error("string should be true")
}
if valid.Required(0, "zero").Ok {
t.Error("Integer should not be equal 0")
}
if !valid.Required(1, "int").Ok {
t.Error("Integer except 0 should be true")
}
if !valid.Required(time.Now(), "time").Ok {
t.Error("time should be true")
}
if valid.Required([]string{}, "emptySlice").Ok {
t.Error("empty slice should be false")
}
if !valid.Required([]interface{}{"ok"}, "slice").Ok {
t.Error("slice should be true")
}
assert.False(t, valid.Required(nil, "nil").Ok)
assert.True(t, valid.Required(true, "bool").Ok)
assert.True(t, valid.Required(false, "bool").Ok)
assert.False(t, valid.Required("", "string").Ok)
assert.False(t, valid.Required(" ", "string").Ok)
assert.False(t, valid.Required("\n", "string").Ok)
assert.True(t, valid.Required("astaxie", "string").Ok)
assert.False(t, valid.Required(0, "zero").Ok)
assert.True(t, valid.Required(1, "int").Ok)
assert.True(t, valid.Required(time.Now(), "time").Ok)
assert.False(t, valid.Required([]string{}, "emptySlice").Ok)
assert.True(t, valid.Required([]interface{}{"ok"}, "slice").Ok)
}
func TestMin(t *testing.T) {
valid := Validation{}
if valid.Min(-1, 0, "min0").Ok {
t.Error("-1 is less than the minimum value of 0 should be false")
}
if !valid.Min(1, 0, "min0").Ok {
t.Error("1 is greater or equal than the minimum value of 0 should be true")
}
assert.False(t, valid.Min(-1, 0, "min0").Ok)
assert.True(t, valid.Min(1, 0, "min0").Ok)
}
func TestMax(t *testing.T) {
valid := Validation{}
if valid.Max(1, 0, "max0").Ok {
t.Error("1 is greater than the minimum value of 0 should be false")
}
if !valid.Max(-1, 0, "max0").Ok {
t.Error("-1 is less or equal than the maximum value of 0 should be true")
}
assert.False(t, valid.Max(1, 0, "max0").Ok)
assert.True(t, valid.Max(-1, 0, "max0").Ok)
}
func TestRange(t *testing.T) {
valid := Validation{}
if valid.Range(-1, 0, 1, "range0_1").Ok {
t.Error("-1 is between 0 and 1 should be false")
}
if !valid.Range(1, 0, 1, "range0_1").Ok {
t.Error("1 is between 0 and 1 should be true")
}
assert.False(t, valid.Range(-1, 0, 1, "range0_1").Ok)
assert.True(t, valid.Range(1, 0, 1, "range0_1").Ok)
}
func TestMinSize(t *testing.T) {
valid := Validation{}
if valid.MinSize("", 1, "minSize1").Ok {
t.Error("the length of \"\" is less than the minimum value of 1 should be false")
}
if !valid.MinSize("ok", 1, "minSize1").Ok {
t.Error("the length of \"ok\" is greater or equal than the minimum value of 1 should be true")
}
if valid.MinSize([]string{}, 1, "minSize1").Ok {
t.Error("the length of empty slice is less than the minimum value of 1 should be false")
}
if !valid.MinSize([]interface{}{"ok"}, 1, "minSize1").Ok {
t.Error("the length of [\"ok\"] is greater or equal than the minimum value of 1 should be true")
}
assert.False(t, valid.MinSize("", 1, "minSize1").Ok)
assert.True(t, valid.MinSize("ok", 1, "minSize1").Ok)
assert.False(t, valid.MinSize([]string{}, 1, "minSize1").Ok)
assert.True(t, valid.MinSize([]interface{}{"ok"}, 1, "minSize1").Ok)
}
func TestMaxSize(t *testing.T) {
valid := Validation{}
if valid.MaxSize("ok", 1, "maxSize1").Ok {
t.Error("the length of \"ok\" is greater than the maximum value of 1 should be false")
}
if !valid.MaxSize("", 1, "maxSize1").Ok {
t.Error("the length of \"\" is less or equal than the maximum value of 1 should be true")
}
if valid.MaxSize([]interface{}{"ok", false}, 1, "maxSize1").Ok {
t.Error("the length of [\"ok\", false] is greater than the maximum value of 1 should be false")
}
if !valid.MaxSize([]string{}, 1, "maxSize1").Ok {
t.Error("the length of empty slice is less or equal than the maximum value of 1 should be true")
}
assert.False(t, valid.MaxSize("ok", 1, "maxSize1").Ok)
assert.True(t, valid.MaxSize("", 1, "maxSize1").Ok)
assert.False(t, valid.MaxSize([]interface{}{"ok", false}, 1, "maxSize1").Ok)
assert.True(t, valid.MaxSize([]string{}, 1, "maxSize1").Ok)
}
func TestLength(t *testing.T) {
valid := Validation{}
if valid.Length("", 1, "length1").Ok {
t.Error("the length of \"\" must equal 1 should be false")
}
if !valid.Length("1", 1, "length1").Ok {
t.Error("the length of \"1\" must equal 1 should be true")
}
if valid.Length([]string{}, 1, "length1").Ok {
t.Error("the length of empty slice must equal 1 should be false")
}
if !valid.Length([]interface{}{"ok"}, 1, "length1").Ok {
t.Error("the length of [\"ok\"] must equal 1 should be true")
}
assert.False(t, valid.Length("", 1, "length1").Ok)
assert.True(t, valid.Length("1", 1, "length1").Ok)
assert.False(t, valid.Length([]string{}, 1, "length1").Ok)
assert.True(t, valid.Length([]interface{}{"ok"}, 1, "length1").Ok)
}
func TestAlpha(t *testing.T) {
@ -178,13 +130,16 @@ func TestAlphaNumeric(t *testing.T) {
}
}
const email = "suchuangji@gmail.com"
func TestMatch(t *testing.T) {
valid := Validation{}
if valid.Match("suchuangji@gmail", regexp.MustCompile(`^\w+@\w+\.\w+$`), "match").Ok {
t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be false")
}
if !valid.Match("suchuangji@gmail.com", regexp.MustCompile(`^\w+@\w+\.\w+$`), "match").Ok {
if !valid.Match(email, regexp.MustCompile(`^\w+@\w+\.\w+$`), "match").Ok {
t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be true")
}
}
@ -217,7 +172,7 @@ func TestEmail(t *testing.T) {
if valid.Email("not@a email", "email").Ok {
t.Error("\"not@a email\" is a valid email address should be false")
}
if !valid.Email("suchuangji@gmail.com", "email").Ok {
if !valid.Email(email, "email").Ok {
t.Error("\"suchuangji@gmail.com\" is a valid email address should be true")
}
if valid.Email("@suchuangji@gmail.com", "email").Ok {
@ -242,7 +197,7 @@ func TestIP(t *testing.T) {
func TestBase64(t *testing.T) {
valid := Validation{}
if valid.Base64("suchuangji@gmail.com", "base64").Ok {
if valid.Base64(email, "base64").Ok {
t.Error("\"suchuangji@gmail.com\" are a valid base64 characters should be false")
}
if !valid.Base64("c3VjaHVhbmdqaUBnbWFpbC5jb20=", "base64").Ok {
@ -370,44 +325,25 @@ func TestValid(t *testing.T) {
u := user{Name: "test@/test/;com", Age: 40}
b, err := valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if !b {
t.Error("validation should be passed")
}
assert.Nil(t, err)
assert.True(t, b)
uptr := &user{Name: "test", Age: 40}
valid.Clear()
b, err = valid.Valid(uptr)
if err != nil {
t.Fatal(err)
}
if b {
t.Error("validation should not be passed")
}
if len(valid.Errors) != 1 {
t.Fatalf("valid errors len should be 1 but got %d", len(valid.Errors))
}
if valid.Errors[0].Key != "Name.Match" {
t.Errorf("Message key should be `Name.Match` but got %s", valid.Errors[0].Key)
}
assert.Nil(t, err)
assert.False(t, b)
assert.Equal(t, 1, len(valid.Errors))
assert.Equal(t, "Name.Match", valid.Errors[0].Key)
u = user{Name: "test@/test/;com", Age: 180}
valid.Clear()
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Error("validation should not be passed")
}
if len(valid.Errors) != 1 {
t.Fatalf("valid errors len should be 1 but got %d", len(valid.Errors))
}
if valid.Errors[0].Key != "Age.Range." {
t.Errorf("Message key should be `Age.Range` but got %s", valid.Errors[0].Key)
}
assert.Nil(t, err)
assert.False(t, b)
assert.Equal(t, 1, len(valid.Errors))
assert.Equal(t, "Age.Range.", valid.Errors[0].Key)
}
func TestRecursiveValid(t *testing.T) {
@ -432,12 +368,8 @@ func TestRecursiveValid(t *testing.T) {
u := Account{Password: "abc123_", U: User{}}
b, err := valid.RecursiveValid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Error("validation should not be passed")
}
assert.Nil(t, err)
assert.False(t, b)
}
func TestSkipValid(t *testing.T) {
@ -474,21 +406,13 @@ func TestSkipValid(t *testing.T) {
valid := Validation{}
b, err := valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
assert.Nil(t, err)
assert.False(t, b)
valid = Validation{RequiredFirst: true}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if !b {
t.Fatal("validation should be passed")
}
assert.Nil(t, err)
assert.True(t, b)
}
func TestPointer(t *testing.T) {
@ -506,12 +430,8 @@ func TestPointer(t *testing.T) {
valid := Validation{}
b, err := valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
assert.Nil(t, err)
assert.False(t, b)
validEmail := "a@a.com"
u = User{
@ -521,12 +441,8 @@ func TestPointer(t *testing.T) {
valid = Validation{RequiredFirst: true}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if !b {
t.Fatal("validation should be passed")
}
assert.Nil(t, err)
assert.True(t, b)
u = User{
ReqEmail: &validEmail,
@ -535,12 +451,8 @@ func TestPointer(t *testing.T) {
valid = Validation{}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
assert.Nil(t, err)
assert.False(t, b)
invalidEmail := "a@a"
u = User{
@ -550,12 +462,8 @@ func TestPointer(t *testing.T) {
valid = Validation{RequiredFirst: true}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
assert.Nil(t, err)
assert.False(t, b)
u = User{
ReqEmail: &validEmail,
@ -564,12 +472,8 @@ func TestPointer(t *testing.T) {
valid = Validation{}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
assert.Nil(t, err)
assert.False(t, b)
}
func TestCanSkipAlso(t *testing.T) {
@ -589,21 +493,14 @@ func TestCanSkipAlso(t *testing.T) {
valid := Validation{RequiredFirst: true}
b, err := valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
assert.Nil(t, err)
assert.False(t, b)
valid = Validation{RequiredFirst: true}
valid.CanSkipAlso("Range")
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if !b {
t.Fatal("validation should be passed")
}
assert.Nil(t, err)
assert.True(t, b)
}

View File

@ -4,7 +4,7 @@ cache is a Go cache manager. It can use many cache adapters. The repo is inspire
## How to install?
go get github.com/beego/beego/v2/cache
go get github.com/beego/beego/v2/client/cache
## What adapters are supported?
@ -15,7 +15,7 @@ As of now this cache support memory, Memcache and Redis.
First you must import it
import (
"github.com/beego/beego/v2/cache"
"github.com/beego/beego/v2/client/cache"
)
Then init a Cache (example with memory adapter)

11
client/cache/cache.go vendored
View File

@ -16,7 +16,7 @@
// Usage:
//
// import(
// "github.com/beego/beego/v2/cache"
// "github.com/beego/beego/v2/client/cache"
// )
//
// bm, err := cache.NewCache("memory", `{"interval":60}`)
@ -33,8 +33,9 @@ package cache
import (
"context"
"fmt"
"time"
"github.com/beego/beego/v2/core/berror"
)
// Cache interface contains all behaviors for cache adapter.
@ -55,12 +56,14 @@ type Cache interface {
// Set a cached value with key and expire time.
Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error
// Delete cached value by key.
// Should not return error if key not found
Delete(ctx context.Context, key string) error
// Increment a cached int value by key, as a counter.
Incr(ctx context.Context, key string) error
// Decrement a cached int value by key, as a counter.
Decr(ctx context.Context, key string) error
// Check if a cached value exists or not.
// if key is expired, return (false, nil)
IsExist(ctx context.Context, key string) (bool, error)
// Clear all cache.
ClearAll(ctx context.Context) error
@ -78,7 +81,7 @@ var adapters = make(map[string]Instance)
// it panics.
func Register(name string, adapter Instance) {
if adapter == nil {
panic("cache: Register adapter is nil")
panic(berror.Error(NilCacheAdapter, "cache: Register adapter is nil").Error())
}
if _, ok := adapters[name]; ok {
panic("cache: Register called twice for adapter " + name)
@ -92,7 +95,7 @@ func Register(name string, adapter Instance) {
func NewCache(adapterName, config string) (adapter Cache, err error) {
instanceFunc, ok := adapters[adapterName]
if !ok {
err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
err = berror.Errorf(UnknownAdapter, "cache: unknown adapter name %s (forgot to import?)", adapterName)
return
}
adapter = instanceFunc()

View File

@ -16,17 +16,19 @@ package cache
import (
"context"
"math"
"os"
"strings"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCacheIncr(t *testing.T) {
bm, err := NewCache("memory", `{"interval":20}`)
if err != nil {
t.Error("init err")
}
assert.Nil(t, err)
// timeoutDuration := 10 * time.Second
bm.Put(context.Background(), "edwardhey", 0, time.Second*20)
@ -46,11 +48,9 @@ func TestCacheIncr(t *testing.T) {
}
func TestCache(t *testing.T) {
bm, err := NewCache("memory", `{"interval":20}`)
if err != nil {
t.Error("init err")
}
timeoutDuration := 10 * time.Second
bm, err := NewCache("memory", `{"interval":1}`)
assert.Nil(t, err)
timeoutDuration := 5 * time.Second
if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err)
}
@ -62,7 +62,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")
@ -73,130 +73,97 @@ func TestCache(t *testing.T) {
}
// test different integer type for incr & decr
testMultiIncrDecr(t, bm, timeoutDuration)
testMultiTypeIncrDecr(t, bm, timeoutDuration)
// test overflow of incr&decr
testIncrOverFlow(t, bm, timeoutDuration)
testDecrOverFlow(t, bm, timeoutDuration)
bm.Delete(context.Background(), "astaxie")
if res, _ := bm.IsExist(context.Background(), "astaxie"); res {
t.Error("delete err")
}
res, _ := bm.IsExist(context.Background(), "astaxie")
assert.False(t, res)
// test GetMulti
if err = bm.Put(context.Background(), "astaxie", "author", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if res, _ := bm.IsExist(context.Background(), "astaxie"); !res {
t.Error("check err")
}
if v, _ := bm.Get(context.Background(), "astaxie"); v.(string) != "author" {
t.Error("get err")
}
assert.Nil(t, bm.Put(context.Background(), "astaxie", "author", timeoutDuration))
if err = bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if res, _ := bm.IsExist(context.Background(), "astaxie1"); !res {
t.Error("check err")
}
res, _ = bm.IsExist(context.Background(), "astaxie")
assert.True(t, res)
v, _ := bm.Get(context.Background(), "astaxie")
assert.Equal(t, "author", v)
assert.Nil(t, bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration))
res, _ = bm.IsExist(context.Background(), "astaxie1")
assert.True(t, res)
vv, _ := bm.GetMulti(context.Background(), []string{"astaxie", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if vv[0].(string) != "author" {
t.Error("GetMulti ERROR")
}
if vv[1].(string) != "author1" {
t.Error("GetMulti ERROR")
}
assert.Equal(t, 2, len(vv))
assert.Equal(t, "author", vv[0])
assert.Equal(t,"author1", vv[1])
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")
}
assert.Equal(t, 2, len(vv))
assert.Nil(t, vv[0])
assert.Equal(t, "author1", vv[1])
assert.NotNil(t, err)
assert.True(t, strings.Contains(err.Error(), "key isn't exist"))
}
func TestFileCache(t *testing.T) {
bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`)
if err != nil {
t.Error("init err")
}
assert.Nil(t, err)
timeoutDuration := 10 * time.Second
if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err)
}
if res, _ := bm.IsExist(context.Background(), "astaxie"); !res {
t.Error("check err")
}
assert.Nil(t, bm.Put(context.Background(), "astaxie", 1, timeoutDuration))
if v, _ := bm.Get(context.Background(), "astaxie"); v.(int) != 1 {
t.Error("get err")
}
res, _ := bm.IsExist(context.Background(), "astaxie")
assert.True(t, res)
v, _ := bm.Get(context.Background(), "astaxie")
assert.Equal(t, 1, v)
// test different integer type for incr & decr
testMultiIncrDecr(t, bm, timeoutDuration)
testMultiTypeIncrDecr(t, bm, timeoutDuration)
// test overflow of incr&decr
testIncrOverFlow(t, bm, timeoutDuration)
testDecrOverFlow(t, bm, timeoutDuration)
bm.Delete(context.Background(), "astaxie")
if res, _ := bm.IsExist(context.Background(), "astaxie"); res {
t.Error("delete err")
}
res, _ = bm.IsExist(context.Background(), "astaxie")
assert.False(t, res)
// test string
if err = bm.Put(context.Background(), "astaxie", "author", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if res, _ := bm.IsExist(context.Background(), "astaxie"); !res {
t.Error("check err")
}
if v, _ := bm.Get(context.Background(), "astaxie"); v.(string) != "author" {
t.Error("get err")
}
assert.Nil(t, bm.Put(context.Background(), "astaxie", "author", timeoutDuration))
res, _ = bm.IsExist(context.Background(), "astaxie")
assert.True(t, res)
v, _ = bm.Get(context.Background(), "astaxie")
assert.Equal(t, "author", v)
// test GetMulti
if err = bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if res, _ := bm.IsExist(context.Background(), "astaxie1"); !res {
t.Error("check err")
}
assert.Nil(t, bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration))
res, _ = bm.IsExist(context.Background(), "astaxie1")
assert.True(t, res)
vv, _ := bm.GetMulti(context.Background(), []string{"astaxie", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if vv[0].(string) != "author" {
t.Error("GetMulti ERROR")
}
if vv[1].(string) != "author1" {
t.Error("GetMulti ERROR")
}
assert.Equal(t, 2, len(vv))
assert.Equal(t, "author", vv[0])
assert.Equal(t, "author1", vv[1])
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")
}
assert.Equal(t, 2, len(vv))
assert.Nil(t, vv[0])
assert.Equal(t, "author1", vv[1])
assert.NotNil(t, err)
os.RemoveAll("cache")
}
func testMultiIncrDecr(t *testing.T, c Cache, timeout time.Duration) {
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)
@ -206,30 +173,52 @@ func testMultiIncrDecr(t *testing.T, c Cache, timeout time.Duration) {
}
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)
}
assert.Nil(t, c.Put(ctx, key, beforeIncr, timeout))
assert.Nil(t, c.Incr(ctx, key))
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)
}
v, _ := c.Get(ctx, key)
assert.Equal(t, afterIncr, v)
if v, _ := c.Get(ctx, key); v != beforeIncr {
t.Error("Get Error")
}
assert.Nil(t, c.Decr(ctx, key))
if err := c.Delete(ctx, key); err != nil {
t.Error("Delete Error")
v, _ = c.Get(ctx, key)
assert.Equal(t, v, beforeIncr)
assert.Nil(t, c.Delete(ctx, key))
}
func testIncrOverFlow(t *testing.T, c Cache, timeout time.Duration) {
ctx := context.Background()
key := "incKey"
assert.Nil(t, c.Put(ctx, key, int64(math.MaxInt64), timeout))
// int64
defer func() {
assert.Nil(t, c.Delete(ctx, key))
}()
assert.NotNil(t, c.Incr(ctx, key))
}
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
}
}

92
client/cache/calc_utils.go vendored Normal file
View File

@ -0,0 +1,92 @@
package cache
import (
"math"
"github.com/beego/beego/v2/core/berror"
)
var (
ErrIncrementOverflow = berror.Error(IncrementOverflow, "this incr invocation will overflow.")
ErrDecrementOverflow = berror.Error(DecrementOverflow, "this decr invocation will overflow.")
ErrNotIntegerType = berror.Error(NotIntegerType, "item val is not (u)int (u)int32 (u)int64")
)
func incr(originVal interface{}) (interface{}, error) {
switch val := originVal.(type) {
case int:
tmp := val + 1
if val > 0 && tmp < 0 {
return nil, ErrIncrementOverflow
}
return tmp, nil
case int32:
if val == math.MaxInt32 {
return nil, ErrIncrementOverflow
}
return val + 1, nil
case int64:
if val == math.MaxInt64 {
return nil, ErrIncrementOverflow
}
return val + 1, nil
case uint:
tmp := val + 1
if tmp < val {
return nil, ErrIncrementOverflow
}
return tmp, nil
case uint32:
if val == math.MaxUint32 {
return nil, ErrIncrementOverflow
}
return val + 1, nil
case uint64:
if val == math.MaxUint64 {
return nil, ErrIncrementOverflow
}
return val + 1, nil
default:
return nil, ErrNotIntegerType
}
}
func decr(originVal interface{}) (interface{}, error) {
switch val := originVal.(type) {
case int:
tmp := val - 1
if val < 0 && tmp > 0 {
return nil, ErrDecrementOverflow
}
return tmp, nil
case int32:
if val == math.MinInt32 {
return nil, ErrDecrementOverflow
}
return val - 1, nil
case int64:
if val == math.MinInt64 {
return nil, ErrDecrementOverflow
}
return val - 1, nil
case uint:
if val == 0 {
return nil, ErrDecrementOverflow
}
return val - 1, nil
case uint32:
if val == 0 {
return nil, ErrDecrementOverflow
}
return val - 1, nil
case uint64:
if val == 0 {
return nil, ErrDecrementOverflow
}
return val - 1, nil
default:
return nil, ErrNotIntegerType
}
}

140
client/cache/calc_utils_test.go vendored Normal file
View File

@ -0,0 +1,140 @@
package cache
import (
"math"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestIncr(t *testing.T) {
// int
var originVal interface{} = int(1)
var updateVal interface{} = int(2)
val, err := incr(originVal)
assert.Nil(t, err)
assert.Equal(t, val, updateVal)
_, err = incr(int(1<<(strconv.IntSize-1) - 1))
assert.Equal(t, ErrIncrementOverflow, err)
// int32
originVal = int32(1)
updateVal = int32(2)
val, err = incr(originVal)
assert.Nil(t, err)
assert.Equal(t, val, updateVal)
_, err = incr(int32(math.MaxInt32))
assert.Equal(t, ErrIncrementOverflow, err)
// int64
originVal = int64(1)
updateVal = int64(2)
val, err = incr(originVal)
assert.Nil(t, err)
assert.Equal(t, val, updateVal)
_, err = incr(int64(math.MaxInt64))
assert.Equal(t, ErrIncrementOverflow, err)
// uint
originVal = uint(1)
updateVal = uint(2)
val, err = incr(originVal)
assert.Nil(t, err)
assert.Equal(t, val, updateVal)
_, err = incr(uint(1<<(strconv.IntSize) - 1))
assert.Equal(t, ErrIncrementOverflow, err)
// uint32
originVal = uint32(1)
updateVal = uint32(2)
val, err = incr(originVal)
assert.Nil(t, err)
assert.Equal(t, val, updateVal)
_, err = incr(uint32(math.MaxUint32))
assert.Equal(t, ErrIncrementOverflow, err)
// uint64
originVal = uint64(1)
updateVal = uint64(2)
val, err = incr(originVal)
assert.Nil(t, err)
assert.Equal(t, val, updateVal)
_, err = incr(uint64(math.MaxUint64))
assert.Equal(t, ErrIncrementOverflow, err)
// other type
_, err = incr("string")
assert.Equal(t, ErrNotIntegerType, err)
}
func TestDecr(t *testing.T) {
// int
var originVal interface{} = int(2)
var updateVal interface{} = int(1)
val, err := decr(originVal)
assert.Nil(t, err)
assert.Equal(t, val, updateVal)
_, err = decr(int(-1 << (strconv.IntSize - 1)))
assert.Equal(t, ErrDecrementOverflow, err)
// int32
originVal = int32(2)
updateVal = int32(1)
val, err = decr(originVal)
assert.Nil(t, err)
assert.Equal(t, val, updateVal)
_, err = decr(int32(math.MinInt32))
assert.Equal(t, ErrDecrementOverflow, err)
// int64
originVal = int64(2)
updateVal = int64(1)
val, err = decr(originVal)
assert.Nil(t, err)
assert.Equal(t, val, updateVal)
_, err = decr(int64(math.MinInt64))
assert.Equal(t, ErrDecrementOverflow, err)
// uint
originVal = uint(2)
updateVal = uint(1)
val, err = decr(originVal)
assert.Nil(t, err)
assert.Equal(t, val, updateVal)
_, err = decr(uint(0))
assert.Equal(t, ErrDecrementOverflow, err)
// uint32
originVal = uint32(2)
updateVal = uint32(1)
val, err = decr(originVal)
assert.Nil(t, err)
assert.Equal(t, val, updateVal)
_, err = decr(uint32(0))
assert.Equal(t, ErrDecrementOverflow, err)
// uint64
originVal = uint64(2)
updateVal = uint64(1)
val, err = decr(originVal)
assert.Nil(t, err)
assert.Equal(t, val, updateVal)
_, err = decr(uint64(0))
assert.Equal(t, ErrDecrementOverflow, err)
// other type
_, err = decr("string")
assert.Equal(t, ErrNotIntegerType, err)
}

View File

@ -16,128 +16,74 @@ package cache
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetString(t *testing.T) {
var t1 = "test1"
if "test1" != GetString(t1) {
t.Error("get string from string error")
}
var t2 = []byte("test2")
if "test2" != GetString(t2) {
t.Error("get string from byte array error")
}
var t3 = 1
if "1" != GetString(t3) {
t.Error("get string from int error")
}
var t4 int64 = 1
if "1" != GetString(t4) {
t.Error("get string from int64 error")
}
var t5 = 1.1
if "1.1" != GetString(t5) {
t.Error("get string from float64 error")
}
if "" != GetString(nil) {
t.Error("get string from nil error")
}
assert.Equal(t, "test1", GetString(t1))
var t2 = []byte("test2")
assert.Equal(t, "test2", GetString(t2))
var t3 = 1
assert.Equal(t, "1", GetString(t3))
var t4 int64 = 1
assert.Equal(t, "1", GetString(t4))
var t5 = 1.1
assert.Equal(t, "1.1", GetString(t5))
assert.Equal(t, "", GetString(nil))
}
func TestGetInt(t *testing.T) {
var t1 = 1
if 1 != GetInt(t1) {
t.Error("get int from int error")
}
assert.Equal(t, 1, GetInt(t1))
var t2 int32 = 32
if 32 != GetInt(t2) {
t.Error("get int from int32 error")
}
assert.Equal(t, 32, GetInt(t2))
var t3 int64 = 64
if 64 != GetInt(t3) {
t.Error("get int from int64 error")
}
assert.Equal(t, 64, GetInt(t3))
var t4 = "128"
if 128 != GetInt(t4) {
t.Error("get int from num string error")
}
if 0 != GetInt(nil) {
t.Error("get int from nil error")
}
assert.Equal(t, 128, GetInt(t4))
assert.Equal(t, 0, GetInt(nil))
}
func TestGetInt64(t *testing.T) {
var i int64 = 1
var t1 = 1
if i != GetInt64(t1) {
t.Error("get int64 from int error")
}
assert.Equal(t, i, GetInt64(t1))
var t2 int32 = 1
if i != GetInt64(t2) {
t.Error("get int64 from int32 error")
}
assert.Equal(t, i, GetInt64(t2))
var t3 int64 = 1
if i != GetInt64(t3) {
t.Error("get int64 from int64 error")
}
assert.Equal(t, i, GetInt64(t3))
var t4 = "1"
if i != GetInt64(t4) {
t.Error("get int64 from num string error")
}
if 0 != GetInt64(nil) {
t.Error("get int64 from nil")
}
assert.Equal(t, i, GetInt64(t4))
assert.Equal(t, int64(0), GetInt64(nil))
}
func TestGetFloat64(t *testing.T) {
var f = 1.11
var t1 float32 = 1.11
if f != GetFloat64(t1) {
t.Error("get float64 from float32 error")
}
assert.Equal(t, f, GetFloat64(t1))
var t2 = 1.11
if f != GetFloat64(t2) {
t.Error("get float64 from float64 error")
}
assert.Equal(t, f, GetFloat64(t2))
var t3 = "1.11"
if f != GetFloat64(t3) {
t.Error("get float64 from string error")
}
assert.Equal(t, f, GetFloat64(t3))
var f2 float64 = 1
var t4 = 1
if f2 != GetFloat64(t4) {
t.Error("get float64 from int error")
}
assert.Equal(t, f2, GetFloat64(t4))
if 0 != GetFloat64(nil) {
t.Error("get float64 from nil error")
}
assert.Equal(t, float64(0), GetFloat64(nil))
}
func TestGetBool(t *testing.T) {
var t1 = true
if !GetBool(t1) {
t.Error("get bool from bool error")
}
assert.True(t, GetBool(t1))
var t2 = "true"
if !GetBool(t2) {
t.Error("get bool from string error")
}
if GetBool(nil) {
t.Error("get bool from nil error")
}
}
assert.True(t, GetBool(t2))
func byteArrayEquals(a []byte, b []byte) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
assert.False(t, GetBool(nil))
}

182
client/cache/error_code.go vendored Normal file
View File

@ -0,0 +1,182 @@
// Copyright 2021 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 cache
import (
"github.com/beego/beego/v2/core/berror"
)
var NilCacheAdapter = berror.DefineCode(4002001, moduleName, "NilCacheAdapter", `
It means that you register cache adapter by pass nil.
A cache adapter is an instance of Cache interface.
`)
var DuplicateAdapter = berror.DefineCode(4002002, moduleName, "DuplicateAdapter", `
You register two adapter with same name. In beego cache module, one name one adapter.
Once you got this error, please check the error stack, search adapter
`)
var UnknownAdapter = berror.DefineCode(4002003, moduleName, "UnknownAdapter", `
Unknown adapter, do you forget to register the adapter?
You must register adapter before use it. For example, if you want to use redis implementation,
you must import the cache/redis package.
`)
var IncrementOverflow = berror.DefineCode(4002004, moduleName, "IncrementOverflow", `
The increment operation will overflow.
`)
var DecrementOverflow = berror.DefineCode(4002005, moduleName, "DecrementOverflow", `
The decrement operation will overflow.
`)
var NotIntegerType = berror.DefineCode(4002006, moduleName, "NotIntegerType", `
The type of value is not (u)int (u)int32 (u)int64.
When you want to call Incr or Decr function of Cache API, you must confirm that the value's type is one of (u)int (u)int32 (u)int64.
`)
var InvalidFileCacheDirectoryLevelCfg = berror.DefineCode(4002007, moduleName, "InvalidFileCacheDirectoryLevelCfg", `
You pass invalid DirectoryLevel parameter when you try to StartAndGC file cache instance.
This parameter must be a integer, and please check your input.
`)
var InvalidFileCacheEmbedExpiryCfg = berror.DefineCode(4002008, moduleName, "InvalidFileCacheEmbedExpiryCfg", `
You pass invalid EmbedExpiry parameter when you try to StartAndGC file cache instance.
This parameter must be a integer, and please check your input.
`)
var CreateFileCacheDirFailed = berror.DefineCode(4002009, moduleName, "CreateFileCacheDirFailed", `
Beego failed to create file cache directory. There are two cases:
1. You pass invalid CachePath parameter. Please check your input.
2. Beego doesn't have the permission to create this directory. Please check your file mode.
`)
var InvalidFileCachePath = berror.DefineCode(4002010, moduleName, "InvalidFilePath", `
The file path of FileCache is invalid. Please correct the config.
`)
var ReadFileCacheContentFailed = berror.DefineCode(4002011, moduleName, "ReadFileCacheContentFailed", `
Usually you won't got this error. It means that Beego cannot read the data from the file.
You need to check whether the file exist. Sometimes it may be deleted by other processes.
If the file exists, please check the permission that Beego is able to read data from the file.
`)
var InvalidGobEncodedData = berror.DefineCode(4002012, moduleName, "InvalidEncodedData", `
The data is invalid. When you try to decode the invalid data, you got this error.
Please confirm that the data is encoded by GOB correctly.
`)
var GobEncodeDataFailed = berror.DefineCode(4002013, moduleName, "GobEncodeDataFailed", `
Beego could not encode the data to GOB byte array. In general, the data type is invalid.
For example, GOB doesn't support function type.
Basic types, string, structure, structure pointer are supported.
`)
var KeyExpired = berror.DefineCode(4002014, moduleName, "KeyExpired", `
Cache key is expired.
You should notice that, a key is expired and then it may be deleted by GC goroutine.
So when you query a key which may be expired, you may got this code, or KeyNotExist.
`)
var KeyNotExist = berror.DefineCode(4002015, moduleName, "KeyNotExist", `
Key not found.
`)
var MultiGetFailed = berror.DefineCode(4002016, moduleName, "MultiGetFailed", `
Get multiple keys failed. Please check the detail msg to find out the root cause.
`)
var InvalidMemoryCacheCfg = berror.DefineCode(4002017, moduleName, "InvalidMemoryCacheCfg", `
The config is invalid. Please check your input. It must be a json string.
`)
var InvalidMemCacheCfg = berror.DefineCode(4002018, moduleName, "InvalidMemCacheCfg", `
The config is invalid. Please check your input, it must be json string and contains "conn" field.
`)
var InvalidMemCacheValue = berror.DefineCode(4002019, moduleName, "InvalidMemCacheValue", `
The value must be string or byte[], please check your input.
`)
var InvalidRedisCacheCfg = berror.DefineCode(4002020, moduleName, "InvalidRedisCacheCfg", `
The config must be json string, and has "conn" field.
`)
var InvalidSsdbCacheCfg = berror.DefineCode(4002021, moduleName, "InvalidSsdbCacheCfg", `
The config must be json string, and has "conn" field. The value of "conn" field should be "host:port".
"port" must be a valid integer.
`)
var InvalidSsdbCacheValue = berror.DefineCode(4002022, moduleName, "InvalidSsdbCacheValue", `
SSDB cache only accept string value. Please check your input.
`)
var DeleteFileCacheItemFailed = berror.DefineCode(5002001, moduleName, "DeleteFileCacheItemFailed", `
Beego try to delete file cache item failed.
Please check whether Beego generated file correctly.
And then confirm whether this file is already deleted by other processes or other people.
`)
var MemCacheCurdFailed = berror.DefineCode(5002002, moduleName, "MemCacheError", `
When you want to get, put, delete key-value from remote memcache servers, you may get error:
1. You pass invalid servers address, so Beego could not connect to remote server;
2. The servers address is correct, but there is some net issue. Typically there is some firewalls between application and memcache server;
3. Key is invalid. The key's length should be less than 250 and must not contains special characters;
4. The response from memcache server is invalid;
`)
var RedisCacheCurdFailed = berror.DefineCode(5002003, moduleName, "RedisCacheCurdFailed", `
When Beego uses client to send request to redis server, it failed.
1. The server addresses is invalid;
2. Network issue, firewall issue or network is unstable;
3. Client failed to manage connection. In extreme cases, Beego's redis client didn't maintain connections correctly, for example, Beego try to send request via closed connection;
4. The request are huge and redis server spent too much time to process it, and client is timeout;
In general, if you always got this error whatever you do, in most cases, it was caused by network issue.
You could check your network state, and confirm that firewall rules are correct.
`)
var InvalidConnection = berror.DefineCode(5002004, moduleName, "InvalidConnection", `
The connection is invalid. Please check your connection info, network, firewall.
You could simply uses ping, telnet or write some simple tests to test network.
`)
var DialFailed = berror.DefineCode(5002005, moduleName, "DialFailed", `
When Beego try to dial to remote servers, it failed. Please check your connection info and network state, server state.
`)
var SsdbCacheCurdFailed = berror.DefineCode(5002006, moduleName, "SsdbCacheCurdFailed", `
When you try to use SSDB cache, it failed. There are many cases:
1. servers unavailable;
2. network issue, including network unstable, firewall;
3. connection issue;
4. request are huge and servers spent too much time to process it, got timeout;
`)
var SsdbBadResponse = berror.DefineCode(5002007, moduleName, "SsdbBadResponse", `
The reponse from SSDB server is invalid.
Usually it indicates something wrong on server side.
`)
var ErrKeyExpired = berror.Error(KeyExpired, "the key is expired")
var ErrKeyNotExist = berror.Error(KeyNotExist, "the key isn't exist")

197
client/cache/file.go vendored
View File

@ -30,7 +30,7 @@ import (
"strings"
"time"
"github.com/pkg/errors"
"github.com/beego/beego/v2/core/berror"
)
// FileCacheItem is basic unit of file cache adapter which
@ -73,38 +73,60 @@ func (fc *FileCache) StartAndGC(config string) error {
if err != nil {
return err
}
if _, ok := cfg["CachePath"]; !ok {
cfg["CachePath"] = FileCachePath
}
if _, ok := cfg["FileSuffix"]; !ok {
cfg["FileSuffix"] = FileCacheFileSuffix
}
if _, ok := cfg["DirectoryLevel"]; !ok {
cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel)
}
if _, ok := cfg["EmbedExpiry"]; !ok {
cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
}
fc.CachePath = cfg["CachePath"]
fc.FileSuffix = cfg["FileSuffix"]
fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])
fc.Init()
return nil
const cpKey = "CachePath"
const fsKey = "FileSuffix"
const dlKey = "DirectoryLevel"
const eeKey = "EmbedExpiry"
if _, ok := cfg[cpKey]; !ok {
cfg[cpKey] = FileCachePath
}
if _, ok := cfg[fsKey]; !ok {
cfg[fsKey] = FileCacheFileSuffix
}
if _, ok := cfg[dlKey]; !ok {
cfg[dlKey] = strconv.Itoa(FileCacheDirectoryLevel)
}
if _, ok := cfg[eeKey]; !ok {
cfg[eeKey] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
}
fc.CachePath = cfg[cpKey]
fc.FileSuffix = cfg[fsKey]
fc.DirectoryLevel, err = strconv.Atoi(cfg[dlKey])
if err != nil {
return berror.Wrapf(err, InvalidFileCacheDirectoryLevelCfg,
"invalid directory level config, please check your input, it must be integer: %s", cfg[dlKey])
}
fc.EmbedExpiry, err = strconv.Atoi(cfg[eeKey])
if err != nil {
return berror.Wrapf(err, InvalidFileCacheEmbedExpiryCfg,
"invalid embed expiry config, please check your input, it must be integer: %s", cfg[eeKey])
}
return fc.Init()
}
// Init makes new a dir for file cache if it does not already exist
func (fc *FileCache) Init() {
if ok, _ := exists(fc.CachePath); !ok { // todo : error handle
_ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle
func (fc *FileCache) Init() error {
ok, err := exists(fc.CachePath)
if err != nil || ok {
return err
}
err = os.MkdirAll(fc.CachePath, os.ModePerm)
if err != nil {
return berror.Wrapf(err, CreateFileCacheDirFailed,
"could not create directory, please check the config [%s] and file mode.", fc.CachePath)
}
return nil
}
// getCachedFilename returns an md5 encoded file name.
func (fc *FileCache) getCacheFileName(key string) string {
func (fc *FileCache) getCacheFileName(key string) (string, error) {
m := md5.New()
io.WriteString(m, key)
_, _ = io.WriteString(m, key)
keyMd5 := hex.EncodeToString(m.Sum(nil))
cachePath := fc.CachePath
switch fc.DirectoryLevel {
@ -113,18 +135,29 @@ func (fc *FileCache) getCacheFileName(key string) string {
case 1:
cachePath = filepath.Join(cachePath, keyMd5[0:2])
}
if ok, _ := exists(cachePath); !ok { // todo : error handle
_ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle
ok, err := exists(cachePath)
if err != nil {
return "", err
}
if !ok {
err = os.MkdirAll(cachePath, os.ModePerm)
if err != nil {
return "", berror.Wrapf(err, CreateFileCacheDirFailed,
"could not create the directory: %s", cachePath)
}
}
return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix))
return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix)), nil
}
// Get value from file cache.
// if nonexistent or expired return an empty string.
func (fc *FileCache) Get(ctx context.Context, key string) (interface{}, error) {
fileData, err := FileGetContents(fc.getCacheFileName(key))
fn, err := fc.getCacheFileName(key)
if err != nil {
return nil, err
}
fileData, err := FileGetContents(fn)
if err != nil {
return nil, err
}
@ -136,7 +169,7 @@ func (fc *FileCache) Get(ctx context.Context, key string) (interface{}, error) {
}
if to.Expired.Before(time.Now()) {
return nil, errors.New("The key is expired")
return nil, ErrKeyExpired
}
return to.Data, nil
}
@ -159,7 +192,7 @@ func (fc *FileCache) GetMulti(ctx context.Context, keys []string) ([]interface{}
if len(keysErr) == 0 {
return rc, nil
}
return rc, errors.New(strings.Join(keysErr, "; "))
return rc, berror.Error(MultiGetFailed, strings.Join(keysErr, "; "))
}
// Put value into file cache.
@ -179,14 +212,26 @@ func (fc *FileCache) Put(ctx context.Context, key string, val interface{}, timeo
if err != nil {
return err
}
return FilePutContents(fc.getCacheFileName(key), data)
fn, err := fc.getCacheFileName(key)
if err != nil {
return err
}
return FilePutContents(fn, data)
}
// Delete file cache value.
func (fc *FileCache) Delete(ctx context.Context, key string) error {
filename := fc.getCacheFileName(key)
filename, err := fc.getCacheFileName(key)
if err != nil {
return err
}
if ok, _ := exists(filename); ok {
return os.Remove(filename)
err = os.Remove(filename)
if err != nil {
return berror.Wrapf(err, DeleteFileCacheItemFailed,
"can not delete this file cache key-value, key is %s and file name is %s", key, filename)
}
}
return nil
}
@ -199,25 +244,12 @@ func (fc *FileCache) Incr(ctx context.Context, key string) error {
return err
}
var res interface{}
switch val := data.(type) {
case int:
res = val + 1
case int32:
res = val + 1
case int64:
res = val + 1
case uint:
res = val + 1
case uint32:
res = val + 1
case uint64:
res = val + 1
default:
return errors.Errorf("data is not (u)int (u)int32 (u)int64")
val, err := incr(data)
if err != nil {
return err
}
return fc.Put(context.Background(), key, res, time.Duration(fc.EmbedExpiry))
return fc.Put(context.Background(), key, val, time.Duration(fc.EmbedExpiry))
}
// Decr decreases cached int value.
@ -227,43 +259,21 @@ func (fc *FileCache) Decr(ctx context.Context, key string) error {
return err
}
var res interface{}
switch val := data.(type) {
case int:
res = val - 1
case int32:
res = val - 1
case int64:
res = val - 1
case uint:
if val > 0 {
res = val - 1
} else {
return errors.New("data val is less than 0")
}
case uint32:
if val > 0 {
res = val - 1
} else {
return errors.New("data val is less than 0")
}
case uint64:
if val > 0 {
res = val - 1
} else {
return errors.New("data val is less than 0")
}
default:
return errors.Errorf("data is not (u)int (u)int32 (u)int64")
val, err := decr(data)
if err != nil {
return err
}
return fc.Put(context.Background(), key, res, time.Duration(fc.EmbedExpiry))
return fc.Put(context.Background(), key, val, time.Duration(fc.EmbedExpiry))
}
// IsExist checks if value exists.
func (fc *FileCache) IsExist(ctx context.Context, key string) (bool, error) {
ret, _ := exists(fc.getCacheFileName(key))
return ret, nil
fn, err := fc.getCacheFileName(key)
if err != nil {
return false, err
}
return exists(fn)
}
// ClearAll cleans cached files (not implemented)
@ -280,13 +290,19 @@ func exists(path string) (bool, error) {
if os.IsNotExist(err) {
return false, nil
}
return false, err
return false, berror.Wrapf(err, InvalidFileCachePath, "file cache path is invalid: %s", path)
}
// FileGetContents Reads bytes from a file.
// if non-existent, create this file.
func FileGetContents(filename string) (data []byte, e error) {
return ioutil.ReadFile(filename)
func FileGetContents(filename string) ([]byte, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, berror.Wrapf(err, ReadFileCacheContentFailed,
"could not read the data from the file: %s, " +
"please confirm that file exist and Beego has the permission to read the content.", filename)
}
return data, nil
}
// FilePutContents puts bytes into a file.
@ -301,16 +317,21 @@ func GobEncode(data interface{}) ([]byte, error) {
enc := gob.NewEncoder(buf)
err := enc.Encode(data)
if err != nil {
return nil, err
return nil, berror.Wrap(err, GobEncodeDataFailed, "could not encode this data")
}
return buf.Bytes(), err
return buf.Bytes(), nil
}
// GobDecode Gob decodes a file cache item.
func GobDecode(data []byte, to *FileCacheItem) error {
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
return dec.Decode(&to)
err := dec.Decode(&to)
if err != nil {
return berror.Wrap(err, InvalidGobEncodedData,
"could not decode this data to FileCacheItem. Make sure that the data is encoded by GOB.")
}
return nil
}
func init() {

108
client/cache/file_test.go vendored Normal file
View File

@ -0,0 +1,108 @@
// Copyright 2021 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 cache
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFileCacheStartAndGC(t *testing.T) {
fc := NewFileCache().(*FileCache)
err := fc.StartAndGC(`{`)
assert.NotNil(t, err)
err = fc.StartAndGC(`{}`)
assert.Nil(t, err)
assert.Equal(t, fc.CachePath, FileCachePath)
assert.Equal(t, fc.DirectoryLevel, FileCacheDirectoryLevel)
assert.Equal(t, fc.EmbedExpiry, int(FileCacheEmbedExpiry))
assert.Equal(t, fc.FileSuffix, FileCacheFileSuffix)
err = fc.StartAndGC(`{"CachePath":"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`)
// could not create dir
assert.NotNil(t, err)
str := getTestCacheFilePath()
err = fc.StartAndGC(fmt.Sprintf(`{"CachePath":"%s","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`, str))
assert.Nil(t, err)
assert.Equal(t, fc.CachePath, str)
assert.Equal(t, fc.DirectoryLevel, 2)
assert.Equal(t, fc.EmbedExpiry, 0)
assert.Equal(t, fc.FileSuffix, ".bin")
err = fc.StartAndGC(fmt.Sprintf(`{"CachePath":"%s","FileSuffix":".bin","DirectoryLevel":"aaa","EmbedExpiry":"0"}`, str))
assert.NotNil(t, err)
err = fc.StartAndGC(fmt.Sprintf(`{"CachePath":"%s","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"aaa"}`, str))
assert.NotNil(t, err)
}
func TestFileCacheInit(t *testing.T) {
fc := NewFileCache().(*FileCache)
fc.CachePath = "////aaa"
err := fc.Init()
assert.NotNil(t, err)
fc.CachePath = getTestCacheFilePath()
err = fc.Init()
assert.Nil(t, err)
}
func TestFileGetContents(t *testing.T) {
_, err := FileGetContents("/bin/aaa")
assert.NotNil(t, err)
fn := filepath.Join(os.TempDir(), "fileCache.txt")
f, err := os.Create(fn)
assert.Nil(t, err)
_, err = f.WriteString("text")
assert.Nil(t, err)
data, err := FileGetContents(fn)
assert.Nil(t, err)
assert.Equal(t, "text", string(data))
}
func TestGobEncodeDecode(t *testing.T) {
_, err := GobEncode(func() {
fmt.Print("test func")
})
assert.NotNil(t, err)
data, err := GobEncode(&FileCacheItem{
Data: "hello",
})
assert.Nil(t, err)
err = GobDecode([]byte("wrong data"), &FileCacheItem{})
assert.NotNil(t, err)
dci := &FileCacheItem{}
err = GobDecode(data, dci)
assert.Nil(t, err)
assert.Equal(t, "hello", dci.Data)
}
func TestFileCacheDelete(t *testing.T) {
fc := NewFileCache()
err := fc.StartAndGC(`{}`)
assert.Nil(t, err)
err = fc.Delete(context.Background(), "my-key")
assert.Nil(t, err)
}
func getTestCacheFilePath() string {
return filepath.Join(os.TempDir(), "test", "file.txt")
}

View File

@ -20,8 +20,8 @@
//
// Usage:
// import(
// _ "github.com/beego/beego/v2/cache/memcache"
// "github.com/beego/beego/v2/cache"
// _ "github.com/beego/beego/v2/client/cache/memcache"
// "github.com/beego/beego/v2/client/cache"
// )
//
// bm, err := cache.NewCache("memcache", `{"conn":"127.0.0.1:11211"}`)
@ -32,7 +32,6 @@ package memcache
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
@ -40,6 +39,7 @@ import (
"github.com/bradfitz/gomemcache/memcache"
"github.com/beego/beego/v2/client/cache"
"github.com/beego/beego/v2/core/berror"
)
// Cache Memcache adapter.
@ -55,36 +55,31 @@ func NewMemCache() cache.Cache {
// Get get value from memcache.
func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return nil, err
}
}
if item, err := rc.conn.Get(key); err == nil {
return item.Value, nil
} else {
return nil, err
return nil, berror.Wrapf(err, cache.MemCacheCurdFailed,
"could not read data from memcache, please check your key, network and connection. Root cause: %s",
err.Error())
}
}
// GetMulti gets a value from a key in memcache.
func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
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 {
return rv, err
return rv, berror.Wrapf(err, cache.MemCacheCurdFailed,
"could not read multiple key-values from memcache, " +
"please check your keys, network and connection. Root cause: %s",
err.Error())
}
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"))
keysErr = append(keysErr, fmt.Sprintf("key [%s] error: %s", ki, "key not exist"))
continue
}
rv[i] = mv[ki].Value
@ -93,78 +88,54 @@ func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, er
if len(keysErr) == 0 {
return rv, nil
}
return rv, fmt.Errorf(strings.Join(keysErr, "; "))
return rv, berror.Error(cache.MultiGetFailed, strings.Join(keysErr, "; "))
}
// Put puts a value into memcache.
func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
item := memcache.Item{Key: key, Expiration: int32(timeout / time.Second)}
if v, ok := val.([]byte); ok {
item.Value = v
} else if str, ok := val.(string); ok {
item.Value = []byte(str)
} else {
return errors.New("val only support string and []byte")
return berror.Errorf(cache.InvalidMemCacheValue,
"the value must be string or byte[]. key: %s, value:%v", key, val)
}
return rc.conn.Set(&item)
return berror.Wrapf(rc.conn.Set(&item), cache.MemCacheCurdFailed,
"could not put key-value to memcache, key: %s", key)
}
// Delete deletes a value in memcache.
func (rc *Cache) Delete(ctx context.Context, key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
return rc.conn.Delete(key)
return berror.Wrapf(rc.conn.Delete(key), cache.MemCacheCurdFailed,
"could not delete key-value from memcache, key: %s", key)
}
// Incr increases counter.
func (rc *Cache) Incr(ctx context.Context, key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Increment(key, 1)
return err
return berror.Wrapf(err, cache.MemCacheCurdFailed,
"could not increase value for key: %s", key)
}
// Decr decreases counter.
func (rc *Cache) Decr(ctx context.Context, key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Decrement(key, 1)
return err
return berror.Wrapf(err, cache.MemCacheCurdFailed,
"could not decrease value for key: %s", key)
}
// IsExist checks if a value exists in memcache.
func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return false, err
}
}
_, err := rc.conn.Get(key)
_, err := rc.Get(ctx, key)
return err == nil, err
}
// ClearAll clears all cache in memcache.
func (rc *Cache) ClearAll(context.Context) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
return rc.conn.FlushAll()
return berror.Wrap(rc.conn.FlushAll(), cache.MemCacheCurdFailed,
"try to clear all key-value pairs failed")
}
// StartAndGC starts the memcache adapter.
@ -172,21 +143,15 @@ func (rc *Cache) ClearAll(context.Context) error {
// If an error occurs during connecting, an error is returned
func (rc *Cache) StartAndGC(config string) error {
var cf map[string]string
json.Unmarshal([]byte(config), &cf)
if err := json.Unmarshal([]byte(config), &cf); err != nil {
return berror.Wrapf(err, cache.InvalidMemCacheCfg,
"could not unmarshal this config, it must be valid json stringP: %s", config)
}
if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key")
return berror.Errorf(cache.InvalidMemCacheCfg, `config must contains "conn" field: %s`, config)
}
rc.conninfo = strings.Split(cf["conn"], ";")
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
return nil
}
// connect to memcache and keep the connection.
func (rc *Cache) connectInit() error {
rc.conn = memcache.New(rc.conninfo...)
return nil
}

View File

@ -19,10 +19,12 @@ import (
"fmt"
"os"
"strconv"
"strings"
"testing"
"time"
_ "github.com/bradfitz/gomemcache/memcache"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/client/cache"
)
@ -34,78 +36,63 @@ func TestMemcacheCache(t *testing.T) {
}
bm, err := cache.NewCache("memcache", fmt.Sprintf(`{"conn": "%s"}`, addr))
if err != nil {
t.Error("init err")
}
assert.Nil(t, err)
timeoutDuration := 10 * time.Second
if err = bm.Put(context.Background(), "astaxie", "1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if res, _ := bm.IsExist(context.Background(), "astaxie"); !res {
t.Error("check err")
}
assert.Nil(t, bm.Put(context.Background(), "astaxie", "1", timeoutDuration))
res, _ := bm.IsExist(context.Background(), "astaxie")
assert.True(t, res)
time.Sleep(11 * time.Second)
if res, _ := bm.IsExist(context.Background(), "astaxie"); res {
t.Error("check err")
}
if err = bm.Put(context.Background(), "astaxie", "1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
res, _ = bm.IsExist(context.Background(), "astaxie")
assert.False(t, res)
assert.Nil(t, bm.Put(context.Background(), "astaxie", "1", timeoutDuration))
val, _ := bm.Get(context.Background(), "astaxie")
if v, err := strconv.Atoi(string(val.([]byte))); err != nil || v != 1 {
t.Error("get err")
}
v, err := strconv.Atoi(string(val.([]byte)))
assert.Nil(t, err)
assert.Equal(t, 1, v)
if err = bm.Incr(context.Background(), "astaxie"); err != nil {
t.Error("Incr Error", err)
}
assert.Nil(t, bm.Incr(context.Background(), "astaxie"))
val, _ = bm.Get(context.Background(), "astaxie")
if v, err := strconv.Atoi(string(val.([]byte))); err != nil || v != 2 {
t.Error("get err")
}
v, err = strconv.Atoi(string(val.([]byte)))
assert.Nil(t, err)
assert.Equal(t, 2, v)
if err = bm.Decr(context.Background(), "astaxie"); err != nil {
t.Error("Decr Error", err)
}
assert.Nil(t, bm.Decr(context.Background(), "astaxie"))
val, _ = bm.Get(context.Background(), "astaxie")
if v, err := strconv.Atoi(string(val.([]byte))); err != nil || v != 1 {
t.Error("get err")
}
v, err = strconv.Atoi(string(val.([]byte)))
assert.Nil(t, err)
assert.Equal(t, 1, v)
bm.Delete(context.Background(), "astaxie")
if res, _ := bm.IsExist(context.Background(), "astaxie"); res {
t.Error("delete err")
}
res, _ = bm.IsExist(context.Background(), "astaxie")
assert.False(t, res)
assert.Nil(t,bm.Put(context.Background(), "astaxie", "author", timeoutDuration) )
// test string
if err = bm.Put(context.Background(), "astaxie", "author", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if res, _ := bm.IsExist(context.Background(), "astaxie"); !res {
t.Error("check err")
}
res, _ = bm.IsExist(context.Background(), "astaxie")
assert.True(t, res)
val, _ = bm.Get(context.Background(), "astaxie")
if v := val.([]byte); string(v) != "author" {
t.Error("get err")
}
vs := val.([]byte)
assert.Equal(t, "author", string(vs))
// test GetMulti
if err = bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if res, _ := bm.IsExist(context.Background(), "astaxie1"); !res {
t.Error("check err")
}
assert.Nil(t, bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration))
res, _ = bm.IsExist(context.Background(), "astaxie1")
assert.True(t, res)
vv, _ := bm.GetMulti(context.Background(), []string{"astaxie", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
assert.Equal(t, 2, len(vv))
if string(vv[0].([]byte)) != "author" && string(vv[0].([]byte)) != "author1" {
t.Error("GetMulti ERROR")
}
@ -114,21 +101,14 @@ func TestMemcacheCache(t *testing.T) {
}
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")
}
assert.Equal(t, 2, len(vv))
assert.Nil(t, vv[0])
assert.Equal(t, "author1", string(vv[1].([]byte)))
assert.NotNil(t, err)
assert.True(t, strings.Contains(err.Error(), "key not exist"))
assert.Nil(t, bm.ClearAll(context.Background()))
// test clear all
if err = bm.ClearAll(context.Background()); err != nil {
t.Error("clear all err")
}
}

View File

@ -17,11 +17,12 @@ package cache
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/beego/beego/v2/core/berror"
)
var (
@ -41,7 +42,7 @@ func (mi *MemoryItem) isExpire() bool {
if mi.lifespan == 0 {
return false
}
return time.Now().Sub(mi.createdTime) > mi.lifespan
return time.Since(mi.createdTime) > mi.lifespan
}
// MemoryCache is a memory cache adapter.
@ -64,13 +65,14 @@ func NewMemoryCache() Cache {
func (bc *MemoryCache) Get(ctx context.Context, key string) (interface{}, error) {
bc.RLock()
defer bc.RUnlock()
if itm, ok := bc.items[key]; ok {
if itm, ok :=
bc.items[key]; ok {
if itm.isExpire() {
return nil, errors.New("the key is expired")
return nil, ErrKeyExpired
}
return itm.val, nil
}
return nil, errors.New("the key isn't exist")
return nil, ErrKeyNotExist
}
// GetMulti gets caches from memory.
@ -91,7 +93,7 @@ func (bc *MemoryCache) GetMulti(ctx context.Context, keys []string) ([]interface
if len(keysErr) == 0 {
return rc, nil
}
return rc, errors.New(strings.Join(keysErr, "; "))
return rc, berror.Error(MultiGetFailed, strings.Join(keysErr, "; "))
}
// Put puts cache into memory.
@ -108,16 +110,11 @@ func (bc *MemoryCache) Put(ctx context.Context, key string, val interface{}, tim
}
// Delete cache in memory.
// If the key is not found, it will not return error
func (bc *MemoryCache) Delete(ctx context.Context, key string) error {
bc.Lock()
defer bc.Unlock()
if _, ok := bc.items[key]; !ok {
return errors.New("key not exist")
}
delete(bc.items, key)
if _, ok := bc.items[key]; ok {
return errors.New("delete key error")
}
return nil
}
@ -128,24 +125,14 @@ func (bc *MemoryCache) Incr(ctx context.Context, key string) error {
defer bc.Unlock()
itm, ok := bc.items[key]
if !ok {
return errors.New("key not exist")
return ErrKeyNotExist
}
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
}
@ -155,36 +142,14 @@ func (bc *MemoryCache) Decr(ctx context.Context, key string) error {
defer bc.Unlock()
itm, ok := bc.items[key]
if !ok {
return errors.New("key not exist")
return ErrKeyNotExist
}
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
}
@ -209,7 +174,9 @@ func (bc *MemoryCache) ClearAll(context.Context) error {
// StartAndGC starts memory cache. Checks expiration in every clock time.
func (bc *MemoryCache) StartAndGC(config string) error {
var cf map[string]int
json.Unmarshal([]byte(config), &cf)
if err := json.Unmarshal([]byte(config), &cf); err != nil {
return berror.Wrapf(err, InvalidMemoryCacheCfg, "invalid config, please check your input: %s", config)
}
if _, ok := cf["interval"]; !ok {
cf = make(map[string]int)
cf["interval"] = DefaultEvery

17
client/cache/module.go vendored Normal file
View File

@ -0,0 +1,17 @@
// Copyright 2021 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 cache
const moduleName = "cache"

View File

@ -20,8 +20,8 @@
//
// Usage:
// import(
// _ "github.com/beego/beego/v2/cache/redis"
// "github.com/beego/beego/v2/cache"
// _ "github.com/beego/beego/v2/client/cache/redis"
// "github.com/beego/beego/v2/client/cache"
// )
//
// bm, err := cache.NewCache("redis", `{"conn":"127.0.0.1:11211"}`)
@ -32,7 +32,6 @@ package redis
import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
@ -41,6 +40,7 @@ import (
"github.com/gomodule/redigo/redis"
"github.com/beego/beego/v2/client/cache"
"github.com/beego/beego/v2/core/berror"
)
var (
@ -67,15 +67,20 @@ func NewRedisCache() cache.Cache {
}
// Execute the redis commands. args[0] must be the key name
func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
if len(args) < 1 {
return nil, errors.New("missing required arguments")
}
func (rc *Cache) do(commandName string, args ...interface{}) (interface{}, error) {
args[0] = rc.associate(args[0])
c := rc.p.Get()
defer c.Close()
defer func() {
_ = c.Close()
}()
return c.Do(commandName, args...)
reply, err := c.Do(commandName, args...)
if err != nil {
return nil, berror.Wrapf(err, cache.RedisCacheCurdFailed,
"could not execute this command: %s", commandName)
}
return reply, nil
}
// associate with config key.
@ -95,7 +100,9 @@ func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) {
// GetMulti gets cache from redis.
func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
c := rc.p.Get()
defer c.Close()
defer func() {
_ = c.Close()
}()
var args []interface{}
for _, key := range keys {
args = append(args, rc.associate(key))
@ -137,13 +144,16 @@ func (rc *Cache) Decr(ctx context.Context, key string) error {
}
// ClearAll deletes all cache in the redis collection
// Be careful about this method, because it scans all keys and the delete them one by one
func (rc *Cache) ClearAll(context.Context) error {
cachedKeys, err := rc.Scan(rc.key + ":*")
if err != nil {
return err
}
c := rc.p.Get()
defer c.Close()
defer func() {
_ = c.Close()
}()
for _, str := range cachedKeys {
if _, err = c.Do("DEL", str); err != nil {
return err
@ -155,7 +165,9 @@ func (rc *Cache) ClearAll(context.Context) error {
// Scan scans all keys matching a given pattern.
func (rc *Cache) Scan(pattern string) (keys []string, err error) {
c := rc.p.Get()
defer c.Close()
defer func() {
_ = c.Close()
}()
var (
cursor uint64 = 0 // start
result []interface{}
@ -186,13 +198,16 @@ func (rc *Cache) Scan(pattern string) (keys []string, err error) {
// Cached items in redis are stored forever, no garbage collection happens
func (rc *Cache) StartAndGC(config string) error {
var cf map[string]string
json.Unmarshal([]byte(config), &cf)
err := json.Unmarshal([]byte(config), &cf)
if err != nil {
return berror.Wrapf(err, cache.InvalidRedisCacheCfg, "could not unmarshal the config: %s", config)
}
if _, ok := cf["key"]; !ok {
cf["key"] = DefaultKey
}
if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key")
return berror.Wrapf(err, cache.InvalidRedisCacheCfg, "config missing conn field: %s", config)
}
// Format redis://<password>@<host>:<port>
@ -229,9 +244,16 @@ func (rc *Cache) StartAndGC(config string) error {
rc.connectInit()
c := rc.p.Get()
defer c.Close()
defer func() {
_ = c.Close()
}()
return c.Err()
// test connection
if err = c.Err(); err != nil {
return berror.Wrapf(err, cache.InvalidConnection,
"can not connect to remote redis server, please check the connection info and network state: %s", config)
}
return nil
}
// connect to redis.
@ -239,19 +261,20 @@ func (rc *Cache) connectInit() {
dialFunc := func() (c redis.Conn, err error) {
c, err = redis.Dial("tcp", rc.conninfo)
if err != nil {
return nil, err
return nil, berror.Wrapf(err, cache.DialFailed,
"could not dial to remote server: %s ", rc.conninfo)
}
if rc.password != "" {
if _, err := c.Do("AUTH", rc.password); err != nil {
c.Close()
if _, err = c.Do("AUTH", rc.password); err != nil {
_ = c.Close()
return nil, err
}
}
_, selecterr := c.Do("SELECT", rc.dbNum)
if selecterr != nil {
c.Close()
_ = c.Close()
return nil, selecterr
}
return

View File

@ -35,96 +35,74 @@ func TestRedisCache(t *testing.T) {
}
bm, err := cache.NewCache("redis", fmt.Sprintf(`{"conn": "%s"}`, redisAddr))
if err != nil {
t.Error("init err")
}
timeoutDuration := 10 * time.Second
if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err)
}
if res, _ := bm.IsExist(context.Background(), "astaxie"); !res {
t.Error("check err")
}
assert.Nil(t, err)
timeoutDuration := 3 * time.Second
time.Sleep(11 * time.Second)
assert.Nil(t, bm.Put(context.Background(), "astaxie", 1, timeoutDuration))
res, _ := bm.IsExist(context.Background(), "astaxie")
assert.True(t, res)
time.Sleep(5 * time.Second)
res, _ = bm.IsExist(context.Background(), "astaxie")
assert.False(t, res)
assert.Nil(t, bm.Put(context.Background(), "astaxie", 1, timeoutDuration))
if res, _ := bm.IsExist(context.Background(), "astaxie"); res {
t.Error("check err")
}
if err = bm.Put(context.Background(), "astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err)
}
val, _ := bm.Get(context.Background(), "astaxie")
if v, _ := redis.Int(val, err); v != 1 {
t.Error("get err")
}
v, _ := redis.Int(val, err)
assert.Equal(t, 1, v)
if err = bm.Incr(context.Background(), "astaxie"); err != nil {
t.Error("Incr Error", err)
}
assert.Nil(t, bm.Incr(context.Background(), "astaxie"))
val, _ = bm.Get(context.Background(), "astaxie")
if v, _ := redis.Int(val, err); v != 2 {
t.Error("get err")
}
v, _ = redis.Int(val, err)
assert.Equal(t, 2, v)
if err = bm.Decr(context.Background(), "astaxie"); err != nil {
t.Error("Decr Error", err)
}
assert.Nil(t, bm.Decr(context.Background(), "astaxie"))
val, _ = bm.Get(context.Background(), "astaxie")
if v, _ := redis.Int(val, err); v != 1 {
t.Error("get err")
}
v, _ = redis.Int(val, err)
assert.Equal(t, 1, v)
bm.Delete(context.Background(), "astaxie")
if res, _ := bm.IsExist(context.Background(), "astaxie"); res {
t.Error("delete err")
}
res, _ = bm.IsExist(context.Background(), "astaxie")
assert.False(t, res)
assert.Nil(t, bm.Put(context.Background(), "astaxie", "author", timeoutDuration))
// test string
if err = bm.Put(context.Background(), "astaxie", "author", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if res, _ := bm.IsExist(context.Background(), "astaxie"); !res {
t.Error("check err")
}
res, _ = bm.IsExist(context.Background(), "astaxie")
assert.True(t, res)
val, _ = bm.Get(context.Background(), "astaxie")
if v, _ := redis.String(val, err); v != "author" {
t.Error("get err")
}
vs, _ := redis.String(val, err)
assert.Equal(t, "author", vs)
// test GetMulti
if err = bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if res, _ := bm.IsExist(context.Background(), "astaxie1"); !res {
t.Error("check err")
}
assert.Nil(t, bm.Put(context.Background(), "astaxie1", "author1", timeoutDuration))
res, _ = bm.IsExist(context.Background(), "astaxie1")
assert.True(t, res)
vv, _ := bm.GetMulti(context.Background(), []string{"astaxie", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if v, _ := redis.String(vv[0], nil); v != "author" {
t.Error("GetMulti ERROR")
}
if v, _ := redis.String(vv[1], nil); v != "author1" {
t.Error("GetMulti ERROR")
}
assert.Equal(t, 2, len(vv))
vs, _ = redis.String(vv[0], nil)
assert.Equal(t, "author", vs)
vs, _ = redis.String(vv[1], nil)
assert.Equal(t, "author1", vs)
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")
}
assert.Nil(t, vv[0])
vs, _ = redis.String(vv[1], nil)
assert.Equal(t, "author1", vs)
// test clear all
if err = bm.ClearAll(context.Background()); err != nil {
t.Error("clear all err")
}
assert.Nil(t, bm.ClearAll(context.Background()))
}
func TestCache_Scan(t *testing.T) {
@ -137,35 +115,24 @@ func TestCache_Scan(t *testing.T) {
// init
bm, err := cache.NewCache("redis", fmt.Sprintf(`{"conn": "%s"}`, addr))
if err != nil {
t.Error("init err")
}
assert.Nil(t, err)
// insert all
for i := 0; i < 100; i++ {
if err = bm.Put(context.Background(), fmt.Sprintf("astaxie%d", i), fmt.Sprintf("author%d", i), timeoutDuration); err != nil {
t.Error("set Error", err)
}
assert.Nil(t, bm.Put(context.Background(), fmt.Sprintf("astaxie%d", i), fmt.Sprintf("author%d", i), timeoutDuration))
}
time.Sleep(time.Second)
// scan all for the first time
keys, err := bm.(*Cache).Scan(DefaultKey + ":*")
if err != nil {
t.Error("scan Error", err)
}
assert.Nil(t, err)
assert.Equal(t, 100, len(keys), "scan all error")
// clear all
if err = bm.ClearAll(context.Background()); err != nil {
t.Error("clear all err")
}
assert.Nil(t, bm.ClearAll(context.Background()))
// scan all for the second time
keys, err = bm.(*Cache).Scan(DefaultKey + ":*")
if err != nil {
t.Error("scan Error", err)
}
if len(keys) != 0 {
t.Error("scan all err")
}
assert.Nil(t, err)
assert.Equal(t, 0, len(keys))
}

View File

@ -3,7 +3,6 @@ package ssdb
import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
@ -12,6 +11,7 @@ import (
"github.com/ssdb/gossdb/ssdb"
"github.com/beego/beego/v2/client/cache"
"github.com/beego/beego/v2/core/berror"
)
// Cache SSDB adapter
@ -27,31 +27,21 @@ func NewSsdbCache() cache.Cache {
// Get gets a key's value from memcache.
func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return nil, err
}
}
value, err := rc.conn.Get(key)
if err == nil {
return value, nil
}
return nil, err
return nil, berror.Wrapf(err, cache.SsdbCacheCurdFailed, "could not get value, key: %s", key)
}
// GetMulti gets one or keys values from ssdb.
func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) {
size := len(keys)
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
return values, berror.Wrapf(err, cache.SsdbCacheCurdFailed, "multi_get failed, key: %v", keys)
}
resSize := len(res)
@ -63,14 +53,14 @@ func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, er
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"))
keysErr = append(keysErr, fmt.Sprintf("key [%s] error: %s", ki, "key not exist"))
continue
}
values[i] = res[keyIdx[ki]+1]
}
if len(keysErr) != 0 {
return values, fmt.Errorf(strings.Join(keysErr, "; "))
return values, berror.Error(cache.MultiGetFailed, strings.Join(keysErr, "; "))
}
return values, nil
@ -78,26 +68,16 @@ func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, er
// DelMulti deletes one or more keys from memcache
func (rc *Cache) DelMulti(keys []string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Do("multi_del", keys)
return err
return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "multi_del failed: %v", keys)
}
// Put puts value into memcache.
// value: must be of type string
func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
v, ok := val.(string)
if !ok {
return errors.New("value must string")
return berror.Errorf(cache.InvalidSsdbCacheValue, "value must be string: %v", val)
}
var resp []string
var err error
@ -108,57 +88,37 @@ func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout t
resp, err = rc.conn.Do("setx", key, v, ttl)
}
if err != nil {
return err
return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "set or setx failed, key: %s", key)
}
if len(resp) == 2 && resp[0] == "ok" {
return nil
}
return errors.New("bad response")
return berror.Errorf(cache.SsdbBadResponse, "the response from SSDB server is invalid: %v", resp)
}
// Delete deletes a value in memcache.
func (rc *Cache) Delete(ctx context.Context, key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Del(key)
return err
return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "del failed: %s", key)
}
// Incr increases a key's counter.
func (rc *Cache) Incr(ctx context.Context, key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Do("incr", key, 1)
return err
return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "increase failed: %s", key)
}
// Decr decrements a key's counter.
func (rc *Cache) Decr(ctx context.Context, key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Do("incr", key, -1)
return err
return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "decrease failed: %s", key)
}
// IsExist checks if a key exists in memcache.
func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return false, err
}
}
resp, err := rc.conn.Do("exists", key)
if err != nil {
return false, err
return false, berror.Wrapf(err, cache.SsdbCacheCurdFailed, "exists failed: %s", key)
}
if len(resp) == 2 && resp[1] == "1" {
return true, nil
@ -167,13 +127,9 @@ func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) {
}
// ClearAll clears all cached items in memcache.
// ClearAll clears all cached items in ssdb.
// If there are many keys, this method may spent much time.
func (rc *Cache) ClearAll(context.Context) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
keyStart, keyEnd, limit := "", "", 50
resp, err := rc.Scan(keyStart, keyEnd, limit)
for err == nil {
@ -187,21 +143,16 @@ func (rc *Cache) ClearAll(context.Context) error {
}
_, e := rc.conn.Do("multi_del", keys)
if e != nil {
return e
return berror.Wrapf(e, cache.SsdbCacheCurdFailed, "multi_del failed: %v", keys)
}
keyStart = resp[size-2]
resp, err = rc.Scan(keyStart, keyEnd, limit)
}
return err
return berror.Wrap(err, cache.SsdbCacheCurdFailed, "scan failed")
}
// Scan key all cached in ssdb.
func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, error) {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return nil, err
}
}
resp, err := rc.conn.Do("scan", keyStart, keyEnd, limit)
if err != nil {
return nil, err
@ -214,30 +165,36 @@ func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, erro
// If an error occurs during connection, an error is returned
func (rc *Cache) StartAndGC(config string) error {
var cf map[string]string
json.Unmarshal([]byte(config), &cf)
err := json.Unmarshal([]byte(config), &cf)
if err != nil {
return berror.Wrapf(err, cache.InvalidSsdbCacheCfg,
"unmarshal this config failed, it must be a valid json string: %s", config)
}
if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key")
return berror.Wrapf(err, cache.InvalidSsdbCacheCfg,
"Missing conn field: %s", config)
}
rc.conninfo = strings.Split(cf["conn"], ";")
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
return nil
return rc.connectInit()
}
// connect to memcache and keep the connection.
func (rc *Cache) connectInit() error {
conninfoArray := strings.Split(rc.conninfo[0], ":")
if len(conninfoArray) < 2 {
return berror.Errorf(cache.InvalidSsdbCacheCfg, "The value of conn should be host:port: %s", rc.conninfo[0])
}
host := conninfoArray[0]
port, e := strconv.Atoi(conninfoArray[1])
if e != nil {
return e
return berror.Errorf(cache.InvalidSsdbCacheCfg, "Port is invalid. It must be integer, %s", rc.conninfo[0])
}
var err error
rc.conn, err = ssdb.Connect(host, port)
return err
if rc.conn, err = ssdb.Connect(host, port); err != nil {
return berror.Wrapf(err, cache.InvalidConnection,
"could not connect to SSDB, please check your connection info, network and firewall: %s", rc.conninfo[0])
}
return nil
}
func init() {

View File

@ -5,9 +5,12 @@ import (
"fmt"
"os"
"strconv"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/client/cache"
)
@ -19,114 +22,80 @@ func TestSsdbcacheCache(t *testing.T) {
}
ssdb, err := cache.NewCache("ssdb", fmt.Sprintf(`{"conn": "%s"}`, ssdbAddr))
if err != nil {
t.Error("init err")
}
assert.Nil(t, err)
// test put and exist
if res, _ := ssdb.IsExist(context.Background(), "ssdb"); res {
t.Error("check err")
}
timeoutDuration := 10 * time.Second
res, _ := ssdb.IsExist(context.Background(), "ssdb")
assert.False(t, res)
timeoutDuration := 3 * time.Second
// timeoutDuration := -10*time.Second if timeoutDuration is negtive,it means permanent
if err = ssdb.Put(context.Background(), "ssdb", "ssdb", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if res, _ := ssdb.IsExist(context.Background(), "ssdb"); !res {
t.Error("check err")
}
assert.Nil(t, ssdb.Put(context.Background(), "ssdb", "ssdb", timeoutDuration))
res, _ = ssdb.IsExist(context.Background(), "ssdb")
assert.True(t, res)
// Get test done
if err = ssdb.Put(context.Background(), "ssdb", "ssdb", timeoutDuration); err != nil {
t.Error("set Error", err)
}
assert.Nil(t, ssdb.Put(context.Background(), "ssdb", "ssdb", timeoutDuration))
if v, _ := ssdb.Get(context.Background(), "ssdb"); v != "ssdb" {
t.Error("get Error")
}
v, _ := ssdb.Get(context.Background(), "ssdb")
assert.Equal(t, "ssdb", v)
// inc/dec test done
if err = ssdb.Put(context.Background(), "ssdb", "2", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if err = ssdb.Incr(context.Background(), "ssdb"); err != nil {
t.Error("incr Error", err)
}
assert.Nil(t, ssdb.Put(context.Background(), "ssdb", "2", timeoutDuration))
assert.Nil(t, ssdb.Incr(context.Background(), "ssdb"))
val, _ := ssdb.Get(context.Background(), "ssdb")
if v, err := strconv.Atoi(val.(string)); err != nil || v != 3 {
t.Error("get err")
}
v, err = strconv.Atoi(val.(string))
assert.Nil(t, err)
assert.Equal(t, 3, v)
if err = ssdb.Decr(context.Background(), "ssdb"); err != nil {
t.Error("decr error")
}
assert.Nil(t, ssdb.Decr(context.Background(), "ssdb"))
// test del
if err = ssdb.Put(context.Background(), "ssdb", "3", timeoutDuration); err != nil {
t.Error("set Error", err)
}
assert.Nil(t, ssdb.Put(context.Background(), "ssdb", "3", timeoutDuration))
val, _ = ssdb.Get(context.Background(), "ssdb")
if v, err := strconv.Atoi(val.(string)); err != nil || v != 3 {
t.Error("get err")
}
if err := ssdb.Delete(context.Background(), "ssdb"); err == nil {
if e, _ := ssdb.IsExist(context.Background(), "ssdb"); e {
t.Error("delete err")
}
}
v, err = strconv.Atoi(val.(string))
assert.Equal(t, 3, v)
assert.Nil(t, err)
assert.Nil(t, ssdb.Delete(context.Background(), "ssdb"))
assert.Nil(t, ssdb.Put(context.Background(), "ssdb", "ssdb", -10*time.Second))
// test string
if err = ssdb.Put(context.Background(), "ssdb", "ssdb", -10*time.Second); err != nil {
t.Error("set Error", err)
}
if res, _ := ssdb.IsExist(context.Background(), "ssdb"); !res {
t.Error("check err")
}
if v, _ := ssdb.Get(context.Background(), "ssdb"); v.(string) != "ssdb" {
t.Error("get err")
}
res, _ = ssdb.IsExist(context.Background(), "ssdb")
assert.True(t, res)
v, _ = ssdb.Get(context.Background(), "ssdb")
assert.Equal(t, "ssdb", v.(string))
// test GetMulti done
if err = ssdb.Put(context.Background(), "ssdb1", "ssdb1", -10*time.Second); err != nil {
t.Error("set Error", err)
}
if res, _ := ssdb.IsExist(context.Background(), "ssdb1"); !res {
t.Error("check err")
}
assert.Nil(t, ssdb.Put(context.Background(), "ssdb1", "ssdb1", -10*time.Second))
res, _ = ssdb.IsExist(context.Background(), "ssdb1")
assert.True(t, res)
vv, _ := ssdb.GetMulti(context.Background(), []string{"ssdb", "ssdb1"})
if len(vv) != 2 {
t.Error("getmulti error")
}
if vv[0].(string) != "ssdb" {
t.Error("getmulti error")
}
if vv[1].(string) != "ssdb1" {
t.Error("getmulti error")
}
assert.Equal(t, 2, len(vv))
assert.Equal(t, "ssdb", vv[0])
assert.Equal(t, "ssdb1", vv[1])
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")
}
assert.Equal(t, 2, len(vv))
assert.Equal(t, "ssdb", vv[0])
assert.Nil(t, vv[1])
assert.NotNil(t, err)
assert.True(t, strings.Contains(err.Error(), "key not exist"))
// test clear all done
if err = ssdb.ClearAll(context.Background()); err != nil {
t.Error("clear all err")
}
assert.Nil(t, ssdb.ClearAll(context.Background()))
e1, _ := ssdb.IsExist(context.Background(), "ssdb")
e2, _ := ssdb.IsExist(context.Background(), "ssdb1")
if e1 || e2 {
t.Error("check err")
}
assert.False(t, e1)
assert.False(t, e2)
}

View File

@ -8,7 +8,7 @@ httplib is an libs help you to curl remote url.
you can use Get to crawl data.
import "github.com/beego/beego/v2/httplib"
import "github.com/beego/beego/v2/client/httplib"
str, err := httplib.Get("http://beego.me/").String()
if err != nil {
@ -95,4 +95,4 @@ httplib support mutil file upload, use `req.PostFile()`
See godoc for further documentation and examples.
* [godoc.org/github.com/beego/beego/v2/httplib](https://godoc.org/github.com/beego/beego/v2/httplib)
* [godoc.org/github.com/beego/beego/v2/client/httplib](https://godoc.org/github.com/beego/beego/v2/client/httplib)

View File

@ -0,0 +1,126 @@
// 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 (
"github.com/beego/beego/v2/core/berror"
)
var InvalidUrl = berror.DefineCode(4001001, moduleName, "InvalidUrl", `
You pass a invalid url to httplib module. Please check your url, be careful about special character.
`)
var InvalidUrlProtocolVersion = berror.DefineCode(4001002, moduleName, "InvalidUrlProtocolVersion", `
You pass a invalid protocol version. In practice, we use HTTP/1.0, HTTP/1.1, HTTP/1.2
But something like HTTP/3.2 is valid for client, and the major version is 3, minor version is 2.
but you must confirm that server support those abnormal protocol version.
`)
var UnsupportedBodyType = berror.DefineCode(4001003, moduleName, "UnsupportedBodyType", `
You use a invalid data as request body.
For now, we only support type string and byte[].
`)
var InvalidXMLBody = berror.DefineCode(4001004, moduleName, "InvalidXMLBody", `
You pass invalid data which could not be converted to XML documents. In general, if you pass structure, it works well.
Sometimes you got XML document and you want to make it as request body. So you call XMLBody.
If you do this, you got this code. Instead, you should call Header to set Content-type and call Body to set body data.
`)
var InvalidYAMLBody = berror.DefineCode(4001005, moduleName, "InvalidYAMLBody", `
You pass invalid data which could not be converted to YAML documents. In general, if you pass structure, it works well.
Sometimes you got YAML document and you want to make it as request body. So you call YAMLBody.
If you do this, you got this code. Instead, you should call Header to set Content-type and call Body to set body data.
`)
var InvalidJSONBody = berror.DefineCode(4001006, moduleName, "InvalidJSONBody", `
You pass invalid data which could not be converted to JSON documents. In general, if you pass structure, it works well.
Sometimes you got JSON document and you want to make it as request body. So you call JSONBody.
If you do this, you got this code. Instead, you should call Header to set Content-type and call Body to set body data.
`)
// start with 5 --------------------------------------------------------------------------
var CreateFormFileFailed = berror.DefineCode(5001001, moduleName, "CreateFormFileFailed", `
In normal case than handling files with BeegoRequest, you should not see this error code.
Unexpected EOF, invalid characters, bad file descriptor may cause this error.
`)
var ReadFileFailed = berror.DefineCode(5001002, moduleName, "ReadFileFailed", `
There are several cases that cause this error:
1. file not found. Please check the file name;
2. file not found, but file name is correct. If you use relative file path, it's very possible for you to see this code.
make sure that this file is in correct directory which Beego looks for;
3. Beego don't have the privilege to read this file, please change file mode;
`)
var CopyFileFailed = berror.DefineCode(5001003, moduleName, "CopyFileFailed", `
When we try to read file content and then copy it to another writer, and failed.
1. Unexpected EOF;
2. Bad file descriptor;
3. Write conflict;
Please check your file content, and confirm that file is not processed by other process (or by user manually).
`)
var CloseFileFailed = berror.DefineCode(5001004, moduleName, "CloseFileFailed", `
After handling files, Beego try to close file but failed. Usually it was caused by bad file descriptor.
`)
var SendRequestFailed = berror.DefineCode(5001005, moduleName, "SendRequestRetryExhausted", `
Beego send HTTP request, but it failed.
If you config retry times, it means that Beego had retried and failed.
When you got this error, there are vary kind of reason:
1. Network unstable and timeout. In this case, sometimes server has received the request.
2. Server error. Make sure that server works well.
3. The request is invalid, which means that you pass some invalid parameter.
`)
var ReadGzipBodyFailed = berror.DefineCode(5001006, moduleName, "BuildGzipReaderFailed", `
Beego parse gzip-encode body failed. Usually Beego got invalid response.
Please confirm that server returns gzip data.
`)
var CreateFileIfNotExistFailed = berror.DefineCode(5001007, moduleName, "CreateFileIfNotExist", `
Beego want to create file if not exist and failed.
In most cases, it means that Beego doesn't have the privilege to create this file.
Please change file mode to ensure that Beego is able to create files on specific directory.
Or you can run Beego with higher authority.
In some cases, you pass invalid filename. Make sure that the file name is valid on your system.
`)
var UnmarshalJSONResponseToObjectFailed = berror.DefineCode(5001008, moduleName,
"UnmarshalResponseToObjectFailed", `
Beego trying to unmarshal response's body to structure but failed.
Make sure that:
1. You pass valid structure pointer to the function;
2. The body is valid json document
`)
var UnmarshalXMLResponseToObjectFailed = berror.DefineCode(5001009, moduleName,
"UnmarshalResponseToObjectFailed", `
Beego trying to unmarshal response's body to structure but failed.
Make sure that:
1. You pass valid structure pointer to the function;
2. The body is valid XML document
`)
var UnmarshalYAMLResponseToObjectFailed = berror.DefineCode(5001010, moduleName,
"UnmarshalResponseToObjectFailed", `
Beego trying to unmarshal response's body to structure but failed.
Make sure that:
1. You pass valid structure pointer to the function;
2. The body is valid YAML document
`)

View File

@ -0,0 +1,130 @@
// 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 log
import (
"context"
"io"
"net/http"
"net/http/httputil"
"github.com/beego/beego/v2/client/httplib"
"github.com/beego/beego/v2/core/logs"
)
// FilterChainBuilder can build a log filter
type FilterChainBuilder struct {
printableContentTypes []string // only print the body of included mime types of request and response
log func(f interface{}, v ...interface{}) // custom log function
}
// BuilderOption option constructor
type BuilderOption func(*FilterChainBuilder)
type logInfo struct {
req []byte
resp []byte
err error
}
var defaultprintableContentTypes = []string{
"text/plain", "text/xml", "text/html", "text/csv",
"text/calendar", "text/javascript", "text/javascript",
"text/css",
}
// NewFilterChainBuilder initialize a filterChainBuilder, pass options to customize
func NewFilterChainBuilder(opts ...BuilderOption) *FilterChainBuilder {
res := &FilterChainBuilder{
printableContentTypes: defaultprintableContentTypes,
log: logs.Debug,
}
for _, o := range opts {
o(res)
}
return res
}
// WithLog return option constructor modify log function
func WithLog(f func(f interface{}, v ...interface{})) BuilderOption {
return func(h *FilterChainBuilder) {
h.log = f
}
}
// WithprintableContentTypes return option constructor modify printableContentTypes
func WithprintableContentTypes(types []string) BuilderOption {
return func(h *FilterChainBuilder) {
h.printableContentTypes = types
}
}
// FilterChain can print the request after FilterChain processing and response before processsing
func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filter {
return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
info := &logInfo{}
defer info.print(builder.log)
resp, err := next(ctx, req)
info.err = err
contentType := req.GetRequest().Header.Get("Content-Type")
shouldPrintBody := builder.shouldPrintBody(contentType, req.GetRequest().Body)
dump, err := httputil.DumpRequest(req.GetRequest(), shouldPrintBody)
info.req = dump
if err != nil {
logs.Error(err)
}
if resp != nil {
contentType = resp.Header.Get("Content-Type")
shouldPrintBody = builder.shouldPrintBody(contentType, resp.Body)
dump, err = httputil.DumpResponse(resp, shouldPrintBody)
info.resp = dump
if err != nil {
logs.Error(err)
}
}
return resp, err
}
}
func (builder *FilterChainBuilder) shouldPrintBody(contentType string, body io.ReadCloser) bool {
if contains(builder.printableContentTypes, contentType) {
return true
}
if body != nil {
logs.Warn("printableContentTypes do not contain %s, if you want to print request and response body please add it.", contentType)
}
return false
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
func (info *logInfo) print(log func(f interface{}, v ...interface{})) {
log("Request: ====================")
log("%q", info.req)
log("Response: ===================")
log("%q", info.resp)
if info.err != nil {
log("Error: ======================")
log("%q", info.err)
}
}

View File

@ -0,0 +1,62 @@
// 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 log
import (
"context"
"net/http"
"testing"
"time"
"github.com/beego/beego/v2/client/httplib"
"github.com/stretchr/testify/assert"
)
func TestFilterChain(t *testing.T) {
next := func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
time.Sleep(100 * time.Millisecond)
return &http.Response{
StatusCode: 404,
}, nil
}
builder := NewFilterChainBuilder()
filter := builder.FilterChain(next)
req := httplib.Get("https://github.com/notifications?query=repo%3Aastaxie%2Fbeego")
resp, err := filter(context.Background(), req)
assert.NotNil(t, resp)
assert.Nil(t, err)
}
func TestContains(t *testing.T) {
jsonType := "application/json"
cases := []struct {
Name string
Types []string
ContentType string
Expected bool
}{
{"case1", []string{jsonType}, jsonType, true},
{"case2", []string{"text/plain"}, jsonType, false},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
if ans := contains(c.Types, c.ContentType); ans != c.Expected {
t.Fatalf("Types: %v, ContentType: %v, expected %v, but %v got",
c.Types, c.ContentType, c.Expected, ans)
}
})
}
}

View File

@ -21,11 +21,14 @@ import (
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)
@ -50,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)
}

View File

@ -26,14 +26,16 @@ import (
"github.com/beego/beego/v2/client/httplib"
)
func TestFilterChainBuilder_FilterChain(t *testing.T) {
func TestFilterChainBuilderFilterChain(t *testing.T) {
next := func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
time.Sleep(100 * time.Millisecond)
return &http.Response{
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)

View File

@ -25,7 +25,7 @@ import (
"github.com/beego/beego/v2/client/httplib"
)
func TestFilterChainBuilder_FilterChain(t *testing.T) {
func TestFilterChainBuilderFilterChain(t *testing.T) {
next := func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
time.Sleep(100 * time.Millisecond)
return &http.Response{

View 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)),
}
}

View File

@ -0,0 +1,35 @@
// 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)
}

View File

@ -15,7 +15,7 @@
// Package httplib is used as http.Client
// Usage:
//
// import "github.com/beego/beego/v2/httplib"
// import "github.com/beego/beego/v2/client/httplib"
//
// b := httplib.Post("http://beego.me/")
// b.Param("username","astaxie")
@ -40,58 +40,36 @@ import (
"encoding/xml"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net"
"net/http"
"net/http/cookiejar"
"net/http/httputil"
"net/url"
"os"
"path"
"strings"
"sync"
"time"
"gopkg.in/yaml.v2"
"github.com/beego/beego/v2/core/berror"
"github.com/beego/beego/v2/core/logs"
)
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
const contentTypeKey = "Content-Type"
// 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
// TODO add error as return value
// I think if we don't return error
// users are hard to check whether we create Beego request successfully
func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest {
var resp http.Response
u, err := url.Parse(rawurl)
if err != nil {
log.Println("Httplib:", err)
logs.Error("%+v", berror.Wrapf(err, InvalidUrl, "invalid raw url: %s", rawurl))
}
req := http.Request{
URL: u,
@ -136,24 +114,6 @@ 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 {
url string
@ -195,12 +155,6 @@ func (b *BeegoHTTPRequest) SetUserAgent(useragent string) *BeegoHTTPRequest {
return b
}
// Debug sets show debug or not when executing request.
func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest {
b.setting.ShowDebug = isdebug
return b
}
// Retries sets Retries times.
// default is 0 (never retry)
// -1 retry indefinitely (forever)
@ -216,17 +170,6 @@ func (b *BeegoHTTPRequest) RetryDelay(delay time.Duration) *BeegoHTTPRequest {
return b
}
// DumpBody sets the DumbBody field
func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest {
b.setting.DumpBody = isdump
return b
}
// DumpRequest returns the DumpRequest
func (b *BeegoHTTPRequest) DumpRequest() []byte {
return b.dump
}
// SetTimeout sets connect time out and read-write time out for BeegoRequest.
func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHTTPRequest {
b.setting.ConnectTimeout = connectTimeout
@ -253,7 +196,7 @@ func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest {
}
// SetProtocolVersion sets the protocol version for incoming requests.
// Client requests always use HTTP/1.1.
// Client requests always use HTTP/1.1
func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest {
if len(vers) == 0 {
vers = "HTTP/1.1"
@ -264,8 +207,9 @@ func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest {
b.req.Proto = vers
b.req.ProtoMajor = major
b.req.ProtoMinor = minor
return b
}
logs.Error("%+v", berror.Errorf(InvalidUrlProtocolVersion, "invalid protocol: %s", vers))
return b
}
@ -333,16 +277,25 @@ func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest
// Body adds request raw body.
// Supports string and []byte.
// TODO return error if data is invalid
func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
switch t := data.(type) {
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))
default:
logs.Error("%+v", berror.Errorf(UnsupportedBodyType, "unsupported body data type: %s", t))
}
return b
}
@ -352,11 +305,14 @@ func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
if b.req.Body == nil && obj != nil {
byts, err := xml.Marshal(obj)
if err != nil {
return b, err
return b, berror.Wrap(err, InvalidXMLBody, "obj could not be converted to XML data")
}
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")
b.req.Header.Set(contentTypeKey, "application/xml")
}
return b, nil
}
@ -366,11 +322,11 @@ func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error)
if b.req.Body == nil && obj != nil {
byts, err := yaml.Marshal(obj)
if err != nil {
return b, err
return b, berror.Wrap(err, InvalidYAMLBody, "obj could not be converted to YAML data")
}
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
b.req.ContentLength = int64(len(byts))
b.req.Header.Set("Content-Type", "application/x+yaml")
b.req.Header.Set(contentTypeKey, "application/x+yaml")
}
return b, nil
}
@ -380,11 +336,11 @@ func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error)
if b.req.Body == nil && obj != nil {
byts, err := json.Marshal(obj)
if err != nil {
return b, err
return b, berror.Wrap(err, InvalidJSONBody, "obj could not be converted to JSON body")
}
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
b.req.ContentLength = int64(len(byts))
b.req.Header.Set("Content-Type", "application/json")
b.req.Header.Set(contentTypeKey, "application/json")
}
return b, nil
}
@ -404,47 +360,61 @@ func (b *BeegoHTTPRequest) buildURL(paramBody string) {
if (b.req.Method == "POST" || b.req.Method == "PUT" || b.req.Method == "PATCH" || b.req.Method == "DELETE") && b.req.Body == nil {
// with files
if len(b.files) > 0 {
pr, pw := io.Pipe()
bodyWriter := multipart.NewWriter(pw)
go func() {
for formname, filename := range b.files {
fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
if err != nil {
log.Println("Httplib:", err)
}
fh, err := os.Open(filename)
if err != nil {
log.Println("Httplib:", err)
}
// iocopy
_, err = io.Copy(fileWriter, fh)
fh.Close()
if err != nil {
log.Println("Httplib:", err)
}
}
for k, v := range b.params {
for _, vv := range v {
bodyWriter.WriteField(k, vv)
}
}
bodyWriter.Close()
pw.Close()
}()
b.Header("Content-Type", bodyWriter.FormDataContentType())
b.req.Body = ioutil.NopCloser(pr)
b.Header("Transfer-Encoding", "chunked")
b.handleFiles()
return
}
// with params
if len(paramBody) > 0 {
b.Header("Content-Type", "application/x-www-form-urlencoded")
b.Header(contentTypeKey, "application/x-www-form-urlencoded")
b.Body(paramBody)
}
}
}
func (b *BeegoHTTPRequest) handleFiles() {
pr, pw := io.Pipe()
bodyWriter := multipart.NewWriter(pw)
go func() {
for formname, filename := range b.files {
b.handleFileToBody(bodyWriter, formname, filename)
}
for k, v := range b.params {
for _, vv := range v {
_ = bodyWriter.WriteField(k, vv)
}
}
_ = bodyWriter.Close()
_ = pw.Close()
}()
b.Header(contentTypeKey, bodyWriter.FormDataContentType())
b.req.Body = ioutil.NopCloser(pr)
b.Header("Transfer-Encoding", "chunked")
}
func (b *BeegoHTTPRequest) handleFileToBody(bodyWriter *multipart.Writer, formname string, filename string) {
fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
const errFmt = "Httplib: %+v"
if err != nil {
logs.Error(errFmt, berror.Wrapf(err, CreateFormFileFailed,
"could not create form file, formname: %s, filename: %s", formname, filename))
}
fh, err := os.Open(filename)
if err != nil {
logs.Error(errFmt, berror.Wrapf(err, ReadFileFailed, "could not open this file %s", filename))
}
// iocopy
_, err = io.Copy(fileWriter, fh)
if err != nil {
logs.Error(errFmt, berror.Wrapf(err, CopyFileFailed, "could not copy this file %s", filename))
}
err = fh.Close()
if err != nil {
logs.Error(errFmt, berror.Wrapf(err, CloseFileFailed, "could not close this file %s", filename))
}
}
func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) {
if b.resp.StatusCode != 0 {
return b.resp, nil
@ -463,7 +433,6 @@ func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
}
func (b *BeegoHTTPRequest) DoRequestWithCtx(ctx context.Context) (resp *http.Response, err error) {
root := doRequestFilter
if len(b.setting.FilterChains) > 0 {
for i := len(b.setting.FilterChains) - 1; i >= 0; i-- {
@ -473,62 +442,20 @@ func (b *BeegoHTTPRequest) DoRequestWithCtx(ctx context.Context) (resp *http.Res
return root(ctx, b)
}
func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response, err error) {
var paramBody string
if len(b.params) > 0 {
var buf bytes.Buffer
for k, v := range b.params {
for _, vv := range v {
buf.WriteString(url.QueryEscape(k))
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(vv))
buf.WriteByte('&')
}
}
paramBody = buf.String()
paramBody = paramBody[0 : len(paramBody)-1]
}
func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (*http.Response, error) {
paramBody := b.buildParamBody()
b.buildURL(paramBody)
urlParsed, err := url.Parse(b.url)
if err != nil {
return nil, err
return nil, berror.Wrapf(err, InvalidUrl, "parse url failed, the url is %s", b.url)
}
b.req.URL = urlParsed
trans := b.setting.Transport
trans := b.buildTrans()
if trans == nil {
// create default transport
trans = &http.Transport{
TLSClientConfig: b.setting.TLSClientConfig,
Proxy: b.setting.Proxy,
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
MaxIdleConnsPerHost: 100,
}
} else {
// if b.transport is *http.Transport then set the settings.
if t, ok := trans.(*http.Transport); ok {
if t.TLSClientConfig == nil {
t.TLSClientConfig = b.setting.TLSClientConfig
}
if t.Proxy == nil {
t.Proxy = b.setting.Proxy
}
if t.Dial == nil {
t.Dial = TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout)
}
}
}
var jar http.CookieJar
if b.setting.EnableCookie {
if defaultCookieJar == nil {
createDefaultCookie()
}
jar = defaultCookieJar
}
jar := b.buildCookieJar()
client := &http.Client{
Transport: trans,
@ -543,13 +470,10 @@ func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response,
client.CheckRedirect = b.setting.CheckRedirect
}
if b.setting.ShowDebug {
dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody)
if err != nil {
log.Println(err.Error())
}
b.dump = dump
}
return b.sendRequest(client)
}
func (b *BeegoHTTPRequest) sendRequest(client *http.Client) (resp *http.Response, err error) {
// 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.
@ -557,11 +481,68 @@ func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response,
for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ {
resp, err = client.Do(b.req)
if err == nil {
break
return
}
time.Sleep(b.setting.RetryDelay)
}
return resp, err
return nil, berror.Wrap(err, SendRequestFailed, "sending request fail")
}
func (b *BeegoHTTPRequest) buildCookieJar() http.CookieJar {
var jar http.CookieJar
if b.setting.EnableCookie {
if defaultCookieJar == nil {
createDefaultCookie()
}
jar = defaultCookieJar
}
return jar
}
func (b *BeegoHTTPRequest) buildTrans() http.RoundTripper {
trans := b.setting.Transport
if trans == nil {
// create default transport
trans = &http.Transport{
TLSClientConfig: b.setting.TLSClientConfig,
Proxy: b.setting.Proxy,
DialContext: TimeoutDialerCtx(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
MaxIdleConnsPerHost: 100,
}
} else {
// if b.transport is *http.Transport then set the settings.
if t, ok := trans.(*http.Transport); ok {
if t.TLSClientConfig == nil {
t.TLSClientConfig = b.setting.TLSClientConfig
}
if t.Proxy == nil {
t.Proxy = b.setting.Proxy
}
if t.DialContext == nil {
t.DialContext = TimeoutDialerCtx(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout)
}
}
}
return trans
}
func (b *BeegoHTTPRequest) buildParamBody() string {
var paramBody string
if len(b.params) > 0 {
var buf bytes.Buffer
for k, v := range b.params {
for _, vv := range v {
buf.WriteString(url.QueryEscape(k))
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(vv))
buf.WriteByte('&')
}
}
paramBody = buf.String()
paramBody = paramBody[0 : len(paramBody)-1]
}
return paramBody
}
// String returns the body string in response.
@ -592,10 +573,10 @@ func (b *BeegoHTTPRequest) Bytes() ([]byte, error) {
if b.setting.Gzip && resp.Header.Get("Content-Encoding") == "gzip" {
reader, err := gzip.NewReader(resp.Body)
if err != nil {
return nil, err
return nil, berror.Wrap(err, ReadGzipBodyFailed, "building gzip reader failed")
}
b.body, err = ioutil.ReadAll(reader)
return b.body, err
return b.body, berror.Wrap(err, ReadGzipBodyFailed, "reading gzip data failed")
}
b.body, err = ioutil.ReadAll(resp.Body)
return b.body, err
@ -638,7 +619,7 @@ func pathExistAndMkdir(filename string) (err error) {
return nil
}
}
return err
return berror.Wrapf(err, CreateFileIfNotExistFailed, "try to create(if not exist) failed: %s", filename)
}
// ToJSON returns the map that marshals from the body bytes as json in response.
@ -648,7 +629,8 @@ func (b *BeegoHTTPRequest) ToJSON(v interface{}) error {
if err != nil {
return err
}
return json.Unmarshal(data, v)
return berror.Wrap(json.Unmarshal(data, v),
UnmarshalJSONResponseToObjectFailed, "unmarshal json body to object failed.")
}
// ToXML returns the map that marshals from the body bytes as xml in response .
@ -658,7 +640,8 @@ func (b *BeegoHTTPRequest) ToXML(v interface{}) error {
if err != nil {
return err
}
return xml.Unmarshal(data, v)
return berror.Wrap(xml.Unmarshal(data, v),
UnmarshalXMLResponseToObjectFailed, "unmarshal xml body to object failed.")
}
// ToYAML returns the map that marshals from the body bytes as yaml in response .
@ -668,7 +651,8 @@ func (b *BeegoHTTPRequest) ToYAML(v interface{}) error {
if err != nil {
return err
}
return yaml.Unmarshal(data, v)
return berror.Wrap(yaml.Unmarshal(data, v),
UnmarshalYAMLResponseToObjectFailed, "unmarshal yaml body to object failed.")
}
// Response executes request client gets response manually.
@ -677,8 +661,18 @@ func (b *BeegoHTTPRequest) Response() (*http.Response, error) {
}
// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
// Deprecated
// we will move this at the end of 2021
// please use TimeoutDialerCtx
func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
return func(netw, addr string) (net.Conn, error) {
return TimeoutDialerCtx(cTimeout, rwTimeout)(context.Background(), netw, addr)
}
}
func TimeoutDialerCtx(cTimeout time.Duration,
rwTimeout time.Duration) func(ctx context.Context, net, addr string) (c net.Conn, err error) {
return func(ctx context.Context, netw, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(netw, addr, cTimeout)
if err != nil {
return nil, err

View File

@ -15,6 +15,7 @@
package httplib
import (
"bytes"
"context"
"errors"
"io/ioutil"
@ -259,7 +260,7 @@ func TestToFile(t *testing.T) {
}
defer os.Remove(f)
b, err := ioutil.ReadFile(f)
if n := strings.Index(string(b), "origin"); n == -1 {
if n := bytes.Index(b, []byte("origin")); n == -1 {
t.Fatal(err)
}
}
@ -273,7 +274,7 @@ func TestToFileDir(t *testing.T) {
}
defer os.RemoveAll("./files")
b, err := ioutil.ReadFile(f)
if n := strings.Index(string(b), "origin"); n == -1 {
if n := bytes.Index(b, []byte("origin")); n == -1 {
t.Fatal(err)
}
}
@ -300,3 +301,135 @@ func TestAddFilter(t *testing.T) {
r := Get("http://beego.me")
assert.Equal(t, 1, len(req.setting.FilterChains)-len(r.setting.FilterChains))
}
func TestFilterChainOrder(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 NewHttpResponseWithJsonBody("first"), nil
}
})
req.AddFilters(func(next Filter) Filter {
return func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) {
return NewHttpResponseWithJsonBody("second"), nil
}
})
resp, err := req.DoRequestWithCtx(context.Background())
assert.Nil(t, err)
data := make([]byte, 5)
_, _ = resp.Body.Read(data)
assert.Equal(t, "first", string(data))
}
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)
// invalid case but still go request
req = NewBeegoRequest("httpa\ta://beego.me", "GET")
assert.NotNil(t, req)
}
func TestBeegoHTTPRequest_SetProtocolVersion(t *testing.T) {
req := NewBeegoRequest("http://beego.me", "GET")
req.SetProtocolVersion("HTTP/3.10")
assert.Equal(t, "HTTP/3.10", req.req.Proto)
assert.Equal(t, 3, req.req.ProtoMajor)
assert.Equal(t, 10, req.req.ProtoMinor)
req.SetProtocolVersion("")
assert.Equal(t, "HTTP/1.1", req.req.Proto)
assert.Equal(t, 1, req.req.ProtoMajor)
assert.Equal(t, 1, req.req.ProtoMinor)
// invalid case
req.SetProtocolVersion("HTTP/aaa1.1")
assert.Equal(t, "HTTP/1.1", req.req.Proto)
assert.Equal(t, 1, req.req.ProtoMajor)
assert.Equal(t, 1, req.req.ProtoMinor)
}
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)
assert.NotNil(t, req.req.Body)
body = "hhhh, i am test"
req.Body(body)
assert.Equal(t, int64(len(body)), req.req.ContentLength)
assert.NotNil(t, req.req.GetBody)
assert.NotNil(t, req.req.Body)
// invalid case
req.Body(13)
}
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)
}

View File

@ -0,0 +1,78 @@
// 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,
}
}

View File

@ -0,0 +1,176 @@
// 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
}
}

View 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))
}

View 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))
}

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