Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"config.go",
"config2.go",
"http.go",
"local.go",
],
importpath = "go-common/app/infra/config/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/infra/config/conf:go_default_library",
"//app/infra/config/model:go_default_library",
"//app/infra/config/service/v1:go_default_library",
"//app/infra/config/service/v2:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/antispam:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//vendor/github.com/dgryski/go-farm:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,500 @@
package http
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"time"
"go-common/app/infra/config/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
// push config update
func push(c *bm.Context) {
var (
err error
svr string
buildVer string
ver int64
env string
)
query := c.Request.Form
verStr := query.Get("version")
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if buildVer = query.Get("build_ver"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if ver, err = strconv.ParseInt(verStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
service := &model.Service{Name: svr, BuildVersion: buildVer, Version: ver, Env: env}
// update & write cache
c.JSON(nil, confSvc.Push(c, service))
}
// hosts client hosts
func hosts(c *bm.Context) {
var (
err error
svr string
data []*model.Host
env string
)
query := c.Request.URL.Query()
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if data, err = confSvc.Hosts(c, svr, env); err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// versions client versions which the configuration is complete
func versions(c *bm.Context) {
var (
err error
svr string
data *model.Versions
env string
bver string
)
query := c.Request.URL.Query()
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if bver = query.Get("build"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if data, err = confSvc.VersionSuccess(c, svr, env, bver); err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// versions client versions which the configuration is complete
func versionIng(c *bm.Context) {
var (
err error
svr string
data []int64
env string
)
query := c.Request.URL.Query()
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if data, err = confSvc.VersionIng(c, svr, env); err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// config get config file
func config(c *bm.Context) {
var (
err error
svr string
host string
buildVer string
version int64
env string
token string
)
query := c.Request.URL.Query()
verStr := query.Get("version")
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if token = query.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if host = query.Get("hostname"); host == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if buildVer = query.Get("build"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if version, err = strconv.ParseInt(verStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
service := &model.Service{Name: svr, BuildVersion: buildVer, Env: env, Token: token, Version: version, Host: host}
data, err := confSvc.Config(c, service)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// file get one file value
func file(c *bm.Context) {
var (
err error
svr string
buildVer string
env string
token string
file string
ver int64
data string
)
query := c.Request.URL.Query()
// params
if buildVer = query.Get("build"); buildVer == "" {
data = "build is null"
}
if !strings.HasPrefix(buildVer, "shsb") && !strings.HasPrefix(buildVer, "shylf") &&
query.Get("zone") != "" && query.Get("env") != "" && query.Get("treeid") != "" {
file2(c)
return
}
if verStr := query.Get("version"); verStr == "" {
ver = model.UnknownVersion
} else {
if ver, err = strconv.ParseInt(verStr, 10, 64); err != nil {
data = "version must be num"
}
}
if svr = query.Get("service"); svr == "" {
data = "service is null"
}
if env = query.Get("environment"); env == "" {
data = "environment is null"
}
if token = query.Get("token"); token == "" {
data = "token is null"
}
if file = query.Get("fileName"); file == "" {
data = "fileName is null"
}
service := &model.Service{Name: svr, BuildVersion: buildVer, Env: env, File: file, Token: token, Version: ver}
if data == "" {
if data, err = confSvc.File(c, service); err != nil {
data = err.Error()
c.AbortWithStatus(http.StatusInternalServerError)
}
} else {
c.AbortWithStatus(http.StatusBadRequest)
}
if _, err = c.Writer.Write([]byte(data)); err != nil {
log.Error("Response().Write(%v) error(%v)", data, err)
}
}
// check check config version
func check(c *bm.Context) {
var (
err error
svr string
host string
buildVer string
ip string
ver int64
env string
token string
appoint int64
query = c.Request.URL.Query()
)
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if token = query.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if ip = query.Get("ip"); ip == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if host = query.Get("hostname"); host == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if buildVer = query.Get("build"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if ver, err = strconv.ParseInt(query.Get("version"), 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
appoint, _ = strconv.ParseInt(query.Get("appoint"), 10, 64)
// check config version
rhost := &model.Host{Service: svr, Name: host, BuildVersion: buildVer, IP: ip, ConfigVersion: ver, Appoint: appoint, Customize: query.Get("customize")}
evt, err := confSvc.CheckVersion(c, rhost, env, token)
if err != nil {
c.JSON(nil, err)
return
}
// wait for version change
select {
case e := <-evt:
c.JSON(e, nil)
case <-time.After(time.Duration(cnf.PollTimeout)):
c.JSON(nil, ecode.NotModified)
case <-c.Writer.(http.CloseNotifier).CloseNotify():
c.JSON(nil, ecode.NotModified)
}
confSvc.Unsub(svr, host, env)
}
//clear host in redis
func clearhost(c *bm.Context) {
var (
svr string
env string
)
query := c.Request.Form
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, confSvc.ClearHost(c, svr, env))
}
// versions client versions which the configuration is complete
func builds(c *bm.Context) {
var (
svr string
bs, bs2 []string
env string
)
query := c.Request.URL.Query()
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
bs, _ = confSvc.Builds(c, svr, env)
bs2, _ = confSvc2.TmpBuilds(svr, env)
bs = append(bs, bs2...)
if len(bs) == 0 {
c.JSON(nil, ecode.NothingFound)
return
}
c.JSON(bs, nil)
}
func addConfigs(c *bm.Context) {
var (
svr string
env string
token string
user string
data map[string]string
err error
values = c.Request.PostForm
)
// params
if svr = values.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = values.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if token = values.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if user = values.Get("user"); user == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if err = json.Unmarshal([]byte(values.Get("data")), &data); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, confSvc.AddConfigs(c, svr, env, token, user, data))
}
func copyConfigs(c *bm.Context) {
var (
svr string
env string
token string
build string
user string
err error
ver int64
values = c.Request.PostForm
)
// params
if svr = values.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = values.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if token = values.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if user = values.Get("user"); user == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if build = values.Get("build"); build == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if ver, err = confSvc.CopyConfigs(c, svr, env, token, user, build); err != nil {
c.JSON(nil, err)
return
}
c.JSON(ver, nil)
}
func updateConfigs(c *bm.Context) {
var (
svr string
env string
token string
ver int64
user string
data map[string]string
err error
values = c.Request.PostForm
)
// params
if svr = values.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = values.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if ver, err = strconv.ParseInt(values.Get("version"), 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if token = values.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if user = values.Get("user"); user == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if err = json.Unmarshal([]byte(values.Get("data")), &data); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, confSvc.UpdateConfigs(c, svr, env, token, user, ver, data))
}
// configN get config namespace file
func configN(c *bm.Context) {
var (
err error
svr string
host string
buildVer string
version int64
env string
token string
)
query := c.Request.URL.Query()
verStr := query.Get("version")
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if token = query.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if host = query.Get("hostname"); host == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if buildVer = query.Get("build"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if version, err = strconv.ParseInt(verStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
service := &model.Service{Name: svr, BuildVersion: buildVer, Env: env, Token: token, Version: version, Host: host}
data, err := confSvc.Config2(c, service)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}

View File

@@ -0,0 +1,352 @@
package http
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"go-common/app/infra/config/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
// versions client versions which the configuration is complete
func versions2(c *bm.Context) {
var (
err error
svr string
data *model.Versions
bver string
token string
env string
zone string
)
query := c.Request.URL.Query()
// params
svr = query.Get("service")
if svr == "" {
token = query.Get("token")
zone = query.Get("zone")
env = query.Get("env")
if zone == "" || env == "" || token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if bver = query.Get("build"); bver == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if data, err = confSvc2.VersionSuccess(c, svr, bver); err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// config get config file
func config2(c *bm.Context) {
var (
err error
svr string
buildVer string
version int64
token string
ids []int64
zone string
env string
)
query := c.Request.URL.Query()
// params
if token = query.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
svr = query.Get("service")
if svr == "" {
zone = query.Get("zone")
env = query.Get("env")
if zone == "" || env == "" || token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if buildVer = query.Get("build"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if version, err = strconv.ParseInt(query.Get("version"), 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if idsStr := query.Get("ids"); len(idsStr) != 0 {
if err = json.Unmarshal([]byte(query.Get("ids")), &ids); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
data, err := confSvc2.Config(c, svr, token, version, ids)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// file get one file value
func file2(c *bm.Context) {
var (
err error
svr string
buildVer string
token string
file string
ver int64
treeID string
env string
zone string
data string
)
query := c.Request.URL.Query()
// params
if verStr := query.Get("version"); verStr == "" {
ver = model.UnknownVersion
} else {
if ver, err = strconv.ParseInt(verStr, 10, 64); err != nil {
data = "version must be num"
}
}
if env = query.Get("env"); env == "" {
data = "env is null"
}
if zone = query.Get("zone"); zone == "" {
data = "zone is null"
}
if token = query.Get("token"); token == "" {
data = "token is null"
}
if treeID = query.Get("treeid"); treeID == "" {
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
data = "appid is null"
}
} else {
svr = fmt.Sprintf("%s_%s_%s", treeID, env, zone)
}
if buildVer = query.Get("build"); buildVer == "" {
data = "build is null"
}
if file = query.Get("fileName"); file == "" {
data = "fileName is null"
}
service := &model.Service{Name: svr, BuildVersion: buildVer, File: file, Token: token, Version: ver}
if data == "" {
if data, err = confSvc2.File(c, service); err != nil {
data = err.Error()
c.AbortWithStatus(http.StatusInternalServerError)
}
} else {
c.AbortWithStatus(http.StatusBadRequest)
}
if _, err = c.Writer.Write([]byte(data)); err != nil {
log.Error("Response().Write(%v) error(%v)", data, err)
}
}
// check check config version
func check2(c *bm.Context) {
var (
err error
svr string
host string
buildVer string
ip string
ver int64
token string
appoint int64
zone string
env string
query = c.Request.URL.Query()
)
// params
if token = query.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
svr = query.Get("service")
if svr == "" {
zone = query.Get("zone")
env = query.Get("env")
if zone == "" || env == "" || token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if ip = query.Get("ip"); ip == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if host = query.Get("hostname"); host == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if buildVer = query.Get("build"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if ver, err = strconv.ParseInt(query.Get("version"), 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
appoint, _ = strconv.ParseInt(query.Get("appoint"), 10, 64)
// check config version
rhost := &model.Host{Service: svr, Name: host, BuildVersion: buildVer, IP: ip, ConfigVersion: ver, Appoint: appoint, Customize: query.Get("customize")}
evt, err := confSvc2.CheckVersion(c, rhost, token)
if err != nil {
c.JSON(nil, err)
return
}
// wait for version change
select {
case e := <-evt:
c.JSON(e, nil)
case <-time.After(time.Duration(cnf.PollTimeout)):
c.JSON(nil, ecode.NotModified)
case <-c.Writer.(http.CloseNotifier).CloseNotify():
c.JSON(nil, ecode.NotModified)
}
confSvc2.Unsub(svr, host)
}
//clear host in redis
func clearhost2(c *bm.Context) {
var (
svr string
zone string
env string
token string
err error
)
query := c.Request.Form
svr = query.Get("service")
if svr == "" {
token = query.Get("token")
zone = query.Get("zone")
env = query.Get("env")
if zone == "" || env == "" || token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
c.JSON(nil, confSvc2.ClearHost(c, svr))
}
// versions client versions which the configuration is complete
func builds2(c *bm.Context) {
var (
err error
svr string
data []string
token string
env string
zone string
)
query := c.Request.URL.Query()
// params
svr = query.Get("service")
if svr == "" {
token = query.Get("token")
zone = query.Get("zone")
env = query.Get("env")
if zone == "" || env == "" || token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if data, err = confSvc2.Builds(c, svr); err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// config get config file
func latest(c *bm.Context) {
var (
err error
svr string
buildVer string
version int64
token string
ids []int64
zone string
env string
verStr string
)
query := c.Request.URL.Query()
// params
if token = query.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
svr = query.Get("service")
if svr == "" {
zone = query.Get("zone")
env = query.Get("env")
if zone == "" || env == "" || token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if buildVer = query.Get("build"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
verStr = query.Get("version")
if len(verStr) > 0 {
if version, err = strconv.ParseInt(verStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
} else {
rhost := &model.Host{Service: svr, BuildVersion: buildVer}
version, err = confSvc2.CheckLatest(c, rhost, token)
if err != nil {
c.JSON(nil, err)
return
}
}
data, err := confSvc2.ConfigCheck(c, svr, token, version, ids)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}

View File

@@ -0,0 +1,117 @@
package http
import (
"io"
"strconv"
"strings"
"go-common/app/infra/config/conf"
"go-common/app/infra/config/service/v1"
"go-common/app/infra/config/service/v2"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/antispam"
v "go-common/library/net/http/blademaster/middleware/verify"
"github.com/dgryski/go-farm"
)
var (
cnf *conf.Config
verify *v.Verify
confSvc *v1.Service
confSvc2 *v2.Service
anti *antispam.Antispam
)
// Init init.
func Init(c *conf.Config, s *v1.Service, s2 *v2.Service, rpcCloser io.Closer) {
initService(c)
verify = v.New(c.Verify)
cnf = c
confSvc = s
confSvc2 = s2
engine := bm.DefaultServer(c.BM)
innerRouter(engine)
if err := engine.Start(); err != nil {
log.Error("engine.Start() error(%v)", err)
panic(err)
}
}
// innerRouter init inner router.
func innerRouter(e *bm.Engine) {
e.Ping(ping)
e.Register(register)
b := e.Group("/", verify.Verify)
noAuth := e.Group("/")
{
v1 := b.Group("v1/config/")
{
v1.GET("host/infos", hosts)
v1.POST("host/clear", clearhost)
v1.POST("push", push)
}
{
noAuth.GET("v1/config/versions", versions)
noAuth.GET("v1/config/builds", builds)
noAuth.GET("v1/config/check", check)
noAuth.GET("v1/config/get", config)
noAuth.GET("v1/config/get2", configN)
noAuth.GET("v1/config/file.so", file)
noAuth.GET("v1/config/version/ing", versionIng)
noAuth.POST("v1/config/config/add", addConfigs)
noAuth.POST("v1/config/config/copy", copyConfigs)
noAuth.POST("v1/config/config/update", updateConfigs)
noAuth.GET("config/v2/versions", versions2)
noAuth.GET("config/v2/builds", builds2)
noAuth.GET("config/v2/check", check2)
noAuth.GET("config/v2/get", setMid, anti.ServeHTTP, config2)
noAuth.GET("config/v2/file.so", file2)
noAuth.GET("config/v2/latest", latest)
}
v2 := b.Group("config/v2/")
{
v2.POST("host/clear", clearhost2)
}
}
}
func setMid(c *bm.Context) {
var (
token string
service string
query = c.Request.URL.Query()
hash uint64
)
service = query.Get("service")
if service == "" {
token = query.Get("token")
if token == "" {
c.JSON(nil, ecode.RequestErr)
c.Abort()
return
}
hash = farm.Hash64([]byte(token))
} else {
arrs := strings.Split(service, "_")
if len(arrs) != 3 {
c.JSON(nil, ecode.RequestErr)
c.Abort()
return
}
_, err := strconv.ParseInt(arrs[0], 10, 64)
if err != nil {
c.JSON(nil, ecode.RequestErr)
c.Abort()
return
}
hash = farm.Hash64([]byte(service))
}
c.Set("mid", int64(hash))
}
func initService(c *conf.Config) {
anti = antispam.New(c.Antispam)
}

View File

@@ -0,0 +1,25 @@
package http
import (
"net/http"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
// ping check server ok.
func ping(c *bm.Context) {
var (
err error
)
if err = confSvc.Ping(c); err != nil {
log.Error("config service ping error(%v)", err)
c.JSON(nil, err)
http.Error(c.Writer, "", http.StatusServiceUnavailable)
}
}
// register check server ok.
func register(c *bm.Context) {
c.JSON(map[string]struct{}{}, nil)
}