1// Copyright 2014 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// +build h2demo 6 7package main 8 9import ( 10 "bytes" 11 "crypto/tls" 12 "flag" 13 "fmt" 14 "hash/crc32" 15 "image" 16 "image/jpeg" 17 "io" 18 "io/ioutil" 19 "log" 20 "net" 21 "net/http" 22 "os" 23 "path" 24 "regexp" 25 "runtime" 26 "strconv" 27 "strings" 28 "sync" 29 "time" 30 31 "go4.org/syncutil/singleflight" 32 "golang.org/x/crypto/acme/autocert" 33 "golang.org/x/net/http2" 34) 35 36var ( 37 prod = flag.Bool("prod", false, "Whether to configure itself to be the production http2.golang.org server.") 38 39 httpsAddr = flag.String("https_addr", "localhost:4430", "TLS address to listen on ('host:port' or ':port'). Required.") 40 httpAddr = flag.String("http_addr", "", "Plain HTTP address to listen on ('host:port', or ':port'). Empty means no HTTP.") 41 42 hostHTTP = flag.String("http_host", "", "Optional host or host:port to use for http:// links to this service. By default, this is implied from -http_addr.") 43 hostHTTPS = flag.String("https_host", "", "Optional host or host:port to use for http:// links to this service. By default, this is implied from -https_addr.") 44) 45 46func homeOldHTTP(w http.ResponseWriter, r *http.Request) { 47 io.WriteString(w, `<html> 48<body> 49<h1>Go + HTTP/2</h1> 50<p>Welcome to <a href="https://golang.org/">the Go language</a>'s <a href="https://http2.github.io/">HTTP/2</a> demo & interop server.</p> 51<p>Unfortunately, you're <b>not</b> using HTTP/2 right now. To do so:</p> 52<ul> 53 <li>Use Firefox Nightly or go to <b>about:config</b> and enable "network.http.spdy.enabled.http2draft"</li> 54 <li>Use Google Chrome Canary and/or go to <b>chrome://flags/#enable-spdy4</b> to <i>Enable SPDY/4</i> (Chrome's name for HTTP/2)</li> 55</ul> 56<p>See code & instructions for connecting at <a href="https://github.com/golang/net/tree/master/http2">https://github.com/golang/net/tree/master/http2</a>.</p> 57 58</body></html>`) 59} 60 61func home(w http.ResponseWriter, r *http.Request) { 62 if r.URL.Path != "/" { 63 http.NotFound(w, r) 64 return 65 } 66 io.WriteString(w, `<html> 67<body> 68<h1>Go + HTTP/2</h1> 69 70<p>Welcome to <a href="https://golang.org/">the Go language</a>'s <a 71href="https://http2.github.io/">HTTP/2</a> demo & interop server.</p> 72 73<p>Congratulations, <b>you're using HTTP/2 right now</b>.</p> 74 75<p>This server exists for others in the HTTP/2 community to test their HTTP/2 client implementations and point out flaws in our server.</p> 76 77<p> 78The code is at <a href="https://golang.org/x/net/http2">golang.org/x/net/http2</a> and 79is used transparently by the Go standard library from Go 1.6 and later. 80</p> 81 82<p>Contact info: <i>bradfitz@golang.org</i>, or <a 83href="https://golang.org/s/http2bug">file a bug</a>.</p> 84 85<h2>Handlers for testing</h2> 86<ul> 87 <li>GET <a href="/reqinfo">/reqinfo</a> to dump the request + headers received</li> 88 <li>GET <a href="/clockstream">/clockstream</a> streams the current time every second</li> 89 <li>GET <a href="/gophertiles">/gophertiles</a> to see a page with a bunch of images</li> 90 <li>GET <a href="/serverpush">/serverpush</a> to see a page with server push</li> 91 <li>GET <a href="/file/gopher.png">/file/gopher.png</a> for a small file (does If-Modified-Since, Content-Range, etc)</li> 92 <li>GET <a href="/file/go.src.tar.gz">/file/go.src.tar.gz</a> for a larger file (~10 MB)</li> 93 <li>GET <a href="/redirect">/redirect</a> to redirect back to / (this page)</li> 94 <li>GET <a href="/goroutines">/goroutines</a> to see all active goroutines in this server</li> 95 <li>PUT something to <a href="/crc32">/crc32</a> to get a count of number of bytes and its CRC-32</li> 96 <li>PUT something to <a href="/ECHO">/ECHO</a> and it will be streamed back to you capitalized</li> 97</ul> 98 99</body></html>`) 100} 101 102func reqInfoHandler(w http.ResponseWriter, r *http.Request) { 103 w.Header().Set("Content-Type", "text/plain") 104 fmt.Fprintf(w, "Method: %s\n", r.Method) 105 fmt.Fprintf(w, "Protocol: %s\n", r.Proto) 106 fmt.Fprintf(w, "Host: %s\n", r.Host) 107 fmt.Fprintf(w, "RemoteAddr: %s\n", r.RemoteAddr) 108 fmt.Fprintf(w, "RequestURI: %q\n", r.RequestURI) 109 fmt.Fprintf(w, "URL: %#v\n", r.URL) 110 fmt.Fprintf(w, "Body.ContentLength: %d (-1 means unknown)\n", r.ContentLength) 111 fmt.Fprintf(w, "Close: %v (relevant for HTTP/1 only)\n", r.Close) 112 fmt.Fprintf(w, "TLS: %#v\n", r.TLS) 113 fmt.Fprintf(w, "\nHeaders:\n") 114 r.Header.Write(w) 115} 116 117func crcHandler(w http.ResponseWriter, r *http.Request) { 118 if r.Method != "PUT" { 119 http.Error(w, "PUT required.", 400) 120 return 121 } 122 crc := crc32.NewIEEE() 123 n, err := io.Copy(crc, r.Body) 124 if err == nil { 125 w.Header().Set("Content-Type", "text/plain") 126 fmt.Fprintf(w, "bytes=%d, CRC32=%x", n, crc.Sum(nil)) 127 } 128} 129 130type capitalizeReader struct { 131 r io.Reader 132} 133 134func (cr capitalizeReader) Read(p []byte) (n int, err error) { 135 n, err = cr.r.Read(p) 136 for i, b := range p[:n] { 137 if b >= 'a' && b <= 'z' { 138 p[i] = b - ('a' - 'A') 139 } 140 } 141 return 142} 143 144type flushWriter struct { 145 w io.Writer 146} 147 148func (fw flushWriter) Write(p []byte) (n int, err error) { 149 n, err = fw.w.Write(p) 150 if f, ok := fw.w.(http.Flusher); ok { 151 f.Flush() 152 } 153 return 154} 155 156func echoCapitalHandler(w http.ResponseWriter, r *http.Request) { 157 if r.Method != "PUT" { 158 http.Error(w, "PUT required.", 400) 159 return 160 } 161 io.Copy(flushWriter{w}, capitalizeReader{r.Body}) 162} 163 164var ( 165 fsGrp singleflight.Group 166 fsMu sync.Mutex // guards fsCache 167 fsCache = map[string]http.Handler{} 168) 169 170// fileServer returns a file-serving handler that proxies URL. 171// It lazily fetches URL on the first access and caches its contents forever. 172func fileServer(url string, latency time.Duration) http.Handler { 173 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 174 if latency > 0 { 175 time.Sleep(latency) 176 } 177 hi, err := fsGrp.Do(url, func() (interface{}, error) { 178 fsMu.Lock() 179 if h, ok := fsCache[url]; ok { 180 fsMu.Unlock() 181 return h, nil 182 } 183 fsMu.Unlock() 184 185 res, err := http.Get(url) 186 if err != nil { 187 return nil, err 188 } 189 defer res.Body.Close() 190 slurp, err := ioutil.ReadAll(res.Body) 191 if err != nil { 192 return nil, err 193 } 194 195 modTime := time.Now() 196 var h http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 197 http.ServeContent(w, r, path.Base(url), modTime, bytes.NewReader(slurp)) 198 }) 199 fsMu.Lock() 200 fsCache[url] = h 201 fsMu.Unlock() 202 return h, nil 203 }) 204 if err != nil { 205 http.Error(w, err.Error(), 500) 206 return 207 } 208 hi.(http.Handler).ServeHTTP(w, r) 209 }) 210} 211 212func clockStreamHandler(w http.ResponseWriter, r *http.Request) { 213 clientGone := w.(http.CloseNotifier).CloseNotify() 214 w.Header().Set("Content-Type", "text/plain") 215 ticker := time.NewTicker(1 * time.Second) 216 defer ticker.Stop() 217 fmt.Fprintf(w, "# ~1KB of junk to force browsers to start rendering immediately: \n") 218 io.WriteString(w, strings.Repeat("# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n", 13)) 219 220 for { 221 fmt.Fprintf(w, "%v\n", time.Now()) 222 w.(http.Flusher).Flush() 223 select { 224 case <-ticker.C: 225 case <-clientGone: 226 log.Printf("Client %v disconnected from the clock", r.RemoteAddr) 227 return 228 } 229 } 230} 231 232func registerHandlers() { 233 tiles := newGopherTilesHandler() 234 push := newPushHandler() 235 236 mux2 := http.NewServeMux() 237 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 238 switch { 239 case r.URL.Path == "/gophertiles": 240 tiles.ServeHTTP(w, r) // allow HTTP/2 + HTTP/1.x 241 return 242 case strings.HasPrefix(r.URL.Path, "/serverpush"): 243 push.ServeHTTP(w, r) // allow HTTP/2 + HTTP/1.x 244 return 245 case r.TLS == nil: // do not allow HTTP/1.x for anything else 246 http.Redirect(w, r, "https://"+httpsHost()+"/", http.StatusFound) 247 return 248 } 249 if r.ProtoMajor == 1 { 250 if r.URL.Path == "/reqinfo" { 251 reqInfoHandler(w, r) 252 return 253 } 254 homeOldHTTP(w, r) 255 return 256 } 257 mux2.ServeHTTP(w, r) 258 }) 259 mux2.HandleFunc("/", home) 260 mux2.Handle("/file/gopher.png", fileServer("https://golang.org/doc/gopher/frontpage.png", 0)) 261 mux2.Handle("/file/go.src.tar.gz", fileServer("https://storage.googleapis.com/golang/go1.4.1.src.tar.gz", 0)) 262 mux2.HandleFunc("/reqinfo", reqInfoHandler) 263 mux2.HandleFunc("/crc32", crcHandler) 264 mux2.HandleFunc("/ECHO", echoCapitalHandler) 265 mux2.HandleFunc("/clockstream", clockStreamHandler) 266 mux2.Handle("/gophertiles", tiles) 267 mux2.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) { 268 http.Redirect(w, r, "/", http.StatusFound) 269 }) 270 stripHomedir := regexp.MustCompile(`/(Users|home)/\w+`) 271 mux2.HandleFunc("/goroutines", func(w http.ResponseWriter, r *http.Request) { 272 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 273 buf := make([]byte, 2<<20) 274 w.Write(stripHomedir.ReplaceAll(buf[:runtime.Stack(buf, true)], nil)) 275 }) 276} 277 278var pushResources = map[string]http.Handler{ 279 "/serverpush/static/jquery.min.js": fileServer("https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js", 100*time.Millisecond), 280 "/serverpush/static/godocs.js": fileServer("https://golang.org/lib/godoc/godocs.js", 100*time.Millisecond), 281 "/serverpush/static/playground.js": fileServer("https://golang.org/lib/godoc/playground.js", 100*time.Millisecond), 282 "/serverpush/static/style.css": fileServer("https://golang.org/lib/godoc/style.css", 100*time.Millisecond), 283} 284 285func newPushHandler() http.Handler { 286 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 287 for path, handler := range pushResources { 288 if r.URL.Path == path { 289 handler.ServeHTTP(w, r) 290 return 291 } 292 } 293 294 cacheBust := time.Now().UnixNano() 295 if pusher, ok := w.(http.Pusher); ok { 296 for path := range pushResources { 297 url := fmt.Sprintf("%s?%d", path, cacheBust) 298 if err := pusher.Push(url, nil); err != nil { 299 log.Printf("Failed to push %v: %v", path, err) 300 } 301 } 302 } 303 time.Sleep(100 * time.Millisecond) // fake network latency + parsing time 304 if err := pushTmpl.Execute(w, struct { 305 CacheBust int64 306 HTTPSHost string 307 HTTPHost string 308 }{ 309 CacheBust: cacheBust, 310 HTTPSHost: httpsHost(), 311 HTTPHost: httpHost(), 312 }); err != nil { 313 log.Printf("Executing server push template: %v", err) 314 } 315 }) 316} 317 318func newGopherTilesHandler() http.Handler { 319 const gopherURL = "https://blog.golang.org/go-programming-language-turns-two_gophers.jpg" 320 res, err := http.Get(gopherURL) 321 if err != nil { 322 log.Fatal(err) 323 } 324 if res.StatusCode != 200 { 325 log.Fatalf("Error fetching %s: %v", gopherURL, res.Status) 326 } 327 slurp, err := ioutil.ReadAll(res.Body) 328 res.Body.Close() 329 if err != nil { 330 log.Fatal(err) 331 } 332 im, err := jpeg.Decode(bytes.NewReader(slurp)) 333 if err != nil { 334 if len(slurp) > 1024 { 335 slurp = slurp[:1024] 336 } 337 log.Fatalf("Failed to decode gopher image: %v (got %q)", err, slurp) 338 } 339 340 type subImager interface { 341 SubImage(image.Rectangle) image.Image 342 } 343 const tileSize = 32 344 xt := im.Bounds().Max.X / tileSize 345 yt := im.Bounds().Max.Y / tileSize 346 var tile [][][]byte // y -> x -> jpeg bytes 347 for yi := 0; yi < yt; yi++ { 348 var row [][]byte 349 for xi := 0; xi < xt; xi++ { 350 si := im.(subImager).SubImage(image.Rectangle{ 351 Min: image.Point{xi * tileSize, yi * tileSize}, 352 Max: image.Point{(xi + 1) * tileSize, (yi + 1) * tileSize}, 353 }) 354 buf := new(bytes.Buffer) 355 if err := jpeg.Encode(buf, si, &jpeg.Options{Quality: 90}); err != nil { 356 log.Fatal(err) 357 } 358 row = append(row, buf.Bytes()) 359 } 360 tile = append(tile, row) 361 } 362 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 363 ms, _ := strconv.Atoi(r.FormValue("latency")) 364 const nanosPerMilli = 1e6 365 if r.FormValue("x") != "" { 366 x, _ := strconv.Atoi(r.FormValue("x")) 367 y, _ := strconv.Atoi(r.FormValue("y")) 368 if ms <= 1000 { 369 time.Sleep(time.Duration(ms) * nanosPerMilli) 370 } 371 if x >= 0 && x < xt && y >= 0 && y < yt { 372 http.ServeContent(w, r, "", time.Time{}, bytes.NewReader(tile[y][x])) 373 return 374 } 375 } 376 io.WriteString(w, "<html><body onload='showtimes()'>") 377 fmt.Fprintf(w, "A grid of %d tiled images is below. Compare:<p>", xt*yt) 378 for _, ms := range []int{0, 30, 200, 1000} { 379 d := time.Duration(ms) * nanosPerMilli 380 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", 381 httpsHost(), ms, d, 382 httpHost(), ms, d, 383 ) 384 } 385 io.WriteString(w, "<p>\n") 386 cacheBust := time.Now().UnixNano() 387 for y := 0; y < yt; y++ { 388 for x := 0; x < xt; x++ { 389 fmt.Fprintf(w, "<img width=%d height=%d src='/gophertiles?x=%d&y=%d&cachebust=%d&latency=%d'>", 390 tileSize, tileSize, x, y, cacheBust, ms) 391 } 392 io.WriteString(w, "<br/>\n") 393 } 394 io.WriteString(w, `<p><div id='loadtimes'></div></p> 395<script> 396function showtimes() { 397 var times = 'Times from connection start:<br>' 398 times += 'DOM loaded: ' + (window.performance.timing.domContentLoadedEventEnd - window.performance.timing.connectStart) + 'ms<br>' 399 times += 'DOM complete (images loaded): ' + (window.performance.timing.domComplete - window.performance.timing.connectStart) + 'ms<br>' 400 document.getElementById('loadtimes').innerHTML = times 401} 402</script> 403<hr><a href='/'><< Back to Go HTTP/2 demo server</a></body></html>`) 404 }) 405} 406 407func httpsHost() string { 408 if *hostHTTPS != "" { 409 return *hostHTTPS 410 } 411 if v := *httpsAddr; strings.HasPrefix(v, ":") { 412 return "localhost" + v 413 } else { 414 return v 415 } 416} 417 418func httpHost() string { 419 if *hostHTTP != "" { 420 return *hostHTTP 421 } 422 if v := *httpAddr; strings.HasPrefix(v, ":") { 423 return "localhost" + v 424 } else { 425 return v 426 } 427} 428 429func serveProdTLS() error { 430 const cacheDir = "/var/cache/autocert" 431 if err := os.MkdirAll(cacheDir, 0700); err != nil { 432 return err 433 } 434 m := autocert.Manager{ 435 Cache: autocert.DirCache(cacheDir), 436 Prompt: autocert.AcceptTOS, 437 HostPolicy: autocert.HostWhitelist("http2.golang.org"), 438 } 439 srv := &http.Server{ 440 TLSConfig: &tls.Config{ 441 GetCertificate: m.GetCertificate, 442 }, 443 } 444 http2.ConfigureServer(srv, &http2.Server{ 445 NewWriteScheduler: func() http2.WriteScheduler { 446 return http2.NewPriorityWriteScheduler(nil) 447 }, 448 }) 449 ln, err := net.Listen("tcp", ":443") 450 if err != nil { 451 return err 452 } 453 return srv.Serve(tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig)) 454} 455 456type tcpKeepAliveListener struct { 457 *net.TCPListener 458} 459 460func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { 461 tc, err := ln.AcceptTCP() 462 if err != nil { 463 return 464 } 465 tc.SetKeepAlive(true) 466 tc.SetKeepAlivePeriod(3 * time.Minute) 467 return tc, nil 468} 469 470func serveProd() error { 471 errc := make(chan error, 2) 472 go func() { errc <- http.ListenAndServe(":80", nil) }() 473 go func() { errc <- serveProdTLS() }() 474 return <-errc 475} 476 477const idleTimeout = 5 * time.Minute 478const activeTimeout = 10 * time.Minute 479 480// TODO: put this into the standard library and actually send 481// PING frames and GOAWAY, etc: golang.org/issue/14204 482func idleTimeoutHook() func(net.Conn, http.ConnState) { 483 var mu sync.Mutex 484 m := map[net.Conn]*time.Timer{} 485 return func(c net.Conn, cs http.ConnState) { 486 mu.Lock() 487 defer mu.Unlock() 488 if t, ok := m[c]; ok { 489 delete(m, c) 490 t.Stop() 491 } 492 var d time.Duration 493 switch cs { 494 case http.StateNew, http.StateIdle: 495 d = idleTimeout 496 case http.StateActive: 497 d = activeTimeout 498 default: 499 return 500 } 501 m[c] = time.AfterFunc(d, func() { 502 log.Printf("closing idle conn %v after %v", c.RemoteAddr(), d) 503 go c.Close() 504 }) 505 } 506} 507 508func main() { 509 var srv http.Server 510 flag.BoolVar(&http2.VerboseLogs, "verbose", false, "Verbose HTTP/2 debugging.") 511 flag.Parse() 512 srv.Addr = *httpsAddr 513 srv.ConnState = idleTimeoutHook() 514 515 registerHandlers() 516 517 if *prod { 518 *hostHTTP = "http2.golang.org" 519 *hostHTTPS = "http2.golang.org" 520 log.Fatal(serveProd()) 521 } 522 523 url := "https://" + httpsHost() + "/" 524 log.Printf("Listening on " + url) 525 http2.ConfigureServer(&srv, &http2.Server{}) 526 527 if *httpAddr != "" { 528 go func() { 529 log.Printf("Listening on http://" + httpHost() + "/ (for unencrypted HTTP/1)") 530 log.Fatal(http.ListenAndServe(*httpAddr, nil)) 531 }() 532 } 533 534 go func() { 535 log.Fatal(srv.ListenAndServeTLS("server.crt", "server.key")) 536 }() 537 select {} 538} 539