Skip to content

Commit

Permalink
add spa fileserver to the web service (#3109)
Browse files Browse the repository at this point in the history
* add spa fileserver to the web service
  • Loading branch information
fschade authored Feb 23, 2022
1 parent d92dc89 commit f519b49
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 60 deletions.
6 changes: 6 additions & 0 deletions changelog/unreleased/enhancement-web-cache-control.md
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions changelog/unreleased/enhancement-web-spa-fileserver.md
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions storage/pkg/command/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 != "" {
Expand All @@ -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)

Expand Down
80 changes: 80 additions & 0 deletions web/pkg/assets/server.go
Original file line number Diff line number Diff line change
@@ -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)
}
69 changes: 12 additions & 57 deletions web/pkg/service/v0/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package svc

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -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 {
Expand All @@ -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
}

0 comments on commit f519b49

Please sign in to comment.