|
|
@@ -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='/'><< 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"))
|
|
|
}()
|