github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/experimental/clashapi/proxies.go (about) 1 package clashapi 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "sort" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/inazumav/sing-box/adapter" 13 "github.com/inazumav/sing-box/common/badjson" 14 "github.com/inazumav/sing-box/common/urltest" 15 C "github.com/inazumav/sing-box/constant" 16 "github.com/inazumav/sing-box/outbound" 17 "github.com/sagernet/sing/common" 18 F "github.com/sagernet/sing/common/format" 19 N "github.com/sagernet/sing/common/network" 20 21 "github.com/go-chi/chi/v5" 22 "github.com/go-chi/render" 23 ) 24 25 func proxyRouter(server *Server, router adapter.Router) http.Handler { 26 r := chi.NewRouter() 27 r.Get("/", getProxies(server, router)) 28 29 r.Route("/{name}", func(r chi.Router) { 30 r.Use(parseProxyName, findProxyByName(router)) 31 r.Get("/", getProxy(server)) 32 r.Get("/delay", getProxyDelay(server)) 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(router adapter.Router) func(next http.Handler) http.Handler { 47 return func(next http.Handler) http.Handler { 48 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 49 name := r.Context().Value(CtxKeyProxyName).(string) 50 proxy, exist := router.Outbound(name) 51 if !exist { 52 render.Status(r, http.StatusNotFound) 53 render.JSON(w, r, ErrNotFound) 54 return 55 } 56 ctx := context.WithValue(r.Context(), CtxKeyProxy, proxy) 57 next.ServeHTTP(w, r.WithContext(ctx)) 58 }) 59 } 60 } 61 62 func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject { 63 var info badjson.JSONObject 64 var clashType string 65 switch detour.Type() { 66 case C.TypeBlock: 67 clashType = "Reject" 68 default: 69 clashType = C.ProxyDisplayName(detour.Type()) 70 } 71 info.Put("type", clashType) 72 info.Put("name", detour.Tag()) 73 info.Put("udp", common.Contains(detour.Network(), N.NetworkUDP)) 74 delayHistory := server.urlTestHistory.LoadURLTestHistory(adapter.OutboundTag(detour)) 75 if delayHistory != nil { 76 info.Put("history", []*urltest.History{delayHistory}) 77 } else { 78 info.Put("history", []*urltest.History{}) 79 } 80 if group, isGroup := detour.(adapter.OutboundGroup); isGroup { 81 info.Put("now", group.Now()) 82 info.Put("all", group.All()) 83 } 84 return &info 85 } 86 87 func getProxies(server *Server, router adapter.Router) func(w http.ResponseWriter, r *http.Request) { 88 return func(w http.ResponseWriter, r *http.Request) { 89 var proxyMap badjson.JSONObject 90 outbounds := common.Filter(router.Outbounds(), func(detour adapter.Outbound) bool { 91 return detour.Tag() != "" 92 }) 93 94 allProxies := make([]string, 0, len(outbounds)) 95 96 for _, detour := range outbounds { 97 switch detour.Type() { 98 case C.TypeDirect, C.TypeBlock, C.TypeDNS: 99 continue 100 } 101 allProxies = append(allProxies, detour.Tag()) 102 } 103 104 defaultTag := router.DefaultOutbound(N.NetworkTCP).Tag() 105 if defaultTag == "" { 106 defaultTag = allProxies[0] 107 } 108 109 sort.SliceStable(allProxies, func(i, j int) bool { 110 return allProxies[i] == defaultTag 111 }) 112 113 // fix clash dashboard 114 proxyMap.Put("GLOBAL", map[string]any{ 115 "type": "Fallback", 116 "name": "GLOBAL", 117 "udp": true, 118 "history": []*urltest.History{}, 119 "all": allProxies, 120 "now": defaultTag, 121 }) 122 123 for i, detour := range outbounds { 124 var tag string 125 if detour.Tag() == "" { 126 tag = F.ToString(i) 127 } else { 128 tag = detour.Tag() 129 } 130 proxyMap.Put(tag, proxyInfo(server, detour)) 131 } 132 var responseMap badjson.JSONObject 133 responseMap.Put("proxies", &proxyMap) 134 response, err := responseMap.MarshalJSON() 135 if err != nil { 136 render.Status(r, http.StatusInternalServerError) 137 render.JSON(w, r, newError(err.Error())) 138 return 139 } 140 w.Write(response) 141 } 142 } 143 144 func getProxy(server *Server) func(w http.ResponseWriter, r *http.Request) { 145 return func(w http.ResponseWriter, r *http.Request) { 146 proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound) 147 response, err := proxyInfo(server, proxy).MarshalJSON() 148 if err != nil { 149 render.Status(r, http.StatusInternalServerError) 150 render.JSON(w, r, newError(err.Error())) 151 return 152 } 153 w.Write(response) 154 } 155 } 156 157 type UpdateProxyRequest struct { 158 Name string `json:"name"` 159 } 160 161 func updateProxy(w http.ResponseWriter, r *http.Request) { 162 req := UpdateProxyRequest{} 163 if err := render.DecodeJSON(r.Body, &req); err != nil { 164 render.Status(r, http.StatusBadRequest) 165 render.JSON(w, r, ErrBadRequest) 166 return 167 } 168 169 proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound) 170 selector, ok := proxy.(*outbound.Selector) 171 if !ok { 172 render.Status(r, http.StatusBadRequest) 173 render.JSON(w, r, newError("Must be a Selector")) 174 return 175 } 176 177 if !selector.SelectOutbound(req.Name) { 178 render.Status(r, http.StatusBadRequest) 179 render.JSON(w, r, newError(fmt.Sprintf("Selector update error: not found"))) 180 return 181 } 182 183 render.NoContent(w, r) 184 } 185 186 func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request) { 187 return func(w http.ResponseWriter, r *http.Request) { 188 query := r.URL.Query() 189 url := query.Get("url") 190 if strings.HasPrefix(url, "http://") { 191 url = "" 192 } 193 timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16) 194 if err != nil { 195 render.Status(r, http.StatusBadRequest) 196 render.JSON(w, r, ErrBadRequest) 197 return 198 } 199 200 proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound) 201 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout)) 202 defer cancel() 203 204 delay, err := urltest.URLTest(ctx, url, proxy) 205 defer func() { 206 realTag := outbound.RealTag(proxy) 207 if err != nil { 208 server.urlTestHistory.DeleteURLTestHistory(realTag) 209 } else { 210 server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{ 211 Time: time.Now(), 212 Delay: delay, 213 }) 214 } 215 }() 216 217 if ctx.Err() != nil { 218 render.Status(r, http.StatusGatewayTimeout) 219 render.JSON(w, r, ErrRequestTimeout) 220 return 221 } 222 223 if err != nil || delay == 0 { 224 render.Status(r, http.StatusServiceUnavailable) 225 render.JSON(w, r, newError("An error occurred in the delay test")) 226 return 227 } 228 229 render.JSON(w, r, render.M{ 230 "delay": delay, 231 }) 232 } 233 }