Compare commits
No commits in common. "main" and "v0.1.1" have entirely different histories.
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@ -1,10 +1,3 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"apihub",
|
||||
"appender",
|
||||
"livereloader",
|
||||
"onclose",
|
||||
"spahandler",
|
||||
"Upgrader"
|
||||
]
|
||||
"cSpell.words": ["apihub", "spahandler"]
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@ -1,5 +1,3 @@
|
||||
module git.apihub24.de/admin/spa-handler
|
||||
|
||||
go 1.22.0
|
||||
|
||||
require github.com/gorilla/websocket v1.5.3
|
||||
|
||||
2
go.sum
2
go.sum
@ -1,2 +0,0 @@
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
76
handler.go
76
handler.go
@ -1,76 +0,0 @@
|
||||
package spa_handler
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
staticFS embed.FS
|
||||
indexPath string
|
||||
addLiveReloadScript bool
|
||||
clientVersion string
|
||||
}
|
||||
|
||||
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
reqPath := r.URL.Path
|
||||
lookupPath := path.Dir(h.indexPath)
|
||||
f, err := h.staticFS.Open(fmt.Sprintf("%s%s", lookupPath, reqPath))
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
indexFile, indexErr := h.staticFS.Open(h.indexPath)
|
||||
if indexErr != nil {
|
||||
http.Error(w, "Index file not found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer indexFile.Close()
|
||||
if h.addLiveReloadScript {
|
||||
tryToDeliverFile(indexFile, h.indexPath, w, r, clientScript(h.clientVersion))
|
||||
} else {
|
||||
tryToDeliverFile(indexFile, h.indexPath, w, r)
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
tryToDeliverFile(f, h.indexPath, w, r)
|
||||
}
|
||||
|
||||
func HandleFiles(path string, options HandlerOptions, mux *http.ServeMux) {
|
||||
if options.AdditionalMimeTypeMapping != nil {
|
||||
for key, value := range options.AdditionalMimeTypeMapping {
|
||||
contentTypeMapping[key] = value
|
||||
}
|
||||
}
|
||||
if mux == nil {
|
||||
http.Handle(path, handler{
|
||||
staticFS: options.Files,
|
||||
indexPath: options.IndexPath,
|
||||
addLiveReloadScript: options.ActivateLiveReloading,
|
||||
clientVersion: options.ClientVersion,
|
||||
})
|
||||
if options.ActivateLiveReloading {
|
||||
http.Handle("/_livereloader_", reloader{
|
||||
clientVersion: options.ClientVersion,
|
||||
})
|
||||
}
|
||||
return
|
||||
} else {
|
||||
mux.Handle(path, handler{
|
||||
staticFS: options.Files,
|
||||
indexPath: options.IndexPath,
|
||||
})
|
||||
if options.ActivateLiveReloading {
|
||||
mux.Handle("/_livereloader_", reloader{
|
||||
clientVersion: options.ClientVersion,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
71
reloader.go
71
reloader.go
@ -1,71 +0,0 @@
|
||||
package spa_handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func clientScript(clientVersion string) string {
|
||||
return fmt.Sprintf(`
|
||||
<script type="application/javascript">
|
||||
// [spa_handler] live reload script
|
||||
function spaHandlerLiveReloaderConnect() {
|
||||
const ws = new WebSocket("/_livereloader_");
|
||||
ws.onopen = function() {
|
||||
console.info('[spa_handler]: live reloader connected!');
|
||||
};
|
||||
ws.onmessage = function(event) {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
if (message && message.clientVersion && message.clientVersion !== '%s') {
|
||||
console.info('[spa_handler]: receive reload');
|
||||
location.reload(true);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warning('[spa_handler]: something was wrong! ', err)
|
||||
}
|
||||
};
|
||||
ws.onclose = function() {
|
||||
console.info('[spa_handler]: connection closed');
|
||||
setTimeout(function() {
|
||||
console.info('[spa_handler]: try reconnect');
|
||||
spaHandlerLiveReloaderConnect();
|
||||
}, 5000);
|
||||
};
|
||||
ws.onerror = function(err) {
|
||||
console.warning('[spa_handler]: something was wrong with websocket connection! ', err)
|
||||
};
|
||||
}
|
||||
spaHandlerLiveReloaderConnect();
|
||||
</script>
|
||||
`, clientVersion)
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
type reloader struct {
|
||||
clientVersion string
|
||||
}
|
||||
|
||||
func (rel reloader) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
fmt.Println("Error upgrading:", err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
_ = conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("{\"clientVersion\":\"%s\"}", rel.clientVersion)))
|
||||
for {
|
||||
_, _, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
fmt.Println("Error reading message:", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
48
spa-handler/handler.go
Normal file
48
spa-handler/handler.go
Normal file
@ -0,0 +1,48 @@
|
||||
package spahandler
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
staticFS embed.FS
|
||||
indexPath string
|
||||
}
|
||||
|
||||
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
reqPath := r.URL.Path
|
||||
f, err := h.staticFS.Open(fmt.Sprintf("ng-client/browser%s", reqPath))
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
indexFile, indexErr := h.staticFS.Open(h.indexPath)
|
||||
if indexErr != nil {
|
||||
http.Error(w, "Index file not found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer indexFile.Close()
|
||||
tryToDeliverFile(indexFile, h.indexPath, w, r)
|
||||
return
|
||||
} else if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
tryToDeliverFile(f, h.indexPath, w, r)
|
||||
}
|
||||
|
||||
func HandleFiles(path string, options HandlerOptions) {
|
||||
if options.AdditionalMimeTypeMapping != nil {
|
||||
for key, value := range options.AdditionalMimeTypeMapping {
|
||||
contentTypeMapping[key] = value
|
||||
}
|
||||
}
|
||||
http.Handle(path, handler{
|
||||
staticFS: options.Files,
|
||||
indexPath: options.IndexPath,
|
||||
})
|
||||
}
|
||||
@ -1,28 +1,19 @@
|
||||
package spa_handler
|
||||
package spahandler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func tryToDeliverFile(file fs.File, path string, w http.ResponseWriter, r *http.Request, appender ...string) {
|
||||
content, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
func tryToDeliverFile(file fs.File, path string, w http.ResponseWriter, r *http.Request) {
|
||||
fileSeeker, ok := file.(io.ReadSeeker)
|
||||
if !ok {
|
||||
file.Close()
|
||||
http.Error(w, "can not read file", http.StatusInternalServerError)
|
||||
http.Error(w, "Index file does not implement ReadSeeker", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
merge := bytes.NewBuffer([]byte{})
|
||||
for _, a := range appender {
|
||||
merge.WriteString(a)
|
||||
}
|
||||
newContent := strings.Replace(string(content), "</html>", fmt.Sprintf("%s\n</html>", merge.String()), 1)
|
||||
|
||||
stats, statErr := file.Stat()
|
||||
if statErr != nil {
|
||||
http.Error(w, "Cannot stat index file", http.StatusInternalServerError)
|
||||
@ -33,10 +24,8 @@ func tryToDeliverFile(file fs.File, path string, w http.ResponseWriter, r *http.
|
||||
contentType = "text/html"
|
||||
}
|
||||
contentType = getContentTypeDetail(contentType, stats)
|
||||
|
||||
fileSeeker := bytes.NewReader([]byte(newContent))
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
http.ServeContent(w, r, path, time.Now(), fileSeeker)
|
||||
http.ServeContent(w, r, path, stats.ModTime(), fileSeeker)
|
||||
}
|
||||
|
||||
func detectContentType(file fs.File) (string, error) {
|
||||
@ -1,4 +1,4 @@
|
||||
package spa_handler
|
||||
package spahandler
|
||||
|
||||
var contentTypeMapping = map[string]string{
|
||||
".html": "text/html",
|
||||
@ -1,11 +1,9 @@
|
||||
package spa_handler
|
||||
package spahandler
|
||||
|
||||
import "embed"
|
||||
|
||||
type HandlerOptions struct {
|
||||
Files embed.FS
|
||||
IndexPath string
|
||||
ClientVersion string
|
||||
ActivateLiveReloading bool
|
||||
AdditionalMimeTypeMapping map[string]string
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user