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: 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
|
||||
|
||||
|
||||
|
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"
|
||||
|
||||
To build the web server, you'll need to install the Go compiler, and run `make`
|
||||
from the _xP_ directory. Then start it from the _public_ subdirectory,
|
||||
and navigate to the adress you gave it as its first argument--in the following
|
||||
example, that would be http://localhost:8080[]:
|
||||
To build the web server, install the Go compiler, and run `make`
|
||||
from the _xP_ directory. Then start the resulting binary, and navigate to
|
||||
the adress you give it as its first argument--in the following example,
|
||||
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,
|
||||
and some form of HTTP authentication. Pass the external URL of the WebSocket
|
||||
|
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
|
||||
|
||||
package main
|
||||
@ -6,12 +6,16 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"embed"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -23,7 +27,12 @@ import (
|
||||
)
|
||||
|
||||
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
|
||||
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>
|
||||
<html>
|
||||
<head>
|
||||
<title>xP</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<base href="{{ .Root }}/">
|
||||
<link rel="stylesheet" href="xP.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script src="mithril.js">
|
||||
</script>
|
||||
<script>
|
||||
let proxy = '{{ . }}'
|
||||
let proxy = '{{ .Proxy }}'
|
||||
</script>
|
||||
<script type="module" src="xP.js">
|
||||
</script>
|
||||
@ -262,20 +270,49 @@ var page = template.Must(template.New("/").Parse(`<!DOCTYPE html>
|
||||
</html>`))
|
||||
|
||||
func handleDefault(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
staticHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
wsURI := addressWS
|
||||
if wsURI == "" {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(flag.CommandLine.Output(),
|
||||
@ -294,6 +331,21 @@ func main() {
|
||||
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("/", http.HandlerFunc(handleDefault))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user