| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538 | // Copyright 2014 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.// +build h2demopackage mainimport (	"bytes"	"crypto/tls"	"flag"	"fmt"	"hash/crc32"	"image"	"image/jpeg"	"io"	"io/ioutil"	"log"	"net"	"net/http"	"os"	"path"	"regexp"	"runtime"	"strconv"	"strings"	"sync"	"time"	"go4.org/syncutil/singleflight"	"golang.org/x/crypto/acme/autocert"	"golang.org/x/net/http2")var (	prod = flag.Bool("prod", false, "Whether to configure itself to be the production http2.golang.org server.")	httpsAddr = flag.String("https_addr", "localhost:4430", "TLS address to listen on ('host:port' or ':port'). Required.")	httpAddr  = flag.String("http_addr", "", "Plain HTTP address to listen on ('host:port', or ':port'). Empty means no HTTP.")	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.")	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."))func homeOldHTTP(w http.ResponseWriter, r *http.Request) {	io.WriteString(w, `<html><body><h1>Go + HTTP/2</h1><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><p>Unfortunately, you're <b>not</b> using HTTP/2 right now. To do so:</p><ul>   <li>Use Firefox Nightly or go to <b>about:config</b> and enable "network.http.spdy.enabled.http2draft"</li>   <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></ul><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></body></html>`)}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><p>Welcome to <a href="https://golang.org/">the Go language</a>'s <ahref="https://http2.github.io/">HTTP/2</a> demo & interop server.</p><p>Congratulations, <b>you're using HTTP/2 right now</b>.</p><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><p>The code is at <a href="https://golang.org/x/net/http2">golang.org/x/net/http2</a> andis used transparently by the Go standard library from Go 1.6 and later.</p><p>Contact info: <i>[email protected]</i>, or <ahref="https://golang.org/s/http2bug">file a bug</a>.</p><h2>Handlers for testing</h2><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="/serverpush">/serverpush</a> to see a page with server push</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>  <li>GET <a href="/goroutines">/goroutines</a> to see all active goroutines in this server</li>  <li>PUT something to <a href="/crc32">/crc32</a> to get a count of number of bytes and its CRC-32</li>  <li>PUT something to <a href="/ECHO">/ECHO</a> and it will be streamed back to you capitalized</li></ul></body></html>`)}func reqInfoHandler(w http.ResponseWriter, r *http.Request) {	w.Header().Set("Content-Type", "text/plain")	fmt.Fprintf(w, "Method: %s\n", r.Method)	fmt.Fprintf(w, "Protocol: %s\n", r.Proto)	fmt.Fprintf(w, "Host: %s\n", r.Host)	fmt.Fprintf(w, "RemoteAddr: %s\n", r.RemoteAddr)	fmt.Fprintf(w, "RequestURI: %q\n", r.RequestURI)	fmt.Fprintf(w, "URL: %#v\n", r.URL)	fmt.Fprintf(w, "Body.ContentLength: %d (-1 means unknown)\n", r.ContentLength)	fmt.Fprintf(w, "Close: %v (relevant for HTTP/1 only)\n", r.Close)	fmt.Fprintf(w, "TLS: %#v\n", r.TLS)	fmt.Fprintf(w, "\nHeaders:\n")	r.Header.Write(w)}func crcHandler(w http.ResponseWriter, r *http.Request) {	if r.Method != "PUT" {		http.Error(w, "PUT required.", 400)		return	}	crc := crc32.NewIEEE()	n, err := io.Copy(crc, r.Body)	if err == nil {		w.Header().Set("Content-Type", "text/plain")		fmt.Fprintf(w, "bytes=%d, CRC32=%x", n, crc.Sum(nil))	}}type capitalizeReader struct {	r io.Reader}func (cr capitalizeReader) Read(p []byte) (n int, err error) {	n, err = cr.r.Read(p)	for i, b := range p[:n] {		if b >= 'a' && b <= 'z' {			p[i] = b - ('a' - 'A')		}	}	return}type flushWriter struct {	w io.Writer}func (fw flushWriter) Write(p []byte) (n int, err error) {	n, err = fw.w.Write(p)	if f, ok := fw.w.(http.Flusher); ok {		f.Flush()	}	return}func echoCapitalHandler(w http.ResponseWriter, r *http.Request) {	if r.Method != "PUT" {		http.Error(w, "PUT required.", 400)		return	}	io.Copy(flushWriter{w}, capitalizeReader{r.Body})}var (	fsGrp   singleflight.Group	fsMu    sync.Mutex // guards fsCache	fsCache = map[string]http.Handler{})// fileServer returns a file-serving handler that proxies URL.// It lazily fetches URL on the first access and caches its contents forever.func fileServer(url string, latency time.Duration) http.Handler {	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {		if latency > 0 {			time.Sleep(latency)		}		hi, err := fsGrp.Do(url, func() (interface{}, error) {			fsMu.Lock()			if h, ok := fsCache[url]; ok {				fsMu.Unlock()				return h, nil			}			fsMu.Unlock()			res, err := http.Get(url)			if err != nil {				return nil, err			}			defer res.Body.Close()			slurp, err := ioutil.ReadAll(res.Body)			if err != nil {				return nil, err			}			modTime := time.Now()			var h http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {				http.ServeContent(w, r, path.Base(url), modTime, bytes.NewReader(slurp))			})			fsMu.Lock()			fsCache[url] = h			fsMu.Unlock()			return h, nil		})		if err != nil {			http.Error(w, err.Error(), 500)			return		}		hi.(http.Handler).ServeHTTP(w, r)	})}func clockStreamHandler(w http.ResponseWriter, r *http.Request) {	clientGone := w.(http.CloseNotifier).CloseNotify()	w.Header().Set("Content-Type", "text/plain")	ticker := time.NewTicker(1 * time.Second)	defer ticker.Stop()	fmt.Fprintf(w, "# ~1KB of junk to force browsers to start rendering immediately: \n")	io.WriteString(w, strings.Repeat("# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n", 13))	for {		fmt.Fprintf(w, "%v\n", time.Now())		w.(http.Flusher).Flush()		select {		case <-ticker.C:		case <-clientGone:			log.Printf("Client %v disconnected from the clock", r.RemoteAddr)			return		}	}}func registerHandlers() {	tiles := newGopherTilesHandler()	push := newPushHandler()	mux2 := http.NewServeMux()	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {		switch {		case r.URL.Path == "/gophertiles":			tiles.ServeHTTP(w, r) // allow HTTP/2 + HTTP/1.x			return		case strings.HasPrefix(r.URL.Path, "/serverpush"):			push.ServeHTTP(w, r) // allow HTTP/2 + HTTP/1.x			return		case r.TLS == nil: // do not allow HTTP/1.x for anything else			http.Redirect(w, r, "https://"+httpsHost()+"/", http.StatusFound)			return		}		if r.ProtoMajor == 1 {			if r.URL.Path == "/reqinfo" {				reqInfoHandler(w, r)				return			}			homeOldHTTP(w, r)			return		}		mux2.ServeHTTP(w, r)	})	mux2.HandleFunc("/", home)	mux2.Handle("/file/gopher.png", fileServer("https://golang.org/doc/gopher/frontpage.png", 0))	mux2.Handle("/file/go.src.tar.gz", fileServer("https://storage.googleapis.com/golang/go1.4.1.src.tar.gz", 0))	mux2.HandleFunc("/reqinfo", reqInfoHandler)	mux2.HandleFunc("/crc32", crcHandler)	mux2.HandleFunc("/ECHO", echoCapitalHandler)	mux2.HandleFunc("/clockstream", clockStreamHandler)	mux2.Handle("/gophertiles", tiles)	mux2.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {		http.Redirect(w, r, "/", http.StatusFound)	})	stripHomedir := regexp.MustCompile(`/(Users|home)/\w+`)	mux2.HandleFunc("/goroutines", func(w http.ResponseWriter, r *http.Request) {		w.Header().Set("Content-Type", "text/plain; charset=utf-8")		buf := make([]byte, 2<<20)		w.Write(stripHomedir.ReplaceAll(buf[:runtime.Stack(buf, true)], nil))	})}var pushResources = map[string]http.Handler{	"/serverpush/static/jquery.min.js": fileServer("https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js", 100*time.Millisecond),	"/serverpush/static/godocs.js":     fileServer("https://golang.org/lib/godoc/godocs.js", 100*time.Millisecond),	"/serverpush/static/playground.js": fileServer("https://golang.org/lib/godoc/playground.js", 100*time.Millisecond),	"/serverpush/static/style.css":     fileServer("https://golang.org/lib/godoc/style.css", 100*time.Millisecond),}func newPushHandler() http.Handler {	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {		for path, handler := range pushResources {			if r.URL.Path == path {				handler.ServeHTTP(w, r)				return			}		}		cacheBust := time.Now().UnixNano()		if pusher, ok := w.(http.Pusher); ok {			for path := range pushResources {				url := fmt.Sprintf("%s?%d", path, cacheBust)				if err := pusher.Push(url, nil); err != nil {					log.Printf("Failed to push %v: %v", path, err)				}			}		}		time.Sleep(100 * time.Millisecond) // fake network latency + parsing time		if err := pushTmpl.Execute(w, struct {			CacheBust int64			HTTPSHost string			HTTPHost  string		}{			CacheBust: cacheBust,			HTTPSHost: httpsHost(),			HTTPHost:  httpHost(),		}); err != nil {			log.Printf("Executing server push template: %v", err)		}	})}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 onload='showtimes()'>")		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, `<p><div id='loadtimes'></div></p><script>function showtimes() {	var times = 'Times from connection start:<br>'	times += 'DOM loaded: ' + (window.performance.timing.domContentLoadedEventEnd - window.performance.timing.connectStart) + 'ms<br>'	times += 'DOM complete (images loaded): ' + (window.performance.timing.domComplete - window.performance.timing.connectStart) + 'ms<br>'	document.getElementById('loadtimes').innerHTML = times}</script><hr><a href='/'><< Back to Go HTTP/2 demo server</a></body></html>`)	})}func httpsHost() string {	if *hostHTTPS != "" {		return *hostHTTPS	}	if v := *httpsAddr; strings.HasPrefix(v, ":") {		return "localhost" + v	} else {		return v	}}func httpHost() string {	if *hostHTTP != "" {		return *hostHTTP	}	if v := *httpAddr; strings.HasPrefix(v, ":") {		return "localhost" + v	} else {		return v	}}func serveProdTLS() error {	const cacheDir = "/var/cache/autocert"	if err := os.MkdirAll(cacheDir, 0700); err != nil {		return err	}	m := autocert.Manager{		Cache:      autocert.DirCache(cacheDir),		Prompt:     autocert.AcceptTOS,		HostPolicy: autocert.HostWhitelist("http2.golang.org"),	}	srv := &http.Server{		TLSConfig: &tls.Config{			GetCertificate: m.GetCertificate,		},	}	http2.ConfigureServer(srv, &http2.Server{		NewWriteScheduler: func() http2.WriteScheduler {			return http2.NewPriorityWriteScheduler(nil)		},	})	ln, err := net.Listen("tcp", ":443")	if err != nil {		return err	}	return srv.Serve(tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig))}type tcpKeepAliveListener struct {	*net.TCPListener}func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {	tc, err := ln.AcceptTCP()	if err != nil {		return	}	tc.SetKeepAlive(true)	tc.SetKeepAlivePeriod(3 * time.Minute)	return tc, nil}func serveProd() error {	errc := make(chan error, 2)	go func() { errc <- http.ListenAndServe(":80", nil) }()	go func() { errc <- serveProdTLS() }()	return <-errc}const idleTimeout = 5 * time.Minuteconst activeTimeout = 10 * time.Minute// TODO: put this into the standard library and actually send// PING frames and GOAWAY, etc: golang.org/issue/14204func idleTimeoutHook() func(net.Conn, http.ConnState) {	var mu sync.Mutex	m := map[net.Conn]*time.Timer{}	return func(c net.Conn, cs http.ConnState) {		mu.Lock()		defer mu.Unlock()		if t, ok := m[c]; ok {			delete(m, c)			t.Stop()		}		var d time.Duration		switch cs {		case http.StateNew, http.StateIdle:			d = idleTimeout		case http.StateActive:			d = activeTimeout		default:			return		}		m[c] = time.AfterFunc(d, func() {			log.Printf("closing idle conn %v after %v", c.RemoteAddr(), d)			go c.Close()		})	}}func main() {	var srv http.Server	flag.BoolVar(&http2.VerboseLogs, "verbose", false, "Verbose HTTP/2 debugging.")	flag.Parse()	srv.Addr = *httpsAddr	srv.ConnState = idleTimeoutHook()	registerHandlers()	if *prod {		*hostHTTP = "http2.golang.org"		*hostHTTPS = "http2.golang.org"		log.Fatal(serveProd())	}	url := "https://" + httpsHost() + "/"	log.Printf("Listening on " + url)	http2.ConfigureServer(&srv, &http2.Server{})	if *httpAddr != "" {		go func() {			log.Printf("Listening on http://" + httpHost() + "/ (for unencrypted HTTP/1)")			log.Fatal(http.ListenAndServe(*httpAddr, nil))		}()	}	go func() {		log.Fatal(srv.ListenAndServeTLS("server.crt", "server.key"))	}()	select {}}
 |