Home Download Docs Code Community
     1	/*
     2	Copyright 2011 The Perkeep Authors
     3	
     4	Licensed under the Apache License, Version 2.0 (the "License");
     5	you may not use this file except in compliance with the License.
     6	You may obtain a copy of the License at
     7	
     8	     http://www.apache.org/licenses/LICENSE-2.0
     9	
    10	Unless required by applicable law or agreed to in writing, software
    11	distributed under the License is distributed on an "AS IS" BASIS,
    12	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13	See the License for the specific language governing permissions and
    14	limitations under the License.
    15	*/
    16	
    17	// Package gethandler implements the HTTP handler for fetching blobs.
    18	package gethandler // import "perkeep.org/pkg/blobserver/gethandler"
    19	
    20	import (
    21		"fmt"
    22		"io"
    23		"log"
    24		"net/http"
    25		"os"
    26		"regexp"
    27		"strings"
    28		"time"
    29	
    30		"perkeep.org/internal/httputil"
    31		"perkeep.org/pkg/blob"
    32	
    33		"go4.org/readerutil"
    34	)
    35	
    36	const (
    37		HTTP_CACHE_DURATION = 10 * 356 * 24 * time.Hour
    38	)
    39	
    40	var getPattern = regexp.MustCompile(`/camli/` + blob.Pattern + `$`)
    41	
    42	// Handler is the HTTP handler for serving GET requests of blobs.
    43	type Handler struct {
    44		Fetcher blob.Fetcher
    45	}
    46	
    47	// CreateGetHandler returns an http Handler for serving blobs from fetcher.
    48	func CreateGetHandler(fetcher blob.Fetcher) http.Handler {
    49		return &Handler{Fetcher: fetcher}
    50	}
    51	
    52	func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    53		blobRef := blobFromURLPath(req.URL.Path)
    54		if !blobRef.Valid() {
    55			http.Error(rw, "Malformed GET URL.", http.StatusBadRequest)
    56			return
    57		}
    58	
    59		ServeBlobRef(rw, req, blobRef, h.Fetcher)
    60	}
    61	
    62	// ServeBlobRef serves a blob.
    63	func ServeBlobRef(rw http.ResponseWriter, req *http.Request, blobRef blob.Ref, fetcher blob.Fetcher) {
    64		ctx := req.Context()
    65		if fetcher == nil {
    66			log.Printf("gethandler: no fetcher configured for %s (ref=%v)", req.URL.Path, blobRef)
    67			rw.WriteHeader(http.StatusNotFound)
    68			io.WriteString(rw, "no fetcher configured")
    69			return
    70		}
    71		rc, size, err := fetcher.Fetch(ctx, blobRef)
    72		switch err {
    73		case nil:
    74			break
    75		case os.ErrNotExist:
    76			rw.WriteHeader(http.StatusNotFound)
    77			fmt.Fprintf(rw, "Blob %q not found", blobRef)
    78			return
    79		default:
    80			httputil.ServeError(rw, req, err)
    81			return
    82		}
    83		defer rc.Close()
    84		rw.Header().Set("Content-Type", "application/octet-stream")
    85		rw.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, immutable", int(HTTP_CACHE_DURATION.Seconds())))
    86	
    87		var content io.ReadSeeker = readerutil.NewFakeSeeker(rc, int64(size))
    88		rangeHeader := req.Header.Get("Range") != ""
    89		const small = 32 << 10
    90		var b *blob.Blob
    91		if rangeHeader || size < small {
    92			// Slurp to memory, so we can actually seek on it (for Range support),
    93			// or if we're going to be showing it in the browser (below).
    94			b, err = blob.FromReader(ctx, blobRef, rc, size)
    95			if err != nil {
    96				httputil.ServeError(rw, req, err)
    97				return
    98			}
    99			content, err = b.ReadAll(ctx)
   100			if err != nil {
   101				httputil.ServeError(rw, req, err)
   102				return
   103			}
   104		}
   105		if !rangeHeader && size < small {
   106			// If it's small and all UTF-8, assume it's text and
   107			// just render it in the browser.  This is more for
   108			// demos/debuggability than anything else.  It isn't
   109			// part of the spec.
   110			isUTF8, err := b.IsUTF8(ctx)
   111			if err != nil {
   112				httputil.ServeError(rw, req, err)
   113				return
   114			}
   115			if isUTF8 {
   116				rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
   117			}
   118		}
   119		http.ServeContent(rw, req, "", dummyModTime, content)
   120	}
   121	
   122	// dummyModTime is an arbitrary point in time that we send as fake modtimes for blobs.
   123	// Because blobs are content-addressable, they can never change, so it's better to send
   124	// *some* modtime and let clients do "If-Modified-Since" requests for it.
   125	// This time is the first commit of the Perkeep project.
   126	var dummyModTime = time.Unix(1276213335, 0)
   127	
   128	func blobFromURLPath(path string) blob.Ref {
   129		matches := getPattern.FindStringSubmatch(path)
   130		if len(matches) != 3 {
   131			return blob.Ref{}
   132		}
   133		return blob.ParseOrZero(strings.TrimPrefix(matches[0], "/camli/"))
   134	}
Website layout inspired by memcached.
Content by the authors.