1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
package server

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"rss_parrot/shared"
	"strings"
)

const (
	wwwPathPrefx       = "www/"
	apiKeyHeader       = "X-API-KEY"
	metricsAuthHeader  = "Authorization"
	rootPlacholder     = "*root*"
	notFoundPlacholder = "*404*"
	internalErrorStr   = "500 Internal Server Error"
	badRequestStr      = "400 Invalid Request"
	notFoundStr        = "404 Not Found"
	badApiKeyStr       = "401 Missing or Invalid API Key"
	badAuthorization   = "401 Missing or Invalid Authorization Header"
)

type responseType int

const (
	rtPlainJson = iota
	rtActivityJson
	rtJrdJson
)

// Defines a single HTTP handler (endpoint)
type handlerDef struct {
	method  string
	pattern string
	handler func(http.ResponseWriter, *http.Request)
}

// IHandlerGroup groups together multiple HTTP handler definitions.
type IHandlerGroup interface {
	Prefix() string
	GroupDefs() []handlerDef
	AuthMW() func(next http.Handler) http.Handler
}

func emptyMW(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		h.ServeHTTP(w, r)
	})
}

func acceptsJson(r *http.Request) bool {
	accept := r.Header.Get("Accept")
	if strings.HasPrefix(accept, "application/") && strings.Contains(accept, "json") {
		return true
	}
	return false
}

// Returns the JSON serialized object as the response body; handles errors.
func writeJsonResponse(logger shared.ILogger, w http.ResponseWriter, rt responseType, resp interface{}) {
	if rt == rtActivityJson {
		w.Header().Set("Content-Type", "application/activity+json; charset=utf-8")
	} else if rt == rtJrdJson {
		w.Header().Set("Content-Type", "application/jrd+json; charset=utf-8")
	} else {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
	}
	var err error
	var respJson []byte
	if respJson, err = json.Marshal(resp); err != nil {
		logger.Warnf("Failed to serialize response: %v\n", err)
		http.Error(w, internalErrorStr, http.StatusInternalServerError)
		return
	}
	if _, err = fmt.Fprintln(w, string(respJson)); err != nil {
		logger.Warnf("Failed to write response: %v\n", err)
		http.Error(w, internalErrorStr, http.StatusInternalServerError)
		return
	}
}

type errorResp struct {
	Error  string `json:"error"`
	Status int    `json:"status"`
}

func writeErrorResponse(w http.ResponseWriter, msg string, code int) {
	w.Header().Set("Content-Type", "application/json")
	resp := errorResp{msg, code}
	respJson, _ := json.Marshal(resp)
	http.Error(w, string(respJson), code)
}

func readBody(logger shared.ILogger, w http.ResponseWriter, r *http.Request) []byte {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		logger.Warnf("Failed to read request body: %v", err)
		http.Error(w, badRequestStr, http.StatusBadRequest)
		return nil
	}
	return body
}