diff --git a/README.md b/README.md index 7bbcc4e0..afc41691 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,16 @@ Unfortunately, there is no native elegant way to implement golang's daemon. So we can use third-party daemon tools like pm2, supervisor, and so on. -In the case of pm2, we can start the daemon this way: +In the case of [pm2](https://github.com/Unitech/pm2), we can start the daemon this way: ```sh pm2 start clash ``` +If you have Docker installed, you can run clash directly using `docker-compose`. + +[Run clash in docker](https://github.com/Dreamacro/clash/wiki/Run-clash-in-docker) + ## Config Configuration file at `$HOME/.config/clash/config.ini` @@ -45,6 +49,9 @@ Below is a simple demo configuration file: port = 7890 socks-port = 7891 +# A RESTful API for clash +external-controller = 127.0.0.1:8080 + [Proxy] # name = ss, server, port, cipher, password # The types of cipher are consistent with go-shadowsocks2 diff --git a/hub/server.go b/hub/server.go new file mode 100644 index 00000000..1caad82a --- /dev/null +++ b/hub/server.go @@ -0,0 +1,83 @@ +package hub + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/Dreamacro/clash/tunnel" + + "github.com/go-chi/chi" + "github.com/go-chi/render" + log "github.com/sirupsen/logrus" +) + +var ( + tun = tunnel.GetInstance() +) + +type Traffic struct { + Up int64 `json:"up"` + Down int64 `json:"down"` +} + +type Log struct { + Type string `json:"type"` + Payload string `json:"payload"` +} + +type Error struct { + Error string `json:"error"` +} + +func NewHub(addr string) { + r := chi.NewRouter() + + r.Get("/traffic", traffic) + r.Get("/logs", getLogs) + + err := http.ListenAndServe(addr, r) + if err != nil { + log.Fatalf("External controller error: %s", err.Error()) + } +} + +func traffic(w http.ResponseWriter, r *http.Request) { + render.Status(r, http.StatusOK) + + tick := time.NewTicker(time.Second) + t := tun.Traffic() + for range tick.C { + up, down := t.Now() + if err := json.NewEncoder(w).Encode(Traffic{ + Up: up, + Down: down, + }); err != nil { + break + } + w.(http.Flusher).Flush() + } +} + +func getLogs(w http.ResponseWriter, r *http.Request) { + src := tun.Log() + sub, err := src.Subscribe() + defer src.UnSubscribe(sub) + if err != nil { + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, Error{ + Error: err.Error(), + }) + } + render.Status(r, http.StatusOK) + for elm := range sub { + log := elm.(tunnel.Log) + if err := json.NewEncoder(w).Encode(Log{ + Type: log.Type(), + Payload: log.Payload, + }); err != nil { + break + } + w.(http.Flusher).Flush() + } +} diff --git a/main.go b/main.go index f173b42b..8f409cba 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "syscall" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/hub" "github.com/Dreamacro/clash/proxy/http" "github.com/Dreamacro/clash/proxy/socks" "github.com/Dreamacro/clash/tunnel" @@ -37,6 +38,11 @@ func main() { go http.NewHttpProxy(port) go socks.NewSocksProxy(socksPort) + // Hub + if key, err := section.GetKey("external-controller"); err == nil { + go hub.NewHub(key.Value()) + } + sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) <-sigCh diff --git a/tunnel/log.go b/tunnel/log.go index 639b8838..a4fb6840 100644 --- a/tunnel/log.go +++ b/tunnel/log.go @@ -20,6 +20,21 @@ type Log struct { Payload string } +func (l *Log) Type() string { + switch l.LogType { + case INFO: + return "Info" + case WARNING: + return "Warning" + case ERROR: + return "Error" + case DEBUG: + return "Debug" + default: + return "Unknow" + } +} + func print(data Log) { switch data.LogType { case INFO: diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 57615cef..e2879101 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -34,6 +34,14 @@ func (t *Tunnel) Add(req C.ServerAdapter) { t.queue.In() <- req } +func (t *Tunnel) Traffic() *C.Traffic { + return t.traffic +} + +func (t *Tunnel) Log() *observable.Observable { + return t.observable +} + func (t *Tunnel) UpdateConfig() (err error) { cfg, err := C.GetConfig() if err != nil {