diff --git a/.vscode/settings.json b/.vscode/settings.json index ebf40f7..b0046b6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,10 @@ { - "cSpell.words": ["apihub", "spahandler"] + "cSpell.words": [ + "apihub", + "appender", + "livereloader", + "onclose", + "spahandler", + "Upgrader" + ] } diff --git a/go.mod b/go.mod index 820f3de..4d833dd 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.apihub24.de/admin/spa-handler go 1.22.0 + +require github.com/gorilla/websocket v1.5.3 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..25a9fc4 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= diff --git a/handler.go b/handler.go index 4f0bff3..975932e 100644 --- a/handler.go +++ b/handler.go @@ -9,8 +9,10 @@ import ( ) type handler struct { - staticFS embed.FS - indexPath string + staticFS embed.FS + indexPath string + addLiveReloadScript bool + clientVersion string } func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -28,7 +30,11 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } defer indexFile.Close() - tryToDeliverFile(indexFile, h.indexPath, w, r) + 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) @@ -37,14 +43,34 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { tryToDeliverFile(f, h.indexPath, w, r) } -func HandleFiles(path string, options HandlerOptions) { +func HandleFiles(path string, options HandlerOptions, mux *http.ServeMux) { if options.AdditionalMimeTypeMapping != nil { for key, value := range options.AdditionalMimeTypeMapping { contentTypeMapping[key] = value } } - http.Handle(path, handler{ - staticFS: options.Files, - indexPath: options.IndexPath, - }) + if mux == nil { + http.Handle(path, handler{ + staticFS: options.Files, + indexPath: options.IndexPath, + addLiveReloadScript: options.ActivateLiveReloading, + clientVersion: options.ClientVersion, + }) + if options.ActivateLiveReloading { + mux.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, + }) + } + } } diff --git a/helper.go b/helper.go index b6ba5f0..994a8d9 100644 --- a/helper.go +++ b/helper.go @@ -1,19 +1,27 @@ package spa_handler import ( + "bytes" "io" "io/fs" "net/http" "strings" + "time" ) -func tryToDeliverFile(file fs.File, path string, w http.ResponseWriter, r *http.Request) { - fileSeeker, ok := file.(io.ReadSeeker) - if !ok { +func tryToDeliverFile(file fs.File, path string, w http.ResponseWriter, r *http.Request, appender ...string) { + content, err := io.ReadAll(file) + if err != nil { file.Close() - http.Error(w, "Index file does not implement ReadSeeker", http.StatusInternalServerError) + http.Error(w, "can not read file", http.StatusInternalServerError) return } + modified := []byte{} + for _, a := range appender { + modified = append(modified, []byte(a)...) + } + modified = append(modified, content...) + stats, statErr := file.Stat() if statErr != nil { http.Error(w, "Cannot stat index file", http.StatusInternalServerError) @@ -24,8 +32,10 @@ func tryToDeliverFile(file fs.File, path string, w http.ResponseWriter, r *http. contentType = "text/html" } contentType = getContentTypeDetail(contentType, stats) + + fileSeeker := bytes.NewReader(modified) w.Header().Set("Content-Type", contentType) - http.ServeContent(w, r, path, stats.ModTime(), fileSeeker) + http.ServeContent(w, r, path, time.Now(), fileSeeker) } func detectContentType(file fs.File) (string, error) { @@ -44,4 +54,4 @@ func getContentTypeDetail(contentType string, info fs.FileInfo) string { } } return contentType -} \ No newline at end of file +} diff --git a/options.go b/options.go index 04ff6aa..cf201c0 100644 --- a/options.go +++ b/options.go @@ -5,5 +5,7 @@ import "embed" type HandlerOptions struct { Files embed.FS IndexPath string + ClientVersion string + ActivateLiveReloading bool AdditionalMimeTypeMapping map[string]string } diff --git a/reloader.go b/reloader.go new file mode 100644 index 0000000..1f98633 --- /dev/null +++ b/reloader.go @@ -0,0 +1,74 @@ +package spa_handler + +import ( + "fmt" + "net/http" + + "github.com/gorilla/websocket" +) + +func clientScript(clientVersion string) string { + return fmt.Sprintf(` + + + +`, 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 + } + } +}