diff --git a/Dockerfile.moth b/Dockerfile.moth
index 23a16da..b7bfd37 100644
--- a/Dockerfile.moth
+++ b/Dockerfile.moth
@@ -1,7 +1,9 @@
-FROM alpine:3.8 AS builder
-RUN apk --no-cache add go libc-dev
-COPY src /src
-RUN go build -o /mothd /src/*.go
+FROM alpine:3.9 AS builder
+RUN apk --no-cache add go libc-dev git
+COPY src /root/go/src/github.com/dirtbags/moth/src
+WORKDIR /root/go/src/github.com/dirtbags/moth/src
+RUN go get .
+RUN go build -o /mothd *.go
FROM alpine
COPY --from=builder /mothd /mothd
diff --git a/src/handlers.go b/src/handlers.go
index 3d3984b..b60b760 100644
--- a/src/handlers.go
+++ b/src/handlers.go
@@ -21,14 +21,20 @@ type JSendData struct {
Description string `json:"description"`
}
-// ShowJSend renders a JSend response to w
-func ShowJSend(w http.ResponseWriter, status Status, short string, description string) {
+type Status int
+const (
+ Success = iota
+ Fail
+ Error
+)
+
+func respond(w http.ResponseWriter, req *http.Request, status Status, short string, format string, a ...interface{}) {
resp := JSend{
Status: "success",
Data: JSendData{
Short: short,
- Description: description,
+ Description: fmt.Sprintf(format, a...),
},
}
switch status {
@@ -51,59 +57,6 @@ func ShowJSend(w http.ResponseWriter, status Status, short string, description s
w.Write(respBytes)
}
-type Status int
-
-const (
- Success = iota
- Fail
- Error
-)
-
-// ShowHtml delevers an HTML response to w
-func ShowHtml(w http.ResponseWriter, status Status, title string, body string) {
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- w.WriteHeader(http.StatusOK)
-
- statusStr := ""
- switch status {
- case Success:
- statusStr = "Success"
- case Fail:
- statusStr = "Fail"
- default:
- statusStr = "Error"
- }
-
- fmt.Fprintf(w, "")
- fmt.Fprintf(w, "\n")
- fmt.Fprintf(w, "
")
- fmt.Fprintf(w, "%s", title)
- fmt.Fprintf(w, "")
- fmt.Fprintf(w, "")
- fmt.Fprintf(w, "")
- fmt.Fprintf(w, "")
- fmt.Fprintf(w, "%s
", statusStr, title)
- fmt.Fprintf(w, "", body)
- fmt.Fprintf(w, "")
- fmt.Fprintf(w, "")
-}
-
-func respond(w http.ResponseWriter, req *http.Request, status Status, short string, format string, a ...interface{}) {
- long := fmt.Sprintf(format, a...)
- // This is a kludge. Do proper parsing when this causes problems.
- accept := req.Header.Get("Accept")
- if strings.Contains(accept, "application/json") {
- ShowJSend(w, status, short, long)
- } else {
- ShowHtml(w, status, short, long)
- }
-}
-
// hasLine returns true if line appears in r.
// The entire line must match.
func hasLine(r io.Reader, line string) bool {
@@ -301,11 +254,53 @@ func (ctx *Instance) staticHandler(w http.ResponseWriter, req *http.Request) {
http.ServeContent(w, req, path, d.ModTime(), f)
}
-func (ctx *Instance) BindHandlers(mux *http.ServeMux) {
- mux.HandleFunc(ctx.Base+"/", ctx.staticHandler)
- mux.HandleFunc(ctx.Base+"/register", ctx.registerHandler)
- mux.HandleFunc(ctx.Base+"/answer", ctx.answerHandler)
- mux.HandleFunc(ctx.Base+"/content/", ctx.contentHandler)
- mux.HandleFunc(ctx.Base+"/puzzles.json", ctx.puzzlesHandler)
- mux.HandleFunc(ctx.Base+"/points.json", ctx.pointsHandler)
+type FurtiveResponseWriter struct {
+ w http.ResponseWriter
+ statusCode *int
}
+
+func (w FurtiveResponseWriter) WriteHeader(statusCode int) {
+ *w.statusCode = statusCode
+ w.w.WriteHeader(statusCode)
+}
+
+func (w FurtiveResponseWriter) Write(buf []byte) (n int, err error) {
+ n, err = w.w.Write(buf)
+ return
+}
+
+func (w FurtiveResponseWriter) Header() http.Header {
+ return w.w.Header()
+}
+
+// This gives Instances the signature of http.Handler
+func (ctx *Instance) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
+ w := FurtiveResponseWriter{
+ w: wOrig,
+ statusCode: new(int),
+ }
+ w.Header().Set("WWW-Authenticate", "Basic")
+ _, password, _ := r.BasicAuth()
+ if password != ctx.Password {
+ http.Error(w, "Authentication Required", 401)
+ } else {
+ ctx.mux.ServeHTTP(w, r)
+ }
+ log.Printf(
+ "%s %s %s %d\n",
+ r.RemoteAddr,
+ r.Method,
+ r.URL,
+ *w.statusCode,
+ )
+}
+
+func (ctx *Instance) BindHandlers() {
+ ctx.mux.HandleFunc(ctx.Base+"/", ctx.staticHandler)
+ ctx.mux.HandleFunc(ctx.Base+"/register", ctx.registerHandler)
+ ctx.mux.HandleFunc(ctx.Base+"/answer", ctx.answerHandler)
+ ctx.mux.HandleFunc(ctx.Base+"/content/", ctx.contentHandler)
+ ctx.mux.HandleFunc(ctx.Base+"/puzzles.json", ctx.puzzlesHandler)
+ ctx.mux.HandleFunc(ctx.Base+"/points.json", ctx.pointsHandler)
+}
+
diff --git a/src/instance.go b/src/instance.go
index 2794768..5b6cb61 100644
--- a/src/instance.go
+++ b/src/instance.go
@@ -6,6 +6,7 @@ import (
"io"
"io/ioutil"
"log"
+ "net/http"
"os"
"path"
"strings"
@@ -17,20 +18,24 @@ type Instance struct {
MothballDir string
StateDir string
ResourcesDir string
+ Password string
Categories map[string]*Mothball
update chan bool
jPuzzleList []byte
jPointsLog []byte
+ mux *http.ServeMux
}
-func NewInstance(base, mothballDir, stateDir, resourcesDir string) (*Instance, error) {
+func NewInstance(base, mothballDir, stateDir, resourcesDir, password string) (*Instance, error) {
ctx := &Instance{
Base: strings.TrimRight(base, "/"),
MothballDir: mothballDir,
StateDir: stateDir,
ResourcesDir: resourcesDir,
+ Password: password,
Categories: map[string]*Mothball{},
update: make(chan bool, 10),
+ mux: http.NewServeMux(),
}
// Roll over and die if directories aren't even set up
@@ -40,7 +45,8 @@ func NewInstance(base, mothballDir, stateDir, resourcesDir string) (*Instance, e
if _, err := os.Stat(stateDir); err != nil {
return nil, err
}
-
+
+ ctx.BindHandlers()
ctx.MaybeInitialize()
return ctx, nil
diff --git a/src/mothd.go b/src/mothd.go
index 5834f24..5f7fef5 100644
--- a/src/mothd.go
+++ b/src/mothd.go
@@ -1,24 +1,19 @@
package main
import (
- "flag"
+ "github.com/namsral/flag"
"log"
"mime"
"net/http"
"time"
)
-func logRequest(handler http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- log.Printf("HTTP %s %s %s\n", r.RemoteAddr, r.Method, r.URL)
- handler.ServeHTTP(w, r)
- })
-}
func setup() error {
return nil
}
+
func main() {
base := flag.String(
"base",
@@ -40,6 +35,11 @@ func main() {
"/theme",
"Path to static theme resources (HTML, images, css, ...)",
)
+ password := flag.String(
+ "password",
+ "sesame",
+ "Pass Word (in the 1920s sense) to view the site. Not a secure passphrase.",
+ )
maintenanceInterval := flag.Duration(
"maint",
20*time.Second,
@@ -56,11 +56,10 @@ func main() {
log.Fatal(err)
}
- ctx, err := NewInstance(*base, *mothballDir, *stateDir, *themeDir)
+ ctx, err := NewInstance(*base, *mothballDir, *stateDir, *themeDir, *password)
if err != nil {
log.Fatal(err)
}
- ctx.BindHandlers(http.DefaultServeMux)
// Add some MIME extensions
// Doing this avoids decompressing a mothball entry twice per request
@@ -70,5 +69,5 @@ func main() {
go ctx.Maintenance(*maintenanceInterval)
log.Printf("Listening on %s", *listen)
- log.Fatal(http.ListenAndServe(*listen, logRequest(http.DefaultServeMux)))
+ log.Fatal(http.ListenAndServe(*listen, ctx))
}