mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-05 19:59:37 -06:00
add spa fileserver to the web service (#3109)
* add spa fileserver to the web service
This commit is contained in:
6
changelog/unreleased/enhancement-web-cache-control.md
Normal file
6
changelog/unreleased/enhancement-web-cache-control.md
Normal 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
|
||||
6
changelog/unreleased/enhancement-web-spa-fileserver.md
Normal file
6
changelog/unreleased/enhancement-web-spa-fileserver.md
Normal 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
|
||||
@@ -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
80
web/pkg/assets/server.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user