xP: embed web resources, tame browser caching
This commit is contained in:
parent
80af5c22d6
commit
71e1a744c5
4
NEWS
4
NEWS
@ -7,6 +7,10 @@ Unreleased
|
|||||||
|
|
||||||
* xP: added a network lag indicator to the user interface
|
* xP: added a network lag indicator to the user interface
|
||||||
|
|
||||||
|
* xP: started embedding the necessary web resources,
|
||||||
|
and making sure that the files have unique paths after change,
|
||||||
|
so that stale copies are not cached by browsers indefinitely
|
||||||
|
|
||||||
* Bumped relay protocol version
|
* Bumped relay protocol version
|
||||||
|
|
||||||
|
|
||||||
|
10
README.adoc
10
README.adoc
@ -136,12 +136,12 @@ The precondition for running 'xC' frontends is enabling its relay interface:
|
|||||||
|
|
||||||
/set general.relay_bind = "127.0.0.1:9000"
|
/set general.relay_bind = "127.0.0.1:9000"
|
||||||
|
|
||||||
To build the web server, you'll need to install the Go compiler, and run `make`
|
To build the web server, install the Go compiler, and run `make`
|
||||||
from the _xP_ directory. Then start it from the _public_ subdirectory,
|
from the _xP_ directory. Then start the resulting binary, and navigate to
|
||||||
and navigate to the adress you gave it as its first argument--in the following
|
the adress you give it as its first argument--in the following example,
|
||||||
example, that would be http://localhost:8080[]:
|
that would be http://localhost:8080[]:
|
||||||
|
|
||||||
$ ../xP 127.0.0.1:8080 127.0.0.1:9000
|
$ ./xP 127.0.0.1:8080 127.0.0.1:9000
|
||||||
|
|
||||||
For remote use, it's recommended to put 'xP' behind a reverse proxy, with TLS,
|
For remote use, it's recommended to put 'xP' behind a reverse proxy, with TLS,
|
||||||
and some form of HTTP authentication. Pass the external URL of the WebSocket
|
and some form of HTTP authentication. Pass the external URL of the WebSocket
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module janouch.name/xK/xP
|
module janouch.name/xK/xP
|
||||||
|
|
||||||
go 1.21
|
go 1.22
|
||||||
|
|
||||||
toolchain go1.23.2
|
toolchain go1.23.2
|
||||||
|
|
||||||
|
74
xP/xP.go
74
xP/xP.go
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
|
// Copyright (c) 2022 - 2025, Přemysl Eric Janouch <p@janouch.name>
|
||||||
// SPDX-License-Identifier: 0BSD
|
// SPDX-License-Identifier: 0BSD
|
||||||
|
|
||||||
package main
|
package main
|
||||||
@ -6,12 +6,16 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha1"
|
||||||
|
"embed"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -23,7 +27,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
debug = flag.Bool("debug", false, "enable debug output")
|
debug = flag.Bool("debug", false, "enable debug output")
|
||||||
|
webRoot = flag.String("webroot", "", "override bundled web resources")
|
||||||
|
|
||||||
|
//go:embed public/*
|
||||||
|
webResources embed.FS
|
||||||
|
webResourcesHash string
|
||||||
|
|
||||||
addressBind string
|
addressBind string
|
||||||
addressConnect string
|
addressConnect string
|
||||||
@ -240,21 +249,20 @@ func handleWS(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
var staticHandler = http.FileServer(http.Dir("."))
|
|
||||||
|
|
||||||
var page = template.Must(template.New("/").Parse(`<!DOCTYPE html>
|
var page = template.Must(template.New("/").Parse(`<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>xP</title>
|
<title>xP</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<base href="{{ .Root }}/">
|
||||||
<link rel="stylesheet" href="xP.css" />
|
<link rel="stylesheet" href="xP.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="mithril.js">
|
<script src="mithril.js">
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
let proxy = '{{ . }}'
|
let proxy = '{{ .Proxy }}'
|
||||||
</script>
|
</script>
|
||||||
<script type="module" src="xP.js">
|
<script type="module" src="xP.js">
|
||||||
</script>
|
</script>
|
||||||
@ -262,20 +270,49 @@ var page = template.Must(template.New("/").Parse(`<!DOCTYPE html>
|
|||||||
</html>`))
|
</html>`))
|
||||||
|
|
||||||
func handleDefault(w http.ResponseWriter, r *http.Request) {
|
func handleDefault(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/" {
|
|
||||||
staticHandler.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wsURI := addressWS
|
wsURI := addressWS
|
||||||
if wsURI == "" {
|
if wsURI == "" {
|
||||||
wsURI = fmt.Sprintf("ws://%s/ws", r.Host)
|
wsURI = fmt.Sprintf("ws://%s/ws", r.Host)
|
||||||
}
|
}
|
||||||
if err := page.Execute(w, wsURI); err != nil {
|
|
||||||
|
args := struct {
|
||||||
|
Root string
|
||||||
|
Proxy string
|
||||||
|
}{
|
||||||
|
Root: webResourcesHash,
|
||||||
|
Proxy: wsURI,
|
||||||
|
}
|
||||||
|
if err := page.Execute(w, &args); err != nil {
|
||||||
log.Println("Template execution failed: " + err.Error())
|
log.Println("Template execution failed: " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hashFS(root fs.FS) []byte {
|
||||||
|
hasher := sha1.New()
|
||||||
|
callback := func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that this can be fooled.
|
||||||
|
fmt.Fprintln(hasher, path)
|
||||||
|
|
||||||
|
if !d.IsDir() {
|
||||||
|
file, err := root.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
io.Copy(hasher, file)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := fs.WalkDir(root, ".", callback); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
return hasher.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
fmt.Fprintf(flag.CommandLine.Output(),
|
fmt.Fprintf(flag.CommandLine.Output(),
|
||||||
@ -294,6 +331,21 @@ func main() {
|
|||||||
addressWS = flag.Arg(2)
|
addressWS = flag.Arg(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subResources, err := fs.Sub(webResources, "public")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
if *webRoot != "" {
|
||||||
|
subResources = os.DirFS(*webRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The simplest way of ensuring that web browsers don't use
|
||||||
|
// stale cached copies of our files.
|
||||||
|
webResourcesHash = hex.EncodeToString(hashFS(subResources))
|
||||||
|
http.Handle("/"+webResourcesHash+"/",
|
||||||
|
http.StripPrefix("/"+webResourcesHash+"/",
|
||||||
|
http.FileServerFS(subResources)))
|
||||||
|
|
||||||
http.Handle("/ws", http.HandlerFunc(handleWS))
|
http.Handle("/ws", http.HandlerFunc(handleWS))
|
||||||
http.Handle("/", http.HandlerFunc(handleDefault))
|
http.Handle("/", http.HandlerFunc(handleDefault))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user