🧪 Add SSE test tools

This commit is contained in:
LittleSheep 2024-01-26 13:07:42 +08:00
parent 97df54a315
commit 450250c419
11 changed files with 138 additions and 25 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
/config
/letsencrypt /letsencrypt
.DS_Store .DS_Store

View File

@ -1,9 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4"> <module type="WEB_MODULE" version="4">
<component name="FacetManager">
<facet type="Python" name="Python facet">
<configuration sdkName="Python 3.9" />
</facet>
</component>
<component name="Go" enabled="true" /> <component name="Go" enabled="true" />
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
</component> </component>
</module> </module>

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.9" />
</component>
</project>

28
config/example.toml Normal file
View File

@ -0,0 +1,28 @@
id = "example-region"
[[locations]]
id = "example-sse"
host = ["localhost:8000"]
path = ["/sse"]
[[locations.destinations]]
id = "example-sse-destination"
uri = "http://localhost:5000?sse=enable"
[[locations.transformers]]
type = "replacePath"
options = { pattern = "/sse", value = "" }
[[locations]]
id = "example-websocket"
host = ["localhost:8000"]
path = ["/ws"]
[[locations.destinations]]
id = "example-websocket-destination"
uri = "http://localhost:8765"
[[locations]]
id = "example-location"
host = ["localhost:8000"]
path = ["/"]
[[locations.destinations]]
id = "example-destination"
uri = "files://test/data"

View File

