func TestLimiterMiddleware(t *testing.T) {
	api := NewAPI()

	// the middleware to test
	api.Use(Limiter(tollbooth.NewLimiter(1, time.Second)))

	// a simple app
	api.SetApp(AppSimple(func(ctx context.Context, w ResponseWriter, r *Request) {
		w.WriteJSON(map[string]string{"Id": "123"})
	}))

	// wrap all
	handler := api.MakeHandler()

	for i := 0; i < 100; i++ {
		req := test.MakeSimpleRequest("GET", "http://localhost/", nil)
		req.RemoteAddr = "127.0.0.1:45344"
		recorded := test.RunRequest(t, handler, req)
		if i > 1 {
			recorded.CodeIs(429)
			recorded.ContentTypeIsJSON()
		}
	}
	for i := 0; i < 5; i++ {
		time.Sleep(time.Second)
		req := test.MakeSimpleRequest("GET", "http://localhost/", nil)
		req.RemoteAddr = "127.0.0.1:45344"
		recorded := test.RunRequest(t, handler, req)
		recorded.CodeIs(200)
		recorded.ContentTypeIsJSON()
	}
}
Beispiel #2
0
func runwebsite() {
	ilimit, _ := strconv.ParseInt(limitpersecond, 10, 64)
	fmt.Println("response time is " + responsetime)
	fmt.Println("limitpersecond is " + limitpersecond)
	limiter := tollbooth.NewLimiter(ilimit, time.Second)
	http.Handle("/", tollbooth.LimitFuncHandler(limiter, slowHandler))
	log.Fatal(http.ListenAndServe(":8888", nil))
}
Beispiel #3
0
// Serve all files in the current directory, or only a few select filetypes (html, css, js, png and txt)
func registerHandlers(mux *http.ServeMux, handlePath, servedir string, perm pinterface.IPermissions, luapool *lStatePool, cache *FileCache, addDomain bool) {

	// Handle all requests with this function
	allRequests := func(w http.ResponseWriter, req *http.Request) {
		if perm.Rejected(w, req) {
			// Get and call the Permission Denied function
			perm.DenyFunction()(w, req)
			// Reject the request by returning
			return
		}

		// Local to this function
		servedir := servedir

		// Look for the directory that is named the same as the host
		if addDomain {
			servedir = filepath.Join(servedir, getDomain(req))
		}

		urlpath := req.URL.Path
		filename := url2filename(servedir, urlpath)
		// Remove the trailing slash from the filename, if any
		noslash := filename
		if strings.HasSuffix(filename, pathsep) {
			noslash = filename[:len(filename)-1]
		}
		hasdir := fs.exists(filename) && fs.isDir(filename)
		dirname := filename
		hasfile := fs.exists(noslash)

		// Set the server header.
		serverHeaders(w)

		// Share the directory or file
		if hasdir {
			dirPage(w, req, servedir, dirname, perm, luapool, cache)
			return
		} else if !hasdir && hasfile {
			// Share a single file instead of a directory
			filePage(w, req, noslash, perm, luapool, cache)
			return
		}
		// Not found
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprint(w, noPage(filename))
	}

	// Handle requests differently depending on if rate limiting is enabled or not
	if disableRateLimiting {
		mux.HandleFunc(handlePath, allRequests)
	} else {
		limiter := tollbooth.NewLimiter(limitRequests, time.Second)
		limiter.MessageContentType = "text/html; charset=utf-8"
		limiter.Message = easyPage("Rate-limit exceeded", "<div style='color:red'>You have reached the maximum request limit.</div>")
		mux.Handle(handlePath, tollbooth.LimitFuncHandler(limiter, allRequests))
	}
}
Beispiel #4
0
func main() {
	e := echo.New()

	// Create a limiter struct.
	limiter := tollbooth.NewLimiter(1, time.Second)

	e.Get("/", echo.HandlerFunc(func(c echo.Context) error {
		return c.String(200, "Hello, World!")
	}), tollbooth_echo.LimitHandler(limiter))

	e.Run(standard.New(":4444"))
}
Beispiel #5
0
// Make functions related to handling HTTP requests available to Lua scripts
func exportLuaHandlerFunctions(L *lua.LState, filename string, perm pinterface.IPermissions, luapool *lStatePool, cache *FileCache, mux *http.ServeMux, addDomain bool) {

	L.SetGlobal("handle", L.NewFunction(func(L *lua.LState) int {
		handlePath := L.ToString(1)
		handleFunc := L.ToFunction(2)

		wrappedHandleFunc := func(w http.ResponseWriter, req *http.Request) {
			// Set up a new Lua state with the current http.ResponseWriter and *http.Request
			exportCommonFunctions(w, req, filename, perm, L, luapool, nil, cache)

			// Then run the given Lua function
			L.Push(handleFunc)
			if err := L.PCall(0, lua.MultRet, nil); err != nil {
				// Non-fatal error
				log.Error("Handler for "+handlePath+" failed:", err)
			}
		}

		// Handle requests differently depending on if rate limiting is enabled or not
		if disableRateLimiting {
			mux.HandleFunc(handlePath, wrappedHandleFunc)
		} else {
			limiter := tollbooth.NewLimiter(limitRequests, time.Second)
			limiter.MessageContentType = "text/html; charset=utf-8"
			limiter.Message = easyPage("Rate-limit exceeded", "<div style='color:red'>You have reached the maximum request limit.</div>")
			mux.Handle(handlePath, tollbooth.LimitFuncHandler(limiter, wrappedHandleFunc))
		}

		return 0 // number of results
	}))

	L.SetGlobal("servedir", L.NewFunction(func(L *lua.LState) int {
		handlePath := L.ToString(1) // serve as (ie. "/")
		rootdir := L.ToString(2)    // filesystem directory (ie. "./public")
		rootdir = filepath.Join(filepath.Dir(filename), rootdir)

		registerHandlers(mux, handlePath, rootdir, perm, luapool, cache, addDomain)

		return 0 // number of results
	}))

}
Beispiel #6
0
// SimpleLimiter create a simple limiter
func SimpleLimiter(max int64, ttl time.Duration) *LimiterMiddleware {
	return Limiter(tollbooth.NewLimiter(max, ttl))
}
Beispiel #7
0
// 启动代理
func SeviceProxy(config ProxyConfig) error {
	// 服务器配置映射
	serverMap := map[string]ProxyServerConfig{}
	for _, singleServer := range config.Server {
		serverMap[singleServer.Name] = singleServer
	}

	// 频率限制时间
	timeDuration, err := GetConfigTime(config.Rate.Time)
	if err != nil {
		return err
	}

	// 启动日志
	err = InitRateLogger(config.Rate.Log)
	if err != nil {
		return err
	}

	// 路由分发
	for _, singleLocation := range config.Location {
		url := singleLocation.Url
		server := singleLocation.Server

		timeoutWarn, err := GetConfigTime(singleLocation.TimeoutWarn)
		if err != nil {
			return err
		}
		if timeoutWarn == 0 {
			timeoutWarn = 5 * time.Second
		}

		timeoutError, err := GetConfigTime(singleLocation.TimeoutError)
		if err != nil {
			return err
		}
		if timeoutError == 0 {
			timeoutError = 30 * time.Second
		}

		cacheExpireTime, err := GetConfigTime(singleLocation.CacheTime)
		if err != nil {
			return err
		}

		cacheSize, err := GetConfigSize(singleLocation.CacheSize)
		if err != nil {
			return err
		}

		singleServer, ok := serverMap[server]
		if ok == false {
			return errors.New("没有url找到对应的server " + url)
		}

		client, err := NewHandler(singleServer, timeoutError)
		if err != nil {
			return err
		}

		Logger.Info("Handle Url " + singleLocation.Url)

		// 初始化请求频率限制中间件
		limiter := tollbooth.NewLimiter(config.Rate.Max, timeDuration)
		limiter.IPLookups = []string{"X-Real-IP"} // 指定获取IP的header字段
		handler := RateLimitHandler(
			limiter,
			&RouteHandler{
				TimeoutWarn: timeoutWarn,
				Cache:       NewCache(cacheSize, cacheExpireTime),
				Client:      client,
			},
		)

		// 配置路由
		http.Handle(singleLocation.Url, handler)
	}

	// 代理监听端口
	listener, err := GetConfigListener(config.Listen)
	if err != nil {
		return err
	}

	// 开启代理
	Logger.Info("Start Proxy Server Listen On " + config.Listen)
	err = http.Serve(listener, nil)
	if err != nil {
		return err
	}
	return nil
}
Beispiel #8
0
func main() {
	// Configure logging: http://godoc.org/github.com/op/go-logging#Formatter
	loggingBackend := logging.NewLogBackend(os.Stdout, "", 0)
	logFormat := logging.MustStringFormatter( // https://golang.org/pkg/time/#Time.Format
		`%{time:Mon Jan 2 15:04:05 MST 2006} - %{level:.4s} - %{shortfile} - %{message}`,
	)
	loggingBackendFormatted := logging.NewBackendFormatter(loggingBackend, logFormat)
	logging.SetBackend(loggingBackendFormatted)

	// Load the .env file which contains environment variables with secret values
	err := godotenv.Load(projectPath + "/.env")
	if err != nil {
		log.Fatal("Failed to load .env file:", err)
	}

	// Create a session store
	sessionSecret := os.Getenv("SESSION_SECRET")
	sessionStore = sessions.NewCookieStore([]byte(sessionSecret))
	maxAge := 60 * 60 * 24 * 30 // 1 month
	if useSSL == true {
		sessionStore.Options = &sessions.Options{
			Domain:   domain,
			Path:     "/",
			MaxAge:   maxAge,
			Secure:   true, // Only send the cookie over HTTPS: https://www.owasp.org/index.php/Testing_for_cookies_attributes_(OTG-SESS-002)
			HttpOnly: true, // Mitigate XSS attacks: https://www.owasp.org/index.php/HttpOnly
		}
	} else {
		sessionStore.Options = &sessions.Options{
			Domain:   domain,
			Path:     "/",
			MaxAge:   maxAge,
			HttpOnly: true, // Mitigate XSS attacks: https://www.owasp.org/index.php/HttpOnly
		}
	}

	// Initialize the database model
	if db, err = models.GetModels(projectPath + "/database.sqlite"); err != nil {
		log.Fatal("Failed to open the database:", err)
	}

	// Clean up any non-started games before we start
	if leftoverGames, err := db.Games.Cleanup(); err != nil {
		log.Fatal("Failed to cleanup the leftover games:", err)
	} else {
		for _, gameID := range leftoverGames {
			log.Info("Deleted game", gameID, "during starting cleanup.")
		}
	}

	// Initialize the achievements
	//achievementsInit()

	// Create a WebSocket router using the Golem framework
	router := golem.NewRouter()
	router.SetConnectionExtension(NewExtendedConnection)
	router.OnHandshake(validateSession)
	router.OnConnect(connOpen)
	router.OnClose(connClose)

	/*
	 *  The websocket commands
	 */

	// Chat commands
	router.On("roomJoin", roomJoin)
	router.On("roomLeave", roomLeave)
	router.On("roomMessage", roomMessage)
	router.On("privateMessage", privateMessage)

	// Game commands
	router.On("gameCreate", gameCreate)
	router.On("gameJoin", gameJoin)
	router.On("gameLeave", gameLeave)
	router.On("gameStart", gameStart)

	// Action commands
	router.On("actionPlay", actionPlay)
	router.On("actionClue", actionClue)
	router.On("actionDiscard", actionDiscard)

	// Profile commands
	/*router.On("profileGet", profileGet)
	router.On("profileSetUsername", profileSetUsername)*/

	// Admin commands
	/*router.On("adminBan", adminBan)
	router.On("adminUnban", adminUnban)
	router.On("adminBanIP", adminBanIP)
	router.On("adminUnbanIP", adminUnbanIP)
	router.On("adminSquelch", adminSquelch)
	router.On("adminUnsquelch", adminUnsquelch)
	router.On("adminPromote", adminPromote)
	router.On("adminDemote", adminDemote)*/

	/*
	 *  HTTP stuff
	 */

	// Minify CSS and JS
	m := minify.New()
	m.AddFunc("text/css", css.Minify)
	for _, fileName := range []string{"main"} {
		inputFile, _ := os.Open("public/css/" + fileName + ".css")
		outputFile, _ := os.Create("public/css/" + fileName + ".min.css")
		if err := m.Minify("text/css", outputFile, inputFile); err != nil {
			log.Error("Failed to minify \""+fileName+".css\":", err)
		}
	}
	m.AddFunc("text/javascript", js.Minify)
	for _, fileName := range []string{"login", "main"} {
		inputFile, _ := os.Open("public/js/" + fileName + ".js")
		outputFile, _ := os.Create("public/js/" + fileName + ".min.js")
		if err := m.Minify("text/javascript", outputFile, inputFile); err != nil {
			log.Error("Failed to minify \""+fileName+".js\":", err)
		}
	}

	// Assign functions to URIs
	http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir("public"))))            // Serve static files
	http.HandleFunc("/", httpHandler)                                                                     // Anything that is not a static file will match this
	http.Handle("/login", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), loginHandler)) // Rate limit the login handler
	http.HandleFunc("/logout", logoutHandler)
	http.HandleFunc("/ws", router.Handler()) // The golem router handles websockets

	/*
	 *  Start the server
	 */

	// Figure out the port that we are using for the HTTP server
	var port int
	if useSSL == true {
		port = 443
	} else {
		port = 80
	}

	// Welcome message
	log.Info("Starting hanabi-server on port " + strconv.Itoa(port) + ".")

	// Listen and serve
	if useSSL == true {
		if err := http.ListenAndServeTLS(
			":"+strconv.Itoa(port), // Nothing before the colon implies 0.0.0.0
			sslCertFile,
			sslKeyFile,
			context.ClearHandler(http.DefaultServeMux), // We wrap with context.ClearHandler or else we will leak memory: http://www.gorillatoolkit.org/pkg/sessions
		); err != nil {
			log.Fatal("ListenAndServeTLS failed:", err)
		}
	} else {
		// Listen and serve (HTTP)
		if err := http.ListenAndServe(
			":"+strconv.Itoa(port),                     // Nothing before the colon implies 0.0.0.0
			context.ClearHandler(http.DefaultServeMux), // We wrap with context.ClearHandler or else we will leak memory: http://www.gorillatoolkit.org/pkg/sessions
		); err != nil {
			log.Fatal("ListenAndServe failed:", err)
		}
	}
}
Beispiel #9
0
// New create a new request rate limiter plug, max requests in ttl time duration
func New(max int64, ttl time.Duration) xrest.Plugger {
	return &limiter{
		limiter:     tollbooth.NewLimiter(max, ttl),
		ErrHandleFn: errHandleFn,
	}
}
Beispiel #10
0
func main() {
	if parseFlags() {
		return
	}

	if err := loadCountries(); err != nil {
		log.Fatalf("error loading countries.json: %s", err.Error())
	}

	inst := instance{}
	{
		var err error
		ident, err := dht.NewIdent()
		if err != nil {
			log.Fatalf("error creating new dht identity: %s", err)
		}
		inst.Ident = ident

		udpTransport, err := transport.NewUDPTransport("udp", ":33450")
		if err != nil {
			log.Fatalf("error creating new udp transport instance: %s", err)
		}
		udpTransport.Handle(dht.PacketIDSendNodes, inst.handleSendNodesPacket)
		udpTransport.Handle(bootstrap.PacketIDBootstrapInfo, handleBootstrapInfoPacket)
		inst.UDPTransport = udpTransport

		/*tcpTransport, err = transport.NewTCPTransport("tcp", ":33450")
		if err != nil {
			panic(err)
		}
		inst.TCPTransport = tcpTransport*/
	}

	//handle stop signal
	interruptChan := make(chan os.Signal)
	signal.Notify(interruptChan, os.Interrupt)

	//setup http server
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", httpListenPort))
	if err != nil {
		log.Fatalf("error in net.Listen: %s", err.Error())
	}
	limiter := tollbooth.NewLimiter(1, 2*time.Second)
	limiter.Methods = []string{"POST"}
	limiter.IPLookups = []string{"X-Forwarded-For", "RemoteAddr", "X-Real-IP"}
	serveMux := http.NewServeMux()
	serveMux.HandleFunc("/", handleHTTPRequest)
	serveMux.Handle("/test", tollbooth.LimitFuncHandler(limiter, handleHTTPRequest))
	serveMux.HandleFunc("/json", handleJSONRequest)
	go func() {
		err := http.Serve(listener, serveMux)
		if err != nil {
			log.Printf("http server error: %s\n", err.Error())
			interruptChan <- os.Interrupt
		}
	}()

	//listen for tox packets
	go func() {
		err := inst.UDPTransport.Listen()
		if err != nil {
			log.Printf("udp transport error: %s\n", err.Error())
			interruptChan <- os.Interrupt
		}
	}()
	//go tcpTransport.Listen()

	err = refreshNodes()
	if err != nil {
		log.Fatal(err.Error())
	}
	inst.probeNodes()

	probeTicker := time.NewTicker(probeRate)
	refreshTicker := time.NewTicker(refreshRate)
	updateTicker := time.NewTicker(30 * time.Second)
	run := true

	for run {
		select {
		case <-interruptChan:
			fmt.Printf("killing routines\n")
			probeTicker.Stop()
			refreshTicker.Stop()
			updateTicker.Stop()
			inst.UDPTransport.Stop()
			//tcpTransport.Stop()
			listener.Close()
			run = false
		case <-probeTicker.C:
			// we want an empty ping list at the start of every probe
			pingsMutex.Lock()
			pings.Clear(false)
			pingsMutex.Unlock()

			nodesMutex.Lock()
			err := inst.probeNodes()
			nodesMutex.Unlock()
			if err != nil {
				log.Printf("error while trying to probe nodes: %s", err.Error())
			}
		case <-refreshTicker.C:
			err := refreshNodes()
			if err != nil {
				log.Printf("error while trying to refresh nodes: %s", err.Error())
			}
		case <-updateTicker.C:
			pingsMutex.Lock()
			pings.Clear(true)
			pingsMutex.Unlock()

			nodesMutex.Lock()
			for _, node := range nodes {
				if time.Now().Sub(time.Unix(node.LastPing, 0)) > time.Minute*2 {
					node.UDPStatus = false
				}
			}
			sort.Stable(nodeSlice(nodes))
			nodesMutex.Unlock()
		}
	}
}
Beispiel #11
0
// NewLimiterMiddleware create a simple limiter
func NewLimiterMiddleware(logger logger.Logger, max int64, ttl time.Duration) *LimiterMiddleware {
	return Limiter(logger, tollbooth.NewLimiter(max, ttl))
}
Beispiel #12
0
func main() {
	// Configure logging: http://godoc.org/github.com/op/go-logging#Formatter
	log = &CustomLogger{
		Logger: logging.MustGetLogger("isaac"),
	}
	loggingBackend := logging.NewLogBackend(os.Stdout, "", 0)
	logFormat := logging.MustStringFormatter( // https://golang.org/pkg/time/#Time.Format
		//`%{time:Mon Jan 2 15:04:05 MST 2006} - %{level:.4s} - %{shortfile} - %{message}`, // We no longer use the line number since the log struct extension breaks it
		`%{time:Mon Jan 2 15:04:05 MST 2006} - %{level:.4s} - %{message}`,
	)
	loggingBackendFormatted := logging.NewBackendFormatter(loggingBackend, logFormat)
	logging.SetBackend(loggingBackendFormatted)

	// Load the .env file which contains environment variables with secret values
	err := godotenv.Load(projectPath + "/.env")
	if err != nil {
		log.Fatal("Failed to load .env file:", err)
	}

	// Configure error reporting to Sentry
	sentrySecret := os.Getenv("SENTRY_SECRET")
	raven.SetDSN("https://*****:*****@sentry.io/124813")

	// Welcome message
	log.Info("-----------------------------")
	log.Info("Starting isaac-racing-server.")
	log.Info("-----------------------------")

	// Create a session store
	sessionSecret := os.Getenv("SESSION_SECRET")
	sessionStore = sessions.NewCookieStore([]byte(sessionSecret))
	maxAge := 5 // 5 seconds
	if useSSL == true {
		sessionStore.Options = &sessions.Options{
			Domain:   domain,
			Path:     "/",
			MaxAge:   maxAge,
			Secure:   true, // Only send the cookie over HTTPS: https://www.owasp.org/index.php/Testing_for_cookies_attributes_(OTG-SESS-002)
			HttpOnly: true, // Mitigate XSS attacks: https://www.owasp.org/index.php/HttpOnly
		}
	} else {
		sessionStore.Options = &sessions.Options{
			Domain:   domain,
			Path:     "/",
			MaxAge:   maxAge,
			HttpOnly: true, // Mitigate XSS attacks: https://www.owasp.org/index.php/HttpOnly
		}
	}

	// Initialize the database model
	if db, err = models.GetModels(projectPath + "/database.sqlite"); err != nil {
		log.Fatal("Failed to open the database:", err)
	}

	// Clean up any non-started races before we start
	if nonStartedRaces, err := db.Races.Cleanup(); err != nil {
		log.Fatal("Failed to cleanup the leftover races:", err)
	} else {
		for _, raceID := range nonStartedRaces {
			log.Info("Deleted race", raceID, "during starting cleanup.")
		}
	}

	// Initiate the "end of race" function for each of the non-finished races in 30 minutes
	// (this is normally initiated on race start)
	if startedRaces, err := db.Races.GetCurrentRaces(); err != nil {
		log.Fatal("Failed to start  the leftover races:", err)
	} else {
		for _, race := range startedRaces {
			go raceCheckStart3(race.ID)
		}
	}

	// Initialize the achievements
	achievementsInit()

	// Start the Twitch bot
	go twitchInit()

	// Create a WebSocket router using the Golem framework
	router := golem.NewRouter()
	router.SetConnectionExtension(NewExtendedConnection)
	router.OnHandshake(validateSession)
	router.OnConnect(connOpen)
	router.OnClose(connClose)

	/*
		The websocket commands
	*/

	// Chat commands
	router.On("roomJoin", roomJoin)
	router.On("roomLeave", roomLeave)
	router.On("roomMessage", roomMessage)
	router.On("privateMessage", privateMessage)
	router.On("roomListAll", roomListAll)

	// Race commands
	router.On("raceCreate", raceCreate)
	router.On("raceJoin", raceJoin)
	router.On("raceLeave", raceLeave)
	router.On("raceReady", raceReady)
	router.On("raceUnready", raceUnready)
	router.On("raceRuleset", raceRuleset)
	router.On("raceFinish", raceFinish)
	router.On("raceQuit", raceQuit)
	router.On("raceComment", raceComment)
	router.On("raceItem", raceItem)
	router.On("raceFloor", raceFloor)

	// Profile commands
	router.On("profileGet", profileGet)
	router.On("profileSetUsername", profileSetUsername)
	router.On("profileSetStreamURL", profileSetStreamURL)
	router.On("profileSetTwitchBotEnabled", profileSetTwitchBotEnabled)
	router.On("profileSetTwitchBotDelay", profileSetTwitchBotDelay)

	// Admin commands
	router.On("adminBan", adminBan)
	router.On("adminUnban", adminUnban)
	router.On("adminBanIP", adminBanIP)
	router.On("adminUnbanIP", adminUnbanIP)
	router.On("adminMute", adminMute)
	router.On("adminUnmute", adminUnmute)
	router.On("adminPromote", adminPromote)
	router.On("adminDemote", adminDemote)

	// Miscellaneous
	router.On("logout", logout)
	router.On("debug", debug)

	/*
		HTTP stuff
	*/

	// Minify CSS and JS
	m := minify.New()
	m.AddFunc("text/css", css.Minify)
	for _, fileName := range []string{"main", "ie8"} {
		inputFile, _ := os.Open("public/css/" + fileName + ".css")
		outputFile, _ := os.Create("public/css/" + fileName + ".min.css")
		if err := m.Minify("text/css", outputFile, inputFile); err != nil {
			log.Error("Failed to minify \""+fileName+".css\":", err)
		}
	}
	m.AddFunc("text/javascript", js.Minify)
	for _, fileName := range []string{"main", "util"} {
		inputFile, _ := os.Open("public/js/" + fileName + ".js")
		outputFile, _ := os.Create("public/js/" + fileName + ".min.js")
		if err := m.Minify("text/javascript", outputFile, inputFile); err != nil {
			log.Error("Failed to minify \""+fileName+".js\":", err)
		}
	}

	// Set up the Pat HTTP router
	p := pat.New()
	p.Get("/", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), httpHome))
	p.Get("/news", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), httpNews))
	p.Get("/races", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), httpRaces))
	p.Get("/profiles", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), httpProfiles))
	p.Get("/leaderboards", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), httpLeaderboards))
	p.Get("/info", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), httpInfo))
	p.Get("/download", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), httpDownload))
	p.Post("/login", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), loginHandler))
	p.Post("/register", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), registerHandler))

	/*
		Assign functions to URIs
	*/

	// Normal requests get assigned to the Pat HTTP router
	http.Handle("/", p)

	// Files in the "public" subdirectory are just images/css/javascript files
	http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(justFilesFilesystem{http.Dir("public")})))

	// Websockets are handled by the Golem websocket router
	http.HandleFunc("/ws", router.Handler())

	/*
		Start the server
	*/

	// Figure out the port that we are using for the HTTP server
	var port int
	if useSSL == true {
		// We want all HTTP requests to be redirected, but we need to make an exception for Let's Encrypt
		// The previous "Handle" and "HandleFunc" were being added to the default serve mux
		// We need to create a new fresh one for the HTTP handler
		HTTPServeMux := http.NewServeMux()
		HTTPServeMux.Handle("/.well-known/acme-challenge/", http.FileServer(http.FileSystem(http.Dir("letsencrypt"))))
		HTTPServeMux.Handle("/", http.HandlerFunc(HTTPRedirect))

		// ListenAndServe is blocking, so start listening on a new goroutine
		go http.ListenAndServe(":80", HTTPServeMux) // Nothing before the colon implies 0.0.0.0

		// 443 is the default port for HTTPS
		port = 443
	} else {
		// 80 is the defeault port for HTTP
		port = 80
	}

	// Listen and serve
	log.Info("Listening on port " + strconv.Itoa(port) + ".")
	if useSSL == true {
		if err := http.ListenAndServeTLS(
			":"+strconv.Itoa(port), // Nothing before the colon implies 0.0.0.0
			sslCertFile,
			sslKeyFile,
			context.ClearHandler(http.DefaultServeMux), // We wrap with context.ClearHandler or else we will leak memory: http://www.gorillatoolkit.org/pkg/sessions
		); err != nil {
			log.Fatal("ListenAndServeTLS failed:", err)
		}
	} else {
		// Listen and serve (HTTP)
		if err := http.ListenAndServe(
			":"+strconv.Itoa(port),                     // Nothing before the colon implies 0.0.0.0
			context.ClearHandler(http.DefaultServeMux), // We wrap with context.ClearHandler or else we will leak memory: http://www.gorillatoolkit.org/pkg/sessions
		); err != nil {
			log.Fatal("ListenAndServeTLS failed:", err)
		}
	}
}