server: use http.ServeContent and use cache headers in download handler

Change-Id: I5c24f39c86f6308b32167cf0d92ee68f801b9181
This commit is contained in:
Brad Fitzpatrick 2013-01-25 15:18:32 -08:00
parent bcd57baf8f
commit 13105dad21
3 changed files with 25 additions and 19 deletions

View File

@ -67,3 +67,11 @@ func MimeTypeFromReader(r io.Reader) (mime string, reader io.Reader) {
mime = MimeType(buf.Bytes())
return mime, io.MultiReader(&buf, r)
}
// MimeTypeFromReader takes a ReaderAt, sniffs the beginning of it,
// and returns the MIME type if sniffed, else the empty string.
func MIMETypeFromReaderAt(ra io.ReaderAt) (mime string) {
var buf [1024]byte
n, _ := ra.ReadAt(buf[:], 0)
return MimeType(buf[:n])
}

View File

@ -19,8 +19,8 @@ package server
import (
"fmt"
"io"
"log"
"net/http"
"time"
"camlistore.org/pkg/blobref"
"camlistore.org/pkg/blobserver"
@ -28,6 +28,8 @@ import (
"camlistore.org/pkg/schema"
)
const oneYear = 365 * 86400 * time.Second
type DownloadHandler struct {
Fetcher blobref.StreamingFetcher
Cache blobserver.Storage
@ -43,6 +45,11 @@ func (dh *DownloadHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request,
http.Error(rw, "Invalid download method", 400)
return
}
if req.Header.Get("If-Modified-Since") != "" {
// Immutable, so any copy's a good copy.
rw.WriteHeader(http.StatusNotModified)
return
}
fr, err := schema.NewFileReader(dh.storageSeekFetcher(), file)
if err != nil {
@ -52,16 +59,18 @@ func (dh *DownloadHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request,
defer fr.Close()
schema := fr.FileSchema()
rw.Header().Set("Content-Length", fmt.Sprintf("%d", schema.SumPartsSize()))
h := rw.Header()
h.Set("Content-Length", fmt.Sprintf("%d", schema.SumPartsSize()))
h.Set("Expires", time.Now().Add(oneYear).Format(http.TimeFormat))
mimeType, reader := magic.MimeTypeFromReader(fr)
mimeType := magic.MIMETypeFromReaderAt(fr)
if dh.ForceMime != "" {
mimeType = dh.ForceMime
}
if mimeType == "" {
mimeType = "application/octet-stream"
}
rw.Header().Set("Content-Type", mimeType)
h.Set("Content-Type", mimeType)
if mimeType == "application/octet-stream" {
// Chrome seems to silently do nothing on
// application/octet-stream unless this is set.
@ -70,7 +79,7 @@ func (dh *DownloadHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request,
rw.Header().Set("Content-Disposition", "attachment; filename=file-"+file.String()+".dat")
}
if req.Method == "HEAD" {
if req.Method == "HEAD" && req.FormValue("verifycontents") != "" {
vbr := blobref.Parse(req.FormValue("verifycontents"))
if vbr == nil {
return
@ -79,22 +88,12 @@ func (dh *DownloadHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request,
if hash == nil {
return
}
io.Copy(hash, reader) // ignore errors, caught later
io.Copy(hash, fr) // ignore errors, caught later
if vbr.HashMatches(hash) {
rw.Header().Set("X-Camli-Contents", vbr.String())
}
return
}
n, err := io.Copy(rw, reader)
if err != nil {
log.Printf("error serving download of file schema %s: %v", file, err)
return
}
if size := schema.SumPartsSize(); n != int64(size) {
log.Printf("error serving download of file schema %s: sent %d, expected size of %d",
file, n, size)
return
}
http.ServeContent(rw, req, "", time.Now(), fr)
}

View File

@ -265,8 +265,7 @@ func (ih *ImageHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, fil
}
h := rw.Header()
const oneYearish = 365 * 86400 * time.Second
h.Set("Expires", time.Now().Add(oneYearish).Format(http.TimeFormat))
h.Set("Expires", time.Now().Add(oneYear).Format(http.TimeFormat))
h.Set("Last-Modified", time.Now().Format(http.TimeFormat))
h.Set("Content-Type", imageContentTypeOfFormat(format))
size := buf.Len()