h2demo.go 17 KB

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