mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-11-18 02:16:10 -05:00
140 lines
3.3 KiB
Go
140 lines
3.3 KiB
Go
package restful_api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/go-playground/validator/v10"
|
|
"github.com/v2fly/v2ray-core/v4/common/net"
|
|
"github.com/v2fly/v2ray-core/v4/transport/internet"
|
|
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
func JSONResponse(w http.ResponseWriter, data interface{}, code int) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
w.WriteHeader(code)
|
|
_ = json.NewEncoder(w).Encode(data)
|
|
}
|
|
|
|
var validate *validator.Validate
|
|
|
|
type StatsUser struct {
|
|
uuid string `validate:"required_without=email,uuid4"`
|
|
email string `validate:"required_without=uuid,email"`
|
|
}
|
|
|
|
type StatsUserResponse struct {
|
|
Uplink int64 `json:"uplink"`
|
|
Downlink int64 `json:"downlink"`
|
|
}
|
|
|
|
func (rs *restfulService) statsUser(w http.ResponseWriter, r *http.Request) {
|
|
query := r.URL.Query()
|
|
statsUser := &StatsUser{
|
|
uuid: query.Get("uuid"),
|
|
email: query.Get("email"),
|
|
}
|
|
|
|
if err := validate.Struct(statsUser); err != nil {
|
|
JSONResponse(w, http.StatusText(422), 422)
|
|
}
|
|
|
|
response := &StatsUserResponse{
|
|
Uplink: 0,
|
|
Downlink: 0,
|
|
}
|
|
|
|
JSONResponse(w, response, 200)
|
|
}
|
|
|
|
type Stats struct {
|
|
tag string `validate:"required,alpha,min=1,max=255"`
|
|
}
|
|
|
|
type StatsBound struct { // Better name?
|
|
Uplink int64 `json:"uplink"`
|
|
Downlink int64 `json:"downlink"`
|
|
}
|
|
|
|
type StatsResponse struct {
|
|
Inbound StatsBound `json:"inbound"`
|
|
Outbound StatsBound `json:"outbound"`
|
|
}
|
|
|
|
func (rs *restfulService) statsRequest(w http.ResponseWriter, r *http.Request) {
|
|
stats := &Stats{
|
|
tag: r.URL.Query().Get("tag"),
|
|
}
|
|
if err := validate.Struct(stats); err != nil {
|
|
JSONResponse(w, http.StatusText(422), 422)
|
|
}
|
|
|
|
response := StatsResponse{
|
|
Inbound: StatsBound{
|
|
Uplink: 1,
|
|
Downlink: 1,
|
|
},
|
|
Outbound: StatsBound{
|
|
Uplink: 1,
|
|
Downlink: 1,
|
|
}}
|
|
|
|
JSONResponse(w, response, 200)
|
|
}
|
|
|
|
func (rs *restfulService) TokenAuthMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
auth := r.Header.Get("Authorization")
|
|
const prefix = "Bearer "
|
|
if !strings.HasPrefix(auth, prefix) {
|
|
JSONResponse(w, http.StatusText(403), 403)
|
|
return
|
|
}
|
|
auth = strings.TrimPrefix(auth, prefix)
|
|
if auth != rs.config.AuthToken {
|
|
JSONResponse(w, http.StatusText(403), 403)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func (rs *restfulService) start() error {
|
|
r := chi.NewRouter()
|
|
r.Use(rs.TokenAuthMiddleware)
|
|
r.Use(middleware.Heartbeat("/ping"))
|
|
|
|
r.Route("/v1", func(r chi.Router) {
|
|
r.Get("/stats/user", rs.statsUser)
|
|
r.Get("/stats", rs.statsRequest)
|
|
})
|
|
|
|
var listener net.Listener
|
|
var err error
|
|
address := net.ParseAddress(rs.config.ListenAddr)
|
|
|
|
switch {
|
|
case address.Family().IsIP():
|
|
listener, err = internet.ListenSystem(rs.ctx, &net.TCPAddr{IP: address.IP(), Port: int(rs.config.ListenPort)}, nil)
|
|
case strings.EqualFold(address.Domain(), "localhost"):
|
|
listener, err = internet.ListenSystem(rs.ctx, &net.TCPAddr{IP: net.IP{127, 0, 0, 1}, Port: int(rs.config.ListenPort)}, nil)
|
|
default:
|
|
return newError("restful api cannot listen on the address: ", address)
|
|
}
|
|
if err != nil {
|
|
return newError("restful api cannot listen on the port ", rs.config.ListenPort).Base(err)
|
|
}
|
|
|
|
go func() {
|
|
err := http.Serve(listener, r)
|
|
if err != nil {
|
|
newError("unable to serve restful api").WriteToLog()
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|