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