diff --git a/frontend/main.go b/frontend/main.go index 2427f571..bbddaef8 100644 --- a/frontend/main.go +++ b/frontend/main.go @@ -1,18 +1,37 @@ package main import ( + "bufio" "flag" "fmt" "log" "net/http" "os" + "path" + "time" ) +const startedFile = "started" + +var TeamsDir string var SubmissionDir string +func touchStartedFile(startSub time.Duration) { + time.Sleep(startSub) + if fd, err := os.Create(path.Join(TeamsDir, startedFile)); err == nil { + log.Println("Started! Go, Go, Go!!") + fd.Close() + } else { + log.Println("Unable to start challenge:", err) + } +} + func main() { var bind = flag.String("bind", "0.0.0.0:8080", "Bind port/socket") var prefix = flag.String("prefix", "", "Request path prefix to strip (from proxy)") + var start = flag.Int64("start", 0, fmt.Sprintf("Challenge start timestamp (in 2 minutes: %d)", time.Now().Unix() / 60 * 60 + 120)) + var duration = flag.Duration("duration", 180 * time.Minute, "Challenge duration") + flag.StringVar(&TeamsDir, "teams", "../TEAMS", "Base directory where save teams JSON files") flag.StringVar(&SubmissionDir, "submission", "./submissions/", "Base directory where save submissions") flag.Parse() @@ -23,8 +42,30 @@ func main() { } } + startTime := time.Unix(*start, 0) + startSub := startTime.Sub(time.Now()) + end := startTime.Add(*duration).Add(time.Duration(1 * time.Second)) + + log.Println("Challenge ends on", end) + if startSub > 0 { + log.Println("Challenge starts at", startTime, "in", startSub) + + fmt.Printf("PRESS ENTER TO LAUNCH THE COUNTDOWN ") + bufio.NewReader(os.Stdin).ReadLine() + + if _, err := os.Stat(path.Join(TeamsDir, startedFile)); !os.IsNotExist(err) { + os.Remove(path.Join(TeamsDir, startedFile)) + } + + go touchStartedFile(startTime.Sub(time.Now().Add(time.Duration(1 * time.Second)))) + } else { + log.Println("Challenge started at", startTime, "since", -startSub) + go touchStartedFile(time.Duration(0)) + } + log.Println("Registering handlers...") - http.Handle(fmt.Sprintf("%s/", *prefix), http.StripPrefix(*prefix, SubmissionHandler{})) + http.Handle(fmt.Sprintf("%s/time.json", *prefix), http.StripPrefix(*prefix, TimeHandler{startTime, *duration})) + http.Handle(fmt.Sprintf("%s/", *prefix), http.StripPrefix(*prefix, SubmissionHandler{end})) log.Println(fmt.Sprintf("Ready, listening on %s", *bind)) if err := http.ListenAndServe(*bind, nil); err != nil { diff --git a/frontend/submit.go b/frontend/submit.go index 51dd2583..75918761 100644 --- a/frontend/submit.go +++ b/frontend/submit.go @@ -8,11 +8,14 @@ import ( "path" "strconv" "strings" + "time" ) -type SubmissionHandler struct{} +type SubmissionHandler struct{ + ChallengeEnd time.Time +} -func (SubmissionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (s SubmissionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent()) w.Header().Set("Content-Type", "application/json") @@ -20,10 +23,10 @@ func (SubmissionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Check request type and size if r.Method != "POST" { - http.Error(w, "{\"errmsg\":\"Bad request.\"}", http.StatusBadRequest) + http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) return - } else if r.ContentLength < 0 || r.ContentLength > 255 { - http.Error(w, "{\"errmsg\":\"Request too large or request size unknown\"}", http.StatusRequestEntityTooLarge) + } else if r.ContentLength < 0 || r.ContentLength > 1023 { + http.Error(w, "{\"errmsg\":\"Requête trop longue ou de taille inconnue\"}", http.StatusRequestEntityTooLarge) return } @@ -32,16 +35,21 @@ func (SubmissionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var sURL = strings.Split(r.URL.Path, "/") if len(sURL) != 3 && len(sURL) != 4 { - http.Error(w, "{\"errmsg\":\"Bad request.\"}", http.StatusBadRequest) + http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) + return + } + + if time.Now().Sub(s.ChallengeEnd) > 0 { + http.Error(w, "{\"errmsg\":\"Vous ne pouvez plus soumettre, le challenge est terminé.\"}", http.StatusForbidden) return } // Parse arguments if team, err := strconv.Atoi(sURL[1]); err != nil { - http.Error(w, "{\"errmsg\":\"Bad request.\"}", http.StatusBadRequest) + http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) return } else if exercice, err := strconv.Atoi(sURL[2]); err != nil { - http.Error(w, "{\"errmsg\":\"Bad request.\"}", http.StatusBadRequest) + http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) return } else { if _, err := os.Stat(path.Join(SubmissionDir, fmt.Sprintf("%d", team))); os.IsNotExist(err) { @@ -56,7 +64,7 @@ func (SubmissionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Previous submission not treated if _, err := os.Stat(path.Join(SubmissionDir, fmt.Sprintf("%d", team), fmt.Sprintf("%d", exercice))); !os.IsNotExist(err) { - http.Error(w, "{\"errmsg\":\"Calm-down. A solution is already being processed.\"}", http.StatusPaymentRequired) + http.Error(w, "{\"errmsg\":\"Du calme ! une tentative est déjà en cours de traitement.\"}", http.StatusPaymentRequired) return } @@ -86,6 +94,6 @@ func (SubmissionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { file.Write(body) file.Close() } - http.Error(w, "{\"errmsg\":\"Submission accepted, please wait...\"}", http.StatusAccepted) + http.Error(w, "{\"errmsg\":\"Son traitement est en cours...\"}", http.StatusAccepted) } } diff --git a/frontend/time.go b/frontend/time.go new file mode 100644 index 00000000..202e98eb --- /dev/null +++ b/frontend/time.go @@ -0,0 +1,30 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "time" +) + +type TimeHandler struct{ + StartTime time.Time + Duration time.Duration +} + +type timeObject struct { + Started int64 `json:"st"` + Time int64 `json:"cu"` + Duration int `json:"du"` +} + +func (t TimeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if j, err := json.Marshal(timeObject{t.StartTime.Unix(), time.Now().Unix(), int(t.Duration.Seconds())}); err != nil { + http.Error(w, fmt.Sprintf("{\"errmsg\":\"%q\"}", err), http.StatusInternalServerError) + } else { + w.WriteHeader(http.StatusOK) + w.Write(j) + } +}