Implement library support for Angular-style single page applications

This commit is contained in:
Laurence Withers 2020-02-15 12:26:01 +00:00
parent f08165b0f1
commit d5b4fcf0be
2 changed files with 60 additions and 2 deletions

View File

@ -28,3 +28,35 @@ else will be ignored.
The interaction between range handling and compression also seems a little The interaction between range handling and compression also seems a little
ill-defined; as we have pre-compressed data, however, we can consistently ill-defined; as we have pre-compressed data, however, we can consistently
serve the exact same byte data for compressed files. 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"`).

View File

@ -72,6 +72,7 @@ type Handler struct {
dir map[string]*packed.File dir map[string]*packed.File
headers map[string]string headers map[string]string
startTime time.Time startTime time.Time
notFound *packed.File
} }
// SetHeader allows a custom header to be set on HTTP responses. These are // 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 // 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 // anything else returning a 405. Exact path matches are required, else a 404 is
// returned. // returned.
@ -130,8 +153,11 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
info := h.dir[path.Clean(req.URL.Path)] info := h.dir[path.Clean(req.URL.Path)]
if info == nil { if info == nil {
http.NotFound(w, req) if h.notFound == nil {
return http.NotFound(w, req)
return
}
info = h.notFound
} }
// set standard headers // set standard headers