1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/api/fileserver"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/middleware"
"github.com/superseriousbusiness/gotosocial/internal/processing"
"github.com/superseriousbusiness/gotosocial/internal/router"
)
type Fileserver struct {
fileserver *fileserver.Module
}
// Attach cache middleware appropriate for file serving.
func useFSCacheMiddleware(grp *gin.RouterGroup) {
// If we're using local storage or proxying s3 (ie., serving
// from here) we can set a long max-age + immutable on file
// requests to reflect that we never host different files at
// the same URL (since ULIDs are generated per piece of media),
// so we can prevent clients having to fetch files repeatedly.
//
// If we *are* using non-proxying s3, however (ie., not serving
// from here) the max age must be set dynamically within the
// request handler, based on how long the signed URL has left
// to live before it expires. This ensures that clients won't
// cache expired links. This is done within fileserver/servefile.go
// so we should not set the middleware here in that case.
//
// See:
//
// - https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#avoiding_revalidation
// - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#immutable
servingFromHere := config.GetStorageBackend() == "local" || config.GetStorageS3Proxy()
if !servingFromHere {
return
}
grp.Use(middleware.CacheControl(middleware.CacheControlConfig{
Directives: []string{"private", "max-age=604800", "immutable"},
Vary: []string{"Range"}, // Cache partial ranges separately.
}))
}
// Route the "main" fileserver group
// that handles everything except emojis.
func (f *Fileserver) Route(
r *router.Router,
m ...gin.HandlerFunc,
) {
const fsGroupPath = "fileserver" +
"/:" + fileserver.AccountIDKey +
"/:" + fileserver.MediaTypeKey
fsGroup := r.AttachGroup(fsGroupPath)
// Attach provided +
// cache middlewares.
fsGroup.Use(m...)
useFSCacheMiddleware(fsGroup)
f.fileserver.Route(fsGroup.Handle)
}
// Route the "emojis" fileserver
// group to handle emojis specifically.
//
// instanceAccount ID is required because
// that is the ID under which all emoji
// files are stored, and from which all
// emoji file requests are therefore served.
func (f *Fileserver) RouteEmojis(
r *router.Router,
instanceAcctID string,
m ...gin.HandlerFunc,
) {
var fsEmojiGroupPath = "fileserver" +
"/" + instanceAcctID +
"/" + string(media.TypeEmoji)
fsEmojiGroup := r.AttachGroup(fsEmojiGroupPath)
// Inject the instance account and emoji media
// type params into the gin context manually,
// since we know we're only going to be serving
// emojis (stored under the instance account ID)
// from this group. This allows us to use the
// same handler functions for both the "main"
// fileserver handler and the emojis handler.
fsEmojiGroup.Use(func(c *gin.Context) {
c.Params = append(c.Params, []gin.Param{
{
Key: fileserver.AccountIDKey,
Value: instanceAcctID,
},
{
Key: fileserver.MediaTypeKey,
Value: string(media.TypeEmoji),
},
}...)
})
// Attach provided +
// cache middlewares.
fsEmojiGroup.Use(m...)
useFSCacheMiddleware(fsEmojiGroup)
f.fileserver.Route(fsEmojiGroup.Handle)
}
func NewFileserver(p *processing.Processor) *Fileserver {
return &Fileserver{
fileserver: fileserver.New(p),
}
}
|