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