123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- package clashapi
- import (
- "context"
- "net/http"
- "strconv"
- "strings"
- "sync"
- "time"
- "github.com/sagernet/sing-box/adapter"
- "github.com/sagernet/sing-box/common/urltest"
- "github.com/sagernet/sing-box/outbound"
- "github.com/sagernet/sing/common"
- "github.com/sagernet/sing/common/batch"
- "github.com/sagernet/sing/common/json/badjson"
- "github.com/go-chi/chi/v5"
- "github.com/go-chi/render"
- )
- func groupRouter(server *Server) http.Handler {
- r := chi.NewRouter()
- r.Get("/", getGroups(server))
- r.Route("/{name}", func(r chi.Router) {
- r.Use(parseProxyName, findProxyByName(server.router))
- r.Get("/", getGroup(server))
- r.Get("/delay", getGroupDelay(server))
- })
- return r
- }
- func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) {
- return func(w http.ResponseWriter, r *http.Request) {
- groups := common.Map(common.Filter(server.router.Outbounds(), func(it adapter.Outbound) bool {
- _, isGroup := it.(adapter.OutboundGroup)
- return isGroup
- }), func(it adapter.Outbound) *badjson.JSONObject {
- return proxyInfo(server, it)
- })
- render.JSON(w, r, render.M{
- "proxies": groups,
- })
- }
- }
- func getGroup(server *Server) func(w http.ResponseWriter, r *http.Request) {
- return func(w http.ResponseWriter, r *http.Request) {
- proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
- if _, ok := proxy.(adapter.OutboundGroup); ok {
- render.JSON(w, r, proxyInfo(server, proxy))
- return
- }
- render.Status(r, http.StatusNotFound)
- render.JSON(w, r, ErrNotFound)
- }
- }
- func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
- return func(w http.ResponseWriter, r *http.Request) {
- proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
- group, ok := proxy.(adapter.OutboundGroup)
- if !ok {
- render.Status(r, http.StatusNotFound)
- render.JSON(w, r, ErrNotFound)
- return
- }
- query := r.URL.Query()
- url := query.Get("url")
- if strings.HasPrefix(url, "http://") {
- url = ""
- }
- timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32)
- if err != nil {
- render.Status(r, http.StatusBadRequest)
- render.JSON(w, r, ErrBadRequest)
- return
- }
- ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*time.Duration(timeout))
- defer cancel()
- var result map[string]uint16
- if urlTestGroup, isURLTestGroup := group.(adapter.URLTestGroup); isURLTestGroup {
- result, err = urlTestGroup.URLTest(ctx)
- } else {
- outbounds := common.FilterNotNil(common.Map(group.All(), func(it string) adapter.Outbound {
- itOutbound, _ := server.router.Outbound(it)
- return itOutbound
- }))
- b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
- checked := make(map[string]bool)
- result = make(map[string]uint16)
- var resultAccess sync.Mutex
- for _, detour := range outbounds {
- tag := detour.Tag()
- realTag := outbound.RealTag(detour)
- if checked[realTag] {
- continue
- }
- checked[realTag] = true
- p, loaded := server.router.Outbound(realTag)
- if !loaded {
- continue
- }
- b.Go(realTag, func() (any, error) {
- t, err := urltest.URLTest(ctx, url, p)
- if err != nil {
- server.logger.Debug("outbound ", tag, " unavailable: ", err)
- server.urlTestHistory.DeleteURLTestHistory(realTag)
- } else {
- server.logger.Debug("outbound ", tag, " available: ", t, "ms")
- server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{
- Time: time.Now(),
- Delay: t,
- })
- resultAccess.Lock()
- result[tag] = t
- resultAccess.Unlock()
- }
- return nil, nil
- })
- }
- b.Wait()
- }
- if err != nil {
- render.Status(r, http.StatusGatewayTimeout)
- render.JSON(w, r, newError(err.Error()))
- return
- }
- render.JSON(w, r, result)
- }
- }
|