Created
August 5, 2022 07:28
-
-
Save alexedwards/e17069d14ae453323d83501f0b280448 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"errors" | |
"fmt" | |
"io" | |
"log" | |
"net/http" | |
"strings" | |
"github.com/golang/gddo/httputil/header" | |
) | |
type Person struct { | |
Name string | |
Age int | |
} | |
func personCreate(w http.ResponseWriter, r *http.Request) { | |
var p Person | |
err := decodeJSONBody(w, r, &p) | |
if err != nil { | |
var mr *malformedRequest | |
if errors.As(err, &mr) { | |
http.Error(w, mr.msg, mr.status) | |
} else { | |
log.Print(err.Error()) | |
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) | |
} | |
return | |
} | |
fmt.Fprintf(w, "Person: %+v", p) | |
} | |
func main() { | |
mux := http.NewServeMux() | |
mux.HandleFunc("/person/create", personCreate) | |
log.Print("Starting server on :4000...") | |
err := http.ListenAndServe(":4000", mux) | |
log.Fatal(err) | |
} | |
type malformedRequest struct { | |
status int | |
msg string | |
} | |
func (mr *malformedRequest) Error() string { | |
return mr.msg | |
} | |
func decodeJSONBody(w http.ResponseWriter, r *http.Request, dst interface{}) error { | |
if r.Header.Get("Content-Type") != "" { | |
value, _ := header.ParseValueAndParams(r.Header, "Content-Type") | |
if value != "application/json" { | |
msg := "Content-Type header is not application/json" | |
return &malformedRequest{status: http.StatusUnsupportedMediaType, msg: msg} | |
} | |
} | |
r.Body = http.MaxBytesReader(w, r.Body, 1048576) | |
dec := json.NewDecoder(r.Body) | |
dec.DisallowUnknownFields() | |
err := dec.Decode(&dst) | |
if err != nil { | |
var syntaxError *json.SyntaxError | |
var unmarshalTypeError *json.UnmarshalTypeError | |
switch { | |
case errors.As(err, &syntaxError): | |
msg := fmt.Sprintf("Request body contains badly-formed JSON (at position %d)", syntaxError.Offset) | |
return &malformedRequest{status: http.StatusBadRequest, msg: msg} | |
case errors.Is(err, io.ErrUnexpectedEOF): | |
msg := fmt.Sprintf("Request body contains badly-formed JSON") | |
return &malformedRequest{status: http.StatusBadRequest, msg: msg} | |
case errors.As(err, &unmarshalTypeError): | |
msg := fmt.Sprintf("Request body contains an invalid value for the %q field (at position %d)", unmarshalTypeError.Field, unmarshalTypeError.Offset) | |
return &malformedRequest{status: http.StatusBadRequest, msg: msg} | |
case strings.HasPrefix(err.Error(), "json: unknown field "): | |
fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ") | |
msg := fmt.Sprintf("Request body contains unknown field %s", fieldName) | |
return &malformedRequest{status: http.StatusBadRequest, msg: msg} | |
case errors.Is(err, io.EOF): | |
msg := "Request body must not be empty" | |
return &malformedRequest{status: http.StatusBadRequest, msg: msg} | |
case err.Error() == "http: request body too large": | |
msg := "Request body must not be larger than 1MB" | |
return &malformedRequest{status: http.StatusRequestEntityTooLarge, msg: msg} | |
default: | |
return err | |
} | |
} | |
err = dec.Decode(&struct{}{}) | |
if err != io.EOF { | |
msg := "Request body must only contain a single JSON object" | |
return &malformedRequest{status: http.StatusBadRequest, msg: msg} | |
} | |
return nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment