Skip to content
This repository was archived by the owner on Sep 29, 2018. It is now read-only.

Commit 4082413

Browse files
committed
Basic WebSocket API Support
1 parent f94c47e commit 4082413

File tree

3 files changed

+200
-8
lines changed

3 files changed

+200
-8
lines changed

main.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ type runCmd struct {
8585
link string
8686
prompt string
8787
websocket string
88+
token string
8889
}
8990

9091
func (*runCmd) Name() string {
@@ -96,7 +97,7 @@ func (*runCmd) Synopsis() string {
9697
}
9798

9899
func (*runCmd) Usage() string {
99-
return "run [-bin] [-data] [-link] [-prompt] [-websocket]\n\tRun Minecraft Server\n"
100+
return "run [-bin] [-data] [-link] [-prompt] [-websocket] [-token]\n\tRun Minecraft Server\n"
100101
}
101102

102103
func (c *runCmd) SetFlags(f *flag.FlagSet) {
@@ -105,6 +106,7 @@ func (c *runCmd) SetFlags(f *flag.FlagSet) {
105106
f.StringVar(&c.link, "link", "games", "World Link Path")
106107
f.StringVar(&c.prompt, "prompt", "{{esc}}[0;36;1mmcpe:{{esc}}[22m//{{username}}@{{hostname}}$ {{esc}}[33;4m", "Prompt String Template")
107108
f.StringVar(&c.websocket, "websocket", "", "WebSocket Server Port(Disabled If Blank)")
109+
f.StringVar(&c.token, "token", "", "WebSocket Server Token(Random If Blank)")
108110
}
109111

110112
func (c *runCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) (ret subcommands.ExitStatus) {
@@ -118,7 +120,7 @@ func (c *runCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) (
118120
c.link, _ = filepath.Abs(c.link)
119121
c.bin, _ = filepath.Abs(c.bin)
120122
prepare(c.data, c.link)
121-
run(c.bin, c.data, c.websocket, fasttemplate.New(c.prompt, "{{", "}}"))
123+
run(c.bin, c.data, fasttemplate.New(c.prompt, "{{", "}}"), c.websocket, c.token)
122124
return subcommands.ExitSuccess
123125
}
124126

run.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"strings"
1212

1313
"github.com/chzyer/readline"
14-
"github.com/gorilla/websocket"
1514
"github.com/kr/pty"
1615
"github.com/valyala/fasttemplate"
1716
)
@@ -54,7 +53,7 @@ func packOutput(input io.Reader, output func(string)) {
5453
if err != nil {
5554
break
5655
}
57-
output(strings.TrimRight(replacer.Replace(line), "\n"))
56+
output(strings.TrimRight(line, "\n"))
5857
}
5958
}
6059

@@ -76,12 +75,14 @@ func runImpl(base string, datapath string) (*os.File, func()) {
7675
}
7776
}
7877

