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  }