🧪 Add SSE test tools
This commit is contained in:
parent
97df54a315
commit
450250c419
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
/config
|
||||
/letsencrypt
|
||||
|
||||
.DS_Store
|
@ -1,9 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
|
||||
</component>
|
||||
</module>
|
6
.idea/misc.xml
Normal file
6
.idea/misc.xml
Normal 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
28
config/example.toml
Normal 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"
|
@ -3,35 +3,45 @@ package navi
|
||||
import (
|
||||
"errors"
|
||||
"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"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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()) {
|
||||
// Handle websocket
|
||||
return makeWebsocketResponse(c, dest)
|
||||
} 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
|
||||
return proxy.Do(c, dest.MakeUri(c), &fasthttp.Client{
|
||||
ReadTimeout: timeout,
|
||||
WriteTimeout: timeout,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var wsUpgrader = websocket.FastHTTPUpgrader{}
|
||||
|
||||
@ -58,7 +68,7 @@ func makeWebsocketResponse(c *fiber.Ctx, dest *Destination) error {
|
||||
for {
|
||||
mode, message, err := remote.ReadMessage()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
log.Warn().Err(err).Msg("An error occurred during the websocket proxying...")
|
||||
return
|
||||
} else {
|
||||
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 {
|
||||
uri, queries := dest.GetRawUri()
|
||||
root := http.Dir(uri)
|
||||
|
@ -14,7 +14,7 @@ type RoadApp struct {
|
||||
func (v *RoadApp) Forward(ctx *fiber.Ctx, dest *Destination) error {
|
||||
switch dest.GetType() {
|
||||
case DestinationHypertext:
|
||||
return makeHypertextResponse(ctx, dest)
|
||||
return makeUnifiedResponse(ctx, dest)
|
||||
case DestinationStaticFile:
|
||||
return makeFileResponse(ctx, dest)
|
||||
default:
|
||||
|
@ -41,8 +41,12 @@ type Destination struct {
|
||||
Transformers []transformers.TransformerConfig `json:"transformers" toml:"transformers"`
|
||||
}
|
||||
|
||||
func (v *Destination) GetProtocol() string {
|
||||
return strings.SplitN(v.Uri, "://", 2)[0]
|
||||
}
|
||||
|
||||
func (v *Destination) GetType() DestinationType {
|
||||
protocol := strings.SplitN(v.Uri, "://", 2)[0]
|
||||
protocol := v.GetProtocol()
|
||||
switch protocol {
|
||||
case "http", "https":
|
||||
return DestinationHypertext
|
||||
@ -56,7 +60,7 @@ func (v *Destination) GetRawUri() (string, url.Values) {
|
||||
uri := strings.SplitN(v.Uri, "://", 2)[1]
|
||||
data := strings.SplitN(uri, "?", 2)
|
||||
data = append(data, " ") // Make data array least have two element
|
||||
qs, _ := url.ParseQuery(data[0])
|
||||
qs, _ := url.ParseQuery(data[1])
|
||||
|
||||
return data[0], qs
|
||||
}
|
||||
@ -71,8 +75,9 @@ func (v *Destination) MakeUri(ctx *fiber.Ctx) string {
|
||||
|
||||
path := string(ctx.Request().URI().Path())
|
||||
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(hash) > 0, "#"+hash, "")
|
||||
}
|
||||
|
@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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" />
|
||||
<title>Vite + Solid + TS</title>
|
||||
<title>RoadSign Sideload</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
@ -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
27
test/data/sse/index.html
Normal 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
28
test/data/sse/server.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user