79-
var upgrader = websocket.Upgrader{}
80-
81-
func run(base string, datapath string, ws string, prompt *fasttemplate.Template) {
78+
func run(base string, datapath string, prompt *fasttemplate.Template, ws string, token string) {
8279
f, stop := runImpl(base, datapath)
8380
defer f.Close()
8481
defer stop()
82+
exec := make(chan string)
83+
defer close(exec)
84+
boardcast, stopWs := newWs(exec, ws, token)
85+
defer stopWs()
8586
username := "nobody"
8687
hostname := "mcpeserver"
8788
{
@@ -118,11 +119,23 @@ func run(base string, datapath string, ws string, prompt *fasttemplate.Template)
118119
cache := 0
119120
go packOutput(f, func(text string) {
120121
if cache == 0 {
121-
fmt.Fprintf(lw, "\033[0m%s\033[0m\n", text)
122+
boardcast(text)
123+
fmt.Fprintf(lw, "\033[0m%s\033[0m\n", replacer.Replace(text))
122124
} else {
125+
boardcast(fmt.Sprintf("input: %s", text))
123126
cache--
124127
}
125128
})
129+
go func() {
130+
for {
131+
cmd, ok := <-exec
132+
if ok {
133+
fmt.Fprintf(f, "%s\n", cmd)
134+
} else {
135+
break
136+
}
137+
}
138+
}()
126139
for {
127140
line, err := rl.Readline()
128141
if err == readline.ErrInterrupt {

ws.go

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"log"
7+
"net/http"
8+
"time"
9+
10+
"github.com/gorilla/websocket"
11+
"github.com/thanhpk/randstr"
12+
)
13+
14+
var upgrader = websocket.Upgrader{
15+
CheckOrigin: func(r *http.Request) bool {
16+
return true
17+
},
18+
}
19+
20+
type hub struct {
21+
clients map[*client]bool
22+
broadcast chan []byte
23+
register chan *client
24+
unregister chan *client
25+
exec chan string
26+
shutdown chan struct{}
27+
}
28+
29+
type client struct {
30+
hub *hub
31+
conn *websocket.Conn
32+
send chan []byte
33+
}
34+
35+
func newHub(exec chan string) *hub {
36+
return &hub{
37+
broadcast: make(chan []byte),
38+
register: make(chan *client),
39+
unregister: make(chan *client),
40+
clients: make(map[*client]bool),
41+
exec: exec,
42+
shutdown: make(chan struct{}),
43+
}
44+
}
45+
46+
func (h *hub) run() {
47+
for {
48+
select {
49+
case client := <-h.register:
50+
h.clients[client] = true
51+
case client := <-h.unregister:
52+
if _, ok := h.clients[client]; ok {
53+
delete(h.clients, client)
54+
close(client.send)
55+
}
56+
case message := <-h.broadcast:
57+
for client := range h.clients {
58+
select {
59+
case client.send <- message:
60+
default:
61+
close(client.send)
62+
delete(h.clients, client)
63+
}
64+
}
65+
case <-h.shutdown:
66+
for client := range h.clients {
67+
close(client.send)
68+
delete(h.clients, client)
69+
}
70+
}
71+
}
72+
}
73+
74+
const (
75+
writeWait = 10 * time.Second
76+
pongWait = 60 * time.Second
77+
pingPeriod = (pongWait * 9) / 10
78+
maxMessageSize = 512
79+
)
80+
81+
var (
82+
newline = []byte{'\n'}
83+
space = []byte{' '}
84+
)
85+
86+
func (c *client) readPump() {
87+
defer func() {
88+
c.hub.unregister <- c
89+
c.conn.Close()
90+
}()
91+
c.conn.SetReadLimit(maxMessageSize)
92+
c.conn.SetReadDeadline(time.Now().Add(pongWait))
93+
c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
94+
for {
95+
_, message, err := c.conn.ReadMessage()
96+
if err != nil {
97+
break
98+
}
99+
message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
100+
c.hub.broadcast <- message
101+
c.hub.exec <- string(message)
102+
}
103+
}
104+
105+
func (c *client) writePump() {
106+
ticker := time.NewTicker(pingPeriod)
107+
defer func() {
108+
ticker.Stop()
109+
c.conn.Close()
110+
}()
111+
for {
112+
select {
113+
case message, ok := <-c.send:
114+
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
115+
if !ok {
116+
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
117+
return
118+
}
119+
w, err := c.conn.NextWriter(websocket.TextMessage)
120+
if err != nil {
121+
return
122+
}
123+
w.Write(message)
124+
n := len(c.send)
125+
for i := 0; i < n; i++ {
126+
w.Write(newline)
127+
w.Write(<-c.send)
128+
}
129+
if err := w.Close(); err != nil {
130+
return
131+
}
132+
case <-ticker.C:
133+
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
134+
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
135+
return
136+
}
137+
}
138+
}
139+
}
140+
141+
func serveWs(hub *hub, w http.ResponseWriter, r *http.Request) {
142+
conn, err := upgrader.Upgrade(w, r, nil)
143+
if err != nil {
144+
log.Println(err)
145+
return
146+
}
147+
client := &client{hub: hub, conn: conn, send: make(chan []byte, 256)}
148+
client.hub.register <- client
149+
150+
go client.writePump()
151+
go client.readPump()
152+
}
153+
154+
func newWs(exec chan string, addr string, token string) (func(string), func()) {
155+
if addr == "" {
156+
return func(ignore string) {}, func() {}
157+
}
158+
printInfo("WebSocket Enabled.")
159+
realtoken := token
160+
if token == "" {
161+
realtoken := randstr.Hex(64)
162+
printPair("WebSocket Token", realtoken)
163+
}
164+
hub := newHub(exec)
165+
go hub.run()
166+
http.HandleFunc(fmt.Sprintf("/%s", realtoken), func(w http.ResponseWriter, r *http.Request) {
167+
serveWs(hub, w, r)
168+
})
169+
srv := http.Server{Addr: addr}
170+
go srv.ListenAndServe()
171+
return func(out string) {
172+
hub.broadcast <- []byte(out)
173+
}, func() {
174+
srv.Shutdown(nil)
175+
hub.shutdown <- struct{}{}
176+
}
177+
}

0 commit comments

Comments
 (0)