httpx: Add new redirect
This commit is contained in:
parent
41d41035e3
commit
8e2d2cce3e
5 changed files with 123 additions and 35 deletions
75
pkg/httpx/errors.go
Normal file
75
pkg/httpx/errors.go
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
package httpx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInterceptor intercepts errors and directly returns specific responses for them
|
||||||
|
type ErrInterceptor struct {
|
||||||
|
Errors map[error]Response
|
||||||
|
Fallback Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept attempts to intercept the given error.
|
||||||
|
// When err is nil, does nothing.
|
||||||
|
//
|
||||||
|
// When err is not nil, first attempts to find a static response in errors and respond with that.
|
||||||
|
// Otherwise it returns the Fallback response.
|
||||||
|
// intercepted indicates if some response was sent.
|
||||||
|
func (ei ErrInterceptor) Intercept(w http.ResponseWriter, r *http.Request, err error) (intercepted bool) {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
res, ok := ei.Errors[err]
|
||||||
|
if !ok {
|
||||||
|
res = ei.Fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
res.ServerHTTP(w, r)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusInterceptor creates a new ErrInterceptor handling default responses.
|
||||||
|
// If body returns err != nil, StatusInterceptor calls panic().
|
||||||
|
func StatusInterceptor(contentType string, body func(code int, text string) ([]byte, error)) ErrInterceptor {
|
||||||
|
makeResponse := func(code int) (res Response) {
|
||||||
|
var err error
|
||||||
|
res.Body, err = body(code, http.StatusText(code))
|
||||||
|
if err != nil {
|
||||||
|
panic("StatusInterceptor: err != nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
res.ContentType = contentType
|
||||||
|
res.StatusCode = code
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrInterceptor{
|
||||||
|
Errors: map[error]Response{
|
||||||
|
ErrNotFound: makeResponse(http.StatusNotFound),
|
||||||
|
ErrForbidden: makeResponse(http.StatusForbidden),
|
||||||
|
},
|
||||||
|
Fallback: makeResponse(http.StatusInternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common errors accepted by all httpx handlers
|
||||||
|
var (
|
||||||
|
ErrNotFound = errors.New("httpx: Not Found")
|
||||||
|
ErrForbidden = errors.New("httpx: Forbidden")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
textInterceptor = StatusInterceptor("text/plain", func(code int, text string) ([]byte, error) {
|
||||||
|
return []byte(text), nil
|
||||||
|
})
|
||||||
|
jsonInterceptor = StatusInterceptor("application/json", func(code int, text string) ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]any{"status": text, "code": code})
|
||||||
|
})
|
||||||
|
htmlInterceptor = StatusInterceptor("text/html", func(code int, text string) ([]byte, error) {
|
||||||
|
return []byte(`<!DOCTYPE HTML><title>` + text + `</title>` + text), nil
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
@ -12,27 +12,13 @@ type HTMLHandler[T any] struct {
|
||||||
TemplateName string // name of template to render, defaults to root
|
TemplateName string // name of template to render, defaults to root
|
||||||
}
|
}
|
||||||
|
|
||||||
var htmlInternalServerErr = []byte(`<!DOCTYPE HTML><title>Internal Server Error</title>Internal Server Error`)
|
|
||||||
var htmlNotFound = []byte(`<!DOCTYPE HTML><title>Not Found</title>Not Found`)
|
|
||||||
|
|
||||||
// ServeHTTP calls j(r) and returns json
|
// ServeHTTP calls j(r) and returns json
|
||||||
func (h HTMLHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h HTMLHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
// call the function
|
// call the function
|
||||||
result, err := h.Handler(r)
|
result, err := h.Handler(r)
|
||||||
|
|
||||||
// entity not found
|
// intercept any errors
|
||||||
if err == ErrNotFound {
|
if htmlInterceptor.Intercept(w, r, err) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
w.Write(htmlNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle other errors
|
|
||||||
if err != nil {
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write(htmlInternalServerErr)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,25 @@
|
||||||
package httpx
|
package httpx
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
// ErrNotFound should be returned from any httpx error to indicate that the item was not found
|
// Response represents a response to an http request.
|
||||||
var ErrNotFound = errors.New("httpx: Error 404")
|
type Response struct {
|
||||||
|
ContentType string // defaults to text/plain
|
||||||
|
Body []byte
|
||||||
|
StatusCode int // defaults to [http.StatusOK]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response Response) ServerHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if response.ContentType == "" {
|
||||||
|
response.ContentType = "text/plain"
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", response.ContentType)
|
||||||
|
|
||||||
|
if response.StatusCode <= 0 {
|
||||||
|
response.StatusCode = http.StatusOK
|
||||||
|
}
|
||||||
|
w.WriteHeader(response.StatusCode)
|
||||||
|
w.Write(response.Body)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
var jsonInternalServerErr = []byte(`{"status":"internal server error"}`)
|
|
||||||
var jsonNotFound = []byte(`{"status":"not found"}`)
|
|
||||||
|
|
||||||
// JSON creates a new JSONHandler
|
// JSON creates a new JSONHandler
|
||||||
func JSON[T any](f func(r *http.Request) (T, error)) JSONHandler[T] {
|
func JSON[T any](f func(r *http.Request) (T, error)) JSONHandler[T] {
|
||||||
return JSONHandler[T](f)
|
return JSONHandler[T](f)
|
||||||
|
|
@ -22,19 +19,8 @@ func (j JSONHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
// call the function
|
// call the function
|
||||||
result, err := j(r)
|
result, err := j(r)
|
||||||
|
|
||||||
// entity not found
|
// handle any errors
|
||||||
if err == ErrNotFound {
|
if jsonInterceptor.Intercept(w, r, err) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
w.Write(jsonNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle other errors
|
|
||||||
if err != nil {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write(jsonInternalServerErr)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
22
pkg/httpx/redirect.go
Normal file
22
pkg/httpx/redirect.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package httpx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RedirectHandler represents a handler that redirects the user to the address returned
|
||||||
|
type RedirectHandler func(r *http.Request) (string, int, error)
|
||||||
|
|
||||||
|
// ServeHTTP calls r(r) and returns json
|
||||||
|
func (rh RedirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// call the function
|
||||||
|
url, code, err := rh(r)
|
||||||
|
|
||||||
|
// intercept the errors
|
||||||
|
if textInterceptor.Intercept(w, r, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// do the redirect
|
||||||
|
http.Redirect(w, r, url, code)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue