h2demo.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. // Copyright 2014 The Go Authors.
  2. // See https://code.google.com/p/go/source/browse/CONTRIBUTORS
  3. // Licensed under the same terms as Go itself:
  4. // https://code.google.com/p/go/source/browse/LICENSE
  5. package main
  6. import (
  7. "bytes"
  8. "crypto/tls"
  9. "flag"
  10. "fmt"
  11. "hash/crc32"
  12. "image"
  13. "image/jpeg"
  14. "io"
  15. "io/ioutil"
  16. "log"
  17. "net"
  18. "net/http"
  19. "os/exec"
  20. "path"
  21. "regexp"
  22. "runtime"
  23. "strconv"
  24. "strings"
  25. "sync"
  26. "time"
  27. "camlistore.org/pkg/googlestorage"
  28. "camlistore.org/pkg/singleflight"
  29. "github.com/bradfitz/http2"
  30. )
  31. var (
  32. openFirefox = flag.Bool("openff", false, "Open Firefox")
  33. addr = flag.String("addr", "localhost:4430", "TLS address to listen on")
  34. httpAddr = flag.String("httpaddr", "", "If non-empty, address to listen for regular HTTP on")
  35. prod = flag.Bool("prod", false, "Whether to configure itself to be the production http2.golang.org server.")
  36. )
  37. func homeOldHTTP(w http.ResponseWriter, r *http.Request) {
  38. io.WriteString(w, `<html>
  39. <body>
  40. <h1>Go + HTTP/2</h1>
  41. <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>
  42. <p>Unfortunately, you're <b>not</b> using HTTP/2 right now.</p>
  43. <p>See code & instructions for connecting at <a href="https://github.com/bradfitz/http2">https://github.com/bradfitz/http2</a>.</p>
  44. </body></html>`)
  45. }
  46. func home(w http.ResponseWriter, r *http.Request) {
  47. if r.URL.Path != "/" {
  48. http.NotFound(w, r)
  49. return
  50. }
  51. io.WriteString(w, `<html>
  52. <body>
  53. <h1>Go + HTTP/2</h1>
  54. <p>Welcome to <a href="https://golang.org/">the Go language</a>'s <a
  55. href="https://http2.github.io/">HTTP/2</a> demo & interop server.</p>
  56. <p>Congratulations, <b>you're using HTTP/2 right now</b>.</p>
  57. <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>
  58. <p> The code is currently at <a
  59. href="https://github.com/bradfitz/http2">github.com/bradfitz/http2</a>
  60. but will move to the Go standard library at some point in the future
  61. (enabled by default, without users needing to change their code).</p>
  62. <p>Contact info: <i>bradfitz@golang.org</i>, or <a
  63. href="https://github.com/bradfitz/http2/issues">file a bug</a>.</p>
  64. <h2>Handlers for testing</h2>
  65. <ul>
  66. <li>GET <a href="/reqinfo">/reqinfo</a> to dump the request + headers received</li>
  67. <li>GET <a href="/clockstream">/clockstream</a> streams the current time every second</li>
  68. <li>GET <a href="/gophertiles">/gophertiles</a> to see a page with a bunch of images</li>
  69. <li>GET <a href="/file/gopher.png">/file/gopher.png</a> for a small file (does If-Modified-Since, Content-Range, etc)</li>
  70. <li>GET <a href="/file/go.src.tar.gz">/file/go.src.tar.gz</a> for a larger file (~10 MB)</li>
  71. <li>GET <a href="/redirect">/redirect</a> to redirect back to / (this page)</li>
  72. <li>GET <a href="/goroutines">/goroutines</a> to see all active goroutines in this server</li>
  73. <li>PUT something to <a href="/crc32">/crc32</a> to get a count of number of bytes and its CRC-32</li>
  74. </ul>
  75. </body></html>`)
  76. }
  77. func reqInfoHandler(w http.ResponseWriter, r *http.Request) {
  78. w.Header().Set("Content-Type", "text/plain")
  79. fmt.Fprintf(w, "Method: %s\n", r.Method)
  80. fmt.Fprintf(w, "Protocol: %s\n", r.Proto)
  81. fmt.Fprintf(w, "Host: %s\n", r.Host)
  82. fmt.Fprintf(w, "RemoteAddr: %s\n", r.RemoteAddr)
  83. fmt.Fprintf(w, "RequestURI: %q\n", r.RequestURI)
  84. fmt.Fprintf(w, "URL: %#v\n", r.URL)
  85. fmt.Fprintf(w, "Body.ContentLength: %d (-1 means unknown)\n", r.ContentLength)
  86. fmt.Fprintf(w, "Close: %v (relevant for HTTP/1 only)\n", r.Close)
  87. fmt.Fprintf(w, "TLS: %#v\n", r.TLS)
  88. fmt.Fprintf(w, "\nHeaders:\n")
  89. r.Header.Write(w)
  90. }
  91. func crcHandler(w http.ResponseWriter, r *http.Request) {
  92. if r.Method != "PUT" {
  93. http.Error(w, "PUT required.", 400)
  94. return
  95. }
  96. crc := crc32.NewIEEE()
  97. n, err := io.Copy(crc, r.Body)
  98. if err == nil {
  99. w.Header().Set("Content-Type", "text/plain")
  100. fmt.Fprintf(w, "bytes=%d, CRC32=%x", n, crc.Sum(nil))
  101. }
  102. }
  103. var (
  104. fsGrp singleflight.Group
  105. fsMu sync.Mutex // guards fsCache
  106. fsCache = map[string]http.Handler{}
  107. )
  108. // fileServer returns a file-serving handler that proxies URL.
  109. // It lazily fetches URL on the first access and caches its contents forever.
  110. func fileServer(url string) http.Handler {
  111. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  112. hi, err := fsGrp.Do(url, func() (interface{}, error) {
  113. fsMu.Lock()
  114. if h, ok := fsCache[url]; ok {
  115. fsMu.Unlock()
  116. return h, nil
  117. }
  118. fsMu.Unlock()
  119. res, err := http.Get(url)
  120. if err != nil {
  121. return nil, err
  122. }
  123. defer res.Body.Close()
  124. slurp, err := ioutil.ReadAll(res.Body)
  125. if err != nil {
  126. return nil, err
  127. }
  128. modTime := time.Now()
  129. var h http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  130. http.ServeContent(w, r, path.Base(url), modTime, bytes.NewReader(slurp))
  131. })
  132. fsMu.Lock()
  133. fsCache[url] = h
  134. fsMu.Unlock()
  135. return h, nil
  136. })
  137. if err != nil {
  138. http.Error(w, err.Error(), 500)
  139. return
  140. }
  141. hi.(http.Handler).ServeHTTP(w, r)
  142. })
  143. }
  144. func clockStreamHandler(w http.ResponseWriter, r *http.Request) {
  145. clientGone := w.(http.CloseNotifier).CloseNotify()
  146. w.Header().Set("Content-Type", "text/plain")
  147. ticker := time.NewTicker(1 * time.Second)
  148. defer ticker.Stop()
  149. fmt.Fprintf(w, "# ~1KB of junk to force browsers to start rendering immediately: \n")
  150. io.WriteString(w, strings.Repeat("# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n", 13))
  151. for {
  152. fmt.Fprintf(w, "%v\n", time.Now())
  153. w.(http.Flusher).Flush()
  154. select {
  155. case <-ticker.C:
  156. case <-clientGone:
  157. log.Printf("Client %v disconnected from the clock", r.RemoteAddr)
  158. return
  159. }
  160. }
  161. }
  162. func registerHandlers() {
  163. tiles := newGopherTilesHandler()
  164. mux2 := http.NewServeMux()
  165. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  166. if r.TLS == nil {
  167. if r.URL.Path == "/gophertiles" {
  168. tiles.ServeHTTP(w, r)
  169. return
  170. }
  171. http.Redirect(w, r, "https://http2.golang.org/", http.StatusFound)
  172. return
  173. }
  174. if r.ProtoMajor == 1 {
  175. if r.URL.Path == "/reqinfo" {
  176. reqInfoHandler(w, r)
  177. return
  178. }
  179. homeOldHTTP(w, r)
  180. return
  181. }
  182. mux2.ServeHTTP(w, r)
  183. })
  184. mux2.HandleFunc("/", home)
  185. mux2.Handle("/file/gopher.png", fileServer("https://golang.org/doc/gopher/frontpage.png"))
  186. mux2.Handle("/file/go.src.tar.gz", fileServer("https://storage.googleapis.com/golang/go1.4rc1.src.tar.gz"))
  187. mux2.HandleFunc("/reqinfo", reqInfoHandler)
  188. mux2.HandleFunc("/crc32", crcHandler)
  189. mux2.HandleFunc("/clockstream", clockStreamHandler)
  190. mux2.Handle("/gophertiles", tiles)
  191. mux2.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
  192. http.Redirect(w, r, "/", http.StatusFound)
  193. })
  194. stripHomedir := regexp.MustCompile(`/(Users|home)/\w+`)
  195. mux2.HandleFunc("/goroutines", func(w http.ResponseWriter, r *http.Request) {
  196. w.Header().Set("Content-Type", "text/plain; charset=utf-8")
  197. buf := make([]byte, 2<<20)
  198. w.Write(stripHomedir.ReplaceAll(buf[:runtime.Stack(buf, true)], nil))
  199. })
  200. }
  201. func newGopherTilesHandler() http.Handler {
  202. const gopherURL = "https://blog.golang.org/go-programming-language-turns-two_gophers.jpg"
  203. res, err := http.Get(gopherURL)
  204. if err != nil {
  205. log.Fatal(err)
  206. }
  207. if res.StatusCode != 200 {
  208. log.Fatalf("Error fetching %s: %v", gopherURL, res.Status)
  209. }
  210. slurp, err := ioutil.ReadAll(res.Body)
  211. res.Body.Close()
  212. if err != nil {
  213. log.Fatal(err)
  214. }
  215. im, err := jpeg.Decode(bytes.NewReader(slurp))
  216. if err != nil {
  217. if len(slurp) > 1024 {
  218. slurp = slurp[:1024]
  219. }
  220. log.Fatalf("Failed to decode gopher image: %v (got %q)", err, slurp)
  221. }
  222. type subImager interface {
  223. SubImage(image.Rectangle) image.Image
  224. }
  225. const tileSize = 32
  226. xt := im.Bounds().Max.X / tileSize
  227. yt := im.Bounds().Max.Y / tileSize
  228. var tile [][][]byte // y -> x -> jpeg bytes
  229. for yi := 0; yi < yt; yi++ {
  230. var row [][]byte
  231. for xi := 0; xi < xt; xi++ {
  232. si := im.(subImager).SubImage(image.Rectangle{
  233. Min: image.Point{xi * tileSize, yi * tileSize},
  234. Max: image.Point{(xi + 1) * tileSize, (yi + 1) * tileSize},
  235. })
  236. buf := new(bytes.Buffer)
  237. if err := jpeg.Encode(buf, si, &jpeg.Options{Quality: 90}); err != nil {
  238. log.Fatal(err)
  239. }
  240. row = append(row, buf.Bytes())
  241. }
  242. tile = append(tile, row)
  243. }
  244. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  245. ms, _ := strconv.Atoi(r.FormValue("latency"))
  246. const nanosPerMilli = 1e6
  247. if r.FormValue("x") != "" {
  248. x, _ := strconv.Atoi(r.FormValue("x"))
  249. y, _ := strconv.Atoi(r.FormValue("y"))
  250. if ms <= 1000 {
  251. time.Sleep(time.Duration(ms) * nanosPerMilli)
  252. }
  253. if x >= 0 && x < xt && y >= 0 && y < yt {
  254. http.ServeContent(w, r, "", time.Time{}, bytes.NewReader(tile[y][x]))
  255. return
  256. }
  257. }
  258. io.WriteString(w, "<html><body>")
  259. fmt.Fprintf(w, "A grid of %d tiled images is below. Compare:<p>", xt*yt)
  260. for _, ms := range []int{0, 30, 200, 1000} {
  261. d := time.Duration(ms) * nanosPerMilli
  262. 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",
  263. httpsHost(), ms, d,
  264. httpHost(), ms, d,
  265. )
  266. }
  267. io.WriteString(w, "<p>\n")
  268. cacheBust := time.Now().UnixNano()
  269. for y := 0; y < yt; y++ {
  270. for x := 0; x < xt; x++ {
  271. fmt.Fprintf(w, "<img width=%d height=%d src='/gophertiles?x=%d&y=%d&cachebust=%d&latency=%d'>",
  272. tileSize, tileSize, x, y, cacheBust, ms)
  273. }
  274. io.WriteString(w, "<br/>\n")
  275. }
  276. io.WriteString(w, "<hr><a href='/'>&lt;&lt Back to Go HTTP/2 demo server</a></body></html>")
  277. })
  278. }
  279. func httpsHost() string {
  280. if *prod {
  281. return "http2.golang.org"
  282. }
  283. if v := *addr; strings.HasPrefix(v, ":") {
  284. return "localhost" + v
  285. } else {
  286. return v
  287. }
  288. }
  289. func httpHost() string {
  290. if *prod {
  291. return "http2.golang.org"
  292. }
  293. if v := *httpAddr; strings.HasPrefix(v, ":") {
  294. return "localhost" + v
  295. } else {
  296. return v
  297. }
  298. }
  299. func serveProdTLS() error {
  300. c, err := googlestorage.NewServiceClient()
  301. if err != nil {
  302. return err
  303. }
  304. slurp := func(key string) ([]byte, error) {
  305. const bucket = "http2-demo-server-tls"
  306. rc, _, err := c.GetObject(&googlestorage.Object{
  307. Bucket: bucket,
  308. Key: key,
  309. })
  310. if err != nil {
  311. return nil, fmt.Errorf("Error fetching GCS object %q in bucket %q: %v", key, bucket, err)
  312. }
  313. defer rc.Close()
  314. return ioutil.ReadAll(rc)
  315. }
  316. certPem, err := slurp("http2.golang.org.chained.pem")
  317. if err != nil {
  318. return err
  319. }
  320. keyPem, err := slurp("http2.golang.org.key")
  321. if err != nil {
  322. return err
  323. }
  324. cert, err := tls.X509KeyPair(certPem, keyPem)
  325. if err != nil {
  326. return err
  327. }
  328. srv := &http.Server{
  329. TLSConfig: &tls.Config{
  330. Certificates: []tls.Certificate{cert},
  331. },
  332. }
  333. http2.ConfigureServer(srv, &http2.Server{})
  334. ln, err := net.Listen("tcp", ":443")
  335. if err != nil {
  336. return err
  337. }
  338. return srv.Serve(tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig))
  339. }
  340. type tcpKeepAliveListener struct {
  341. *net.TCPListener
  342. }
  343. func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
  344. tc, err := ln.AcceptTCP()
  345. if err != nil {
  346. return
  347. }
  348. tc.SetKeepAlive(true)
  349. tc.SetKeepAlivePeriod(3 * time.Minute)
  350. return tc, nil
  351. }
  352. func serveProd() error {
  353. errc := make(chan error, 2)
  354. go func() { errc <- http.ListenAndServe(":80", nil) }()
  355. go func() { errc <- serveProdTLS() }()
  356. return <-errc
  357. }
  358. func main() {
  359. var srv http.Server
  360. flag.BoolVar(&http2.VerboseLogs, "verbose", false, "Verbose HTTP/2 debugging.")
  361. flag.Parse()
  362. srv.Addr = *addr
  363. registerHandlers()
  364. if *prod {
  365. *httpAddr = "http2.golang.org"
  366. log.Fatal(serveProd())
  367. }
  368. url := "https://" + *addr + "/"
  369. log.Printf("Listening on " + url)
  370. http2.ConfigureServer(&srv, &http2.Server{})
  371. if *httpAddr != "" {
  372. go func() { log.Fatal(http.ListenAndServe(*httpAddr, nil)) }()
  373. }
  374. go func() {
  375. log.Fatal(srv.ListenAndServeTLS("server.crt", "server.key"))
  376. }()
  377. if *openFirefox && runtime.GOOS == "darwin" {
  378. time.Sleep(250 * time.Millisecond)
  379. exec.Command("open", "-b", "org.mozilla.nightly", "https://localhost:4430/").Run()
  380. }
  381. select {}
  382. }