Generate ETags and encode PNG directly to response stream.

This commit is contained in:
Sascha L. Teichmann 2014-09-16 18:52:50 +02:00
parent 3943c7cc5d
commit c42be33ba9
2 changed files with 59 additions and 32 deletions

View File

@ -1 +0,0 @@
* Generate and use ETags to improve caching behavior.

View File

@ -12,11 +12,10 @@ import (
"image/png" "image/png"
"log" "log"
"net/http" "net/http"
"os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"bytes"
"bitbucket.org/s_l_teichmann/mtredisalize/common" "bitbucket.org/s_l_teichmann/mtredisalize/common"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -64,6 +63,15 @@ func toUint(s string) uint {
return uint(x) return uint(x)
} }
func createETag(path string) (etag string, err error) {
var fi os.FileInfo
if fi, err = os.Stat(path); err != nil {
return
}
etag = fmt.Sprintf("%x-%x", fi.ModTime().Unix(), fi.Size())
return
}
func main() { func main() {
var ( var (
port int port int
@ -84,41 +92,38 @@ func main() {
r := mux.NewRouter() r := mux.NewRouter()
r.HandleFunc("/map/{z:[0-9]+}/{x:[0-9]+}/{y:[0-9]+}.png", func(rw http.ResponseWriter, r *http.Request) { r.HandleFunc("/map/{z:[0-9]+}/{x:[0-9]+}/{y:[0-9]+}.png", func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
xs := vars["x"] xs := vars["x"]
ys := vars["y"] ys := vars["y"]
zs := vars["z"] zs := vars["z"]
x, y, z := toUint(xs), toUint(ys), toUint(zs) x, y, z := toUint(xs), toUint(ys), toUint(zs)
if z < 9 { if z < 9 {
filename := fmt.Sprintf("%d/%d/%d.png", z, x, y) filename := fmt.Sprintf("%d/%d/%d.png", z, x, y)
http.ServeFile(rw, r, filepath.Join(mapDir, filename)) http.ServeFile(rw, r, filepath.Join(mapDir, filename))
return return
} }
if z > 16 { if z > 16 {
z = 16 z = 16
} }
//fmt.Printf("x %d\n", x)
//fmt.Printf("y %d\n", y)
//fmt.Printf("z %d\n", z)
tx := x >> (z - 8) tx := x >> (z - 8)
ty := y >> (z - 8) ty := y >> (z - 8)
rx := x & ^(^uint(0) << (z - 8))
ry := y & ^(^uint(0) << (z - 8))
parts := uint(1) << (z - 8)
//fmt.Printf("z %d\n", z)
//fmt.Printf("parts %d\n", parts)
w := uint(256) / parts
xo := w * rx
yo := w * (parts - 1 - ry)
//fmt.Printf("w %d\n", w)
//fmt.Printf("xo %d\n", xo)
//fmt.Printf("yo %d\n", yo)
//fmt.Printf("rx %d\n", rx)
//fmt.Printf("ry %d\n", ry)
baseTile := filepath.Join(mapDir, fmt.Sprintf("8/%d/%d.png", tx, ty)) baseTile := filepath.Join(mapDir, fmt.Sprintf("8/%d/%d.png", tx, ty))
var etag string
var err error
if ifNoneMatch := r.Header.Get("If-None-Match"); ifNoneMatch != "" {
if etag, err = createETag(baseTile); err == nil {
if ifNoneMatch == etag {
http.Error(rw, http.StatusText(http.StatusNotModified), http.StatusNotModified)
return
}
}
}
img := common.LoadPNG(baseTile) img := common.LoadPNG(baseTile)
type subImage interface { type subImage interface {
@ -129,24 +134,47 @@ func main() {
var ok bool var ok bool
if si, ok = img.(subImage); !ok { if si, ok = img.(subImage); !ok {
// Should not happen. // Should not happen.
http.ServeFile(rw, r, filepath.Join(mapDir, baseTile)) http.Error(rw,
http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
rx := x & ^(^uint(0) << (z - 8))
ry := y & ^(^uint(0) << (z - 8))
parts := uint(1) << (z - 8)
w := uint(256) / parts
xo := w * rx
yo := w * (parts - 1 - ry)
//fmt.Printf("x %d\n", x)
//fmt.Printf("y %d\n", y)
//fmt.Printf("z %d\n", z)
//fmt.Printf("z %d\n", z)
//fmt.Printf("parts %d\n", parts)
//fmt.Printf("w %d\n", w)
//fmt.Printf("xo %d\n", xo)
//fmt.Printf("yo %d\n", yo)
//fmt.Printf("rx %d\n", rx)
//fmt.Printf("ry %d\n", ry)
img = si.SubImage(image.Rect(int(xo), int(yo), int(xo+w), int(yo+w))) img = si.SubImage(image.Rect(int(xo), int(yo), int(xo+w), int(yo+w)))
img = blowUp(img) img = blowUp(img)
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
http.ServeFile(rw, r, filepath.Join(mapDir, baseTile))
return
}
b := buf.Bytes()
rw.Header().Set("Content-Type", "image/png") rw.Header().Set("Content-Type", "image/png")
// Don't set the content length to use chunked mode. if etag == "" {
// rw.Header().Set("Content-Length", strconv.Itoa(len(b))) if etag, err = createETag(baseTile); err != nil {
if _, err := rw.Write(b); err != nil { log.Printf("Cannot create ETag: %s", baseTile)
log.Printf("WARM: sending image failed: %s", err) } else {
rw.Header().Set("ETag", etag)
}
} else {
rw.Header().Set("ETag", etag)
}
if err = png.Encode(rw, img); err != nil {
log.Printf("WARN: encoding image failed: %s", err)
return
} }
}) })