1762 lines
44 KiB
Go
1762 lines
44 KiB
Go
package blademaster
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"go-common/library/ecode"
|
|
"go-common/library/log"
|
|
"go-common/library/net/http/blademaster/binding"
|
|
"go-common/library/net/http/blademaster/render"
|
|
"go-common/library/net/http/blademaster/tests"
|
|
"go-common/library/net/metadata"
|
|
xtime "go-common/library/time"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
"github.com/pkg/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
var (
|
|
sonce sync.Once
|
|
|
|
once sync.Once
|
|
curEngine atomic.Value
|
|
|
|
ByteContent = []byte(`<html>
|
|
<meta http-equiv="refresh" content="0;uri=http://www.bilibili.com/">
|
|
</html>`)
|
|
CertPEM = `-----BEGIN CERTIFICATE-----
|
|
MIIDJzCCAg8CCQDHIbk1Vp7UbzANBgkqhkiG9w0BAQsFADCBkDELMAkGA1UEBhMC
|
|
Q04xETAPBgNVBAgMCFNoYW5naGFpMREwDwYDVQQHDAhTaGFuZ2hhaTERMA8GA1UE
|
|
CgwIYmlsaWJpbGkxETAPBgNVBAsMCGJpbGliaWxpMRUwEwYDVQQDDAxiaWxpYmls
|
|
aS5jb20xHjAcBgkqhkiG9w0BCQEWD2l0QGJpbGliaWxpLmNvbTAeFw0xNzExMDcw
|
|
NDI1MzJaFw0xODExMDcwNDI1MzJaMBoxGDAWBgNVBAMMD2xvY2FsaG9zdDoxODA4
|
|
MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALNKS9f7sEE+xx2SiIgI
|
|
tfmUVi3HkbWRWiZxZ82gRQnYUvgNsQUenyMH4ukViC7rEqBomscP0YjWhIvunhKe
|
|
RzXqWxZyKF86lL0+n1X+USESWdMQe8nYhCAwTE3JIykBrqjEiYMSI5TLwQrqFUJ9
|
|
nd7EywdlUgolJFO2pbltU9a8stlto9OOLXo5veb30nAW5tnDF5Q1jlKBRpGV4+Wy
|
|
3Tn9V9a6mPaoLQOLQzLWfjIWok0UKdYOWZUwmfboFloI0J0VA8Dn3qr2VGEucUG4
|
|
C5pIzV7/ke0Ymca8H2O1Gt5jrhbieY1XLP7NEoic1xdKTa6TLbReWTUEfqErCD3X
|
|
b28CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAS+tB9PDV0tzFYtVzaWGrqypfnbEC
|
|
l5XoT6kRKn3Pf74MQQDMmuPCNZqf8aznx7+inzf4xeMsM68mkbaWvDkD2W8rSn47
|
|
tnFZNPLzlP5sXrt1RgfEK1azVOqX+PSNqDxFetB/smvsVr0EduX0tcmSNMChzx7I
|
|
Igy/I93TVf/hzu3MubZlCjTIQEvfek/Qc/eei7SQYS3dauSKaLfOwMdan9U2gmSr
|
|
byb4f0vI1wuBSAEceJMrHcPGNgibAUMBMdSOYljYxSgmC0gFW68aD3gdn1Z/KOyd
|
|
r1VaEkBHRoXvVUYPrFDFYO4nP65aZBLWgIn/EtilNektlAZhljSzk6bWXA==
|
|
-----END CERTIFICATE-----
|
|
`
|
|
KeyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
|
MIIEowIBAAKCAQEAs0pL1/uwQT7HHZKIiAi1+ZRWLceRtZFaJnFnzaBFCdhS+A2x
|
|
BR6fIwfi6RWILusSoGiaxw/RiNaEi+6eEp5HNepbFnIoXzqUvT6fVf5RIRJZ0xB7
|
|
ydiEIDBMTckjKQGuqMSJgxIjlMvBCuoVQn2d3sTLB2VSCiUkU7aluW1T1ryy2W2j
|
|
044tejm95vfScBbm2cMXlDWOUoFGkZXj5bLdOf1X1rqY9qgtA4tDMtZ+MhaiTRQp
|
|
1g5ZlTCZ9ugWWgjQnRUDwOfeqvZUYS5xQbgLmkjNXv+R7RiZxrwfY7Ua3mOuFuJ5
|
|
jVcs/s0SiJzXF0pNrpMttF5ZNQR+oSsIPddvbwIDAQABAoIBACLW2hwTNXHIQCr3
|
|
8X31q17fO0vUDvVoVEtKGDC15xR9q8m152MmSygkfSxr2bW8SjdPfFwYL9BWVxVV
|
|
/fOCPDY23xJihoPSv1py08WDEMLLbRb9igB0CWCz4e/vmNx8DjOPVWVZ3f4pBc8Y
|
|
I59zB31lYkqCnsmH5CI8SMHag8MjPmzc7TtoROUbV0rcyoOx/JwrgwuGsC/UP9GY
|
|
Oj021xiLcwP5qD4sBIuXxIPx9zwCDowujjBQ3ViSgcQk92Z1fsPMBvKYUuP2hHcZ
|
|
M/Dnuzz/OyzfP0u3Aq+VpQlXHVmEptU6kfFjK8X3J9tWr7PAvAaFqIs2xL5p6gz8
|
|
q50gfzkCgYEA5Wa89L30UD17s+2RT2Slg5fmSvn2gO0Dz2Dhav/G2UPfbW+22rAC
|
|
iotIfnF4IuA6QQX6tW5L/nVbLYQUtWNxzWsYCQAbRrBQi1BBfh0EOli52ZicuWd3
|
|
6rOqeOzqsXRdyrwVnpfKf1Hdh7Dc++zG920ktXbC33jgGDLmSLxnysMCgYEAyBQf
|
|
du67z3//yOt3z+zl1Tug3t/ioPSWijrlUtsqbRuOWvxRwUtyOB4Tm171j5sWPiJu
|
|
YOisOvjrRI2L9xjdtP4HTAiO4//YiNkCHdFiyHTzMqb1RBbHVQZ6eFmjvdmrgIkG
|
|
4vXt3mZ1kQY264APB99W+BKFtLDPvaz+Hgy9xeUCgYEAhPMcA8OrOm3Hqam/k4HD
|
|
Ixb/0ug3YtT6Zk/BlN+UAQsDDEu4b9meP2klpJJii+PkHxc2C7xWsqyVITXxQobV
|
|
x7WPgnfbVwaMR5FFw69RafdODrwR6Kn8p7tkyxyTkDDewsZqyTUzmMJ7X06zZBX/
|
|
4hoRMlIX8qf9SEkHiZQXmz0CgYAGNamsVUh67iwQHk6/o0iWz5z0jdpIyI6Lh7xq
|
|
T+cHL93BMSeQajkHSNeu8MmKRXPxRbxLQa1mvyb+H66CYsEuxtuPHozgwqYDyUhp
|
|
iIAaXJbXsZrXHCXfm63dYlrUn5bVDGusS5mwV1m6wIif0n+k7OeUF28S5pHr/xx7
|
|
7kVNiQKBgDeXBYkZIRlm0PwtmZcoAU9bVK2m7MnLj6G/MebpzSwSFbbFhxZnnwS9
|
|
EOUtzcssIiFpJiN+ZKFOV+9S9ylYFSYlxGwrWvE3+nmzm8X04tsYhvjO5Q1w1Egs
|
|
U31DFsHpaXEAfBc7ugLimKKKbptJJDPzUfUtXIltCxI9YCzFFRXY
|
|
-----END RSA PRIVATE KEY-----
|
|
`
|
|
)
|
|
|
|
const (
|
|
SockAddr = "localhost:18080"
|
|
AdHocSockAddr = "localhost:18081"
|
|
)
|
|
|
|
func cleanup(dir string) {
|
|
matches, err := filepath.Glob(dir)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, p := range matches {
|
|
os.RemoveAll(p)
|
|
}
|
|
}
|
|
|
|
func setupLogging() {
|
|
dir, err := ioutil.TempDir("", "blademaster")
|
|
defer func() {
|
|
baseDir := path.Dir(dir)
|
|
cleanup(path.Join(baseDir, "blademaster*"))
|
|
}()
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
for l, f := range map[string]string{
|
|
"info.log": "/dev/stdout",
|
|
"warning.log": "/dev/stdout",
|
|
"error.log": "/dev/stderr",
|
|
} {
|
|
p := filepath.Join(dir, l)
|
|
if err := os.Symlink(f, p); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
logConf := &log.Config{
|
|
Dir: dir,
|
|
}
|
|
log.Init(logConf)
|
|
}
|
|
|
|
func init() {
|
|
if os.Getenv("TEST_LOGGING") == "1" {
|
|
setupLogging()
|
|
}
|
|
}
|
|
|
|
func startTestServer() {
|
|
_httpDSN = "tcp://0.0.0.0:18090/?maxlisten=20000&timeout=1s&readtimeout=1s&writetimeout=1s"
|
|
engine := NewServer(nil)
|
|
engine.GET("/test", func(ctx *Context) {
|
|
ctx.JSON("", nil)
|
|
})
|
|
engine.GET("/mirrortest", func(ctx *Context) {
|
|
ctx.JSON(strconv.FormatBool(metadata.Bool(ctx, metadata.Mirror)), nil)
|
|
})
|
|
engine.Start()
|
|
}
|
|
|
|
func TestServer2(t *testing.T) {
|
|
sonce.Do(startTestServer)
|
|
resp, err := http.Get("http://localhost:18090/test")
|
|
if err != nil {
|
|
t.Errorf("HTTPServ: get error(%v)", err)
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Errorf("http.Get get error code:%d", resp.StatusCode)
|
|
}
|
|
resp.Body.Close()
|
|
}
|
|
|
|
func BenchmarkServer2(b *testing.B) {
|
|
once.Do(startServer)
|
|
b.ResetTimer()
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
resp, err := http.Get("http://localhost:18090/test")
|
|
if err != nil {
|
|
b.Errorf("HTTPServ: get error(%v)", err)
|
|
continue
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
b.Errorf("HTTPServ: get error status code:%d", resp.StatusCode)
|
|
}
|
|
resp.Body.Close()
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestDSNParse(t *testing.T) {
|
|
conf := parseDSN("tcp://0.0.0.0:18090/?maxlisten=20000&timeout=1s&readTimeout=1s&writeTimeout=1s")
|
|
assert.Equal(t, ServerConfig{
|
|
Network: "tcp",
|
|
Addr: "0.0.0.0:18090",
|
|
Timeout: xtime.Duration(time.Second),
|
|
ReadTimeout: xtime.Duration(time.Second),
|
|
WriteTimeout: xtime.Duration(time.Second),
|
|
}, *conf)
|
|
conf = parseDSN("unix:///temp/bm.sock?maxlisten=20000&timeout=2s&readTimeout=1s&writeTimeout=1s")
|
|
assert.Equal(t, ServerConfig{
|
|
Network: "unix",
|
|
Addr: "/temp/bm.sock",
|
|
Timeout: xtime.Duration(time.Second * 2),
|
|
ReadTimeout: xtime.Duration(time.Second),
|
|
WriteTimeout: xtime.Duration(time.Second),
|
|
}, *conf)
|
|
}
|
|
|
|
type Timestamp struct {
|
|
Now int64 `json:"now"`
|
|
}
|
|
|
|
type Archive struct {
|
|
Mids []int64 `json:"mids" form:"mids,split" validate:"dive,gt=1,required"`
|
|
Cid int `json:"cid" form:"cid" validate:"max=10,min=1"`
|
|
Title string `json:"title" form:"title" validate:"required"`
|
|
Content string `json:"content" form:"content"`
|
|
}
|
|
|
|
type DevResp struct {
|
|
Code int `json:"code"`
|
|
Data *Device `json:"data"`
|
|
}
|
|
|
|
func uri(base, path string) string {
|
|
return fmt.Sprintf("%s://%s%s", "http", base, path)
|
|
}
|
|
|
|
func now() Timestamp {
|
|
return Timestamp{
|
|
Now: time.Now().Unix(),
|
|
}
|
|
}
|
|
|
|
func setupHandler(engine *Engine) {
|
|
// engine.GET("/", func(ctx *Context) {
|
|
// ctx.Status(200)
|
|
// })
|
|
|
|
engine.Use(CORS(), CSRF(), Mobile())
|
|
// set the global timeout is 2 second
|
|
engine.conf.Timeout = xtime.Duration(time.Second * 2)
|
|
|
|
engine.Ping(func(ctx *Context) {
|
|
ctx.AbortWithStatus(200)
|
|
})
|
|
engine.Register(func(ctx *Context) {
|
|
ctx.JSONMap(map[string]interface{}{
|
|
"region": "aws",
|
|
}, nil)
|
|
})
|
|
engine.HEAD("/head", func(ctx *Context) {
|
|
ctx.Status(200)
|
|
})
|
|
|
|
engine.GET("/get", func(ctx *Context) {
|
|
ctx.String(200, "%s", "pong")
|
|
})
|
|
|
|
engine.POST("/post", func(ctx *Context) {
|
|
ctx.String(200, "%s", "pong")
|
|
})
|
|
|
|
engine.PUT("/put", func(ctx *Context) {
|
|
ctx.String(200, "%s", "pong")
|
|
})
|
|
|
|
engine.DELETE("/delete", func(ctx *Context) {
|
|
ctx.String(200, "%s", "pong")
|
|
})
|
|
|
|
engine.GET("/json", func(ctx *Context) {
|
|
ctx.JSON(now(), nil)
|
|
})
|
|
|
|
engine.GET("/null-json", func(ctx *Context) {
|
|
ctx.JSON(nil, nil)
|
|
})
|
|
|
|
engine.GET("/err", func(ctx *Context) {
|
|
ctx.JSON(now(), errors.New("A error raised from handler func"))
|
|
})
|
|
|
|
engine.GET("/xml", func(ctx *Context) {
|
|
ctx.XML(now(), nil)
|
|
})
|
|
|
|
engine.GET("/bytes", func(ctx *Context) {
|
|
ctx.Bytes(200, "text/html", ByteContent)
|
|
})
|
|
|
|
engine.GET("/bench", func(ctx *Context) {
|
|
ctx.JSON(now(), nil)
|
|
})
|
|
|
|
engine.GET("/sleep5", func(ctx *Context) {
|
|
time.Sleep(time.Second * 30)
|
|
ctx.JSON(now(), nil)
|
|
})
|
|
|
|
engine.GET("/timeout", func(ctx *Context) {
|
|
start := time.Now()
|
|
<-ctx.Done()
|
|
ctx.String(200, "Timeout within %s", time.Since(start))
|
|
})
|
|
|
|
engine.GET("/timeout-from-method-config", func(ctx *Context) {
|
|
start := time.Now()
|
|
<-ctx.Done()
|
|
ctx.String(200, "Timeout within %s", time.Since(start))
|
|
})
|
|
engine.SetMethodConfig("/timeout-from-method-config", &MethodConfig{Timeout: xtime.Duration(time.Second * 3)})
|
|
|
|
engine.GET("/redirect", func(ctx *Context) {
|
|
ctx.Redirect(301, "/bytes")
|
|
})
|
|
|
|
engine.GET("/panic", func(_ *Context) {
|
|
panic("Oupps, Houston, we have a problem")
|
|
})
|
|
|
|
engine.GET("/json-map", func(ctx *Context) {
|
|
ctx.JSONMap(map[string]interface{}{
|
|
"tid": 1,
|
|
}, nil)
|
|
})
|
|
|
|
engine.GET("/bind", func(ctx *Context) {
|
|
v := new(Archive)
|
|
|
|
err := ctx.Bind(v)
|
|
if err != nil {
|
|
return
|
|
}
|
|
ctx.JSON(v, nil)
|
|
})
|
|
|
|
engine.POST("/bindwith", func(ctx *Context) {
|
|
v := new(Archive)
|
|
|
|
err := ctx.BindWith(v, binding.JSON)
|
|
if err != nil {
|
|
return
|
|
}
|
|
ctx.JSON(v, nil)
|
|
})
|
|
|
|
engine.GET("/pb", func(ctx *Context) {
|
|
now := &tests.Time{
|
|
Now: time.Now().Unix(),
|
|
}
|
|
ctx.Protobuf(now, nil)
|
|
})
|
|
|
|
engine.GET("/pb-error", func(ctx *Context) {
|
|
ctx.Protobuf(nil, ecode.RequestErr)
|
|
})
|
|
|
|
engine.GET("/pb-nildata-nilerr", func(ctx *Context) {
|
|
ctx.Protobuf(nil, nil)
|
|
})
|
|
|
|
engine.GET("/pb-data-err", func(ctx *Context) {
|
|
now := &tests.Time{
|
|
Now: time.Now().Unix(),
|
|
}
|
|
ctx.Protobuf(now, ecode.RequestErr)
|
|
})
|
|
|
|
engine.GET("/member-blocked", func(ctx *Context) {
|
|
ctx.JSON(nil, ecode.MemberBlocked)
|
|
})
|
|
|
|
engine.GET("/device", func(ctx *Context) {
|
|
dev, ok := ctx.Get("device")
|
|
if !ok {
|
|
ctx.JSON(nil, ecode.RequestErr)
|
|
return
|
|
}
|
|
ctx.JSON(dev, nil)
|
|
})
|
|
|
|
engine.GET("/device-from-meta", func(ctx *Context) {
|
|
dev, ok := metadata.Value(ctx, "device").(*Device)
|
|
if !ok {
|
|
ctx.JSON(nil, ecode.RequestErr)
|
|
return
|
|
}
|
|
ctx.JSON(dev, nil)
|
|
})
|
|
|
|
engine.GET("/remote-ip", func(ctx *Context) {
|
|
ctx.JSON(metadata.String(ctx, metadata.RemoteIP), nil)
|
|
})
|
|
|
|
m1 := func() HandlerFunc {
|
|
return func(ctx *Context) {
|
|
ctx.Set("m1", "middleware pong")
|
|
ctx.Next()
|
|
}
|
|
}
|
|
m2 := func() func(ctx *Context) {
|
|
return func(ctx *Context) {
|
|
v, isok := ctx.Get("m1")
|
|
if !isok {
|
|
ctx.AbortWithStatus(500)
|
|
return
|
|
}
|
|
ctx.Set("m2", v)
|
|
ctx.Next()
|
|
}
|
|
}
|
|
engine.Use(m1())
|
|
engine.UseFunc(m2())
|
|
engine.GET("/use", func(ctx *Context) {
|
|
v, isok := ctx.Get("m2")
|
|
if !isok {
|
|
ctx.AbortWithStatus(500)
|
|
return
|
|
}
|
|
ctx.String(200, "%s", v.(string))
|
|
})
|
|
|
|
r := engine.Group("/group", func(ctx *Context) {
|
|
})
|
|
r.GET("/get", func(ctx *Context) {
|
|
ctx.String(200, "%s", "pong")
|
|
})
|
|
|
|
rr := r.Group("/abort", func(ctx *Context) {
|
|
ctx.String(200, "%s", "pong")
|
|
ctx.Abort()
|
|
})
|
|
rr.GET("", func(ctx *Context) {
|
|
ctx.String(500, "never get this echo")
|
|
})
|
|
|
|
rrr := r.Group("/test", func(ctx *Context) {
|
|
})
|
|
g1 := func() HandlerFunc {
|
|
return func(ctx *Context) {
|
|
v, isok := ctx.Get("m2")
|
|
if !isok {
|
|
ctx.AbortWithStatus(500)
|
|
return
|
|
}
|
|
ctx.Set("g1", v)
|
|
ctx.Next()
|
|
}
|
|
}
|
|
g2 := func() func(ctx *Context) {
|
|
return func(ctx *Context) {
|
|
v, isok := ctx.Get("g1")
|
|
if !isok {
|
|
ctx.AbortWithStatus(500)
|
|
return
|
|
}
|
|
ctx.Next()
|
|
ctx.String(200, "%s", v.(string))
|
|
}
|
|
}
|
|
rrr.Use(g1()).UseFunc(g2()).GET("/use", func(ctx *Context) {})
|
|
|
|
groupInject := engine.Group("/inject")
|
|
engine.Inject("^/inject", func(ctx *Context) {
|
|
ctx.Set("injected", "injected")
|
|
})
|
|
groupInject.GET("/index", func(ctx *Context) {
|
|
injected, _ := ctx.Get("injected")
|
|
injectedString, _ := injected.(string)
|
|
ctx.String(200, strings.Join([]string{"index", injectedString}, "-"))
|
|
})
|
|
|
|
engine.GET("/group/test/json-status", func(ctx *Context) {
|
|
ctx.JSON(nil, ecode.MemberBlocked)
|
|
})
|
|
engine.GET("/group/test/xml-status", func(ctx *Context) {
|
|
ctx.XML(nil, ecode.MemberBlocked)
|
|
})
|
|
engine.GET("/group/test/proto-status", func(ctx *Context) {
|
|
ctx.Protobuf(nil, ecode.MemberBlocked)
|
|
})
|
|
engine.GET("/group/test/json-map-status", func(ctx *Context) {
|
|
ctx.JSONMap(map[string]interface{}{}, ecode.MemberBlocked)
|
|
})
|
|
}
|
|
|
|
func startServer() {
|
|
e := Default()
|
|
setupHandler(e)
|
|
go e.Run(SockAddr)
|
|
curEngine.Store(e)
|
|
time.Sleep(time.Second)
|
|
}
|
|
|
|
func TestSetupHandler(t *testing.T) {
|
|
engine := Default()
|
|
setupHandler(engine)
|
|
}
|
|
|
|
func TestServeUnix(t *testing.T) {
|
|
engine := Default()
|
|
setupHandler(engine)
|
|
closed := make(chan struct{})
|
|
defer func() {
|
|
if err := engine.Shutdown(context.TODO()); err != nil {
|
|
t.Errorf("Failed to shutdown engine: %s", err)
|
|
}
|
|
<-closed
|
|
}()
|
|
unixs, err := ioutil.TempFile("", "engine.sock")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp file: %s", err)
|
|
}
|
|
|
|
go func() {
|
|
if err := engine.RunUnix(unixs.Name()); err != nil {
|
|
if errors.Cause(err) == http.ErrServerClosed {
|
|
t.Logf("Server stopped due to shutting down command")
|
|
} else {
|
|
t.Errorf("Failed to serve with unix socket: %s", err)
|
|
}
|
|
}
|
|
closed <- struct{}{}
|
|
}()
|
|
// connection test required
|
|
time.Sleep(time.Second)
|
|
}
|
|
|
|
func shutdown() {
|
|
if err := curEngine.Load().(*Engine).Shutdown(context.TODO()); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func TestServeTLS(t *testing.T) {
|
|
engine := New()
|
|
setupHandler(engine)
|
|
closed := make(chan struct{})
|
|
defer func() {
|
|
if err := engine.Shutdown(context.TODO()); err != nil {
|
|
t.Errorf("Failed to shutdown engine: %s", err)
|
|
}
|
|
<-closed
|
|
}()
|
|
|
|
cert, err := ioutil.TempFile("", "cert.pem")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp file: %s", err)
|
|
}
|
|
key, err := ioutil.TempFile("", "key.pem")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp file: %s", err)
|
|
}
|
|
if _, err = cert.WriteString(CertPEM); err != nil {
|
|
t.Fatalf("Failed to write cert file: %s", err)
|
|
}
|
|
if _, err = key.WriteString(KeyPEM); err != nil {
|
|
t.Fatalf("Failed to write key file: %s", err)
|
|
}
|
|
|
|
go func() {
|
|
if rerr := engine.RunTLS(AdHocSockAddr, cert.Name(), key.Name()); rerr != nil {
|
|
if errors.Cause(rerr) == http.ErrServerClosed {
|
|
t.Logf("Server stopped due to shutting down command")
|
|
} else {
|
|
t.Errorf("Failed to serve with tls: %s", rerr)
|
|
}
|
|
}
|
|
closed <- struct{}{}
|
|
}()
|
|
|
|
tr := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
}
|
|
client := &http.Client{Transport: tr}
|
|
resp, err := client.Get(fmt.Sprintf("%s://%s%s", "https", AdHocSockAddr, "/get"))
|
|
if err != nil {
|
|
t.Fatalf("Failed to send https request: %s", err)
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Errorf("http.Get get error code:%d", resp.StatusCode)
|
|
}
|
|
resp.Body.Close()
|
|
}
|
|
|
|
func TestServer(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
resp, err := http.Get(uri(SockAddr, "/get"))
|
|
if err != nil {
|
|
t.Fatalf("BladeMaster: get error(%v)", err)
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Errorf("http.Get get error code:%d", resp.StatusCode)
|
|
}
|
|
resp.Body.Close()
|
|
}
|
|
|
|
func pongValidator(expected string, t *testing.T) func(*http.Response) error {
|
|
return func(resp *http.Response) error {
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("http.Get get error code: %d", resp.StatusCode)
|
|
}
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Logf("ioutil.ReadAll() failed: %s", err)
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
if ps := string(bs); ps != expected {
|
|
return fmt.Errorf("Response body not expected: %s != %s", ps, expected)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func TestPathHandle(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
expected := "pong"
|
|
validateFn := pongValidator(expected, t)
|
|
|
|
testCase := map[string]map[string]func(*http.Response) error{
|
|
"/monitor/ping": {
|
|
"GET": func(resp *http.Response) error {
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
return nil
|
|
},
|
|
},
|
|
|
|
"/register": {
|
|
"GET": func(resp *http.Response) error {
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
assert.NoError(t, err)
|
|
md := make(map[string]interface{})
|
|
assert.NoError(t, json.Unmarshal(bs, &md))
|
|
assert.Equal(t, "aws", md["region"])
|
|
assert.Equal(t, 0, int(md["code"].(float64)))
|
|
assert.Equal(t, "0", md["message"])
|
|
return nil
|
|
},
|
|
},
|
|
|
|
"/head": {
|
|
"HEAD": func(resp *http.Response) error {
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
return nil
|
|
},
|
|
},
|
|
|
|
"/get": {
|
|
"GET": validateFn,
|
|
},
|
|
|
|
"/post": {
|
|
"POST": validateFn,
|
|
},
|
|
|
|
"/put": {
|
|
"PUT": validateFn,
|
|
},
|
|
|
|
"/delete": {
|
|
"DELETE": validateFn,
|
|
},
|
|
|
|
"/not-exist-path": {
|
|
"GET": func(resp *http.Response) error {
|
|
assert.Equal(t, 404, resp.StatusCode)
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
|
|
c := &http.Client{}
|
|
for path := range testCase {
|
|
for method := range testCase[path] {
|
|
validator := testCase[path][method]
|
|
req, err := http.NewRequest(method, uri(SockAddr, path), nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build request: %s", err)
|
|
}
|
|
resp, err := c.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if err := validator(resp); err != nil {
|
|
t.Errorf("Failed to validate request to `%s %s`: %s", method, path, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMiddleWare(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
expected := "middleware pong"
|
|
validateFn := pongValidator(expected, t)
|
|
|
|
testCase := map[string]map[string]func(*http.Response) error{
|
|
"/use": {
|
|
"GET": validateFn,
|
|
},
|
|
|
|
"/group/test/use": {
|
|
"GET": validateFn,
|
|
},
|
|
}
|
|
|
|
c := &http.Client{}
|
|
for path := range testCase {
|
|
for method := range testCase[path] {
|
|
validator := testCase[path][method]
|
|
req, err := http.NewRequest(method, uri(SockAddr, path), nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build request: %s", err)
|
|
}
|
|
resp, err := c.Do(req)
|
|
if err != nil {
|
|
t.Errorf("Failed to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if err := validator(resp); err != nil {
|
|
t.Errorf("Failed to validate request to `%s %s`: %s", method, path, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRouterGroup(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
expected := "pong"
|
|
validateFn := pongValidator(expected, t)
|
|
|
|
testCase := map[string]map[string]func(*http.Response) error{
|
|
"/group/get": {
|
|
"GET": validateFn,
|
|
},
|
|
|
|
"/group/abort": {
|
|
"GET": validateFn,
|
|
},
|
|
}
|
|
|
|
c := &http.Client{}
|
|
for path := range testCase {
|
|
for method := range testCase[path] {
|
|
validator := testCase[path][method]
|
|
req, err := http.NewRequest(method, uri(SockAddr, path), nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build request: %s", err)
|
|
}
|
|
resp, err := c.Do(req)
|
|
if err != nil {
|
|
t.Errorf("Failed to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if err := validator(resp); err != nil {
|
|
t.Errorf("Failed to validate request to `%s %s`: %s", method, path, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMonitor(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
path := "/metrics"
|
|
resp, err := http.Get(uri(SockAddr, path))
|
|
if err != nil {
|
|
t.Fatalf("Failed to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
assert.Equal(t, nil, err)
|
|
assert.False(t, len(bs) <= 0)
|
|
}
|
|
|
|
func TestMetadata(t *testing.T) {
|
|
once.Do(startServer)
|
|
path := "/metadata"
|
|
resp, err := http.Get(uri(SockAddr, path))
|
|
if err != nil {
|
|
t.Fatalf("Failed to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
assert.Equal(t, nil, err)
|
|
assert.False(t, len(bs) <= 0)
|
|
}
|
|
func TestRender(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
bodyTestCase := map[string]map[string]func([]byte) error{
|
|
"/json": {
|
|
"GET": func(bs []byte) error {
|
|
var data struct {
|
|
Code int `json:"code"`
|
|
TTL int `json:"ttl"`
|
|
Ts *Timestamp `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(bs, &data); err != nil {
|
|
t.Logf("json.Unmarshal() failed: %s", err)
|
|
}
|
|
assert.Zero(t, data.Code)
|
|
assert.NotZero(t, data.Ts.Now)
|
|
assert.NotZero(t, data.TTL)
|
|
return nil
|
|
},
|
|
},
|
|
|
|
"/null-json": {
|
|
"GET": func(bs []byte) error {
|
|
res := map[string]interface{}{}
|
|
if err := json.Unmarshal(bs, &res); err != nil {
|
|
t.Logf("json.Unmarshal() failed: %s", err)
|
|
}
|
|
if _, ok := res["data"]; ok {
|
|
t.Errorf("Field `data` should be omitted")
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
|
|
"/xml": {
|
|
"GET": func(bs []byte) error {
|
|
var ts Timestamp
|
|
if err := xml.Unmarshal(bs, &ts); err != nil {
|
|
t.Logf("xml.Unmarshal() failed: %s", err)
|
|
}
|
|
if ts.Now == 0 {
|
|
return fmt.Errorf("Timestamp.Now field cannot be zero: %+v", ts)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
|
|
"/bytes": {
|
|
"GET": func(bs []byte) error {
|
|
respStr := string(bs)
|
|
oriContent := string(ByteContent)
|
|
if respStr != oriContent {
|
|
return fmt.Errorf("Bytes response not expected: %s != %s", respStr, oriContent)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
|
|
"/json-map": {
|
|
"GET": func(bs []byte) error {
|
|
var data struct {
|
|
Code int `json:"code"`
|
|
Tid int `json:"tid"`
|
|
Message string `json:"message"`
|
|
}
|
|
if err := json.Unmarshal(bs, &data); err != nil {
|
|
t.Logf("json.Unmarshal() failed: %s", err)
|
|
}
|
|
if data.Tid != 1 || data.Code != 0 || data.Message != "0" {
|
|
return fmt.Errorf("Invalid respones: %+v", data)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
contentTypeTest := map[string]string{
|
|
"/json": "application/json; charset=utf-8",
|
|
"/xml": "application/xml; charset=utf-8",
|
|
"/bytes": "text/html",
|
|
"/json-map": "application/json; charset=utf-8",
|
|
"/null-json": "application/json; charset=utf-8",
|
|
}
|
|
|
|
c := &http.Client{}
|
|
for path := range bodyTestCase {
|
|
for method := range bodyTestCase[path] {
|
|
validator := bodyTestCase[path][method]
|
|
expHeader := contentTypeTest[path]
|
|
req, err := http.NewRequest(method, uri(SockAddr, path), nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build request: %s", err)
|
|
}
|
|
resp, err := c.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Errorf("http.%s(%s) get error code: %d", method, path, resp.StatusCode)
|
|
}
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Errorf("ioutil.ReadAll() failed: %s", err)
|
|
}
|
|
if ct := resp.Header.Get("Content-Type"); ct != expHeader {
|
|
t.Errorf("Unexpected content-type header on path `%s`: expected %s got %s", path, expHeader, ct)
|
|
}
|
|
if err := validator(bs); err != nil {
|
|
t.Errorf("Failed to validate request to `%s %s`: %s", method, path, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRedirect(t *testing.T) {
|
|
c := &http.Client{
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
req.Header["Referer"] = []string{"http://www.bilibili.com/"}
|
|
return nil
|
|
},
|
|
}
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/redirect"), nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build request: %s", err)
|
|
}
|
|
|
|
resp, err := c.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Errorf("Redirect test filed: %d", resp.StatusCode)
|
|
}
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Errorf("ioutil.ReadAll() failed: %s", err)
|
|
}
|
|
respStr := string(bs)
|
|
oriContent := string(ByteContent)
|
|
if respStr != oriContent {
|
|
t.Errorf("Bytes response not expected: %s != %s", respStr, oriContent)
|
|
}
|
|
}
|
|
|
|
func TestCORSPreflight(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
origin := "ccc.bilibili.com"
|
|
c := &http.Client{}
|
|
req, err := http.NewRequest("OPTIONS", uri(SockAddr, "/get"), nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build request: %s", err)
|
|
}
|
|
req.Header.Set("Referer", "http://www.bilibili.com/")
|
|
req.Header.Set("Origin", origin)
|
|
resp, err := c.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Errorf("CORS preflight request status code unexpected: %d", resp.StatusCode)
|
|
}
|
|
|
|
preflightHeaders := map[string]string{
|
|
"Access-Control-Allow-Origin": origin,
|
|
"Access-Control-Allow-Credentials": "true",
|
|
"Access-Control-Allow-Methods": "GET,POST",
|
|
"Access-Control-Allow-Headers": "Origin,Content-Length,Content-Type",
|
|
}
|
|
for k, v := range preflightHeaders {
|
|
if hv := resp.Header.Get(k); hv != v {
|
|
t.Errorf("Unexpected header %s: %s != %s", k, hv, v)
|
|
}
|
|
}
|
|
|
|
varys := map[string]int{
|
|
"Origin": 0,
|
|
"Access-Control-Request-Method": 0,
|
|
"Access-Control-Request-Headers": 0,
|
|
}
|
|
reqVarys := make(map[string]int)
|
|
rv := resp.Header["Vary"]
|
|
for _, v := range rv {
|
|
reqVarys[v] = 0
|
|
}
|
|
for v := range varys {
|
|
if _, ok := reqVarys[v]; !ok {
|
|
t.Errorf("%s is missed in Vary", v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCORSNormal(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
origin := "ccc.bilibili.com"
|
|
c := &http.Client{}
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/get"), nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build request: %s", err)
|
|
}
|
|
req.Header.Set("Referer", "http://www.bilibili.com/")
|
|
req.Header.Set("Origin", origin)
|
|
resp, err := c.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
normalHeaders := map[string]string{
|
|
"Access-Control-Allow-Origin": origin,
|
|
"Access-Control-Allow-Credentials": "true",
|
|
"Access-Control-Allow-Methods": "GET,POST",
|
|
}
|
|
for k, v := range normalHeaders {
|
|
if hv := resp.Header.Get(k); hv != v {
|
|
t.Errorf("Unexpected header %s: %s != %s", k, hv, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestJSONP(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
origin := "ccc.bilibili.com"
|
|
r := regexp.MustCompile(`onsuccess\((.*)\)`)
|
|
c := &http.Client{}
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/json?cross_domain=true&jsonp=jsonp&callback=onsuccess"), nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build request: %s", err)
|
|
}
|
|
req.Header.Set("Referer", "http://www.bilibili.com/")
|
|
req.Header.Set("Origin", origin)
|
|
resp, err := c.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Errorf("ioutil.ReadAll() failed: %s", err)
|
|
}
|
|
matched := r.FindSubmatch(bs)
|
|
if matched == nil {
|
|
t.Errorf("Response not matched pattern: %s", r)
|
|
}
|
|
|
|
var data struct {
|
|
Code int `json:"code"`
|
|
Ts *Timestamp `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(matched[1], &data); err != nil {
|
|
t.Logf("json.Unmarshal() failed: %s", err)
|
|
}
|
|
if data.Code != 0 || data.Ts.Now == 0 {
|
|
t.Errorf("Request should succeed but got blocked with code(%d) or Timestamp.Now field cannot be zero: %+v",
|
|
data.Code, data)
|
|
}
|
|
}
|
|
|
|
func TestCORSFailed(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
c := &http.Client{}
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/json?cross_domain=true&jsonp=jsonp&callback=onsuccess"), nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build request: %s", err)
|
|
}
|
|
|
|
req.Header.Set("Referer", "http://www.bilibili2.com/")
|
|
req.Header.Set("Origin", "ccc.bilibili2.com")
|
|
resp, err := c.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode == 200 {
|
|
t.Errorf("This request should be blocked, but got status %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestCSRF(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
allowed := []string{
|
|
"http://www.bilibili.com/",
|
|
"http://www.biligame.com/",
|
|
"http://www.im9.com/",
|
|
"http://www.acg.tv/",
|
|
"http://www.hdslb.com/",
|
|
"http://www.bilibili.co/",
|
|
// should match by appid
|
|
"https://servicewechat.com/wx7564fd5313d24844/devtools/page-frame.html",
|
|
"http://servicewechat.com/wx7564fd5313d24844/",
|
|
"http://servicewechat.com/wx7564fd5313d24844",
|
|
"http://servicewechat.com/wx618ca8c24bf06c33",
|
|
|
|
// "http://bilibili.co/",
|
|
// "http://hdslb.com/",
|
|
// "http://acg.tv/",
|
|
// "http://im9.com/",
|
|
// "http://biligame.com/",
|
|
// "http://bilibili.com/",
|
|
}
|
|
notAllowed := []string{
|
|
"http://www.bilibili2.com/",
|
|
"http://www.biligame2.com/",
|
|
"http://www.im92.com/",
|
|
"http://www.acg2.tv/",
|
|
"http://www.hdslb2.com/",
|
|
"http://servicewechat.com/",
|
|
"http://servicewechat.com/wx7564fd5313d24842",
|
|
"http://servicewechat.com/wx618ca8c24bf06c34",
|
|
}
|
|
|
|
c := &http.Client{}
|
|
for _, r := range allowed {
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/json"), nil)
|
|
assert.Nil(t, err)
|
|
|
|
req.Header.Set("Referer", r)
|
|
resp, err := c.Do(req)
|
|
assert.Nil(t, err)
|
|
resp.Body.Close()
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
}
|
|
|
|
for _, r := range notAllowed {
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/json"), nil)
|
|
assert.Nil(t, err)
|
|
|
|
req.Header.Set("Referer", r)
|
|
resp, err := c.Do(req)
|
|
assert.Nil(t, err)
|
|
resp.Body.Close()
|
|
assert.Equal(t, 403, resp.StatusCode)
|
|
}
|
|
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/json"), nil)
|
|
assert.Nil(t, err)
|
|
resp, err := c.Do(req)
|
|
assert.Nil(t, err)
|
|
resp.Body.Close()
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
|
|
req, err = http.NewRequest("GET", uri(SockAddr, "/json?callback=123&jsonp=jsonp"), nil)
|
|
assert.Nil(t, err)
|
|
resp, err = c.Do(req)
|
|
assert.Nil(t, err)
|
|
resp.Body.Close()
|
|
assert.Equal(t, 403, resp.StatusCode)
|
|
|
|
req, err = http.NewRequest("GET", uri(SockAddr, "/json?cross_domain=123"), nil)
|
|
assert.Nil(t, err)
|
|
resp, err = c.Do(req)
|
|
assert.Nil(t, err)
|
|
resp.Body.Close()
|
|
assert.Equal(t, 403, resp.StatusCode)
|
|
}
|
|
|
|
func TestOnError(t *testing.T) {
|
|
once.Do(startServer)
|
|
c := &http.Client{}
|
|
resp, err := c.Get(uri(SockAddr, "/err"))
|
|
if err != nil {
|
|
t.Fatalf("Failed to send request: %s", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Errorf("ioutil.ReadAll() failed: %s", err)
|
|
}
|
|
var data struct {
|
|
Code int `json:"code"`
|
|
Ts *Timestamp `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(bs, &data); err != nil {
|
|
t.Logf("json.Unmarshal() failed: %s", err)
|
|
}
|
|
if data.Code != ecode.ServerErr.Code() {
|
|
t.Errorf("Error code is not expected: %d != %d", data.Code, ecode.ServerErr.Code())
|
|
}
|
|
}
|
|
|
|
func TestRecovery(t *testing.T) {
|
|
once.Do(startServer)
|
|
c := &http.Client{}
|
|
resp, err := c.Get(uri(SockAddr, "/panic"))
|
|
if err != nil {
|
|
t.Fatalf("Failed to send request: %s", err)
|
|
}
|
|
if resp.StatusCode != http.StatusInternalServerError {
|
|
t.Fatalf("Expect status code 500 get %d, maybe recovery not working as expected", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestGlobalTimeout(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
t.Run("Should timeout by default", func(t *testing.T) {
|
|
c := &http.Client{}
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/timeout"), nil)
|
|
assert.Nil(t, err)
|
|
start := time.Now()
|
|
resp, err := c.Do(req)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
assert.InDelta(t, float64(time.Second*2), float64(time.Since(start)), float64(time.Second))
|
|
})
|
|
t.Run("Should timeout by delivered", func(t *testing.T) {
|
|
c := &http.Client{}
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/timeout"), nil)
|
|
assert.Nil(t, err)
|
|
td := int64(time.Second / time.Millisecond)
|
|
req.Header.Set(_httpHeaderTimeout, strconv.FormatInt(td, 10))
|
|
start := time.Now()
|
|
resp, err := c.Do(req)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
assert.InDelta(t, float64(time.Second), float64(time.Since(start)), float64(time.Second))
|
|
})
|
|
t.Run("Should not timeout by delivered", func(t *testing.T) {
|
|
c := &http.Client{}
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/timeout"), nil)
|
|
assert.Nil(t, err)
|
|
td := int64(time.Second * 10 / time.Millisecond)
|
|
req.Header.Set(_httpHeaderTimeout, strconv.FormatInt(td, 10))
|
|
start := time.Now()
|
|
resp, err := c.Do(req)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
assert.InDelta(t, float64(time.Second*2), float64(time.Since(start)), float64(time.Second))
|
|
})
|
|
t.Run("Should timeout by method config", func(t *testing.T) {
|
|
c := &http.Client{}
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/timeout-from-method-config"), nil)
|
|
assert.Nil(t, err)
|
|
start := time.Now()
|
|
resp, err := c.Do(req)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
assert.InDelta(t, float64(time.Second*3), float64(time.Since(start)), float64(time.Second))
|
|
})
|
|
}
|
|
|
|
func TestServerConfigReload(t *testing.T) {
|
|
engine := Default()
|
|
setupHandler(engine)
|
|
|
|
engine.lock.RLock()
|
|
startTm := engine.conf.Timeout
|
|
engine.lock.RUnlock()
|
|
toTm := startTm * 5
|
|
|
|
wg := &sync.WaitGroup{}
|
|
closed := make(chan struct{})
|
|
go func() {
|
|
if err := engine.Run(AdHocSockAddr); err != nil {
|
|
if errors.Cause(err) == http.ErrServerClosed {
|
|
t.Logf("Server stopped due to shutting down command")
|
|
} else {
|
|
t.Errorf("Failed to serve with tls: %s", err)
|
|
}
|
|
}
|
|
closed <- struct{}{}
|
|
}()
|
|
time.Sleep(time.Second)
|
|
|
|
clientNum := 20
|
|
for i := 0; i < clientNum; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
c := &http.Client{}
|
|
resp, err := c.Get(uri(AdHocSockAddr, "/get"))
|
|
if err != nil {
|
|
assert.Nil(t, err)
|
|
return
|
|
}
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
}()
|
|
}
|
|
for i := 2; i < clientNum; i++ {
|
|
wg.Add(1)
|
|
conf := &ServerConfig{
|
|
Timeout: toTm,
|
|
}
|
|
if i%2 == 0 {
|
|
conf.Timeout = 0
|
|
}
|
|
go func() {
|
|
defer wg.Done()
|
|
err := engine.SetConfig(conf)
|
|
if conf.Timeout <= 0 {
|
|
assert.NotNil(t, err)
|
|
return
|
|
}
|
|
assert.Nil(t, err)
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
engine.lock.RLock()
|
|
endTm := engine.conf.Timeout
|
|
engine.lock.RUnlock()
|
|
|
|
assert.NotEqual(t, toTm, startTm)
|
|
assert.Equal(t, toTm, endTm)
|
|
|
|
if err := engine.Shutdown(context.TODO()); err != nil {
|
|
assert.Nil(t, err)
|
|
}
|
|
<-closed
|
|
}
|
|
|
|
func TestGracefulShutdown(t *testing.T) {
|
|
engine := Default()
|
|
setupHandler(engine)
|
|
|
|
closed := make(chan struct{})
|
|
go func() {
|
|
if err := engine.Run(AdHocSockAddr); err != nil {
|
|
if errors.Cause(err) == http.ErrServerClosed {
|
|
t.Logf("Server stopped due to shutting down command")
|
|
} else {
|
|
t.Errorf("Failed to serve with tls: %s", err)
|
|
}
|
|
}
|
|
closed <- struct{}{}
|
|
}()
|
|
time.Sleep(time.Second)
|
|
|
|
clientNum := 10
|
|
wg := &sync.WaitGroup{}
|
|
for i := 0; i < clientNum; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
c := &http.Client{}
|
|
resp, err := c.Get(uri(AdHocSockAddr, "/sleep5"))
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Errorf("Unexpected status code: %d", resp.StatusCode)
|
|
return
|
|
}
|
|
t.Logf("Request finished at: %v", time.Now())
|
|
}()
|
|
}
|
|
time.Sleep(time.Second)
|
|
|
|
t.Logf("Invoke Shutdown method at: %v", time.Now())
|
|
if err := engine.Shutdown(context.TODO()); err != nil {
|
|
t.Fatalf("Failed to shutdown engine: %s", err)
|
|
}
|
|
wg.Wait()
|
|
<-closed
|
|
}
|
|
|
|
func TestProtobuf(t *testing.T) {
|
|
once.Do(startServer)
|
|
c := &http.Client{}
|
|
|
|
t.Run("On-Success", func(t *testing.T) {
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/pb"), nil)
|
|
assert.Nil(t, err)
|
|
|
|
resp, err := c.Do(req)
|
|
assert.Nil(t, err)
|
|
defer resp.Body.Close()
|
|
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
|
assert.Equal(t, resp.Header.Get("Content-Type"), "application/x-protobuf")
|
|
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
assert.Nil(t, err)
|
|
|
|
d := &render.PB{}
|
|
err = proto.Unmarshal(bs, d)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int(d.Code), 0)
|
|
assert.NotNil(t, d.Data)
|
|
|
|
tt := &tests.Time{}
|
|
err = proto.Unmarshal(d.Data.Value, tt)
|
|
assert.Nil(t, err)
|
|
assert.NotZero(t, tt.Now)
|
|
})
|
|
t.Run("On-Error", func(t *testing.T) {
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/pb-error"), nil)
|
|
assert.Nil(t, err)
|
|
|
|
resp, err := c.Do(req)
|
|
assert.Nil(t, err)
|
|
defer resp.Body.Close()
|
|
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
|
assert.Equal(t, resp.Header.Get("Content-Type"), "application/x-protobuf")
|
|
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
assert.Nil(t, err)
|
|
|
|
d := &render.PB{}
|
|
err = proto.Unmarshal(bs, d)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int(d.Code), ecode.RequestErr.Code())
|
|
})
|
|
|
|
t.Run("On-NilData-NilErr", func(t *testing.T) {
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/pb-nildata-nilerr"), nil)
|
|
assert.Nil(t, err)
|
|
|
|
resp, err := c.Do(req)
|
|
assert.Nil(t, err)
|
|
defer resp.Body.Close()
|
|
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
|
assert.Equal(t, resp.Header.Get("Content-Type"), "application/x-protobuf")
|
|
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
assert.Nil(t, err)
|
|
|
|
d := &render.PB{}
|
|
err = proto.Unmarshal(bs, d)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int(d.Code), 0)
|
|
})
|
|
|
|
t.Run("On-Data-Err", func(t *testing.T) {
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/pb-data-err"), nil)
|
|
assert.Nil(t, err)
|
|
|
|
resp, err := c.Do(req)
|
|
assert.Nil(t, err)
|
|
defer resp.Body.Close()
|
|
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
|
assert.Equal(t, resp.Header.Get("Content-Type"), "application/x-protobuf")
|
|
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
assert.Nil(t, err)
|
|
|
|
d := &render.PB{}
|
|
err = proto.Unmarshal(bs, d)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int(d.Code), ecode.RequestErr.Code())
|
|
})
|
|
}
|
|
|
|
func TestBind(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
c := &http.Client{}
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/bind?mids=2,3,4&title=hello&content=world&cid=8"), nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build request: %s", err)
|
|
}
|
|
|
|
resp, err := c.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to send request: %s", err)
|
|
}
|
|
assert.Equal(t, resp.StatusCode, 200)
|
|
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
assert.NoError(t, err)
|
|
resp.Body.Close()
|
|
|
|
var p struct {
|
|
Code int `json:"code"`
|
|
Data *Archive `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(bs, &p); err != nil {
|
|
t.Fatalf("Failed to json.Unmarshal: %v", resp.StatusCode)
|
|
}
|
|
assert.Equal(t, p.Code, 0)
|
|
assert.Equal(t, []int64{2, 3, 4}, p.Data.Mids)
|
|
assert.Equal(t, "hello", p.Data.Title)
|
|
assert.Equal(t, "world", p.Data.Content)
|
|
assert.Equal(t, 8, p.Data.Cid)
|
|
}
|
|
|
|
func TestBindWith(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
a := &Archive{
|
|
Mids: []int64{2, 3, 4},
|
|
Title: "hello",
|
|
Content: "world",
|
|
Cid: 8,
|
|
}
|
|
d, err := json.Marshal(a)
|
|
if err != nil {
|
|
t.Fatalf("Failed to json.Marshal: %v", err)
|
|
}
|
|
|
|
c := &http.Client{}
|
|
req, err := http.NewRequest("POST", uri(SockAddr, "/bindwith"), bytes.NewBuffer(d))
|
|
if err != nil {
|
|
t.Fatalf("Failed to build request: %s", err)
|
|
}
|
|
|
|
resp, err := c.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to send request: %s", err)
|
|
}
|
|
assert.Equal(t, resp.StatusCode, 200)
|
|
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
assert.NoError(t, err)
|
|
resp.Body.Close()
|
|
|
|
var p struct {
|
|
Code int `json:"code"`
|
|
Data *Archive `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(bs, &p); err != nil {
|
|
t.Fatalf("Failed to json.Unmarshal: %v", resp.StatusCode)
|
|
}
|
|
assert.Equal(t, p.Code, 0)
|
|
assert.Equal(t, a.Mids, p.Data.Mids)
|
|
assert.Equal(t, a.Title, p.Data.Title)
|
|
assert.Equal(t, a.Content, p.Data.Content)
|
|
assert.Equal(t, a.Cid, p.Data.Cid)
|
|
}
|
|
|
|
func TestMethodNotAllowed(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
resp, err := http.Get(uri(SockAddr, "/post"))
|
|
assert.NoError(t, err)
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, 405, resp.StatusCode)
|
|
assert.Equal(t, http.StatusText(405), strings.TrimSpace(string(bs)))
|
|
}
|
|
|
|
func TestDevice(t *testing.T) {
|
|
testDevice(t, "/device")
|
|
}
|
|
|
|
func TestDeviceMeta(t *testing.T) {
|
|
testDevice(t, "/device-from-meta")
|
|
}
|
|
|
|
func testDevice(t *testing.T, path string) {
|
|
once.Do(startServer)
|
|
|
|
req, err := http.NewRequest("GET", uri(SockAddr, path), nil)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, req)
|
|
req.Header.Set("Buvid", "9346b9ca66dangerous4764eede8bb2")
|
|
req.AddCookie(&http.Cookie{Name: "buvid3", Value: "25213BD4-841C-449F-8BBF-B96B58A8Fdangerousinfoc"})
|
|
req.AddCookie(&http.Cookie{Name: "sid", Value: "70***dpi"})
|
|
query := req.URL.Query()
|
|
query.Set("build", "6280")
|
|
query.Set("device", "phone")
|
|
query.Set("mobi_app", "iphone")
|
|
query.Set("platform", "ios")
|
|
query.Set("channel", "appstore")
|
|
req.URL.RawQuery = query.Encode()
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, resp)
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
defer resp.Body.Close()
|
|
|
|
rdev := new(DevResp)
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, json.Unmarshal(bs, rdev))
|
|
dev := rdev.Data
|
|
assert.Equal(t, "70***dpi", dev.Sid)
|
|
assert.Equal(t, int64(6280), dev.Build)
|
|
assert.Equal(t, "phone", dev.Device)
|
|
assert.Equal(t, "iphone", dev.RawMobiApp)
|
|
assert.Equal(t, "ios", dev.RawPlatform)
|
|
assert.Equal(t, "appstore", dev.Channel)
|
|
assert.Equal(t, "9346b9ca66dangerous4764eede8bb2", dev.Buvid)
|
|
assert.Equal(t, "25213BD4-841C-449F-8BBF-B96B58A8Fdangerousinfoc", dev.Buvid3)
|
|
assert.Equal(t, PlatIPhone, dev.Plat())
|
|
assert.Equal(t, false, dev.IsAndroid())
|
|
assert.Equal(t, true, dev.IsIOS())
|
|
assert.Equal(t, false, dev.IsOverseas())
|
|
assert.Equal(t, false, dev.InvalidChannel("*"))
|
|
assert.Equal(t, false, dev.InvalidChannel("appstore"))
|
|
assert.Equal(t, "iphone", dev.MobiApp())
|
|
assert.Equal(t, "iphone", dev.MobiAPPBuleChange())
|
|
|
|
dev.RawPlatform = "android"
|
|
dev.RawMobiApp = "android"
|
|
dev.Channel = "test.channel"
|
|
assert.Equal(t, true, dev.InvalidChannel("test.channel2"))
|
|
}
|
|
|
|
func TestRemoteIPFromContext(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
ip := "192.168.22.33"
|
|
req, err := http.NewRequest("GET", uri(SockAddr, "/remote-ip"), nil)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, req)
|
|
req.Header.Set(_httpHeaderRemoteIP, ip)
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, resp)
|
|
defer resp.Body.Close()
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
assert.NoError(t, err)
|
|
|
|
var response struct {
|
|
Data string `json:"data"`
|
|
}
|
|
err = json.Unmarshal(bs, &response)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, ip, response.Data)
|
|
}
|
|
|
|
func TestNewServerMiddleware(t *testing.T) {
|
|
e := DefaultServer(nil)
|
|
// should contain 4 default handlers
|
|
assert.Len(t, e.RouterGroup.Handlers, 5)
|
|
}
|
|
|
|
func TestMethodConfig(t *testing.T) {
|
|
e := New()
|
|
tg := e.Group("/timeout").SetMethodConfig(&MethodConfig{Timeout: xtime.Duration(time.Second * 10)})
|
|
tg.GET("/default", func(ctx *Context) {})
|
|
|
|
e.GET("/timeout/5s", func(ctx *Context) {})
|
|
e.SetMethodConfig("/timeout/5s", &MethodConfig{Timeout: xtime.Duration(time.Second * 5)})
|
|
|
|
pc := e.methodConfig("/timeout/default")
|
|
assert.NotNil(t, pc)
|
|
assert.Equal(t, xtime.Duration(time.Second*10), pc.Timeout)
|
|
|
|
pc = e.methodConfig("/timeout/5s")
|
|
assert.NotNil(t, pc)
|
|
assert.Equal(t, xtime.Duration(time.Second*5), pc.Timeout)
|
|
}
|
|
|
|
func BenchmarkServer(b *testing.B) {
|
|
startServer()
|
|
defer shutdown()
|
|
|
|
b.ResetTimer()
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
resp, err := http.Get(uri(SockAddr, "/bench"))
|
|
if err != nil {
|
|
b.Errorf("HTTPServ: get error(%v)", err)
|
|
continue
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
b.Errorf("HTTPServ: get error status code:%d", resp.StatusCode)
|
|
}
|
|
resp.Body.Close()
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestServerWithMirror(t *testing.T) {
|
|
|
|
var response struct {
|
|
Data string `json:"data"`
|
|
}
|
|
|
|
//sonce.Do(startMirrorServer)
|
|
client := &http.Client{}
|
|
|
|
reqest, _ := http.NewRequest("GET", "http://localhost:18090/mirrortest", nil)
|
|
reqest.Header.Add("x1-bilispy-mirror", "1")
|
|
|
|
resp, err := client.Do(reqest)
|
|
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, resp)
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
bs, _ := ioutil.ReadAll(resp.Body)
|
|
|
|
err = json.Unmarshal(bs, &response)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "true", response.Data)
|
|
resp.Body.Close()
|
|
|
|
reqest, _ = http.NewRequest("GET", "http://localhost:18090/mirrortest", nil)
|
|
reqest.Header.Add("x1-bilispy-mirror", "0")
|
|
resp, err = client.Do(reqest)
|
|
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, resp)
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
bs, _ = ioutil.ReadAll(resp.Body)
|
|
|
|
err = json.Unmarshal(bs, &response)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "false", response.Data)
|
|
resp.Body.Close()
|
|
|
|
reqest, _ = http.NewRequest("GET", "http://localhost:18090/mirrortest", nil)
|
|
reqest.Header.Add("x1-bilispy-mirror", "xxxxxxxxxx")
|
|
resp, err = client.Do(reqest)
|
|
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, resp)
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
bs, _ = ioutil.ReadAll(resp.Body)
|
|
|
|
err = json.Unmarshal(bs, &response)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "false", response.Data)
|
|
resp.Body.Close()
|
|
|
|
reqest, _ = http.NewRequest("GET", "http://localhost:18090/mirrortest", nil)
|
|
resp, err = client.Do(reqest)
|
|
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, resp)
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
bs, _ = ioutil.ReadAll(resp.Body)
|
|
|
|
err = json.Unmarshal(bs, &response)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "false", response.Data)
|
|
resp.Body.Close()
|
|
|
|
}
|
|
|
|
func TestEngineInject(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Get(uri(SockAddr, "/inject/index"))
|
|
assert.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
bs, err := ioutil.ReadAll(resp.Body)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "index-injected", string(bs))
|
|
}
|
|
|
|
func TestBiliStatusCode(t *testing.T) {
|
|
once.Do(startServer)
|
|
|
|
tests := map[string]int{
|
|
"/group/test/json-status": ecode.MemberBlocked.Code(),
|
|
"/group/test/xml-status": ecode.MemberBlocked.Code(),
|
|
"/group/test/proto-status": ecode.MemberBlocked.Code(),
|
|
"/group/test/json-map-status": ecode.MemberBlocked.Code(),
|
|
}
|
|
client := &http.Client{}
|
|
for path, code := range tests {
|
|
resp, err := client.Get(uri(SockAddr, path))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, resp.Header.Get("bili-status-code"), strconv.FormatInt(int64(code), 10))
|
|
}
|
|
}
|