func main() { http.Handle("/", http.FileServer(http.FileSystem(public))) }
// Minimum number of items to request at a time from wrapped Readdir. const batchSize = 100 // Order is a http.FileSystem wrapper that strives to list directory // contents in alphabetical order. // // To limit memory consumption, if the directory is larger than // WindowSize, the Readdir results will only approximate the correct // order. The listing will contain runs of entries in sorted order, // where the runs are broken only when entries are seen further than // WindowSize from their desired ordered location. type Order struct { http.FileSystem } var _ = http.FileSystem(Order{}) // Open opens a file. See http.FileSystem method Open. func (o Order) Open(name string) (http.File, error) { f, err := o.FileSystem.Open(name) if f != nil { f = &file{File: f} } return f, err } type file struct { http.File window fileHeap // if not nil, we're in draining mode, and will return this once // window is empty
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) } } }