diff --git a/pkg/magic/magic.go b/pkg/magic/magic.go index 39eba91e9..e5b1bd80b 100644 --- a/pkg/magic/magic.go +++ b/pkg/magic/magic.go @@ -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]) +} diff --git a/pkg/server/download.go b/pkg/server/download.go index 28b007a10..7af3ca847 100644 --- a/pkg/server/download.go +++ b/pkg/server/download.go @@ -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) } diff --git a/pkg/server/image.go b/pkg/server/image.go index 3896b9be5..11f898fd6 100644 --- a/pkg/server/image.go +++ b/pkg/server/image.go @@ -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()