@ -3,34 +3,44 @@ package navi
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/fasthttp/websocket"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/proxy"
"github.com/gofiber/fiber/v2/utils"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
"github.com/spf13/viper"
"github.com/valyala/fasthttp"
"io/fs" "io/fs"
"net/http" "net/http"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/fasthttp/websocket"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/proxy"
"github.com/gofiber/fiber/v2/utils"
"github.com/samber/lo"
"github.com/spf13/viper"
"github.com/valyala/fasthttp"
) )
func makeHypertextResponse(c *fiber.Ctx, dest *Destination) error { func makeUnifiedResponse(c *fiber.Ctx, dest *Destination) error {
if websocket.FastHTTPIsWebSocketUpgrade(c.Context()) { if websocket.FastHTTPIsWebSocketUpgrade(c.Context()) {
// Handle websocket // Handle websocket
return makeWebsocketResponse(c, dest) return makeWebsocketResponse(c, dest)
} else { } else {
// Handle normal request _, queries := dest.GetRawUri()
if len(queries.Get("sse")) > 0 {
// Handle server-side event
return makeSeverSideEventResponse(c, dest)
} else {
// Handle normal http request
return makeHypertextResponse(c, dest)
}
}
}
func makeHypertextResponse(c *fiber.Ctx, dest *Destination) error {
timeout := time.Duration(viper.GetInt64("performance.network_timeout")) * time.Millisecond timeout := time.Duration(viper.GetInt64("performance.network_timeout")) * time.Millisecond
return proxy.Do(c, dest.MakeUri(c), &fasthttp.Client{ return proxy.Do(c, dest.MakeUri(c), &fasthttp.Client{
ReadTimeout: timeout, ReadTimeout: timeout,
WriteTimeout: timeout, WriteTimeout: timeout,
}) })
}
} }
var wsUpgrader = websocket.FastHTTPUpgrader{} var wsUpgrader = websocket.FastHTTPUpgrader{}
@ -58,7 +68,7 @@ func makeWebsocketResponse(c *fiber.Ctx, dest *Destination) error {
for { for {
mode, message, err := remote.ReadMessage() mode, message, err := remote.ReadMessage()
if err != nil { if err != nil {
fmt.Println(err) log.Warn().Err(err).Msg("An error occurred during the websocket proxying...")
return return
} else { } else {
signal <- struct { signal <- struct {
@ -88,6 +98,11 @@ func makeWebsocketResponse(c *fiber.Ctx, dest *Destination) error {
}) })
} }
func makeSeverSideEventResponse(c *fiber.Ctx, dest *Destination) error {
// TODO Impl SSE with https://github.com/gofiber/recipes/blob/master/sse/main.go
return fiber.NewError(fiber.StatusNotImplemented, "Server-side-events was not available now.")
}
func makeFileResponse(c *fiber.Ctx, dest *Destination) error { func makeFileResponse(c *fiber.Ctx, dest *Destination) error {
uri, queries := dest.GetRawUri() uri, queries := dest.GetRawUri()
root := http.Dir(uri) root := http.Dir(uri)

View File

@ -14,7 +14,7 @@ type RoadApp struct {
func (v *RoadApp) Forward(ctx *fiber.Ctx, dest *Destination) error { func (v *RoadApp) Forward(ctx *fiber.Ctx, dest *Destination) error {
switch dest.GetType() { switch dest.GetType() {
case DestinationHypertext: case DestinationHypertext:
return makeHypertextResponse(ctx, dest) return makeUnifiedResponse(ctx, dest)
case DestinationStaticFile: case DestinationStaticFile:
return makeFileResponse(ctx, dest) return makeFileResponse(ctx, dest)
default: default:

View File

@ -41,8 +41,12 @@ type Destination struct {
Transformers []transformers.TransformerConfig `json:"transformers" toml:"transformers"` Transformers []transformers.TransformerConfig `json:"transformers" toml:"transformers"`
} }
func (v *Destination) GetProtocol() string {
return strings.SplitN(v.Uri, "://", 2)[0]
}
func (v *Destination) GetType() DestinationType { func (v *Destination) GetType() DestinationType {
protocol := strings.SplitN(v.Uri, "://", 2)[0] protocol := v.GetProtocol()
switch protocol { switch protocol {
case "http", "https": case "http", "https":
return DestinationHypertext return DestinationHypertext
@ -56,7 +60,7 @@ func (v *Destination) GetRawUri() (string, url.Values) {
uri := strings.SplitN(v.Uri, "://", 2)[1] uri := strings.SplitN(v.Uri, "://", 2)[1]
data := strings.SplitN(uri, "?", 2) data := strings.SplitN(uri, "?", 2)
data = append(data, " ") // Make data array least have two element data = append(data, " ") // Make data array least have two element
qs, _ := url.ParseQuery(data[0]) qs, _ := url.ParseQuery(data[1])
return data[0], qs return data[0], qs
} }
@ -71,8 +75,9 @@ func (v *Destination) MakeUri(ctx *fiber.Ctx) string {
path := string(ctx.Request().URI().Path()) path := string(ctx.Request().URI().Path())
hash := string(ctx.Request().URI().Hash()) hash := string(ctx.Request().URI().Hash())
uri, _ := v.GetRawUri()
return v.Uri + path + return uri + path +
lo.Ternary(len(queries) > 0, "?"+strings.Join(queries, "&"), "") + lo.Ternary(len(queries) > 0, "?"+strings.Join(queries, "&"), "") +
lo.Ternary(len(hash) > 0, "#"+hash, "") lo.Ternary(len(hash) > 0, "#"+hash, "")
} }

View File

@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Solid + TS</title> <title>RoadSign Sideload</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

27
test/data/sse/index.html Normal file
View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSE Example</title>
</head>
<body>
<h1>Server-Sent Events Example</h1>
<div id="sse-data"></div>
<script>
const eventSource = new EventSource("/sse")
const sseDataElement = document.getElementById("sse-data")
eventSource.onmessage = (event) => {
sseDataElement.innerText = `Data from server: ${event.data}`
}
eventSource.onerror = (error) => {
console.error("EventSource failed:", error)
eventSource.close()
}
</script>
</body>
</html>

28
test/data/sse/server.py Normal file
View File

@ -0,0 +1,28 @@
import time
from flask import Flask, render_template, Response
app = Flask(__name__, template_folder=".")
# Generator function to simulate real-time updates
def event_stream():
count = 0
while True:
time.sleep(1)
count += 1
yield f"data: {count}\n\n"
@app.route('/')
def index():
return render_template('index.html')
@app.route('/sse')
def sse():
return Response(event_stream(), content_type='text/event-stream')
if __name__ == '__main__':
app.run(debug=True, threaded=True)