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='/'>&lt;&lt 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  }