From d5b4fcf0be44db6a8d0b209d53fd80bb2293fee2 Mon Sep 17 00:00:00 2001 From: Laurence Withers Date: Sat, 15 Feb 2020 12:26:01 +0000 Subject: [PATCH] Implement library support for Angular-style single page applications --- README.md | 32 ++++++++++++++++++++++++++++++++ handler.go | 30 ++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f7f576f..9c38b39 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,35 @@ else will be ignored. The interaction between range handling and compression also seems a little ill-defined; as we have pre-compressed data, however, we can consistently serve the exact same byte data for compressed files. + +## Angular-style single-page application handling + +If you wish to support an angular.js-style single page application, in which +a Javascript application uses the browser's history API to create a set of +virtual paths ("routes"), it is necessary to somehow intercept HTTP 404 errors +being returned from the handler and instead return an HTTP 200 with an HTML +document. + +This can be achieved with a number of methods. + +If you have an nginx instance reverse proxying in front of `htpack`, then you +can use a couple of extra directives, for example: + + # prevent page loaded at "http://server.example/my-application" from + # requesting resources at "/*" when it should request them at + # "/my-application/*" instead + location = /my-application { + return 308 /my-application/; + } + + location /my-application/ { + proxy_to http://htpack-addr:8080/; + proxy_intercept_errors on; + error_page 404 =200 /my-application/; + } + +If you are using the handler as a library, then you may call +`handler.SetNotFound(filename)` to select a resource to return (with HTTP 200) +if a request is made for a resource that is not found. The filename must match +a packed resource, so it will be preceded with a `/` (for example it may be +`"/index.html"`). diff --git a/handler.go b/handler.go index a5782a3..92da9d4 100644 --- a/handler.go +++ b/handler.go @@ -72,6 +72,7 @@ type Handler struct { dir map[string]*packed.File headers map[string]string startTime time.Time + notFound *packed.File } // SetHeader allows a custom header to be set on HTTP responses. These are @@ -110,6 +111,28 @@ func (h *Handler) SetIndex(filename string) { } } +// SetNotFound allows overriding the returned resource when a request is made +// for a resource that does not exist. The default behaviour would be to return +// a standard HTTP 404 Not Found response; calling this function with an empty +// string will restore that behaviour. +// +// This function will return an error if the named resource is not present in +// the packfile. +func (h *Handler) SetNotFound(notFound string) error { + if notFound == "" { + h.notFound = nil + return nil + } + + notFound = path.Clean(notFound) + dir := h.dir[path.Clean(notFound)] + if dir == nil { + return fmt.Errorf("no such resource %q", notFound) + } + h.notFound = dir + return nil +} + // ServeHTTP handles requests for files. It supports GET and HEAD methods, with // anything else returning a 405. Exact path matches are required, else a 404 is // returned. @@ -130,8 +153,11 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { info := h.dir[path.Clean(req.URL.Path)] if info == nil { - http.NotFound(w, req) - return + if h.notFound == nil { + http.NotFound(w, req) + return + } + info = h.notFound } // set standard headers