mirror of https://github.com/cloudreve/Cloudreve
Feat: sign http request / read running mode from config file
parent
fd7b6e33c8
commit
90827b2441
|
@ -1,6 +1,9 @@
|
||||||
[System]
|
[System]
|
||||||
|
Mode = slave
|
||||||
|
Listen = :5000
|
||||||
Debug = true
|
Debug = true
|
||||||
SessionSecret = 23333
|
SessionSecret = 23333
|
||||||
|
SlaveSecret = 1234567891234567123456789123456712345678912345671234567891234567
|
||||||
|
|
||||||
[Thumbnail]
|
[Thumbnail]
|
||||||
MaxWidth = 400
|
MaxWidth = 400
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -4,6 +4,7 @@ go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/DATA-DOG/go-sqlmock v1.3.3
|
github.com/DATA-DOG/go-sqlmock v1.3.3
|
||||||
|
github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7
|
||||||
github.com/duo-labs/webauthn v0.0.0-20191119193225-4bf9a0f776d4
|
github.com/duo-labs/webauthn v0.0.0-20191119193225-4bf9a0f776d4
|
||||||
github.com/fatih/color v1.7.0
|
github.com/fatih/color v1.7.0
|
||||||
github.com/garyburd/redigo v1.6.0
|
github.com/garyburd/redigo v1.6.0
|
||||||
|
|
11
main.go
11
main.go
|
@ -12,20 +12,21 @@ import (
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
conf.Init("conf/conf.ini")
|
conf.Init("conf/conf.ini")
|
||||||
cache.Init()
|
|
||||||
model.Init()
|
|
||||||
|
|
||||||
// Debug 关闭时,切换为生产模式
|
// Debug 关闭时,切换为生产模式
|
||||||
if !conf.SystemConfig.Debug {
|
if !conf.SystemConfig.Debug {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
}
|
}
|
||||||
|
cache.Init()
|
||||||
|
if conf.SystemConfig.Mode == "master" {
|
||||||
|
model.Init()
|
||||||
|
authn.Init()
|
||||||
|
}
|
||||||
auth.Init()
|
auth.Init()
|
||||||
authn.Init()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
api := routers.InitRouter()
|
api := routers.InitRouter()
|
||||||
|
|
||||||
api.Run(":5000")
|
api.Run(conf.SystemConfig.Listen)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,11 @@ package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
model "github.com/HFO4/cloudreve/models"
|
model "github.com/HFO4/cloudreve/models"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/conf"
|
||||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,6 +26,26 @@ type Auth interface {
|
||||||
Check(body string, sign string) error
|
Check(body string, sign string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignRequest 对PUT\POST等复杂HTTP请求签名,如果请求Header中
|
||||||
|
// 包含 X-Policy, 则此请求会被认定为上传请求,只会对URI部分和
|
||||||
|
// Policy部分进行签名。其他请求则会对URI和Body部分进行签名。
|
||||||
|
func SignRequest(r *http.Request, expires int64) *http.Request {
|
||||||
|
var rawSignString string
|
||||||
|
if policy, ok := r.Header["X-Policy"]; ok {
|
||||||
|
rawSignString = serializer.NewRequestSignString(r.URL.Path, policy[0], "")
|
||||||
|
} else {
|
||||||
|
body, _ := ioutil.ReadAll(r.Body)
|
||||||
|
rawSignString = serializer.NewRequestSignString(r.URL.Path, "", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成签名
|
||||||
|
sign := General.Sign(rawSignString, expires)
|
||||||
|
|
||||||
|
// 将签名加到请求Header中
|
||||||
|
r.Header["Authorization"] = []string{"Bearer " + sign}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
// SignURI 对URI进行签名,签名只针对Path部分,query部分不做验证
|
// SignURI 对URI进行签名,签名只针对Path部分,query部分不做验证
|
||||||
func SignURI(uri string, expires int64) (*url.URL, error) {
|
func SignURI(uri string, expires int64) (*url.URL, error) {
|
||||||
base, err := url.Parse(uri)
|
base, err := url.Parse(uri)
|
||||||
|
@ -52,9 +76,18 @@ func CheckURI(url *url.URL) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init 初始化通用鉴权器
|
// Init 初始化通用鉴权器
|
||||||
// TODO slave模式下从配置文件获取
|
// TODO 测试
|
||||||
func Init() {
|
func Init() {
|
||||||
|
var secretKey string
|
||||||
|
if conf.SystemConfig.Mode == "master" {
|
||||||
|
secretKey = model.GetSettingByName("secret_key")
|
||||||
|
} else {
|
||||||
|
secretKey = conf.SystemConfig.SlaveSecret
|
||||||
|
if secretKey == "" {
|
||||||
|
util.Log().Panic("未指定 SlaveSecret,请前往配置文件中指定")
|
||||||
|
}
|
||||||
|
}
|
||||||
General = HMACAuth{
|
General = HMACAuth{
|
||||||
SecretKey: []byte(model.GetSettingByName("secret_key")),
|
SecretKey: []byte(secretKey),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package auth
|
||||||
import (
|
import (
|
||||||
"github.com/HFO4/cloudreve/pkg/util"
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -46,3 +48,25 @@ func TestCheckURI(t *testing.T) {
|
||||||
asserts.Error(CheckURI(sign))
|
asserts.Error(CheckURI(sign))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSignRequest(t *testing.T) {
|
||||||
|
asserts := assert.New(t)
|
||||||
|
General = HMACAuth{SecretKey: []byte(util.RandStringRunes(256))}
|
||||||
|
|
||||||
|
// 非上传请求
|
||||||
|
{
|
||||||
|
req, err := http.NewRequest("POST", "http://127.0.0.1/api/v3/upload", strings.NewReader("I am body."))
|
||||||
|
asserts.NoError(err)
|
||||||
|
req = SignRequest(req, 10)
|
||||||
|
asserts.NotEmpty(req.Header["Authorization"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传请求
|
||||||
|
{
|
||||||
|
req, err := http.NewRequest("POST", "http://127.0.0.1/api/v3/upload", strings.NewReader("I am body."))
|
||||||
|
asserts.NoError(err)
|
||||||
|
req.Header["X-Policy"] = []string{"I am Policy"}
|
||||||
|
req = SignRequest(req, 10)
|
||||||
|
asserts.NotEmpty(req.Header["Authorization"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,8 +18,11 @@ type database struct {
|
||||||
|
|
||||||
// system 系统通用配置
|
// system 系统通用配置
|
||||||
type system struct {
|
type system struct {
|
||||||
|
Mode string `validate:"eq=master|eq=slave"`
|
||||||
|
Listen string `validate:"required"`
|
||||||
Debug bool
|
Debug bool
|
||||||
SessionSecret string
|
SessionSecret string
|
||||||
|
SlaveSecret string `validate:"omitempty,gte=64"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// captcha 验证码配置
|
// captcha 验证码配置
|
||||||
|
@ -84,7 +87,7 @@ func Init(path string) {
|
||||||
for sectionName, sectionStruct := range sections {
|
for sectionName, sectionStruct := range sections {
|
||||||
err = mapSection(sectionName, sectionStruct)
|
err = mapSection(sectionName, sectionStruct)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.Log().Warning("配置文件 %s 分区解析失败: %s", sectionName, err)
|
util.Log().Panic("配置文件 %s 分区解析失败: %s", sectionName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,9 @@ var DatabaseConfig = &database{
|
||||||
|
|
||||||
// SystemConfig 系统公用配置
|
// SystemConfig 系统公用配置
|
||||||
var SystemConfig = &system{
|
var SystemConfig = &system{
|
||||||
Debug: false,
|
Debug: false,
|
||||||
|
Mode: "master",
|
||||||
|
Listen: ":5000",
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaptchaConfig 验证码配置
|
// CaptchaConfig 验证码配置
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package serializer
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
// RequestRawSign 待签名的HTTP请求
|
||||||
|
type RequestRawSign struct {
|
||||||
|
Path string
|
||||||
|
Policy string
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequestSignString 返回JSON格式的待签名字符串
|
||||||
|
// TODO 测试
|
||||||
|
func NewRequestSignString(path, policy, body string) string {
|
||||||
|
req := RequestRawSign{
|
||||||
|
Path: path,
|
||||||
|
Policy: policy,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
res, _ := json.Marshal(req)
|
||||||
|
return string(res)
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package serializer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewRequestSignString(t *testing.T) {
|
||||||
|
asserts := assert.New(t)
|
||||||
|
|
||||||
|
sign := NewRequestSignString("1", "2", "3")
|
||||||
|
asserts.NotEmpty(sign)
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SlaveUpload 从机文件上传
|
||||||
|
func SlaveUpload(c *gin.Context) {
|
||||||
|
|
||||||
|
c.JSON(200, serializer.Response{
|
||||||
|
Code: 0,
|
||||||
|
})
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ import (
|
||||||
func TestListDirectoryRoute(t *testing.T) {
|
func TestListDirectoryRoute(t *testing.T) {
|
||||||
switchToMemDB()
|
switchToMemDB()
|
||||||
asserts := assert.New(t)
|
asserts := assert.New(t)
|
||||||
router := InitRouter()
|
router := InitMasterRouter()
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
// 成功
|
// 成功
|
||||||
|
@ -41,7 +41,7 @@ func TestListDirectoryRoute(t *testing.T) {
|
||||||
func TestLocalFileUpload(t *testing.T) {
|
func TestLocalFileUpload(t *testing.T) {
|
||||||
switchToMemDB()
|
switchToMemDB()
|
||||||
asserts := assert.New(t)
|
asserts := assert.New(t)
|
||||||
router := InitRouter()
|
router := InitMasterRouter()
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
middleware.SessionMock = map[string]interface{}{"user_id": 1}
|
middleware.SessionMock = map[string]interface{}{"user_id": 1}
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ func TestLocalFileUpload(t *testing.T) {
|
||||||
|
|
||||||
func TestObjectDelete(t *testing.T) {
|
func TestObjectDelete(t *testing.T) {
|
||||||
asserts := assert.New(t)
|
asserts := assert.New(t)
|
||||||
router := InitRouter()
|
router := InitMasterRouter()
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
middleware.SessionMock = map[string]interface{}{"user_id": 1}
|
middleware.SessionMock = map[string]interface{}{"user_id": 1}
|
||||||
|
|
||||||
|
|
|
@ -3,42 +3,45 @@ package routers
|
||||||
import (
|
import (
|
||||||
"github.com/HFO4/cloudreve/middleware"
|
"github.com/HFO4/cloudreve/middleware"
|
||||||
"github.com/HFO4/cloudreve/pkg/conf"
|
"github.com/HFO4/cloudreve/pkg/conf"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
"github.com/HFO4/cloudreve/routers/controllers"
|
"github.com/HFO4/cloudreve/routers/controllers"
|
||||||
"github.com/gin-contrib/cors"
|
"github.com/gin-contrib/cors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// initWebDAV 初始化WebDAV相关路由
|
|
||||||
func initWebDAV(group *gin.RouterGroup) {
|
|
||||||
{
|
|
||||||
group.Use(middleware.WebDAVAuth())
|
|
||||||
|
|
||||||
group.Any("/*path", controllers.ServeWebDAV)
|
|
||||||
group.Any("", controllers.ServeWebDAV)
|
|
||||||
group.Handle("PROPFIND", "/*path", controllers.ServeWebDAV)
|
|
||||||
group.Handle("PROPFIND", "", controllers.ServeWebDAV)
|
|
||||||
group.Handle("MKCOL", "/*path", controllers.ServeWebDAV)
|
|
||||||
group.Handle("LOCK", "/*path", controllers.ServeWebDAV)
|
|
||||||
group.Handle("UNLOCK", "/*path", controllers.ServeWebDAV)
|
|
||||||
group.Handle("PROPPATCH", "/*path", controllers.ServeWebDAV)
|
|
||||||
group.Handle("COPY", "/*path", controllers.ServeWebDAV)
|
|
||||||
group.Handle("MOVE", "/*path", controllers.ServeWebDAV)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitRouter 初始化路由
|
// InitRouter 初始化路由
|
||||||
func InitRouter() *gin.Engine {
|
func InitRouter() *gin.Engine {
|
||||||
r := gin.Default()
|
if conf.SystemConfig.Mode == "master" {
|
||||||
v3 := r.Group("/api/v3")
|
util.Log().Info("当前运行模式:Master")
|
||||||
/*
|
return InitMasterRouter()
|
||||||
中间件
|
}
|
||||||
*/
|
util.Log().Info("当前运行模式:Slave")
|
||||||
v3.Use(middleware.Session(conf.SystemConfig.SessionSecret))
|
return InitSlaveRouter()
|
||||||
|
|
||||||
// CORS TODO: 根据配置文件来
|
}
|
||||||
|
|
||||||
|
// InitSlaveRouter 初始化从机模式路由
|
||||||
|
func InitSlaveRouter() *gin.Engine {
|
||||||
|
r := gin.Default()
|
||||||
|
v3 := r.Group("/api/v3/slave")
|
||||||
|
// 跨域相关
|
||||||
|
InitCORS(v3)
|
||||||
|
// 鉴权中间件
|
||||||
|
v3.Use(middleware.SignRequired())
|
||||||
|
|
||||||
|
/*
|
||||||
|
路由
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
v3.POST("upload", controllers.SlaveUpload)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitCORS 初始化跨域配置
|
||||||
|
func InitCORS(group *gin.RouterGroup) {
|
||||||
if conf.CORSConfig.AllowOrigins[0] != "UNSET" || conf.CORSConfig.AllowAllOrigins {
|
if conf.CORSConfig.AllowOrigins[0] != "UNSET" || conf.CORSConfig.AllowAllOrigins {
|
||||||
v3.Use(cors.New(cors.Config{
|
group.Use(cors.New(cors.Config{
|
||||||
AllowOrigins: conf.CORSConfig.AllowOrigins,
|
AllowOrigins: conf.CORSConfig.AllowOrigins,
|
||||||
AllowAllOrigins: conf.CORSConfig.AllowAllOrigins,
|
AllowAllOrigins: conf.CORSConfig.AllowAllOrigins,
|
||||||
AllowMethods: conf.CORSConfig.AllowHeaders,
|
AllowMethods: conf.CORSConfig.AllowHeaders,
|
||||||
|
@ -46,13 +49,29 @@ func InitRouter() *gin.Engine {
|
||||||
AllowCredentials: conf.CORSConfig.AllowCredentials,
|
AllowCredentials: conf.CORSConfig.AllowCredentials,
|
||||||
ExposeHeaders: conf.CORSConfig.ExposeHeaders,
|
ExposeHeaders: conf.CORSConfig.ExposeHeaders,
|
||||||
}))
|
}))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// slave模式下未启动跨域的警告
|
||||||
|
if conf.SystemConfig.Mode == "slave" {
|
||||||
|
util.Log().Warning("当前作为存储端(Slave)运行,但未启用跨域配置,可能会导致 Master 端无法正常上传文件")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitMasterRouter 初始化主机模式路由
|
||||||
|
func InitMasterRouter() *gin.Engine {
|
||||||
|
r := gin.Default()
|
||||||
|
v3 := r.Group("/api/v3")
|
||||||
|
/*
|
||||||
|
中间件
|
||||||
|
*/
|
||||||
|
v3.Use(middleware.Session(conf.SystemConfig.SessionSecret))
|
||||||
|
// 跨域相关
|
||||||
|
InitCORS(v3)
|
||||||
// 测试模式加入Mock助手中间件
|
// 测试模式加入Mock助手中间件
|
||||||
if gin.Mode() == gin.TestMode {
|
if gin.Mode() == gin.TestMode {
|
||||||
v3.Use(middleware.MockHelper())
|
v3.Use(middleware.MockHelper())
|
||||||
}
|
}
|
||||||
|
|
||||||
v3.Use(middleware.CurrentUser())
|
v3.Use(middleware.CurrentUser())
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -166,3 +185,22 @@ func InitRouter() *gin.Engine {
|
||||||
initWebDAV(r.Group("dav"))
|
initWebDAV(r.Group("dav"))
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initWebDAV 初始化WebDAV相关路由
|
||||||
|
func initWebDAV(group *gin.RouterGroup) {
|
||||||
|
{
|
||||||
|
group.Use(middleware.WebDAVAuth())
|
||||||
|
|
||||||
|
group.Any("/*path", controllers.ServeWebDAV)
|
||||||
|
group.Any("", controllers.ServeWebDAV)
|
||||||
|
group.Handle("PROPFIND", "/*path", controllers.ServeWebDAV)
|
||||||
|
group.Handle("PROPFIND", "", controllers.ServeWebDAV)
|
||||||
|
group.Handle("MKCOL", "/*path", controllers.ServeWebDAV)
|
||||||
|
group.Handle("LOCK", "/*path", controllers.ServeWebDAV)
|
||||||
|
group.Handle("UNLOCK", "/*path", controllers.ServeWebDAV)
|
||||||
|
group.Handle("PROPPATCH", "/*path", controllers.ServeWebDAV)
|
||||||
|
group.Handle("COPY", "/*path", controllers.ServeWebDAV)
|
||||||
|
group.Handle("MOVE", "/*path", controllers.ServeWebDAV)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
func TestPing(t *testing.T) {
|
func TestPing(t *testing.T) {
|
||||||
asserts := assert.New(t)
|
asserts := assert.New(t)
|
||||||
router := InitRouter()
|
router := InitMasterRouter()
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest("GET", "/api/v3/site/ping", nil)
|
req, _ := http.NewRequest("GET", "/api/v3/site/ping", nil)
|
||||||
|
@ -23,7 +23,7 @@ func TestPing(t *testing.T) {
|
||||||
|
|
||||||
func TestCaptcha(t *testing.T) {
|
func TestCaptcha(t *testing.T) {
|
||||||
asserts := assert.New(t)
|
asserts := assert.New(t)
|
||||||
router := InitRouter()
|
router := InitMasterRouter()
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
req, _ := http.NewRequest(
|
req, _ := http.NewRequest(
|
||||||
|
@ -43,7 +43,7 @@ func TestCaptcha(t *testing.T) {
|
||||||
// defer mutex.Unlock()
|
// defer mutex.Unlock()
|
||||||
// switchToMockDB()
|
// switchToMockDB()
|
||||||
// asserts := assert.New(t)
|
// asserts := assert.New(t)
|
||||||
// router := InitRouter()
|
// router := InitMasterRouter()
|
||||||
// w := httptest.NewRecorder()
|
// w := httptest.NewRecorder()
|
||||||
//
|
//
|
||||||
// // 创建测试用验证码
|
// // 创建测试用验证码
|
||||||
|
@ -153,7 +153,7 @@ func TestCaptcha(t *testing.T) {
|
||||||
// defer mutex.Unlock()
|
// defer mutex.Unlock()
|
||||||
// switchToMockDB()
|
// switchToMockDB()
|
||||||
// asserts := assert.New(t)
|
// asserts := assert.New(t)
|
||||||
// router := InitRouter()
|
// router := InitMasterRouter()
|
||||||
// w := httptest.NewRecorder()
|
// w := httptest.NewRecorder()
|
||||||
//
|
//
|
||||||
// mock.ExpectQuery("^SELECT (.+)").WillReturnRows(sqlmock.NewRows([]string{"email", "nick", "password", "options"}).
|
// mock.ExpectQuery("^SELECT (.+)").WillReturnRows(sqlmock.NewRows([]string{"email", "nick", "password", "options"}).
|
||||||
|
@ -211,7 +211,7 @@ func TestCaptcha(t *testing.T) {
|
||||||
func TestSiteConfigRoute(t *testing.T) {
|
func TestSiteConfigRoute(t *testing.T) {
|
||||||
switchToMemDB()
|
switchToMemDB()
|
||||||
asserts := assert.New(t)
|
asserts := assert.New(t)
|
||||||
router := InitRouter()
|
router := InitMasterRouter()
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
req, _ := http.NewRequest(
|
req, _ := http.NewRequest(
|
||||||
|
|
Loading…
Reference in New Issue