2020-02-29 16:53:08 -07:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"log"
|
2020-02-29 22:37:22 -07:00
|
|
|
"net/http"
|
2020-03-01 14:03:46 -07:00
|
|
|
"strconv"
|
2020-08-14 20:26:04 -06:00
|
|
|
"strings"
|
2020-02-29 16:53:08 -07:00
|
|
|
)
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
// HTTPServer is a MOTH HTTP server
|
2020-02-29 16:53:08 -07:00
|
|
|
type HTTPServer struct {
|
|
|
|
*http.ServeMux
|
2020-08-14 20:26:04 -06:00
|
|
|
server *MothServer
|
|
|
|
base string
|
2020-02-29 16:53:08 -07:00
|
|
|
}
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
// NewHTTPServer creates a MOTH HTTP server, with handler functions registered
|
2020-03-01 16:10:55 -07:00
|
|
|
func NewHTTPServer(base string, server *MothServer) *HTTPServer {
|
2020-03-01 14:03:46 -07:00
|
|
|
base = strings.TrimRight(base, "/")
|
2020-02-29 16:53:08 -07:00
|
|
|
h := &HTTPServer{
|
|
|
|
ServeMux: http.NewServeMux(),
|
2020-03-01 16:10:55 -07:00
|
|
|
server: server,
|
|
|
|
base: base,
|
2020-02-29 16:53:08 -07:00
|
|
|
}
|
2020-03-01 16:10:55 -07:00
|
|
|
h.HandleMothFunc("/", h.ThemeHandler)
|
|
|
|
h.HandleMothFunc("/state", h.StateHandler)
|
|
|
|
h.HandleMothFunc("/register", h.RegisterHandler)
|
|
|
|
h.HandleMothFunc("/answer", h.AnswerHandler)
|
|
|
|
h.HandleMothFunc("/content/", h.ContentHandler)
|
2020-02-29 16:53:08 -07:00
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
// HandleMothFunc binds a new handler function which creates a new MothServer with every request
|
2020-03-01 16:10:55 -07:00
|
|
|
func (h *HTTPServer) HandleMothFunc(
|
|
|
|
pattern string,
|
|
|
|
mothHandler func(MothRequestHandler, http.ResponseWriter, *http.Request),
|
|
|
|
) {
|
|
|
|
handler := func(w http.ResponseWriter, req *http.Request) {
|
|
|
|
mh := h.server.NewHandler(req.FormValue("id"))
|
|
|
|
mothHandler(mh, w, req)
|
|
|
|
}
|
2020-08-14 20:26:04 -06:00
|
|
|
h.HandleFunc(h.base+pattern, handler)
|
2020-02-29 16:53:08 -07:00
|
|
|
}
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
// ServeHTTP provides the http.Handler interface
|
2020-02-29 16:53:08 -07:00
|
|
|
func (h *HTTPServer) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
|
|
|
|
w := MothResponseWriter{
|
2020-02-29 22:37:22 -07:00
|
|
|
statusCode: new(int),
|
2020-02-29 16:53:08 -07:00
|
|
|
ResponseWriter: wOrig,
|
|
|
|
}
|
|
|
|
h.ServeMux.ServeHTTP(w, r)
|
|
|
|
log.Printf(
|
|
|
|
"%s %s %s %d\n",
|
|
|
|
r.RemoteAddr,
|
|
|
|
r.Method,
|
|
|
|
r.URL,
|
|
|
|
*w.statusCode,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
// MothResponseWriter provides a ResponseWriter that remembers what the status code was
|
2020-03-01 16:10:55 -07:00
|
|
|
type MothResponseWriter struct {
|
|
|
|
statusCode *int
|
|
|
|
http.ResponseWriter
|
|
|
|
}
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
// WriteHeader sends an HTTP response header with the provided status code
|
2020-03-01 16:10:55 -07:00
|
|
|
func (w MothResponseWriter) WriteHeader(statusCode int) {
|
|
|
|
*w.statusCode = statusCode
|
|
|
|
w.ResponseWriter.WriteHeader(statusCode)
|
|
|
|
}
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
// Run binds to the provided bindStr, and serves incoming requests until failure
|
2020-03-01 16:10:55 -07:00
|
|
|
func (h *HTTPServer) Run(bindStr string) {
|
|
|
|
log.Printf("Listening on %s", bindStr)
|
|
|
|
log.Fatal(http.ListenAndServe(bindStr, h))
|
|
|
|
}
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
// ThemeHandler serves up static content from the theme directory
|
2020-03-01 16:10:55 -07:00
|
|
|
func (h *HTTPServer) ThemeHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) {
|
2020-02-29 16:53:08 -07:00
|
|
|
path := req.URL.Path
|
|
|
|
if path == "/" {
|
|
|
|
path = "/index.html"
|
|
|
|
}
|
2020-08-14 20:26:04 -06:00
|
|
|
|
2020-03-01 16:10:55 -07:00
|
|
|
f, mtime, err := mh.ThemeOpen(path)
|
2020-02-29 16:53:08 -07:00
|
|
|
if err != nil {
|
|
|
|
http.NotFound(w, req)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
http.ServeContent(w, req, path, mtime, f)
|
|
|
|
}
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
// StateHandler returns the full JSON-encoded state of the event
|
2020-03-01 16:10:55 -07:00
|
|
|
func (h *HTTPServer) StateHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) {
|
|
|
|
JSONWrite(w, mh.ExportState())
|
2020-02-29 16:53:08 -07:00
|
|
|
}
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
// RegisterHandler handles attempts to register a team
|
2020-03-01 16:10:55 -07:00
|
|
|
func (h *HTTPServer) RegisterHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) {
|
2020-02-29 22:37:22 -07:00
|
|
|
teamName := req.FormValue("name")
|
2020-03-01 16:10:55 -07:00
|
|
|
if err := mh.Register(teamName); err != nil {
|
2020-02-29 22:37:22 -07:00
|
|
|
JSendf(w, JSendFail, "not registered", err.Error())
|
|
|
|
} else {
|
|
|
|
JSendf(w, JSendSuccess, "registered", "Team ID registered")
|
|
|
|
}
|
2020-02-29 16:53:08 -07:00
|
|
|
}
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
// AnswerHandler checks answer correctness and awards points
|
2020-03-01 16:10:55 -07:00
|
|
|
func (h *HTTPServer) AnswerHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) {
|
|
|
|
cat := req.FormValue("cat")
|
|
|
|
pointstr := req.FormValue("points")
|
|
|
|
answer := req.FormValue("answer")
|
2020-08-14 20:26:04 -06:00
|
|
|
|
2020-03-01 16:10:55 -07:00
|
|
|
points, _ := strconv.Atoi(pointstr)
|
2020-08-14 20:26:04 -06:00
|
|
|
|
2020-03-01 16:10:55 -07:00
|
|
|
if err := mh.CheckAnswer(cat, points, answer); err != nil {
|
|
|
|
JSendf(w, JSendFail, "not accepted", err.Error())
|
|
|
|
} else {
|
|
|
|
JSendf(w, JSendSuccess, "accepted", "%d points awarded in %s", points, cat)
|
|
|
|
}
|
2020-02-29 16:53:08 -07:00
|
|
|
}
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
// ContentHandler returns static content from a given puzzle
|
2020-03-01 16:10:55 -07:00
|
|
|
func (h *HTTPServer) ContentHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) {
|
|
|
|
trimLen := len(h.base) + len("/content/")
|
2020-03-01 14:03:46 -07:00
|
|
|
parts := strings.SplitN(req.URL.Path[trimLen:], "/", 3)
|
2020-08-14 20:26:04 -06:00
|
|
|
if len(parts) < 3 {
|
|
|
|
http.Error(w, "Not Found", http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
2020-03-01 14:03:46 -07:00
|
|
|
|
|
|
|
cat := parts[0]
|
|
|
|
pointsStr := parts[1]
|
|
|
|
filename := parts[2]
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
if filename == "" {
|
2020-03-01 14:03:46 -07:00
|
|
|
filename = "puzzles.json"
|
|
|
|
}
|
2020-08-14 20:26:04 -06:00
|
|
|
|
2020-03-01 14:03:46 -07:00
|
|
|
points, _ := strconv.Atoi(pointsStr)
|
|
|
|
|
2020-03-01 16:10:55 -07:00
|
|
|
mf, mtime, err := mh.PuzzlesOpen(cat, points, filename)
|
2020-03-01 14:03:46 -07:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer mf.Close()
|
|
|
|
|
2020-08-14 20:26:04 -06:00
|
|
|
http.ServeContent(w, req, filename, mtime, mf)
|
2020-02-29 16:53:08 -07:00
|
|
|
}
|