1
0
Fork 0
cmdui/backend/api/api.go

155 lines
3.4 KiB
Go

package api
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/julienschmidt/httprouter"
"github.com/localhots/cmdui/backend/api/assets"
"github.com/localhots/cmdui/backend/api/auth"
"github.com/localhots/cmdui/backend/config"
"github.com/localhots/cmdui/backend/db"
"github.com/localhots/cmdui/backend/log"
)
// Start starts a web server that runs both backend API and serves assets that
// support the UI.
func Start() error {
assHand := assets.Handler()
router.NotFound = func(w http.ResponseWriter, r *http.Request) {
assHand.ServeHTTP(w, r)
}
cfg := config.Get().Server
log.Logger().Infof("Starting command UI server at %s:%d", cfg.Host, cfg.Port)
return http.ListenAndServe(fmt.Sprintf(":%d", cfg.Port), router)
}
//
// Endpoints
//
type handle func(ctx context.Context, w http.ResponseWriter, r *http.Request)
const rootPath = "/"
var router = httprouter.New()
func openEndpoint(h handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
ctx := contextWithParams(r.Context(), params)
h(ctx, w, r)
}
}
func protectedEndpoint(h handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
ctx, err := auth.AuthenticateRequest(w, r)
if err != nil {
renderUnauthorized(w, err)
return
}
ctx = contextWithParams(ctx, params)
h(ctx, w, r)
}
}
//
// Rendering
//
// renderJSON is a convinience function that encodes any value as JSON and
// writes it to response with appropriate headers included.
func renderJSON(w http.ResponseWriter, v interface{}) {
body, err := json.Marshal(v)
if err != nil {
renderError(w, err, http.StatusInternalServerError, "Failed to encode response into JSON")
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(body)
}
func renderError(w http.ResponseWriter, err error, status int, msg string) {
log.WithFields(log.F{
"status": status,
"error": err,
}).Warnf("Request failed: %s", msg)
http.Error(w, msg, status)
}
func renderUnauthorized(w http.ResponseWriter, err error) {
renderError(w, err, http.StatusUnauthorized, "Unauthorized")
}
//
// Params and context
//
type ctxKey string
const (
ctxParamsKey ctxKey = "params"
)
func contextWithParams(ctx context.Context, params httprouter.Params) context.Context {
return context.WithValue(ctx, ctxParamsKey, params)
}
func paramsFromContext(ctx context.Context) (params httprouter.Params, ok bool) {
v := ctx.Value(ctxParamsKey)
if v == nil {
return nil, false
}
return v.(httprouter.Params), true
}
func param(ctx context.Context, name string) string {
if params, ok := paramsFromContext(ctx); ok {
return params.ByName(name)
}
return ""
}
func requestedPage(r *http.Request) db.Page {
offset := r.FormValue("offset")
limit := r.FormValue("limit")
if offset == "" && limit == "" {
return db.Page{}
}
str2uint := func(s string) uint {
u, _ := strconv.ParseUint(s, 10, 64)
return uint(u)
}
return db.Page{
Offset: str2uint(offset),
Limit: str2uint(limit),
}
}
//
// Utils
//
// unbufferedWriter is an implementation of http.ResponseWriter that flushes the
// buffer after every write.
type unbufferedWriter struct {
http.ResponseWriter
}
func (w unbufferedWriter) Write(p []byte) (int, error) {
n, err := w.ResponseWriter.Write(p)
if f, ok := w.ResponseWriter.(http.Flusher); ok && err == nil {
f.Flush()
}
return n, err
}