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  }