GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

main.go 4.07 KB
Newer Older
1 2 3
package main

import (
4
	"context"
5
	"flag"
6
	"fmt"
7 8 9 10
	"io"
	"log"
	"net/http"
	"os"
11
	"os/signal"
12
	"sync"
13
	"syscall"
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
	"time"

	"git.torproject.org/pluggable-transports/snowflake.git/common/safelog"
	"github.com/gorilla/mux"
)

type Route struct {
	Name        string
	Method      string
	Pattern     string
	HandlerFunc http.HandlerFunc
}

type Routes []Route

var routes = Routes{
	Route{
31 32 33 34
		"BridgeState",
		"GET",
		"/bridge-state",
		BridgeState,
35
	},
36 37 38 39 40 41
	Route{
		"BridgeStateWeb",
		"GET",
		"/result",
		BridgeStateWeb,
	},
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
}

// Logger logs when we receive requests, and the execution time of handling
// these requests.  We don't log client IP addresses or the given obfs4
// parameters.
func Logger(inner http.Handler, name string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()

		inner.ServeHTTP(w, r)

		log.Printf(
			"%s\t%s\t%s\t%s",
			r.Method,
			r.RequestURI,
			name,
			time.Since(start),
		)
	})
}

// NewRouter creates and returns a new request router.
func NewRouter() *mux.Router {

	router := mux.NewRouter().StrictSlash(true)
	for _, route := range routes {
		var handler http.Handler

		handler = route.HandlerFunc
		handler = Logger(handler, route.Name)

		router.
			Methods(route.Method).
			Path(route.Pattern).
			Name(route.Name).
			Handler(handler)
	}

	return router
}

83 84 85 86 87 88 89 90 91 92 93 94
func printPrettyCache() {
	var shortError string
	for bridgeLine, cacheEntry := range cache {
		shortError = cacheEntry.Error
		maxChars := 50
		if len(cacheEntry.Error) > maxChars {
			shortError = cacheEntry.Error[:maxChars]
		}
		fmt.Printf("%-22s %-50s %s\n", bridgeLine, shortError, cacheEntry.Time)
	}
}

95 96 97
func main() {

	var addr string
98
	var web, printCache bool
99
	var certFilename, keyFilename string
100
	var cacheFile string
101
	var templatesDir string
102
	var numSecs int
103

Philipp Winter's avatar
Philipp Winter committed
104
	flag.StringVar(&addr, "addr", ":5000", "Address to listen on.")
105
	flag.BoolVar(&web, "web", false, "Enable the web interface (in addition to the JSON API).")
106
	flag.BoolVar(&printCache, "print-cache", false, "Print the given cache file and exit.")
107 108
	flag.StringVar(&certFilename, "cert", "", "TLS certificate file.")
	flag.StringVar(&keyFilename, "key", "", "TLS private key file.")
109
	flag.StringVar(&cacheFile, "cache", "bridgestrap-cache.bin", "Cache file that contains test results.")
110
	flag.StringVar(&templatesDir, "templates", "templates", "Path to directory that contains our web templates.")
111
	flag.IntVar(&numSecs, "seconds", 2, "Number of seconds after two subsequent requests are handled.")
112 113 114 115
	flag.Parse()

	var logOutput io.Writer = os.Stderr
	// Send the log output through our scrubber first.
116 117 118
	if !printCache {
		log.SetOutput(&safelog.LogScrubber{Output: logOutput})
	}
119 120
	log.SetFlags(log.LstdFlags | log.LUTC)

121 122
	LoadHtmlTemplates(templatesDir)

123 124 125 126 127 128 129 130 131 132 133
	if web {
		log.Println("Enabling web interface.")
		routes = append(routes,
			Route{
				"Index",
				"GET",
				"/",
				Index,
			})
	}

134 135 136
	if err := cache.ReadFromDisk(cacheFile); err != nil {
		log.Printf("Could not read cache because: %s", err)
	}
137 138 139 140
	if printCache {
		printPrettyCache()
		return
	}
141 142 143 144 145

	var srv http.Server
	srv.Addr = addr
	srv.Handler = NewRouter()
	log.Printf("Starting service on port %s.", addr)
146
	go func() {
147 148 149 150
		if certFilename != "" && keyFilename != "" {
			srv.ListenAndServeTLS(certFilename, keyFilename)
		} else {
			srv.ListenAndServe()
151 152 153
		}
	}()

154 155 156
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGINT)
	signal.Notify(signalChan, syscall.SIGTERM)
157 158 159 160

	var wg sync.WaitGroup
	shutdown := make(chan bool)
	go dispatchRequests(shutdown, &wg, numSecs)
161 162
	log.Printf("Waiting for signal to shut down.")
	<-signalChan
163
	shutdown <- true
164 165

	log.Printf("Received signal to shut down.")
166 167
	// Give our Web server a maximum of a minute to finish handling open
	// connections and shut down gracefully.
168 169 170 171 172 173 174 175 176
	t := time.Now().Add(time.Minute)
	ctx, cancel := context.WithDeadline(context.Background(), t)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Printf("Failed to shut down Web server: %s", err)
	}

	if err := cache.WriteToDisk(cacheFile); err != nil {
		log.Printf("Failed to write cache to disk: %s", err)
177
	}
178
	wg.Wait()
179
}