Skip to content

Commit 79e5338

Browse files
committed
Add: external controller
1 parent bc4ca28 commit 79e5338

File tree

5 files changed

+120
-1
lines changed

5 files changed

+120
-1
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ Unfortunately, there is no native elegant way to implement golang's daemon.
2828

2929
So we can use third-party daemon tools like pm2, supervisor, and so on.
3030

31-
In the case of pm2, we can start the daemon this way:
31+
In the case of [pm2](https://github.com/Unitech/pm2), we can start the daemon this way:
3232

3333
```sh
3434
pm2 start clash
3535
```
3636

37+
If you have Docker installed, you can run clash directly using `docker-compose`.
38+
39+
[Run clash in docker](https://github.com/Dreamacro/clash/wiki/Run-clash-in-docker)
40+
3741
## Config
3842

3943
Configuration file at `$HOME/.config/clash/config.ini`
@@ -45,6 +49,9 @@ Below is a simple demo configuration file:
4549
port = 7890
4650
socks-port = 7891
4751

52+
# A RESTful API for clash
53+
external-controller = 127.0.0.1:8080
54+
4855
[Proxy]
4956
# name = ss, server, port, cipher, password
5057
# The types of cipher are consistent with go-shadowsocks2

hub/server.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package hub
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"time"
7+
8+
"github.com/Dreamacro/clash/tunnel"
9+
10+
"github.com/go-chi/chi"
11+
"github.com/go-chi/render"
12+
log "github.com/sirupsen/logrus"
13+
)
14+
15+
var (
16+
tun = tunnel.GetInstance()
17+
)
18+
19+
type Traffic struct {
20+
Up int64 `json:"up"`
21+
Down int64 `json:"down"`
22+
}
23+
24+
type Log struct {
25+
Type string `json:"type"`
26+
Payload string `json:"payload"`
27+
}
28+
29+
type Error struct {
30+
Error string `json:"error"`
31+
}
32+
33+
func NewHub(addr string) {
34+
r := chi.NewRouter()
35+
36+
r.Get("/traffic", traffic)
37+
r.Get("/logs", getLogs)
38+
39+
err := http.ListenAndServe(addr, r)
40+
if err != nil {
41+
log.Fatalf("External controller error: %s", err.Error())
42+
}
43+
}
44+
45+
func traffic(w http.ResponseWriter, r *http.Request) {
46+
render.Status(r, http.StatusOK)
47+
48+
tick := time.NewTicker(time.Second)
49+
t := tun.Traffic()
50+
for range tick.C {
51+
up, down := t.Now()
52+
if err := json.NewEncoder(w).Encode(Traffic{
53+
Up: up,
54+
Down: down,
55+
}); err != nil {
56+
break
57+
}
58+
w.(http.Flusher).Flush()
59+
}
60+
}
61+
62+
func getLogs(w http.ResponseWriter, r *http.Request) {
63+
src := tun.Log()
64+
sub, err := src.Subscribe()
65+
defer src.UnSubscribe(sub)
66+
if err != nil {
67+
render.Status(r, http.StatusInternalServerError)
68+
render.JSON(w, r, Error{
69+
Error: err.Error(),
70+
})
71+
}
72+
render.Status(r, http.StatusOK)
73+
for elm := range sub {
74+
log := elm.(tunnel.Log)
75+
if err := json.NewEncoder(w).Encode(Log{
76+
Type: log.Type(),
77+
Payload: log.Payload,
78+
}); err != nil {
79+
break
80+
}
81+
w.(http.Flusher).Flush()
82+
}
83+
}

main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"syscall"
77

88
C "github.com/Dreamacro/clash/constant"
9+
"github.com/Dreamacro/clash/hub"
910
"github.com/Dreamacro/clash/proxy/http"
1011
"github.com/Dreamacro/clash/proxy/socks"
1112
"github.com/Dreamacro/clash/tunnel"
@@ -37,6 +38,11 @@ func main() {
3738
go http.NewHttpProxy(port)
3839
go socks.NewSocksProxy(socksPort)
3940

41+
// Hub
42+
if key, err := section.GetKey("external-controller"); err == nil {
43+
go hub.NewHub(key.Value())
44+
}
45+
4046
sigCh := make(chan os.Signal, 1)
4147
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
4248
<-sigCh

tunnel/log.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,21 @@ type Log struct {
2020
Payload string
2121
}
2222

23+
func (l *Log) Type() string {
24+
switch l.LogType {
25+
case INFO:
26+
return "Info"
27+
case WARNING:
28+
return "Warning"
29+
case ERROR:
30+
return "Error"
31+
case DEBUG:
32+
return "Debug"
33+
default:
34+
return "Unknow"
35+
}
36+
}
37+
2338
func print(data Log) {
2439
switch data.LogType {
2540
case INFO:

tunnel/tunnel.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ func (t *Tunnel) Add(req C.ServerAdapter) {
3434
t.queue.In() <- req
3535
}
3636

37+
func (t *Tunnel) Traffic() *C.Traffic {
38+
return t.traffic
39+
}
40+
41+
func (t *Tunnel) Log() *observable.Observable {
42+
return t.observable
43+
}
44+
3745
func (t *Tunnel) UpdateConfig() (err error) {
3846
cfg, err := C.GetConfig()
3947
if err != nil {

0 commit comments

Comments
 (0)