mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-02-03 18:02:17 +02:00
[#1125] added support for partial/range file requests
This commit is contained in:
parent
328b99a690
commit
bd65125744
18
CHANGELOG.md
18
CHANGELOG.md
@ -1,14 +1,14 @@
|
||||
## (WIP) v0.9.0
|
||||
|
||||
- Added new event hooks:
|
||||
```
|
||||
```go
|
||||
app.OnBeforeBootstrap()
|
||||
app.OnAfterBootstrap()
|
||||
```
|
||||
|
||||
- Refactored the `migrate` command to support **external JavaScript migration files** using an embedded JS interpreter ([goja](https://github.com/dop251/goja)).
|
||||
This allow writting custom migration scripts such as programmatically creating collections,
|
||||
initializing default settings, running import scripts, etc., with a JavaScript API very similar to the Go one (_more documentation will be available soon_).
|
||||
initializing default settings, running data imports, etc., with a JavaScript API very similar to the Go one (_more documentation will be available soon_).
|
||||
|
||||
The `migrate` command is available by default for the prebult executable,
|
||||
but if you use PocketBase as framework you need register it manually:
|
||||
@ -20,9 +20,9 @@
|
||||
Dir: migrationsDir,
|
||||
})
|
||||
|
||||
// init the `migrate` command
|
||||
// register the `migrate` command
|
||||
migratecmd.MustRegister(app, app.RootCmd, &migratecmd.Options{
|
||||
TemplateLang: migratecmd.TemplateLangGo, // or migratecmd.TemplateLangJS
|
||||
TemplateLang: migratecmd.TemplateLangJS, // or migratecmd.TemplateLangGo (default)
|
||||
Dir: migrationsDir,
|
||||
Automigrate: true,
|
||||
})
|
||||
@ -93,6 +93,16 @@
|
||||
```
|
||||
The new `*mailer.Message` struct is also now a member of the `MailerRecordEvent` and `MailerAdminEvent` events.
|
||||
|
||||
- Added support for `Partial/Range` file requests ([#1125](https://github.com/pocketbase/pocketbase/issues/1125)).
|
||||
This is a minor breaking change if you are using `filesystem.Serve` (eg. as part of a custom `OnFileDownloadRequest` hook):
|
||||
```go
|
||||
// old
|
||||
filesystem.Serve(res, e.ServedPath, e.ServedName)
|
||||
|
||||
// new
|
||||
filesystem.Serve(res, req, e.ServedPath, e.ServedName)
|
||||
```
|
||||
|
||||
|
||||
## v0.8.0
|
||||
|
||||
|
@ -94,7 +94,9 @@ func (api *fileApi) download(c echo.Context) error {
|
||||
}
|
||||
|
||||
return api.app.OnFileDownloadRequest().Trigger(event, func(e *core.FileDownloadEvent) error {
|
||||
if err := fs.Serve(e.HttpContext.Response(), e.ServedPath, e.ServedName); err != nil {
|
||||
res := e.HttpContext.Response()
|
||||
req := e.HttpContext.Request()
|
||||
if err := fs.Serve(res, req, e.ServedPath, e.ServedName); err != nil {
|
||||
return NewNotFoundError("", err)
|
||||
}
|
||||
|
||||
|
@ -237,15 +237,15 @@ var manualExtensionContentTypes = map[string]string{
|
||||
}
|
||||
|
||||
// Serve serves the file at fileKey location to an HTTP response.
|
||||
func (s *System) Serve(response http.ResponseWriter, fileKey string, name string) error {
|
||||
r, readErr := s.bucket.NewReader(s.ctx, fileKey, nil)
|
||||
func (s *System) Serve(res http.ResponseWriter, req *http.Request, fileKey string, name string) error {
|
||||
br, readErr := s.bucket.NewReader(s.ctx, fileKey, nil)
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
defer r.Close()
|
||||
defer br.Close()
|
||||
|
||||
disposition := "attachment"
|
||||
realContentType := r.ContentType()
|
||||
realContentType := br.ContentType()
|
||||
if list.ExistInSlice(realContentType, inlineServeContentTypes) {
|
||||
disposition = "inline"
|
||||
}
|
||||
@ -260,12 +260,12 @@ func (s *System) Serve(response http.ResponseWriter, fileKey string, name string
|
||||
// clickjacking shouldn't be a concern when serving uploaded files,
|
||||
// so it safe to unset the global X-Frame-Options to allow files embedding
|
||||
// (see https://github.com/pocketbase/pocketbase/issues/677)
|
||||
response.Header().Del("X-Frame-Options")
|
||||
res.Header().Del("X-Frame-Options")
|
||||
|
||||
response.Header().Set("Content-Disposition", disposition+"; filename="+name)
|
||||
response.Header().Set("Content-Type", extContentType)
|
||||
response.Header().Set("Content-Length", strconv.FormatInt(r.Size(), 10))
|
||||
response.Header().Set("Content-Security-Policy", "default-src 'none'; media-src 'self'; style-src 'unsafe-inline'; sandbox")
|
||||
res.Header().Set("Content-Disposition", disposition+"; filename="+name)
|
||||
res.Header().Set("Content-Type", extContentType)
|
||||
res.Header().Set("Content-Length", strconv.FormatInt(br.Size(), 10))
|
||||
res.Header().Set("Content-Security-Policy", "default-src 'none'; media-src 'self'; style-src 'unsafe-inline'; sandbox")
|
||||
|
||||
// all HTTP date/time stamps MUST be represented in Greenwich Mean Time (GMT)
|
||||
// (see https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1)
|
||||
@ -273,20 +273,19 @@ func (s *System) Serve(response http.ResponseWriter, fileKey string, name string
|
||||
// NB! time.LoadLocation may fail on non-Unix systems (see https://github.com/pocketbase/pocketbase/issues/45)
|
||||
location, locationErr := time.LoadLocation("GMT")
|
||||
if locationErr == nil {
|
||||
response.Header().Set("Last-Modified", r.ModTime().In(location).Format("Mon, 02 Jan 06 15:04:05 MST"))
|
||||
res.Header().Set("Last-Modified", br.ModTime().In(location).Format("Mon, 02 Jan 06 15:04:05 MST"))
|
||||
}
|
||||
|
||||
// set a default cache-control header
|
||||
// (valid for 30 days but the cache is allowed to reuse the file for any requests
|
||||
// that are made in the last day while revalidating the response in the background)
|
||||
if response.Header().Get("Cache-Control") == "" {
|
||||
response.Header().Set("Cache-Control", "max-age=2592000, stale-while-revalidate=86400")
|
||||
// that are made in the last day while revalidating the res in the background)
|
||||
if res.Header().Get("Cache-Control") == "" {
|
||||
res.Header().Set("Cache-Control", "max-age=2592000, stale-while-revalidate=86400")
|
||||
}
|
||||
|
||||
// copy from the read range to response.
|
||||
_, err := io.Copy(response, r)
|
||||
http.ServeContent(res, req, name, br.ModTime(), br)
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
var ThumbSizeRegex = regexp.MustCompile(`^(\d+)x(\d+)(t|b|f)?$`)
|
||||
|
@ -265,9 +265,10 @@ func TestFileSystemServe(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
r := httptest.NewRecorder()
|
||||
res := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
|
||||
err := fs.Serve(r, scenario.path, scenario.name)
|
||||
err := fs.Serve(res, req, scenario.path, scenario.name)
|
||||
hasErr := err != nil
|
||||
|
||||
if hasErr != scenario.expectError {
|
||||
@ -279,7 +280,7 @@ func TestFileSystemServe(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
result := r.Result()
|
||||
result := res.Result()
|
||||
|
||||
for hName, hValue := range scenario.expectHeaders {
|
||||
v := result.Header.Get(hName)
|
||||
@ -298,6 +299,40 @@ func TestFileSystemServe(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSystemServeRange(t *testing.T) {
|
||||
dir := createTestDir(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
fs, err := filesystem.NewLocal(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fs.Close()
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req.Header.Add("Range", "bytes=0-20")
|
||||
|
||||
if err := fs.Serve(res, req, "image.png", "image.png"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result := res.Result()
|
||||
|
||||
if result.StatusCode != http.StatusPartialContent {
|
||||
t.Fatalf("Expected StatusCode %d, got %d", http.StatusPartialContent, result.StatusCode)
|
||||
}
|
||||
|
||||
expectedRange := "bytes 0-20/73"
|
||||
if cr := result.Header.Get("Content-Range"); cr != expectedRange {
|
||||
t.Fatalf("Expected Content-Range %q, got %q", expectedRange, cr)
|
||||
}
|
||||
|
||||
if l := result.Header.Get("Content-Length"); l != "21" {
|
||||
t.Fatalf("Expected Content-Length %v, got %v", 21, l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSystemCreateThumb(t *testing.T) {
|
||||
dir := createTestDir(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
Loading…
x
Reference in New Issue
Block a user