github.com/chwjbn/xclash@v0.2.0/hub/route/server.go (about) 1 package route 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "net" 7 "net/http" 8 "strings" 9 "time" 10 11 C "github.com/chwjbn/xclash/constant" 12 _ "github.com/chwjbn/xclash/constant/mime" 13 "github.com/chwjbn/xclash/log" 14 "github.com/chwjbn/xclash/tunnel/statistic" 15 16 "github.com/go-chi/chi/v5" 17 "github.com/go-chi/cors" 18 "github.com/go-chi/render" 19 "github.com/gorilla/websocket" 20 ) 21 22 var ( 23 serverSecret = "" 24 serverAddr = "" 25 26 uiPath = "" 27 28 upgrader = websocket.Upgrader{ 29 CheckOrigin: func(r *http.Request) bool { 30 return true 31 }, 32 } 33 ) 34 35 type Traffic struct { 36 Up int64 `json:"up"` 37 Down int64 `json:"down"` 38 } 39 40 func SetUIPath(path string) { 41 uiPath = C.Path.Resolve(path) 42 } 43 44 func Start(addr string, secret string) { 45 if serverAddr != "" { 46 return 47 } 48 49 serverAddr = addr 50 serverSecret = secret 51 52 r := chi.NewRouter() 53 54 cors := cors.New(cors.Options{ 55 AllowedOrigins: []string{"*"}, 56 AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, 57 AllowedHeaders: []string{"Content-Type", "Authorization"}, 58 MaxAge: 300, 59 }) 60 61 r.Use(cors.Handler) 62 r.Group(func(r chi.Router) { 63 r.Use(authentication) 64 65 r.Get("/", hello) 66 r.Get("/logs", getLogs) 67 r.Get("/traffic", traffic) 68 r.Get("/version", version) 69 r.Mount("/configs", configRouter()) 70 r.Mount("/proxies", proxyRouter()) 71 r.Mount("/rules", ruleRouter()) 72 r.Mount("/connections", connectionRouter()) 73 r.Mount("/providers/proxies", proxyProviderRouter()) 74 }) 75 76 if uiPath != "" { 77 r.Group(func(r chi.Router) { 78 fs := http.StripPrefix("/ui", http.FileServer(http.Dir(uiPath))) 79 r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP) 80 r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) { 81 fs.ServeHTTP(w, r) 82 }) 83 }) 84 } 85 86 l, err := net.Listen("tcp", addr) 87 if err != nil { 88 log.Errorln("External controller listen error: %s", err) 89 return 90 } 91 serverAddr = l.Addr().String() 92 log.Infoln("RESTful API listening at: %s", serverAddr) 93 if err = http.Serve(l, r); err != nil { 94 log.Errorln("External controller serve error: %s", err) 95 } 96 } 97 98 func authentication(next http.Handler) http.Handler { 99 fn := func(w http.ResponseWriter, r *http.Request) { 100 if serverSecret == "" { 101 next.ServeHTTP(w, r) 102 return 103 } 104 105 // Browser websocket not support custom header 106 if websocket.IsWebSocketUpgrade(r) && r.URL.Query().Get("token") != "" { 107 token := r.URL.Query().Get("token") 108 if token != serverSecret { 109 render.Status(r, http.StatusUnauthorized) 110 render.JSON(w, r, ErrUnauthorized) 111 return 112 } 113 next.ServeHTTP(w, r) 114 return 115 } 116 117 header := r.Header.Get("Authorization") 118 text := strings.SplitN(header, " ", 2) 119 120 hasInvalidHeader := text[0] != "Bearer" 121 hasInvalidSecret := len(text) != 2 || text[1] != serverSecret 122 if hasInvalidHeader || hasInvalidSecret { 123 render.Status(r, http.StatusUnauthorized) 124 render.JSON(w, r, ErrUnauthorized) 125 return 126 } 127 next.ServeHTTP(w, r) 128 } 129 return http.HandlerFunc(fn) 130 } 131 132 func hello(w http.ResponseWriter, r *http.Request) { 133 render.JSON(w, r, render.M{"hello": "clash"}) 134 } 135 136 func traffic(w http.ResponseWriter, r *http.Request) { 137 var wsConn *websocket.Conn 138 if websocket.IsWebSocketUpgrade(r) { 139 var err error 140 wsConn, err = upgrader.Upgrade(w, r, nil) 141 if err != nil { 142 return 143 } 144 } 145 146 if wsConn == nil { 147 w.Header().Set("Content-Type", "application/json") 148 render.Status(r, http.StatusOK) 149 } 150 151 tick := time.NewTicker(time.Second) 152 defer tick.Stop() 153 t := statistic.DefaultManager 154 buf := &bytes.Buffer{} 155 var err error 156 for range tick.C { 157 buf.Reset() 158 up, down := t.Now() 159 if err := json.NewEncoder(buf).Encode(Traffic{ 160 Up: up, 161 Down: down, 162 }); err != nil { 163 break 164 } 165 166 if wsConn == nil { 167 _, err = w.Write(buf.Bytes()) 168 w.(http.Flusher).Flush() 169 } else { 170 err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes()) 171 } 172 173 if err != nil { 174 break 175 } 176 } 177 } 178 179 type Log struct { 180 Type string `json:"type"` 181 Payload string `json:"payload"` 182 } 183 184 func getLogs(w http.ResponseWriter, r *http.Request) { 185 levelText := r.URL.Query().Get("level") 186 if levelText == "" { 187 levelText = "info" 188 } 189 190 level, ok := log.LogLevelMapping[levelText] 191 if !ok { 192 render.Status(r, http.StatusBadRequest) 193 render.JSON(w, r, ErrBadRequest) 194 return 195 } 196 197 var wsConn *websocket.Conn 198 if websocket.IsWebSocketUpgrade(r) { 199 var err error 200 wsConn, err = upgrader.Upgrade(w, r, nil) 201 if err != nil { 202 return 203 } 204 } 205 206 if wsConn == nil { 207 w.Header().Set("Content-Type", "application/json") 208 render.Status(r, http.StatusOK) 209 } 210 211 sub := log.Subscribe() 212 defer log.UnSubscribe(sub) 213 buf := &bytes.Buffer{} 214 var err error 215 for elm := range sub { 216 buf.Reset() 217 log := elm.(*log.Event) 218 if log.LogLevel < level { 219 continue 220 } 221 222 if err := json.NewEncoder(buf).Encode(Log{ 223 Type: log.Type(), 224 Payload: log.Payload, 225 }); err != nil { 226 break 227 } 228 229 if wsConn == nil { 230 _, err = w.Write(buf.Bytes()) 231 w.(http.Flusher).Flush() 232 } else { 233 err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes()) 234 } 235 236 if err != nil { 237 break 238 } 239 } 240 } 241 242 func version(w http.ResponseWriter, r *http.Request) { 243 render.JSON(w, r, render.M{"version": C.Version}) 244 }