Browse Source

Add a tiled gopher handler demo.

Brad Fitzpatrick 11 years ago
parent
commit
8f15f1c68c
1 changed files with 128 additions and 4 deletions
  1. 128 4
      h2demo/h2demo.go

+ 128 - 4
h2demo/h2demo.go

@@ -11,6 +11,8 @@ import (
 	"flag"
 	"fmt"
 	"hash/crc32"
+	"image"
+	"image/jpeg"
 	"io"
 	"io/ioutil"
 	"log"
@@ -20,6 +22,7 @@ import (
 	"path"
 	"regexp"
 	"runtime"
+	"strconv"
 	"strings"
 	"sync"
 	"time"
@@ -31,6 +34,8 @@ import (
 
 var (
 	openFirefox = flag.Bool("openff", false, "Open Firefox")
+	addr        = flag.String("addr", "localhost:4430", "TLS address to listen on")
+	httpAddr    = flag.String("httpaddr", "", "If non-empty, address to listen for regular HTTP on")
 	prod        = flag.Bool("prod", false, "Whether to configure itself to be the production http2.golang.org server.")
 )
 
@@ -46,6 +51,10 @@ func homeOldHTTP(w http.ResponseWriter, r *http.Request) {
 }
 
 func home(w http.ResponseWriter, r *http.Request) {
+	if r.URL.Path != "/" {
+		http.NotFound(w, r)
+		return
+	}
 	io.WriteString(w, `<html>
 <body>
 <h1>Go + HTTP/2</h1>
@@ -69,6 +78,7 @@ href="https://github.com/bradfitz/http2/issues">file a bug</a>.</p>
 <ul>
   <li>GET <a href="/reqinfo">/reqinfo</a> to dump the request + headers received</li>
   <li>GET <a href="/clockstream">/clockstream</a> streams the current time every second</li>
+  <li>GET <a href="/gophertiles">/gophertiles</a> to see a page with a bunch of images</li>
   <li>GET <a href="/file/gopher.png">/file/gopher.png</a> for a small file (does If-Modified-Since, Content-Range, etc)</li>
   <li>GET <a href="/file/go.src.tar.gz">/file/go.src.tar.gz</a> for a larger file (~10 MB)</li>
   <li>GET <a href="/redirect">/redirect</a> to redirect back to / (this page)</li>
@@ -173,18 +183,24 @@ func clockStreamHandler(w http.ResponseWriter, r *http.Request) {
 }
 
 func registerHandlers() {
+	tiles := newGopherTilesHandler()
+
 	mux2 := http.NewServeMux()
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 		if r.TLS == nil {
+			if r.URL.Path == "/gophertiles" {
+				tiles.ServeHTTP(w, r)
+				return
+			}
 			http.Redirect(w, r, "https://http2.golang.org/", http.StatusFound)
 			return
 		}
 		if r.ProtoMajor == 1 {
 			if r.URL.Path == "/reqinfo" {
 				reqInfoHandler(w, r)
-			} else {
-				homeOldHTTP(w, r)
+				return
 			}
+			homeOldHTTP(w, r)
 			return
 		}
 		mux2.ServeHTTP(w, r)
@@ -195,6 +211,7 @@ func registerHandlers() {
 	mux2.HandleFunc("/reqinfo", reqInfoHandler)
 	mux2.HandleFunc("/crc32", crcHandler)
 	mux2.HandleFunc("/clockstream", clockStreamHandler)
+	mux2.Handle("/gophertiles", tiles)
 	mux2.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
 		http.Redirect(w, r, "/", http.StatusFound)
 	})
@@ -206,6 +223,108 @@ func registerHandlers() {
 	})
 }
 
+func newGopherTilesHandler() http.Handler {
+	const gopherURL = "https://blog.golang.org/go-programming-language-turns-two_gophers.jpg"
+	res, err := http.Get(gopherURL)
+	if err != nil {
+		log.Fatal(err)
+	}
+	if res.StatusCode != 200 {
+		log.Fatalf("Error fetching %s: %v", gopherURL, res.Status)
+	}
+	slurp, err := ioutil.ReadAll(res.Body)
+	res.Body.Close()
+	if err != nil {
+		log.Fatal(err)
+	}
+	im, err := jpeg.Decode(bytes.NewReader(slurp))
+	if err != nil {
+		if len(slurp) > 1024 {
+			slurp = slurp[:1024]
+		}
+		log.Fatalf("Failed to decode gopher image: %v (got %q)", err, slurp)
+	}
+
+	type subImager interface {
+		SubImage(image.Rectangle) image.Image
+	}
+	const tileSize = 32
+	xt := im.Bounds().Max.X / tileSize
+	yt := im.Bounds().Max.Y / tileSize
+	var tile [][][]byte // y -> x -> jpeg bytes
+	for yi := 0; yi < yt; yi++ {
+		var row [][]byte
+		for xi := 0; xi < xt; xi++ {
+			si := im.(subImager).SubImage(image.Rectangle{
+				Min: image.Point{xi * tileSize, yi * tileSize},
+				Max: image.Point{(xi + 1) * tileSize, (yi + 1) * tileSize},
+			})
+			buf := new(bytes.Buffer)
+			if err := jpeg.Encode(buf, si, &jpeg.Options{Quality: 90}); err != nil {
+				log.Fatal(err)
+			}
+			row = append(row, buf.Bytes())
+		}
+		tile = append(tile, row)
+	}
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		ms, _ := strconv.Atoi(r.FormValue("latency"))
+		const nanosPerMilli = 1e6
+		if r.FormValue("x") != "" {
+			x, _ := strconv.Atoi(r.FormValue("x"))
+			y, _ := strconv.Atoi(r.FormValue("y"))
+			if ms <= 1000 {
+				time.Sleep(time.Duration(ms) * nanosPerMilli)
+			}
+			if x >= 0 && x < xt && y >= 0 && y < yt {
+				http.ServeContent(w, r, "", time.Time{}, bytes.NewReader(tile[y][x]))
+				return
+			}
+		}
+		io.WriteString(w, "<html><body>")
+		fmt.Fprintf(w, "A grid of %d tiled images is below. Compare:<p>", xt*yt)
+		for _, ms := range []int{0, 30, 200, 1000} {
+			d := time.Duration(ms) * nanosPerMilli
+			fmt.Fprintf(w, "[<a href='https://%s/gophertiles?latency=%d'>HTTP/2, %v latency</a>] [<a href='http://%s/gophertiles?latency=%d'>HTTP/1, %v latency</a>]<br>\n",
+				httpsHost(), ms, d,
+				httpHost(), ms, d,
+			)
+		}
+		io.WriteString(w, "<p>\n")
+		cacheBust := time.Now().UnixNano()
+		for y := 0; y < yt; y++ {
+			for x := 0; x < xt; x++ {
+				fmt.Fprintf(w, "<img width=%d height=%d src='/gophertiles?x=%d&y=%d&cachebust=%d&latency=%d'>",
+					tileSize, tileSize, x, y, cacheBust, ms)
+			}
+			io.WriteString(w, "<br/>\n")
+		}
+		io.WriteString(w, "<hr><a href='/'>&lt;&lt Back to Go HTTP/2 demo server</a></body></html>")
+	})
+}
+
+func httpsHost() string {
+	if *prod {
+		return "http2.golang.org"
+	}
+	if v := *addr; strings.HasPrefix(v, ":") {
+		return "localhost" + v
+	} else {
+		return v
+	}
+}
+
+func httpHost() string {
+	if *prod {
+		return "http2.golang.org"
+	}
+	if v := *httpAddr; strings.HasPrefix(v, ":") {
+		return "localhost" + v
+	} else {
+		return v
+	}
+}
+
 func serveProdTLS() error {
 	c, err := googlestorage.NewServiceClient()
 	if err != nil {
@@ -272,19 +391,24 @@ func serveProd() error {
 func main() {
 	var srv http.Server
 	flag.BoolVar(&http2.VerboseLogs, "verbose", false, "Verbose HTTP/2 debugging.")
-	flag.StringVar(&srv.Addr, "addr", "localhost:4430", "host:port to listen on ")
 	flag.Parse()
+	srv.Addr = *addr
 
 	registerHandlers()
 
 	if *prod {
+		*httpAddr = "http2.golang.org"
 		log.Fatal(serveProd())
 	}
 
-	url := "https://" + srv.Addr + "/"
+	url := "https://" + *addr + "/"
 	log.Printf("Listening on " + url)
 	http2.ConfigureServer(&srv, &http2.Server{})
 
+	if *httpAddr != "" {
+		go func() { log.Fatal(http.ListenAndServe(*httpAddr, nil)) }()
+	}
+
 	go func() {
 		log.Fatal(srv.ListenAndServeTLS("server.crt", "server.key"))
 	}()