forked from pneymrl2f/nightingale
Add http_middleware to transfer (#402)
* validate ui query, add aggrFun support for resample * add http_middleware to transfermaster
parent
3f352a393b
commit
920dd9a947
@ -1,4 +1,4 @@
|
|||||||
package routes
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -0,0 +1,124 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/toolkits/pkg/logger"
|
||||||
|
"github.com/toolkits/pkg/slice"
|
||||||
|
|
||||||
|
"github.com/didi/nightingale/src/common/address"
|
||||||
|
"github.com/didi/nightingale/src/models"
|
||||||
|
"github.com/didi/nightingale/src/modules/rdb/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func shouldBeLogin() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Set("username", mustUsername(c))
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldBeRoot() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
username := mustUsername(c)
|
||||||
|
|
||||||
|
user, err := models.UserGet("username=?", username)
|
||||||
|
dangerous(err)
|
||||||
|
|
||||||
|
if user.IsRoot != 1 {
|
||||||
|
bomb("forbidden")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("username", username)
|
||||||
|
c.Set("user", user)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldBeService() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
remoteAddr := c.Request.RemoteAddr
|
||||||
|
idx := strings.LastIndex(remoteAddr, ":")
|
||||||
|
ip := ""
|
||||||
|
if idx > 0 {
|
||||||
|
ip = remoteAddr[0:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip == "127.0.0.1" {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip != "" && slice.ContainsString(address.GetAddresses("rdb"), ip) {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := c.GetHeader("X-Srv-Token")
|
||||||
|
if token == "" {
|
||||||
|
c.AbortWithError(http.StatusForbidden, fmt.Errorf("X-Srv-Token is blank"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !slice.ContainsString(config.Config.Tokens, token) {
|
||||||
|
c.AbortWithError(http.StatusForbidden, fmt.Errorf("X-Srv-Token[%s] invalid", token))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustUsername(c *gin.Context) string {
|
||||||
|
username := cookieUsername(c)
|
||||||
|
if username == "" {
|
||||||
|
username = headerUsername(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if username == "" {
|
||||||
|
bomb("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
return username
|
||||||
|
}
|
||||||
|
|
||||||
|
func cookieUsername(c *gin.Context) string {
|
||||||
|
return models.UsernameByUUID(readCookieUser(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func headerUsername(c *gin.Context) string {
|
||||||
|
token := c.GetHeader("X-User-Token")
|
||||||
|
if token == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
ut, err := models.UserTokenGet("token=?", token)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("UserTokenGet[%s] fail: %v", token, err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ut == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return ut.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------
|
||||||
|
|
||||||
|
func readCookieUser(c *gin.Context) string {
|
||||||
|
uuid, err := c.Cookie(config.Config.HTTP.CookieName)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeCookieUser(c *gin.Context, uuid string) {
|
||||||
|
c.SetCookie(config.Config.HTTP.CookieName, uuid, 3600*24, "/", config.Config.HTTP.CookieDomain, false, true)
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/didi/nightingale/src/common/address"
|
||||||
|
"github.com/didi/nightingale/src/common/middleware"
|
||||||
|
"github.com/didi/nightingale/src/modules/transfer/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var srv = &http.Server{
|
||||||
|
ReadTimeout: 10 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
MaxHeaderBytes: 1 << 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
var skipPaths = []string{"/api/rdb/auth/login"}
|
||||||
|
|
||||||
|
func Start() {
|
||||||
|
c := config.Config
|
||||||
|
|
||||||
|
loggerMid := middleware.LoggerWithConfig(middleware.LoggerConfig{SkipPaths: skipPaths})
|
||||||
|
recoveryMid := middleware.Recovery()
|
||||||
|
|
||||||
|
if strings.ToLower(c.HTTP.Mode) == "release" {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
middleware.DisableConsoleColor()
|
||||||
|
}
|
||||||
|
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(loggerMid, recoveryMid)
|
||||||
|
|
||||||
|
Config(r)
|
||||||
|
|
||||||
|
srv.Addr = address.GetHTTPListen("transfer")
|
||||||
|
srv.Handler = r
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
fmt.Println("http.listening:", srv.Addr)
|
||||||
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
fmt.Printf("listening %s occur error: %s\n", srv.Addr, err)
|
||||||
|
os.Exit(3)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown http server
|
||||||
|
func Shutdown() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
|
fmt.Println("cannot shutdown http server:", err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// catching ctx.Done(). timeout of 5 seconds.
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
fmt.Println("shutdown http server timeout of 5 seconds.")
|
||||||
|
default:
|
||||||
|
fmt.Println("http server stopped")
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package routes
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/didi/nightingale/src/common/dataobj"
|
"github.com/didi/nightingale/src/common/dataobj"
|
@ -0,0 +1,158 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/didi/nightingale/src/common/dataobj"
|
||||||
|
"github.com/didi/nightingale/src/modules/transfer/backend"
|
||||||
|
"github.com/didi/nightingale/src/toolkits/http/render"
|
||||||
|
"github.com/didi/nightingale/src/toolkits/stats"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/toolkits/pkg/errors"
|
||||||
|
"github.com/toolkits/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func QueryData(c *gin.Context) {
|
||||||
|
stats.Counter.Set("data.api.qp10s", 1)
|
||||||
|
|
||||||
|
dataSource, err := backend.GetDataSourceFor("")
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("could not find datasource")
|
||||||
|
render.Message(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var input []dataobj.QueryData
|
||||||
|
errors.Dangerous(c.ShouldBindJSON(&input))
|
||||||
|
resp := dataSource.QueryData(input)
|
||||||
|
render.Data(c, resp, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func QueryDataForUI(c *gin.Context) {
|
||||||
|
stats.Counter.Set("data.ui.qp10s", 1)
|
||||||
|
var input dataobj.QueryDataForUI
|
||||||
|
var respData []*dataobj.QueryDataForUIResp
|
||||||
|
|
||||||
|
dangerous(c.ShouldBindJSON(&input))
|
||||||
|
start := input.Start
|
||||||
|
end := input.End
|
||||||
|
|
||||||
|
dataSource, err := backend.GetDataSourceFor("")
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("could not find datasource")
|
||||||
|
render.Message(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp := dataSource.QueryDataForUI(input)
|
||||||
|
for _, d := range resp {
|
||||||
|
data := &dataobj.QueryDataForUIResp{
|
||||||
|
Start: d.Start,
|
||||||
|
End: d.End,
|
||||||
|
Endpoint: d.Endpoint,
|
||||||
|
Nid: d.Nid,
|
||||||
|
Counter: d.Counter,
|
||||||
|
DsType: d.DsType,
|
||||||
|
Step: d.Step,
|
||||||
|
Values: d.Values,
|
||||||
|
}
|
||||||
|
respData = append(respData, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(input.Comparisons) > 1 {
|
||||||
|
for i := 1; i < len(input.Comparisons); i++ {
|
||||||
|
comparison := input.Comparisons[i]
|
||||||
|
input.Start = start - comparison
|
||||||
|
input.End = end - comparison
|
||||||
|
res := dataSource.QueryDataForUI(input)
|
||||||
|
for _, d := range res {
|
||||||
|
for j := range d.Values {
|
||||||
|
d.Values[j].Timestamp += comparison
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &dataobj.QueryDataForUIResp{
|
||||||
|
Start: d.Start,
|
||||||
|
End: d.End,
|
||||||
|
Endpoint: d.Endpoint,
|
||||||
|
Nid: d.Nid,
|
||||||
|
Counter: d.Counter,
|
||||||
|
DsType: d.DsType,
|
||||||
|
Step: d.Step,
|
||||||
|
Values: d.Values,
|
||||||
|
Comparison: comparison,
|
||||||
|
}
|
||||||
|
respData = append(respData, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Data(c, respData, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMetrics(c *gin.Context) {
|
||||||
|
stats.Counter.Set("metric.qp10s", 1)
|
||||||
|
recv := dataobj.EndpointsRecv{}
|
||||||
|
errors.Dangerous(c.ShouldBindJSON(&recv))
|
||||||
|
|
||||||
|
dataSource, err := backend.GetDataSourceFor("")
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("could not find datasource")
|
||||||
|
render.Message(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := dataSource.QueryMetrics(recv)
|
||||||
|
|
||||||
|
render.Data(c, resp, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTagPairs(c *gin.Context) {
|
||||||
|
stats.Counter.Set("tag.qp10s", 1)
|
||||||
|
recv := dataobj.EndpointMetricRecv{}
|
||||||
|
errors.Dangerous(c.ShouldBindJSON(&recv))
|
||||||
|
|
||||||
|
dataSource, err := backend.GetDataSourceFor("")
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("could not find datasource")
|
||||||
|
render.Message(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := dataSource.QueryTagPairs(recv)
|
||||||
|
render.Data(c, resp, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetIndexByClude(c *gin.Context) {
|
||||||
|
stats.Counter.Set("xclude.qp10s", 1)
|
||||||
|
recvs := make([]dataobj.CludeRecv, 0)
|
||||||
|
errors.Dangerous(c.ShouldBindJSON(&recvs))
|
||||||
|
|
||||||
|
dataSource, err := backend.GetDataSourceFor("")
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("could not find datasource")
|
||||||
|
render.Message(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := dataSource.QueryIndexByClude(recvs)
|
||||||
|
render.Data(c, resp, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetIndexByFullTags(c *gin.Context) {
|
||||||
|
stats.Counter.Set("counter.qp10s", 1)
|
||||||
|
recvs := make([]dataobj.IndexByFullTagsRecv, 0)
|
||||||
|
errors.Dangerous(c.ShouldBindJSON(&recvs))
|
||||||
|
|
||||||
|
dataSource, err := backend.GetDataSourceFor("")
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("could not find datasource")
|
||||||
|
render.Message(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := dataSource.QueryIndexByFullTags(recvs)
|
||||||
|
render.Data(c, &listResp{List: resp, Count: len(resp)}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
type listResp struct {
|
||||||
|
List interface{} `json:"list"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
package routes
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-contrib/pprof"
|
"github.com/gin-contrib/pprof"
|
Loading…
Reference in new issue