add spa fileserver to the web service (#3109)

* add spa fileserver to the web service
This commit is contained in:
Florian Schade
2022-02-23 19:31:24 +01:00
committed by GitHub
parent d92dc8951a
commit f519b4965e
5 changed files with 107 additions and 60 deletions

View File

@@ -0,0 +1,6 @@
Enhancement: Re-Enabling web cache control
We've re-enable browser caching headers (`Expires` and `Last-Modified`) for the web service, this was disabled due to a problem in the fileserver used before.
Since we're now using our own fileserver implementation this works again and is enabled by default.
https://github.com/owncloud/ocis/pull/3109

View File

@@ -0,0 +1,6 @@
Enhancement: Add SPA conform fileserver for web
We've added an SPA conform fileserver to the web service.
It enables web to use vue's history mode and behaves like nginx try_files.
https://github.com/owncloud/ocis/pull/3109

View File

@@ -32,6 +32,7 @@ func Groups(cfg *config.Config) *cli.Command {
tracing.Configure(cfg, logger)
gr := run.Group{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// pre-create folders
if cfg.Reva.Groups.Driver == "json" && cfg.Reva.Groups.JSON != "" {
@@ -40,9 +41,8 @@ func Groups(cfg *config.Config) *cli.Command {
}
}
uuid := uuid.Must(uuid.NewV4())
pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+uuid.String()+".pid")
defer cancel()
cuuid := uuid.Must(uuid.NewV4())
pidFile := path.Join(os.TempDir(), "revad-"+c.Command.Name+"-"+cuuid.String()+".pid")
rcfg := groupsConfigFromStruct(c, cfg)

80
web/pkg/assets/server.go Normal file
View File

@@ -0,0 +1,80 @@
package assets
import (
"bytes"
"golang.org/x/net/html"
"io"
"mime"
"net/http"
"path"
"path/filepath"
)
type fileServer struct {
root http.FileSystem
}
func FileServer(root http.FileSystem) http.Handler {
return &fileServer{root}
}
func (f *fileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
upath := path.Clean(path.Join("/", r.URL.Path))
r.URL.Path = upath
asset, err := f.root.Open(upath)
if err != nil {
r.URL.Path = "/index.html"
f.ServeHTTP(w, r)
return
}
defer asset.Close()
s, _ := asset.Stat()
if s.IsDir() {
r.URL.Path = "/index.html"
f.ServeHTTP(w, r)
return
}
w.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(s.Name())))
buf := new(bytes.Buffer)
switch s.Name() {
case "index.html", "oidc-callback.html", "oidc-silent-redirect.html":
_ = withBase(buf, asset, "/")
default:
_, _ = buf.ReadFrom(asset)
}
_, _ = w.Write(buf.Bytes())
}
func withBase(w io.Writer, r io.Reader, base string) error {
doc, _ := html.Parse(r)
var parse func(*html.Node)
parse = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "head" {
n.InsertBefore(&html.Node{
Type: html.ElementNode,
Data: "base",
Attr: []html.Attribute{
{
Key: "href",
Val: base,
},
},
}, n.FirstChild)
return
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
parse(c)
}
}
parse(doc)
return html.Render(w, doc)
}

View File

@@ -2,10 +2,12 @@ package svc
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
@@ -141,28 +143,19 @@ func (p Web) Static(ttl int) http.HandlerFunc {
if !strings.HasSuffix(rootWithSlash, "/") {
rootWithSlash = rootWithSlash + "/"
}
assets := assets.New(
assets.Logger(p.logger),
assets.Config(p.config),
)
notFoundFunc := func(w http.ResponseWriter, r *http.Request) {
// TODO: replace the redirect with a not found page containing a link to the Web UI
http.Redirect(w, r, rootWithSlash, http.StatusTemporaryRedirect)
}
static := http.StripPrefix(
rootWithSlash,
interceptNotFound(
http.FileServer(assets),
notFoundFunc,
assets.FileServer(
assets.New(
assets.Logger(p.logger),
assets.Config(p.config),
),
),
)
// TODO: investigate broken caching - https://github.com/owncloud/ocis/issues/1094
// we don't have a last modification date of the static assets, so we use the service start date
//lastModified := time.Now().UTC().Format(http.TimeFormat)
//expires := time.Now().Add(time.Second * time.Duration(ttl)).UTC().Format(http.TimeFormat)
lastModified := time.Now().UTC().Format(http.TimeFormat)
expires := time.Now().Add(time.Second * time.Duration(ttl)).UTC().Format(http.TimeFormat)
return func(w http.ResponseWriter, r *http.Request) {
if rootWithSlash != "/" && r.URL.Path == p.config.HTTP.Root {
@@ -175,49 +168,11 @@ func (p Web) Static(ttl int) http.HandlerFunc {
return
}
if r.URL.Path != rootWithSlash && strings.HasSuffix(r.URL.Path, "/") {
notFoundFunc(w, r)
return
}
// TODO: investigate broken caching - https://github.com/owncloud/ocis/issues/1094
//w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%s, must-revalidate", strconv.Itoa(ttl)))
//w.Header().Set("Expires", expires)
//w.Header().Set("Last-Modified", lastModified)
w.Header().Set("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value")
w.Header().Set("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%s, must-revalidate", strconv.Itoa(ttl)))
w.Header().Set("Expires", expires)
w.Header().Set("Last-Modified", lastModified)
w.Header().Set("SameSite", "Strict")
static.ServeHTTP(w, r)
}
}
func interceptNotFound(h http.Handler, notFoundFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
notFoundInterceptor := &NotFoundInterceptor{ResponseWriter: w}
h.ServeHTTP(notFoundInterceptor, r)
if notFoundInterceptor.status == http.StatusNotFound {
notFoundFunc(w, r)
}
}
}
type NotFoundInterceptor struct {
http.ResponseWriter
status int
}
func (w *NotFoundInterceptor) WriteHeader(status int) {
w.status = status
if status != http.StatusNotFound {
w.ResponseWriter.WriteHeader(status)
}
}
func (w *NotFoundInterceptor) Write(p []byte) (int, error) {
if w.status != http.StatusNotFound {
return w.ResponseWriter.Write(p)
}
return len(p), nil
}