Fixing and optimisation

This commit is contained in:
Alexei Anoshenko 2026-07-28 12:26:03 +03:00
parent 938432d738
commit fb5582892e
7 changed files with 159 additions and 102 deletions

View File

@ -12,6 +12,7 @@ import (
"net/http" "net/http"
"os/exec" "os/exec"
"runtime" "runtime"
"slices"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -43,6 +44,7 @@ type application struct {
params AppParams params AppParams
createContentFunc func(Session) SessionContent createContentFunc func(Session) SessionContent
sessions map[int]sessionInfo sessions map[int]sessionInfo
usedSessionIds []int
} }
func (app *application) getStartPage() string { func (app *application) getStartPage() string {
@ -50,7 +52,7 @@ func (app *application) getStartPage() string {
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
buffer.WriteString("<!DOCTYPE html>\n<html>\n") buffer.WriteString("<!DOCTYPE html>\n<html>\n")
getStartPage(buffer, app.params) getStartPage(buffer, app.nextSessionID(), app.params)
buffer.WriteString("\n</html>") buffer.WriteString("\n</html>")
return buffer.String() return buffer.String()
} }
@ -80,14 +82,23 @@ func (app *application) Finish() {
} }
} }
func (app *application) getCreateContentFunc() func(Session) SessionContent {
return app.createContentFunc
}
func (app *application) nextSessionID() int { func (app *application) nextSessionID() int {
n := rand.Intn(0x7FFFFFFE) + 1 if app.usedSessionIds == nil {
_, ok := app.sessions[n] app.usedSessionIds = []int{}
for ok { }
n = rand.Intn(0x7FFFFFFE) + 1
_, ok = app.sessions[n] for {
n := rand.Intn(0x7FFFFFFE) + 1
if !slices.Contains(app.usedSessionIds, n) {
app.usedSessionIds = append(app.usedSessionIds, n)
slices.Sort(app.usedSessionIds)
return n
}
} }
return n
} }
func (app *application) removeSession(id int) { func (app *application) removeSession(id int) {
@ -166,77 +177,95 @@ func getSessionIDCookie(req *http.Request) (int, error) {
func (app *application) postHandler(w http.ResponseWriter, req *http.Request) { func (app *application) postHandler(w http.ResponseWriter, req *http.Request) {
if reqBody, err := io.ReadAll(req.Body); err == nil { reqBody, err := io.ReadAll(req.Body)
message := string(reqBody) if err != nil {
ErrorLog(err.Error())
return
}
if ProtocolInDebugLog { message := string(reqBody)
DebugLog(message) if ProtocolInDebugLog {
} DebugLog(message)
}
obj, err := ParseDataText(message) obj, err := ParseDataText(message)
if err != nil { if err != nil {
ErrorLog(err.Error()) ErrorLog(err.Error())
return
}
sessionID, ok := getSessionID(obj)
if !ok {
return
}
var session Session = nil
var response chan string = nil
if info, ok := app.sessions[sessionID]; ok && info.response != nil {
response = info.response
session = info.session
}
command := obj.Tag()
if session == nil || command == "startSession" {
events := make(chan DataObject, 1024)
bridge := createHttpBridge(req)
response = bridge.response
session = app.createSession(obj, events, bridge, response)
if session == nil {
return return
} }
var session Session = nil go sessionEventHandler(session, events, bridge)
var response chan string = nil
if cookie, err := req.Cookie("session"); err == nil { start := func() {
sessionID, err := strconv.Atoi(cookie.Value) startSession(session, app.createContentFunc)
if err != nil {
ErrorLog(err.Error())
} else if info, ok := app.sessions[sessionID]; ok && info.response != nil {
response = info.response
session = info.session
}
}
command := obj.Tag()
startSession := false
if session == nil || command == "startSession" {
events := make(chan DataObject, 1024)
bridge := createHttpBridge(req)
response = bridge.response
answer := ""
session, answer = app.startSession(obj, events, bridge, response)
bridge.writeMessage(answer)
session.onStart()
if command == "session-resume" {
session.onResume()
}
bridge.sendResponse() bridge.sendResponse()
setSessionIDCookie(w, session.ID())
startSession = true
go sessionEventHandler(session, events, bridge)
} }
go start()
}
if !startSession { switch command {
switch command {
case "nop":
session.sendResponse()
case "session-close": case "startSession":
session.onFinish() // do nothing
session.App().removeSession(session.ID())
return
default: case "nop":
if !session.handleAnswer(command, obj) { session.sendResponse()
session.addToEventsQueue(obj)
}
}
}
io.WriteString(w, <-response) case "session-close":
for len(response) > 0 { session.onFinish()
io.WriteString(w, <-response) session.App().removeSession(session.ID())
return
default:
if !session.handleAnswer(command, obj) {
session.addToEventsQueue(obj)
} }
} }
io.WriteString(w, <-response)
for len(response) > 0 {
io.WriteString(w, <-response)
}
}
func getSessionID(obj DataObject) (int, bool) {
sessionText, ok := obj.PropertyValue("session")
if !ok {
ErrorLog(`"session" key not found`)
return 0, false
}
sessionID, err := strconv.Atoi(sessionText)
if err != nil {
ErrorLog(`"session" key text strconv.Atoi error: ` + err.Error())
return 0, false
}
return sessionID, true
} }
func (app *application) socketReader(bridge *wsBridge) { func (app *application) socketReader(bridge *wsBridge) {
@ -262,33 +291,23 @@ func (app *application) socketReader(bridge *wsBridge) {
switch command := obj.Tag(); command { switch command := obj.Tag(); command {
case "startSession": case "startSession":
answer := "" if session = app.createSession(obj, events, bridge, nil); session != nil {
if session, answer = app.startSession(obj, events, bridge, nil); session != nil { go startSession(session, app.createContentFunc)
if !bridge.writeMessage(answer) {
return
}
session.onStart()
go sessionEventHandler(session, events, bridge) go sessionEventHandler(session, events, bridge)
} }
case "reconnect": case "reconnect":
session = nil session = nil
if sessionText, ok := obj.PropertyValue("session"); ok { if sessionID, ok := getSessionID(obj); ok {
if sessionID, err := strconv.Atoi(sessionText); err == nil { if info, ok := app.sessions[sessionID]; ok {
if info, ok := app.sessions[sessionID]; ok { session = info.session
session = info.session session.setBridge(events, bridge)
session.setBridge(events, bridge)
go sessionEventHandler(session, events, bridge) go sessionEventHandler(session, events, bridge)
session.onReconnect() session.onReconnect()
} else {
DebugLogF("Session #%d not exists", sessionID)
}
} else { } else {
ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error()) DebugLogF("Session #%d not exists", sessionID)
} }
} else {
ErrorLog(`"session" key not found`)
} }
if session == nil { if session == nil {
@ -327,37 +346,42 @@ func sessionEventHandler(session Session, events chan DataObject, bridge bridge)
session.onFinish() session.onFinish()
session.App().removeSession(session.ID()) session.App().removeSession(session.ID())
bridge.close() bridge.close()
return
default: default:
session.handleEvent(command, data) go session.handleEvent(command, data)
} }
} }
} }
func (app *application) startSession(params DataObject, events chan DataObject, func (app *application) createSession(params DataObject, events chan DataObject,
bridge bridge, response chan string) (Session, string) { bridge bridge, response chan string) Session {
if app.createContentFunc == nil { sessionID, ok := getSessionID(params)
return nil, "" if !ok || app.createContentFunc == nil {
return nil
} }
session := newSession(app, app.nextSessionID(), "", params) session := newSession(app, sessionID, "", params)
session.setBridge(events, bridge) session.setBridge(events, bridge)
if !session.setContent(app.createContentFunc(session)) {
return nil, ""
}
app.sessions[session.ID()] = sessionInfo{ app.sessions[sessionID] = sessionInfo{
session: session, session: session,
response: response, response: response,
} }
return session
}
func startSession(session Session, createContentFunc func(Session) SessionContent) {
if !session.setContent(createContentFunc(session)) {
return
}
answer := allocStringBuilder() answer := allocStringBuilder()
defer freeStringBuilder(answer) defer freeStringBuilder(answer)
answer.WriteString("sessionID = '")
answer.WriteString(strconv.Itoa(session.ID()))
answer.WriteString("';\n")
session.writeInitScript(answer) session.writeInitScript(answer)
answerText := answer.String() answerText := answer.String()
@ -365,7 +389,9 @@ func (app *application) startSession(params DataObject, events chan DataObject,
DebugLog("Start session:") DebugLog("Start session:")
DebugLog(answerText) DebugLog(answerText)
} }
return session, answerText
session.writeScript(answerText)
session.onStart()
} }
var apps = []*application{} var apps = []*application{}

View File

@ -1,4 +1,4 @@
let sessionID = "0" //let sessionID = "0"
let windowFocus = true let windowFocus = true
window.onresize = function() { window.onresize = function() {
@ -21,7 +21,7 @@ function reloadPage() {
function sessionInfo() { function sessionInfo() {
const touch_screen = (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) ? "1" : "0"; const touch_screen = (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) ? "1" : "0";
let message = "startSession{touch=" + touch_screen let message = "startSession{session=" + sessionID + ",touch=" + touch_screen
const style = window.getComputedStyle(document.body); const style = window.getComputedStyle(document.body);
if (style) { if (style) {

View File

@ -2,6 +2,7 @@ package rui
import ( import (
_ "embed" _ "embed"
"strconv"
"strings" "strings"
) )
@ -23,6 +24,7 @@ type Application interface {
Params() AppParams Params() AppParams
removeSession(id int) removeSession(id int)
getCreateContentFunc() func(Session) SessionContent
} }
// AppParams defines parameters of the app // AppParams defines parameters of the app
@ -66,7 +68,7 @@ type AppParams struct {
GoogleFonts string GoogleFonts string
} }
func getStartPage(buffer *strings.Builder, params AppParams) { func getStartPage(buffer *strings.Builder, sessionID int, params AppParams) {
buffer.WriteString(`<head> buffer.WriteString(`<head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>`) <title>`)
@ -104,7 +106,12 @@ func getStartPage(buffer *strings.Builder, params AppParams) {
buffer.WriteString(appStyles) buffer.WriteString(appStyles)
buffer.WriteString(`</style> buffer.WriteString(`</style>
<style id="ruiAnimations"></style> <style id="ruiAnimations"></style>
<script src="/script.js"></script> <script>
const sessionID = `)
buffer.WriteString(strconv.Itoa(sessionID))
buffer.WriteString(`;
</script>
<script src="/script.js"></script>
</head> </head>
<body id="body" onkeydown="keyDownEvent(this, event)"> <body id="body" onkeydown="keyDownEvent(this, event)">
<div class="ruiRoot" id="ruiRootView"></div> <div class="ruiRoot" id="ruiRootView"></div>

View File

@ -139,7 +139,7 @@ func (storage *clientStorageData) handleEvent(command string, data DataObject) {
} }
delete(storage.getResult, request) delete(storage.getResult, request)
text, ok := data.PropertyValue("error") text, ok := data.PropertyValue("values")
if !ok { if !ok {
ErrorLog("'values' property not found (command: storageValues)") ErrorLog("'values' property not found (command: storageValues)")
return return

View File

@ -11,6 +11,7 @@ import (
) )
type bridge interface { type bridge interface {
writeScript(script string)
startUpdateScript(htmlID string) bool startUpdateScript(htmlID string) bool
finishUpdateScript(htmlID string) finishUpdateScript(htmlID string)
callFunc(funcName string, args ...any) bool callFunc(funcName string, args ...any) bool
@ -175,6 +176,7 @@ type Session interface {
updateCSSProperty(htmlID, property, value string) updateCSSProperty(htmlID, property, value string)
updateProperty(htmlID, property string, value any) updateProperty(htmlID, property string, value any)
removeProperty(htmlID, property string) removeProperty(htmlID, property string)
writeScript(script string)
startUpdateScript(htmlID string) bool startUpdateScript(htmlID string) bool
finishUpdateScript(htmlID string) finishUpdateScript(htmlID string)
sendResponse() sendResponse()
@ -508,6 +510,14 @@ func (session *sessionData) removeProperty(htmlID, property string) {
} }
} }
func (session *sessionData) writeScript(script string) {
if session.bridge != nil {
session.bridge.writeScript(script)
} else {
ErrorLog("No connection")
}
}
func (session *sessionData) startUpdateScript(htmlID string) bool { func (session *sessionData) startUpdateScript(htmlID string) bool {
if session.bridge != nil { if session.bridge != nil {
return session.bridge.startUpdateScript(htmlID) return session.bridge.startUpdateScript(htmlID)

View File

@ -25,6 +25,16 @@ func createWasmBridge(close chan DataObject) bridge {
return bridge return bridge
} }
func (bridge *wasmBridge) writeScript(script string) {
if ProtocolInDebugLog {
DebugLog("Run script:")
DebugLog(script)
}
window := js.Global().Get("window")
window.Call("execScript", script)
}
func (bridge *wasmBridge) startUpdateScript(htmlID string) bool { func (bridge *wasmBridge) startUpdateScript(htmlID string) bool {
return false return false
} }

View File

@ -110,6 +110,10 @@ func (bridge *webBridge) initBridge() {
bridge.updateScripts = map[string]*strings.Builder{} bridge.updateScripts = map[string]*strings.Builder{}
} }
func (bridge *webBridge) writeScript(script string) {
bridge.writeMessage(script)
}
func (bridge *webBridge) startUpdateScript(htmlID string) bool { func (bridge *webBridge) startUpdateScript(htmlID string) bool {
if _, ok := bridge.updateScripts[htmlID]; ok { if _, ok := bridge.updateScripts[htmlID]; ok {
return false return false