Extend the SiteConfig GQL data type with a list of FileConfig to allow
specifying a list of (`glob`, `FileOptions`) pairs, where `FileOptions`
is an extensible set of options, currently only containing a
`cacheControl` attribute. For each published file that matches `glob`,
the respective options will be applied.
For `cacheControl`, the file will be served with a Cache-Control header
of the same value. The implementation uses the S3/minio metadata for
storing the headers.
Any values provided are used as input for the version hash, to make sure
changes to the options lead to actual new site versions.
Fixes: https://todo.sr.ht/~sircmpwn/pages.sr.ht/10
---
gqlgen.yml | 3 ---
graph/schema.graphqls | 18 +++++++++++++++
graph/schema.resolvers.go | 46 ++++++++++++++++++++++++---------------
server.go | 4 ++++
4 files changed, 50 insertions(+), 21 deletions(-)
diff --git a/gqlgen.yml b/gqlgen.yml
index 28cd866..33d349a 100644
--- a/gqlgen.yml
+++ b/gqlgen.yml
@@ -60,6 +60,3 @@ models:
Filter:
model:
- git.sr.ht/~sircmpwn/core-go/model.Filter
- SiteConfig:
- model:
- - "map[string]interface{}"
diff --git a/graph/schema.graphqls b/graph/schema.graphqls
index 90935eb..ed17387 100644
--- a/graph/schema.graphqls
+++ b/graph/schema.graphqls
@@ -90,9 +90,27 @@ type SiteCursor {
cursor: Cursor
}
+"""
+Options for a file being served.
+"""
+input FileOptions {
+ "Value of the Cache-Control header to be used when serving the file."
+ cacheControl: String
+}
+
+"""
+Provides a way to configure options for a set of files matching the glob
+pattern.
+"""
+input FileConfig {
+ glob: String!
+ options: FileOptions!
+}
+
input SiteConfig {
"Path to the file to serve for 404 Not Found responses"
notFound: String
+ fileConfigs: [FileConfig!]
}
type Query {
diff --git a/graph/schema.resolvers.go b/graph/schema.resolvers.go
index 4b5a112..3528135 100644
--- a/graph/schema.resolvers.go
+++ b/graph/schema.resolvers.go
@@ -30,7 +30,7 @@ import (
minio "github.com/minio/minio-go/v7"
)
-func (r *mutationResolver) Publish(ctx context.Context, domain string, content graphql.Upload, protocol *model.Protocol, subdirectory *string, siteConfig map[string]interface{}) (*model.Site, error) {
+func (r *mutationResolver) Publish(ctx context.Context, domain string, content graphql.Upload, protocol *model.Protocol, subdirectory *string, siteConfig *model.SiteConfig) (*model.Site, error) {
conf := config.ForContext(ctx)
bucket, _ := conf.Get("pages.sr.ht", "s3-bucket")
prefix, _ := conf.Get("pages.sr.ht", "s3-prefix")
@@ -62,10 +62,8 @@ func (r *mutationResolver) Publish(ctx context.Context, domain string, content g
}
}
- if value, ok := siteConfig["notFound"].(string); ok {
- if value == "" {
- return nil, fmt.Errorf("Invalid path siteConfig.notFound")
- }
+ if siteConfig.NotFound != nil && *siteConfig.NotFound == "" {
+ return nil, fmt.Errorf("Invalid path siteConfig.notFound")
}
if strings.HasSuffix(domain, "."+userDomain) {
@@ -156,21 +154,20 @@ func (r *mutationResolver) Publish(ctx context.Context, domain string, content g
}
notFound := site.NotFound
- if value, ok := siteConfig["notFound"]; ok {
- switch value.(type) {
- case nil:
- notFound = nil
- case string:
- value := path.Join("/", value.(string))
- notFound = &value
- default:
- panic("GraphQL schema validation broken")
- }
+ if siteConfig.NotFound != nil {
+ value := path.Join("/", *siteConfig.NotFound)
+ notFound = &value
}
if notFound != nil {
io.WriteString(sha, *notFound+"\n")
}
+ for _, fileConfig := range siteConfig.FileConfigs {
+ if fileConfig.Options.CacheControl != nil {
+ io.WriteString(sha, fileConfig.Glob+"|"+*fileConfig.Options.CacheControl+"\n")
+ }
+ }
+
inputReader := io.TeeReader(content.File, sha)
gzipReader, err := gzip.NewReader(inputReader)
if err != nil {
@@ -182,16 +179,29 @@ func (r *mutationResolver) Publish(ctx context.Context, domain string, content g
var header *tar.Header
for header, err = archive.Next(); err == nil; header, err = archive.Next() {
+ name := path.Clean(header.Name)
+ opts := minio.PutObjectOptions{}
+
if header.Typeflag != tar.TypeReg {
continue
}
- fpath := path.Join(s3path, path.Clean(header.Name))
+ for _, fileConfig := range siteConfig.FileConfigs {
+ match, err := path.Match(fileConfig.Glob, name)
+ if err != nil {
+ return err
+ }
+ if match && fileConfig.Options.CacheControl != nil {
+ opts.CacheControl = *fileConfig.Options.CacheControl
+ break
+ }
+ }
+ fpath := path.Join(s3path, name)
_, err := mc.PutObject(ctx, bucket, fpath,
- archive, header.Size, minio.PutObjectOptions{})
+ archive, header.Size, opts)
if err != nil {
return err
}
- files = append(files, path.Clean(header.Name))
+ files = append(files, name)
createdFiles = append(createdFiles, fpath)
}
diff --git a/server.go b/server.go
index ada1c8b..cbad65f 100644
--- a/server.go
+++ b/server.go
@@ -252,6 +252,10 @@ func ServeHTTP(conf ini.File, db *sql.DB, mc *minio.Client) *http.Server {
}
return
}
+ cc := objectInfo.Metadata.Get("Cache-Control")
+ if cc != "" {
+ w.Header().Set("Cache-Control", cc)
+ }
http.ServeContent(w, r, s3path, objectInfo.LastModified, object)
}))
return srv
--
2.35.2