github.com/v2fly/v2ray-core/v5@v5.16.2-0.20240507031116-8191faa6e095/app/restfulapi/restful_api.go (about) 1 package restfulapi 2 3 import ( 4 "net/http" 5 "strings" 6 7 "github.com/go-chi/chi/v5" 8 "github.com/go-chi/chi/v5/middleware" 9 "github.com/go-chi/render" 10 "github.com/go-playground/validator/v10" 11 12 core "github.com/v2fly/v2ray-core/v5" 13 "github.com/v2fly/v2ray-core/v5/common/net" 14 "github.com/v2fly/v2ray-core/v5/transport/internet" 15 ) 16 17 var validate *validator.Validate 18 19 type StatsBound struct { // Better name? 20 Uplink int64 `json:"uplink"` 21 Downlink int64 `json:"downlink"` 22 } 23 24 func (rs *restfulService) tagStats(w http.ResponseWriter, r *http.Request) { 25 boundType := chi.URLParam(r, "bound_type") 26 tag := chi.URLParam(r, "tag") 27 28 if validate.Var(boundType, "required,oneof=inbounds outbounds") != nil || 29 validate.Var(tag, "required,min=1,max=255") != nil { 30 render.Status(r, http.StatusUnprocessableEntity) 31 render.JSON(w, r, render.M{}) 32 return 33 } 34 35 bound := boundType[:len(boundType)-1] 36 upCounter := rs.stats.GetCounter(bound + ">>>" + tag + ">>>traffic>>>uplink") 37 downCounter := rs.stats.GetCounter(bound + ">>>" + tag + ">>>traffic>>>downlink") 38 if upCounter == nil || downCounter == nil { 39 render.Status(r, http.StatusNotFound) 40 render.JSON(w, r, render.M{}) 41 return 42 } 43 44 render.JSON(w, r, &StatsBound{ 45 Uplink: upCounter.Value(), 46 Downlink: downCounter.Value(), 47 }) 48 } 49 50 func (rs *restfulService) version(w http.ResponseWriter, r *http.Request) { 51 render.JSON(w, r, render.M{"version": core.Version()}) 52 } 53 54 func (rs *restfulService) TokenAuthMiddleware(next http.Handler) http.Handler { 55 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 56 header := r.Header.Get("Authorization") 57 text := strings.SplitN(header, " ", 2) 58 59 hasInvalidHeader := text[0] != "Bearer" 60 hasInvalidSecret := len(text) != 2 || text[1] != rs.config.AuthToken 61 if hasInvalidHeader || hasInvalidSecret { 62 render.Status(r, http.StatusUnauthorized) 63 render.JSON(w, r, render.M{}) 64 return 65 } 66 67 next.ServeHTTP(w, r) 68 }) 69 } 70 71 func (rs *restfulService) start() error { 72 r := chi.NewRouter() 73 r.Use(middleware.Heartbeat("/ping")) 74 75 validate = validator.New() 76 r.Route("/v1", func(r chi.Router) { 77 r.Get("/{bound_type}/{tag}/stats", rs.tagStats) 78 }) 79 r.Get("/version", rs.version) 80 81 var listener net.Listener 82 var err error 83 address := net.ParseAddress(rs.config.ListenAddr) 84 85 switch { 86 case address.Family().IsIP(): 87 listener, err = internet.ListenSystem(rs.ctx, &net.TCPAddr{IP: address.IP(), Port: int(rs.config.ListenPort)}, nil) 88 case strings.EqualFold(address.Domain(), "localhost"): 89 listener, err = internet.ListenSystem(rs.ctx, &net.TCPAddr{IP: net.IP{127, 0, 0, 1}, Port: int(rs.config.ListenPort)}, nil) 90 default: 91 return newError("restful api cannot listen on the address: ", address) 92 } 93 if err != nil { 94 return newError("restful api cannot listen on the port ", rs.config.ListenPort).Base(err) 95 } 96 97 go func() { 98 err := http.Serve(listener, r) 99 if err != nil { 100 newError("unable to serve restful api").WriteToLog() 101 } 102 }() 103 return nil 104 }