Skip to content

Commit 47d038a

Browse files
authored
Dynamic target's path in WebSocket (#40)
* dynamic routing first attempt
1 parent b67bb74 commit 47d038a

File tree

10 files changed

+451
-7
lines changed

10 files changed

+451
-7
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ func ProxyHandler(ctx *fasthttp.RequestCtx) {
5959
var err error
6060
proxyServer, err = proxy.NewWSReverseProxyWith(
6161
proxy.WithURL_OptionWS("ws://localhost:8080/echo"),
62+
// [OPTIONAL]: you can override path from `WithURL_OptionWS`
63+
// by providing WithDynamicPath_OptionWS.
64+
proxy.WithDynamicPath_OptionWS(true, proxy.DefaultOverrideHeader),
6265
)
6366
if err != nil {
6467
panic(err)
@@ -67,6 +70,9 @@ func ProxyHandler(ctx *fasthttp.RequestCtx) {
6770

6871
switch string(ctx.Path()) {
6972
case "/echo":
73+
// [OPTIONAL]: you can override path from `WithURL_OptionWS`
74+
// by providing proxy.DefaultOverrideHeader (or any custom) header
75+
// ctx.Request.Header.Set(proxy.DefaultOverrideHeader, "/real_echo")
7076
proxyServer.ServeHTTP(ctx)
7177
case "/":
7278
fasthttp.ServeFileUncompressed(ctx, "./index.html")
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# ws-fasthttp-reverse-proxy
2+
3+
### get start demo
4+
Run main server and proxy server
5+
```sh
6+
go run server/main.go # listen on localhost:8080/echo
7+
go run ws_proxy.go # listen on localhost:8081/echo
8+
```
9+
10+
Run client examples: either check golang one
11+
```sh
12+
go run client/main.go # connect to localhost:8081/echo
13+
```
14+
or plain JS: open `index.html` file
15+
16+
### screenshots
17+
![shot1](./screenshot1.png)
18+
19+
![shot2](./screenshot2.png)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"log"
7+
"net/url"
8+
"os"
9+
"os/signal"
10+
"sync"
11+
"time"
12+
13+
"github.com/fasthttp/websocket"
14+
)
15+
16+
var addr = flag.String("addr", "localhost:8081", "http service address")
17+
18+
func openChat(url string, wg *sync.WaitGroup, interrupt chan os.Signal) {
19+
defer wg.Done()
20+
log.Printf("connecting to %s", url)
21+
c, _, err := websocket.DefaultDialer.Dial(url, nil)
22+
if err != nil {
23+
log.Fatal("dial:", err)
24+
}
25+
defer c.Close()
26+
done := make(chan struct{})
27+
go func() {
28+
defer close(done)
29+
for {
30+
_, message, err := c.ReadMessage()
31+
if err != nil {
32+
log.Println("read:", err)
33+
return
34+
}
35+
log.Printf("recv: %s", message)
36+
}
37+
}()
38+
ticker := time.NewTicker(time.Second)
39+
defer ticker.Stop()
40+
41+
for {
42+
select {
43+
case <-done:
44+
return
45+
case t := <-ticker.C:
46+
err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
47+
if err != nil {
48+
log.Println("write:", err)
49+
return
50+
}
51+
case <-interrupt:
52+
log.Println("interrupt")
53+
54+
// Cleanly close the connection by sending a close message and then
55+
// waiting (with timeout) for the server to close the connection.
56+
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
57+
if err != nil {
58+
log.Println("write close:", err)
59+
return
60+
}
61+
select {
62+
case <-done:
63+
case <-time.After(time.Second):
64+
}
65+
return
66+
}
67+
}
68+
}
69+
70+
func main() {
71+
flag.Parse()
72+
log.SetFlags(0)
73+
74+
interrupt := make(chan os.Signal, 1)
75+
signal.Notify(interrupt, os.Interrupt)
76+
77+
fruits := []string{
78+
"apples",
79+
"oranges",
80+
}
81+
82+
var wg sync.WaitGroup
83+
84+
for i := range fruits {
85+
wg.Add(1)
86+
fruitChat := fruits[i]
87+
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo", RawQuery: fmt.Sprintf("fruit=%s&q=secret", fruitChat)}
88+
go openChat(u.String(), &wg, interrupt)
89+
}
90+
wg.Wait()
91+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<script>
7+
window.addEventListener("load", function (evt) {
8+
var output = document.getElementById("output");
9+
var input = document.getElementById("input");
10+
var wsConnections = new Map();
11+
var lastWs;
12+
var print = function (message) {
13+
var d = document.createElement("div");
14+
d.innerHTML = message;
15+
output.appendChild(d);
16+
};
17+
const baseURL = 'ws://localhost:8081/echo'
18+
document.getElementById("open_apples").onclick = function (evt) {
19+
if (wsConnections.has('apples_chat')) {
20+
lastWs = 'apples_chat'
21+
return false;
22+
}
23+
// Proxy host
24+
// Change this to talk about either 'apples' or 'oranges'
25+
// 'q' field will be deleted by proxy server
26+
const ws = new WebSocket(`${baseURL}?fruit=apples&q=secret`);
27+
ws.onopen = function (evt) {
28+
print("👍 OPENED chat about apples");
29+
}
30+
ws.onclose = function (evt) {
31+
print("😂 CLOSED chat about apples");
32+
ws = null;
33+
}
34+
ws.onmessage = function (evt) {
35+
print("> " + evt.data);
36+
}
37+
ws.onerror = function (evt) {
38+
console.log(evt);
39+
print("[ERR]: " + evt.data);
40+
}
41+
wsConnections.set('apples_chat', ws)
42+
lastWs = 'apples_chat'
43+
return false;
44+
};
45+
document.getElementById("open_oranges").onclick = function (evt) {
46+
if (wsConnections.has('oranges_chat')) {
47+
lastWs = 'oranges_chat'
48+
return false;
49+
}
50+
// Proxy host
51+
// Change this to talk about either 'apples' or 'oranges'
52+
// 'q' field will be deleted by proxy server
53+
const ws = new WebSocket(`${baseURL}?fruit=oranges&q=secret`);
54+
ws.onopen = function (evt) {
55+
print("👍 OPENED chat about oranges");
56+
}
57+
ws.onclose = function (evt) {
58+
print("😂 CLOSED chat about oranges");
59+
ws = null;
60+
}
61+
ws.onmessage = function (evt) {
62+
print("> " + evt.data);
63+
}
64+
ws.onerror = function (evt) {
65+
console.log(evt);
66+
print("[ERR]: " + evt.data);
67+
}
68+
wsConnections.set('oranges_chat', ws)
69+
lastWs = 'oranges_chat'
70+
return false;
71+
};
72+
document.getElementById("send").onclick = function (evt) {
73+
const ws = wsConnections.get(lastWs)
74+
if (!ws) {
75+
return false;
76+
}
77+
print("$ " + input.value);
78+
ws.send(input.value);
79+
return false;
80+
};
81+
document.getElementById("close").onclick = function (evt) {
82+
const ws = wsConnections.get(lastWs)
83+
if (!ws) {
84+
return false;
85+
}
86+
print("$ Wait to close chat with id: " + lastWs);
87+
ws.close();
88+
wsConnections.delete(lastWs);
89+
return false;
90+
};
91+
});
92+
</script>
93+
</head>
94+
95+
<body>
96+
<table>
97+
<tr>
98+
<td valign="top" width="50%">
99+
<p>"Open" to create a connection to the server <br>
100+
"Send" to send a message to the server <br>
101+
"Close" to close the connection <br>
102+
You can change the message and send multiple times (to early activated connection).
103+
<p>
104+
<form>
105+
<button id="open_apples">Open chat about apples</button>
106+
<button id="open_oranges">Open chat about oranges</button>
107+
<button id="close">Close</button>
108+
<p><input id="input" type="text" value="Hello world!">
109+
<button id="send">Send</button>
110+
</form>
111+
</td>
112+
<td valign="top" width="50%">
113+
<div id="output"></div>
114+
</td>
115+
</tr>
116+
</table>
117+
</body>
118+
<style>
119+
#output {
120+
background-color: black;
121+
color: white;
122+
width: auto;
123+
height: 400px;
124+
overflow: auto;
125+
padding: 1em;
126+
}
127+
</style>
128+
129+
</html>
190 KB
Loading
60.1 KB
Loading
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"log"
7+
8+
"github.com/fasthttp/websocket"
9+
"github.com/valyala/fasthttp"
10+
)
11+
12+
var upgrader = websocket.FastHTTPUpgrader{
13+
CheckOrigin: func(ctx *fasthttp.RequestCtx) bool { return true },
14+
}
15+
16+
func applesView(ctx *fasthttp.RequestCtx) {
17+
err := upgrader.Upgrade(ctx, func(ws *websocket.Conn) {
18+
defer ws.Close()
19+
for {
20+
mt, message, err := ws.ReadMessage()
21+
if err != nil {
22+
log.Println("read error:", err)
23+
break
24+
}
25+
log.Printf("'applesView' recv: %s", message)
26+
log.Printf("'applesView' query args: %v", ctx.QueryArgs())
27+
err = ws.WriteMessage(mt, []byte("You are talking about apples"))
28+
if err != nil {
29+
log.Println("'applesView' write error:", err)
30+
break
31+
}
32+
}
33+
})
34+
if err != nil {
35+
if _, ok := err.(websocket.HandshakeError); ok {
36+
log.Println(err)
37+
}
38+
return
39+
}
40+
log.Println("'applesView' connection done")
41+
}
42+
43+
func orangesView(ctx *fasthttp.RequestCtx) {
44+
err := upgrader.Upgrade(ctx, func(ws *websocket.Conn) {
45+
defer ws.Close()
46+
for {
47+
mt, message, err := ws.ReadMessage()
48+
if err != nil {
49+
log.Println("read error:", err)
50+
break
51+
}
52+
log.Printf("'orangesView' recv: %s", message)
53+
log.Printf("'orangesView' query args: %v", ctx.QueryArgs())
54+
err = ws.WriteMessage(mt, []byte("You are talking about oranges"))
55+
if err != nil {
56+
log.Println("'orangesView' write error:", err)
57+
break
58+
}
59+
}
60+
})
61+
if err != nil {
62+
if _, ok := err.(websocket.HandshakeError); ok {
63+
log.Println(err)
64+
}
65+
return
66+
}
67+
log.Println("'orangesView' connection done")
68+
}
69+
70+
func main() {
71+
flag.Parse()
72+
log.SetFlags(0)
73+
74+
requestHandler := func(ctx *fasthttp.RequestCtx) {
75+
switch string(ctx.Path()) {
76+
case "/talk_about/apples":
77+
applesView(ctx)
78+
case "/talk_about/oranges":
79+
orangesView(ctx)
80+
default:
81+
ctx.Error("Unsupported path", fasthttp.StatusNotFound)
82+
}
83+
}
84+
85+
server := fasthttp.Server{
86+
Name: "EchoExample with dynamic routing",
87+
Handler: requestHandler,
88+
}
89+
90+
port := 8080
91+
log.Println("Starting main server on:", port)
92+
log.Fatal(server.ListenAndServe(fmt.Sprintf(":%d", port)))
93+
}

0 commit comments

Comments
 (0)