moth/cmd/mothd/httpd.go

191 lines
5.1 KiB
Go
Raw Normal View History

2020-02-29 16:53:08 -07:00
package main
import (
2020-10-12 17:44:44 -06:00
"bytes"
2020-02-29 16:53:08 -07:00
"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-09-15 15:58:21 -06:00
"time"
2020-08-17 17:43:57 -06:00
"github.com/dirtbags/moth/pkg/jsend"
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-09-15 15:58:21 -06:00
if server.Config.Devel {
h.HandleMothFunc("/mothballer/", h.MothballerHandler)
}
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) {
2020-08-19 18:01:21 -06:00
teamID := req.FormValue("id")
mh := h.server.NewHandler(teamID)
2020-03-01 16:10:55 -07:00
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) {
2020-08-17 17:43:57 -06:00
w := StatusResponseWriter{
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-17 17:43:57 -06:00
// StatusResponseWriter provides a ResponseWriter that remembers what the status code was
type StatusResponseWriter struct {
2020-03-01 16:10:55 -07:00
statusCode *int
http.ResponseWriter
}
2020-08-14 20:26:04 -06:00
// WriteHeader sends an HTTP response header with the provided status code
2020-08-17 17:43:57 -06:00
func (w StatusResponseWriter) WriteHeader(statusCode int) {
2020-03-01 16:10:55 -07:00
*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) {
2020-08-17 17:43:57 -06:00
jsend.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")
teamName = strings.TrimSpace(teamName)
if teamName == "" {
jsend.Sendf(w, jsend.Fail, "empty name", "Team name may not be empty")
return
}
if err := mh.Register(teamName); err == ErrAlreadyRegistered {
jsend.Sendf(w, jsend.Success, "already registered", "team ID has already been registered")
} else if err != nil {
2020-08-17 17:43:57 -06:00
jsend.Sendf(w, jsend.Fail, "not registered", err.Error())
2020-02-29 22:37:22 -07:00
} else {
jsend.Sendf(w, jsend.Success, "registered", "team ID registered")
2020-02-29 22:37:22 -07:00
}
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 {
2020-08-17 17:43:57 -06:00
jsend.Sendf(w, jsend.Fail, "not accepted", err.Error())
2020-03-01 16:10:55 -07:00
} else {
2020-08-17 17:43:57 -06:00
jsend.Sendf(w, jsend.Success, "accepted", "%d points awarded in %s", points, cat)
2020-03-01 16:10:55 -07:00
}
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) {
2020-09-15 15:58:21 -06:00
parts := strings.SplitN(req.URL.Path[len(h.base)+1:], "/", 4)
if len(parts) < 4 {
http.NotFound(w, req)
2020-08-14 20:26:04 -06:00
return
}
2020-03-01 14:03:46 -07:00
2020-09-15 15:58:21 -06:00
// parts[0] == "content"
cat := parts[1]
pointsStr := parts[2]
filename := parts[3]
2020-03-01 14:03:46 -07:00
2020-08-14 20:26:04 -06:00
if filename == "" {
2020-08-21 17:02:38 -06:00
filename = "puzzle.json"
2020-03-01 14:03:46 -07:00
}
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
}
2020-09-15 15:58:21 -06:00
// MothballerHandler returns a mothball
func (h *HTTPServer) MothballerHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) {
parts := strings.SplitN(req.URL.Path[len(h.base)+1:], "/", 2)
if len(parts) < 2 {
http.NotFound(w, req)
return
}
// parts[0] == "mothballer"
filename := parts[1]
cat := strings.TrimSuffix(filename, ".mb")
2020-10-12 17:44:44 -06:00
mb := new(bytes.Buffer)
if err := mh.Mothball(cat, mb); err != nil {
2020-09-15 15:58:21 -06:00
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
2020-10-12 17:44:44 -06:00
mbReader := bytes.NewReader(mb.Bytes())
http.ServeContent(w, req, filename, time.Now(), mbReader)
2020-09-15 15:58:21 -06:00
}