api_meta_group.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. package clashapi
  2. import (
  3. "context"
  4. "net/http"
  5. "strconv"
  6. "strings"
  7. "sync"
  8. "time"
  9. "github.com/sagernet/sing-box/adapter"
  10. "github.com/sagernet/sing-box/common/urltest"
  11. "github.com/sagernet/sing-box/protocol/group"
  12. "github.com/sagernet/sing/common"
  13. "github.com/sagernet/sing/common/batch"
  14. "github.com/sagernet/sing/common/json/badjson"
  15. "github.com/go-chi/chi/v5"
  16. "github.com/go-chi/render"
  17. )
  18. func groupRouter(server *Server) http.Handler {
  19. r := chi.NewRouter()
  20. r.Get("/", getGroups(server))
  21. r.Route("/{name}", func(r chi.Router) {
  22. r.Use(parseProxyName, findProxyByName(server))
  23. r.Get("/", getGroup(server))
  24. r.Get("/delay", getGroupDelay(server))
  25. })
  26. return r
  27. }
  28. func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) {
  29. return func(w http.ResponseWriter, r *http.Request) {
  30. groups := common.Map(common.Filter(server.outbound.Outbounds(), func(it adapter.Outbound) bool {
  31. _, isGroup := it.(adapter.OutboundGroup)
  32. return isGroup
  33. }), func(it adapter.Outbound) *badjson.JSONObject {
  34. return proxyInfo(server, it)
  35. })
  36. render.JSON(w, r, render.M{
  37. "proxies": groups,
  38. })
  39. }
  40. }
  41. func getGroup(server *Server) func(w http.ResponseWriter, r *http.Request) {
  42. return func(w http.ResponseWriter, r *http.Request) {
  43. proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
  44. if _, ok := proxy.(adapter.OutboundGroup); ok {
  45. render.JSON(w, r, proxyInfo(server, proxy))
  46. return
  47. }
  48. render.Status(r, http.StatusNotFound)
  49. render.JSON(w, r, ErrNotFound)
  50. }
  51. }
  52. func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
  53. return func(w http.ResponseWriter, r *http.Request) {
  54. proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
  55. outboundGroup, ok := proxy.(adapter.OutboundGroup)
  56. if !ok {
  57. render.Status(r, http.StatusNotFound)
  58. render.JSON(w, r, ErrNotFound)
  59. return
  60. }
  61. query := r.URL.Query()
  62. url := query.Get("url")
  63. if strings.HasPrefix(url, "http://") {
  64. url = ""
  65. }
  66. timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32)
  67. if err != nil {
  68. render.Status(r, http.StatusBadRequest)
  69. render.JSON(w, r, ErrBadRequest)
  70. return
  71. }
  72. ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*time.Duration(timeout))
  73. defer cancel()
  74. var result map[string]uint16
  75. if urlTestGroup, isURLTestGroup := outboundGroup.(adapter.URLTestGroup); isURLTestGroup {
  76. result, err = urlTestGroup.URLTest(ctx)
  77. } else {
  78. outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
  79. itOutbound, _ := server.outbound.Outbound(it)
  80. return itOutbound
  81. }))
  82. b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
  83. checked := make(map[string]bool)
  84. result = make(map[string]uint16)
  85. var resultAccess sync.Mutex
  86. for _, detour := range outbounds {
  87. tag := detour.Tag()
  88. realTag := group.RealTag(detour)
  89. if checked[realTag] {
  90. continue
  91. }
  92. checked[realTag] = true
  93. p, loaded := server.outbound.Outbound(realTag)
  94. if !loaded {
  95. continue
  96. }
  97. b.Go(realTag, func() (any, error) {
  98. t, err := urltest.URLTest(ctx, url, p)
  99. if err != nil {
  100. server.logger.Debug("outbound ", tag, " unavailable: ", err)
  101. server.urlTestHistory.DeleteURLTestHistory(realTag)
  102. } else {
  103. server.logger.Debug("outbound ", tag, " available: ", t, "ms")
  104. server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{
  105. Time: time.Now(),
  106. Delay: t,
  107. })
  108. resultAccess.Lock()
  109. result[tag] = t
  110. resultAccess.Unlock()
  111. }
  112. return nil, nil
  113. })
  114. }
  115. b.Wait()
  116. }
  117. if err != nil {
  118. render.Status(r, http.StatusGatewayTimeout)
  119. render.JSON(w, r, newError(err.Error()))
  120. return
  121. }
  122. render.JSON(w, r, result)
  123. }
  124. }