github.com/metacubex/mihomo@v1.18.5/hub/route/proxies.go (about)

     1  package route
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/metacubex/mihomo/adapter"
    11  	"github.com/metacubex/mihomo/adapter/outboundgroup"
    12  	"github.com/metacubex/mihomo/common/utils"
    13  	"github.com/metacubex/mihomo/component/profile/cachefile"
    14  	C "github.com/metacubex/mihomo/constant"
    15  	"github.com/metacubex/mihomo/tunnel"
    16  
    17  	"github.com/go-chi/chi/v5"
    18  	"github.com/go-chi/render"
    19  )
    20  
    21  var (
    22  	SwitchProxiesCallback func(sGroup string, sProxy string)
    23  )
    24  
    25  func proxyRouter() http.Handler {
    26  	r := chi.NewRouter()
    27  	r.Get("/", getProxies)
    28  
    29  	r.Route("/{name}", func(r chi.Router) {
    30  		r.Use(parseProxyName, findProxyByName)
    31  		r.Get("/", getProxy)
    32  		r.Get("/delay", getProxyDelay)
    33  		r.Put("/", updateProxy)
    34  	})
    35  	return r
    36  }
    37  
    38  func parseProxyName(next http.Handler) http.Handler {
    39  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    40  		name := getEscapeParam(r, "name")
    41  		ctx := context.WithValue(r.Context(), CtxKeyProxyName, name)
    42  		next.ServeHTTP(w, r.WithContext(ctx))
    43  	})
    44  }
    45  
    46  func findProxyByName(next http.Handler) http.Handler {
    47  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    48  		name := r.Context().Value(CtxKeyProxyName).(string)
    49  		proxies := tunnel.ProxiesWithProviders()
    50  		proxy, exist := proxies[name]
    51  		if !exist {
    52  			render.Status(r, http.StatusNotFound)
    53  			render.JSON(w, r, ErrNotFound)
    54  			return
    55  		}
    56  
    57  		ctx := context.WithValue(r.Context(), CtxKeyProxy, proxy)
    58  		next.ServeHTTP(w, r.WithContext(ctx))
    59  	})
    60  }
    61  
    62  func getProxies(w http.ResponseWriter, r *http.Request) {
    63  	proxies := tunnel.ProxiesWithProviders()
    64  	render.JSON(w, r, render.M{
    65  		"proxies": proxies,
    66  	})
    67  }
    68  
    69  func getProxy(w http.ResponseWriter, r *http.Request) {
    70  	proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
    71  	render.JSON(w, r, proxy)
    72  }
    73  
    74  func updateProxy(w http.ResponseWriter, r *http.Request) {
    75  	req := struct {
    76  		Name string `json:"name"`
    77  	}{}
    78  	if err := render.DecodeJSON(r.Body, &req); err != nil {
    79  		render.Status(r, http.StatusBadRequest)
    80  		render.JSON(w, r, ErrBadRequest)
    81  		return
    82  	}
    83  
    84  	proxy := r.Context().Value(CtxKeyProxy).(*adapter.Proxy)
    85  	selector, ok := proxy.ProxyAdapter.(outboundgroup.SelectAble)
    86  	if !ok {
    87  		render.Status(r, http.StatusBadRequest)
    88  		render.JSON(w, r, newError("Must be a Selector"))
    89  		return
    90  	}
    91  
    92  	if err := selector.Set(req.Name); err != nil {
    93  		render.Status(r, http.StatusBadRequest)
    94  		render.JSON(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error())))
    95  		return
    96  	}
    97  
    98  	cachefile.Cache().SetSelected(proxy.Name(), req.Name)
    99  	if SwitchProxiesCallback != nil {
   100  		// refresh tray menu
   101  		go SwitchProxiesCallback(proxy.Name(), req.Name)
   102  	}
   103  	render.NoContent(w, r)
   104  }
   105  
   106  func getProxyDelay(w http.ResponseWriter, r *http.Request) {
   107  	query := r.URL.Query()
   108  	url := query.Get("url")
   109  	timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
   110  	if err != nil {
   111  		render.Status(r, http.StatusBadRequest)
   112  		render.JSON(w, r, ErrBadRequest)
   113  		return
   114  	}
   115  
   116  	expectedStatus, err := utils.NewUnsignedRanges[uint16](query.Get("expected"))
   117  	if err != nil {
   118  		render.Status(r, http.StatusBadRequest)
   119  		render.JSON(w, r, ErrBadRequest)
   120  		return
   121  	}
   122  
   123  	proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
   124  
   125  	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
   126  	defer cancel()
   127  
   128  	delay, err := proxy.URLTest(ctx, url, expectedStatus)
   129  	if ctx.Err() != nil {
   130  		render.Status(r, http.StatusGatewayTimeout)
   131  		render.JSON(w, r, ErrRequestTimeout)
   132  		return
   133  	}
   134  
   135  	if err != nil || delay == 0 {
   136  		render.Status(r, http.StatusServiceUnavailable)
   137  		if err != nil && delay != 0 {
   138  			render.JSON(w, r, err)
   139  		} else {
   140  			render.JSON(w, r, newError("An error occurred in the delay test"))
   141  		}
   142  		return
   143  	}
   144  
   145  	render.JSON(w, r, render.M{
   146  		"delay": delay,
   147  	})
   148  }