package api import ( "crypto/rand" "database/sql" "fmt" "log" "net/http" "time" "git.hatecomputers.club/hatecomputers/hatecomputers.club/args" ) type RequestContext struct { DBConn *sql.DB Args *args.Arguments Id string Start time.Time } type Continuation func(*RequestContext, *http.Request, http.ResponseWriter) ContinuationChain type ContinuationChain func(Continuation, Continuation) ContinuationChain func randomId() string { uuid := make([]byte, 16) _, err := rand.Read(uuid) if err != nil { panic(err) } uuid[8] = uuid[8]&^0xc0 | 0x80 uuid[6] = uuid[6]&^0xf0 | 0x40 return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]) } func LogRequestContinuation(context *RequestContext, req *http.Request, resp http.ResponseWriter) ContinuationChain { return func(success Continuation, _failure Continuation) ContinuationChain { context.Start = time.Now() context.Id = randomId() log.Println(req.Method, req.URL.Path, req.RemoteAddr, context.Id) return success(context, req, resp) } } func LogExecutionTimeContinuation(context *RequestContext, req *http.Request, resp http.ResponseWriter) ContinuationChain { return func(success Continuation, _failure Continuation) ContinuationChain { end := time.Now() log.Println(context.Id, "took", end.Sub(context.Start)) return success(context, req, resp) } } func HealthCheckContinuation(context *RequestContext, req *http.Request, resp http.ResponseWriter) ContinuationChain { return func(success Continuation, _failure Continuation) ContinuationChain { resp.WriteHeader(200) resp.Write([]byte("healthy")) return success(context, req, resp) } } func FailurePassingContinuation(context *RequestContext, req *http.Request, resp http.ResponseWriter) ContinuationChain { return func(_success Continuation, failure Continuation) ContinuationChain { return failure(context, req, resp) } } func IdContinuation(context *RequestContext, req *http.Request, resp http.ResponseWriter) ContinuationChain { return func(success Continuation, _failure Continuation) ContinuationChain { return success(context, req, resp) } } func MakeServer(argv *args.Arguments, dbConn *sql.DB) *http.Server { mux := http.NewServeMux() fileServer := http.FileServer(http.Dir(argv.StaticPath)) mux.Handle("/static/", http.StripPrefix("/static/", fileServer)) makeRequestContext := func() *RequestContext { return &RequestContext{ DBConn: dbConn, Args: argv, } } mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { requestContext := makeRequestContext() LogRequestContinuation(requestContext, r, w)(TemplateContinuation("home.html", nil, true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation) }) mux.HandleFunc("GET /api/health", func(w http.ResponseWriter, r *http.Request) { requestContext := makeRequestContext() LogRequestContinuation(requestContext, r, w)(HealthCheckContinuation, FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation) }) mux.HandleFunc("GET /{name}", func(w http.ResponseWriter, r *http.Request) { requestContext := makeRequestContext() name := r.PathValue("name") LogRequestContinuation(requestContext, r, w)(TemplateContinuation(name+".html", nil, true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation) }) return &http.Server{ Addr: ":" + fmt.Sprint(argv.Port), Handler: mux, } }