|
2 | 2 | // Use of this source code is governed by a BSD-style
|
3 | 3 | // license that can be found in the LICENSE file.
|
4 | 4 |
|
5 |
| -// Devapp generates the dashboard that powers dev.golang.org. This binary is |
6 |
| -// designed to be run outside of an App Engine context. |
7 |
| -// |
8 |
| -// Usage: |
9 |
| -// |
10 |
| -// devappserver --http=:8081 |
11 |
| -// |
12 |
| -// By default devappserver listens on port 80. |
13 |
| -// |
14 |
| -// For the moment, Github issues and Gerrit CL's are stored in memory |
15 |
| -// in the running process. To trigger an initial download, visit |
16 |
| -// http://localhost:8081/update and/or http://localhost:8081/update/stats in |
17 |
| -// your browser. |
| 5 | +// Devapp generates the dashboard that powers dev.golang.org. |
18 | 6 |
|
19 | 7 | package main
|
20 | 8 |
|
21 | 9 | import (
|
| 10 | + "context" |
| 11 | + "crypto/tls" |
| 12 | + "errors" |
22 | 13 | "flag"
|
23 | 14 | "fmt"
|
24 | 15 | "log"
|
25 | 16 | "net"
|
26 | 17 | "net/http"
|
| 18 | + "net/http/httptest" |
27 | 19 | "os"
|
| 20 | + "strconv" |
| 21 | + "strings" |
| 22 | + "time" |
| 23 | + |
| 24 | + "cloud.google.com/go/storage" |
| 25 | + "golang.org/x/build/autocertcache" |
| 26 | + "golang.org/x/crypto/acme/autocert" |
| 27 | + "golang.org/x/net/http2" |
28 | 28 |
|
29 | 29 | _ "golang.org/x/build/devapp" // registers HTTP handlers
|
30 | 30 | )
|
31 | 31 |
|
32 | 32 | func init() {
|
33 | 33 | flag.Usage = func() {
|
34 |
| - os.Stderr.WriteString(`usage: devappserver [-http=addr] |
35 |
| -
|
36 |
| -devappserver generates the dashboard that powers dev.golang.org. |
37 |
| - `) |
| 34 | + os.Stderr.WriteString("devappserver generates the dashboard that powers dev.golang.org.\n") |
| 35 | + flag.PrintDefaults() |
38 | 36 | }
|
39 | 37 | }
|
40 | 38 |
|
41 | 39 | func main() {
|
42 |
| - httpAddr := flag.String("http", ":80", "HTTP service address (e.g., ':8080')") |
| 40 | + var ( |
| 41 | + listen = flag.String("listen", "localhost:6343", "listen address") |
| 42 | + devTLSPort = flag.Int("dev-tls-port", 0, "if non-zero, port number to run localhost self-signed TLS server") |
| 43 | + autocertBucket = flag.String("autocert-bucket", "", "if non-empty, listen on port 443 and serve a LetsEncrypt TLS cert using this Google Cloud Storage bucket as a cache") |
| 44 | + ) |
43 | 45 | flag.Parse()
|
44 |
| - ln, err := net.Listen("tcp", *httpAddr) |
| 46 | + |
| 47 | + ln, err := net.Listen("tcp", *listen) |
| 48 | + if err != nil { |
| 49 | + log.Fatalf("Error listening on %s: %v\n", *listen, err) |
| 50 | + } |
| 51 | + log.Printf("Listening on %s\n", ln.Addr()) |
| 52 | + |
| 53 | + errc := make(chan error) |
| 54 | + if ln != nil { |
| 55 | + go func() { errc <- fmt.Errorf("http.Serve = %v", http.Serve(ln, nil)) }() |
| 56 | + } |
| 57 | + if *autocertBucket != "" { |
| 58 | + go func() { errc <- serveAutocertTLS(*autocertBucket) }() |
| 59 | + } |
| 60 | + if *devTLSPort != 0 { |
| 61 | + go func() { errc <- serveDevTLS(*devTLSPort) }() |
| 62 | + } |
| 63 | + |
| 64 | + log.Fatal(<-errc) |
| 65 | +} |
| 66 | + |
| 67 | +func serveDevTLS(port int) error { |
| 68 | + ln, err := net.Listen("tcp", "localhost:"+strconv.Itoa(port)) |
| 69 | + if err != nil { |
| 70 | + return err |
| 71 | + } |
| 72 | + defer ln.Close() |
| 73 | + log.Printf("Serving self-signed TLS at https://%s", ln.Addr()) |
| 74 | + // Abuse httptest for its localhost TLS setup code: |
| 75 | + ts := httptest.NewUnstartedServer(http.DefaultServeMux) |
| 76 | + // Ditch the provided listener, replace with our own: |
| 77 | + ts.Listener.Close() |
| 78 | + ts.Listener = ln |
| 79 | + ts.TLS = &tls.Config{ |
| 80 | + NextProtos: []string{"h2", "http/1.1"}, |
| 81 | + InsecureSkipVerify: true, |
| 82 | + } |
| 83 | + ts.StartTLS() |
| 84 | + |
| 85 | + select {} |
| 86 | +} |
| 87 | + |
| 88 | +func serveAutocertTLS(bucket string) error { |
| 89 | + ln, err := net.Listen("tcp", ":443") |
| 90 | + if err != nil { |
| 91 | + return err |
| 92 | + } |
| 93 | + defer ln.Close() |
| 94 | + sc, err := storage.NewClient(context.Background()) |
| 95 | + if err != nil { |
| 96 | + return fmt.Errorf("storage.NewClient: %v", err) |
| 97 | + } |
| 98 | + m := autocert.Manager{ |
| 99 | + Prompt: autocert.AcceptTOS, |
| 100 | + HostPolicy: func(ctx context.Context, host string) error { |
| 101 | + if !strings.HasSuffix(host, ".golang.org") { |
| 102 | + return errors.New("refusing to serve autocert on provided domain") |
| 103 | + } |
| 104 | + return nil |
| 105 | + }, |
| 106 | + Cache: autocertcache.NewGoogleCloudStorageCache(sc, bucket), |
| 107 | + } |
| 108 | + config := &tls.Config{ |
| 109 | + GetCertificate: m.GetCertificate, |
| 110 | + NextProtos: []string{"h2", "http/1.1"}, |
| 111 | + } |
| 112 | + tlsLn := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config) |
| 113 | + server := &http.Server{ |
| 114 | + Addr: ln.Addr().String(), |
| 115 | + } |
| 116 | + if err := http2.ConfigureServer(server, nil); err != nil { |
| 117 | + log.Fatalf("http2.ConfigureServer: %v", err) |
| 118 | + } |
| 119 | + log.Printf("Serving TLS at %s", tlsLn.Addr()) |
| 120 | + return server.Serve(tlsLn) |
| 121 | +} |
| 122 | + |
| 123 | +type tcpKeepAliveListener struct { |
| 124 | + *net.TCPListener |
| 125 | +} |
| 126 | + |
| 127 | +func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { |
| 128 | + tc, err := ln.AcceptTCP() |
45 | 129 | if err != nil {
|
46 |
| - fmt.Fprintf(os.Stderr, "Error listening on %s: %v\n", *httpAddr, err) |
47 |
| - os.Exit(2) |
| 130 | + return |
48 | 131 | }
|
49 |
| - fmt.Fprintf(os.Stderr, "Serving at %s\n", ln.Addr().String()) |
50 |
| - log.Fatal(http.Serve(ln, nil)) |
| 132 | + tc.SetKeepAlive(true) |
| 133 | + tc.SetKeepAlivePeriod(3 * time.Minute) |
| 134 | + return tc, nil |
51 | 135 | }
|
0 commit comments