Skip to content

Commit

Permalink
http, handler, error: created generalized flatend.Error, remove httpE…
Browse files Browse the repository at this point in the history
…rror in place for writeError in flatend.Server,initial work on sql handler
  • Loading branch information
lithdew committed May 12, 2020
1 parent 05545c5 commit a3699e9
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 79 deletions.
13 changes: 13 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package flatend

type Error struct {
Status int
Err error
}

func (e *Error) Error() string {
if e.Err == nil {
return ""
}
return e.Err.Error()
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/julienschmidt/httprouter v1.3.0
github.com/kr/pretty v0.1.0 // indirect
github.com/lithdew/bytesutil v0.0.0-20200409052507-d98389230a59
github.com/lithdew/sqlutil v0.0.0-20200508165435-ee5c68997a73
github.com/stretchr/testify v1.5.1
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lithdew/bytesutil v0.0.0-20200409052507-d98389230a59 h1:CQpoOQecHxhvgOU/ijue/yWuShZYDtNpI9bsD4Dkzrk=
github.com/lithdew/bytesutil v0.0.0-20200409052507-d98389230a59/go.mod h1:89JlULMIJ/+YWzAp5aHXgAD2d02S2mY+a+PMgXDtoNs=
github.com/lithdew/sqlutil v0.0.0-20200508165435-ee5c68997a73 h1:1dJYhA/dkVIRAjSLzwYVHjx+EXCqnU6NrU/gjAN/Y/4=
github.com/lithdew/sqlutil v0.0.0-20200508165435-ee5c68997a73/go.mod h1:4DsjvJVgwIyR/cT5U7KiF1vQwB7zBMRISJO6wI9kwII=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
82 changes: 50 additions & 32 deletions handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package flatend

import (
"database/sql"
"errors"
"fmt"
"github.com/lithdew/flatend/httputil"
Expand All @@ -13,28 +14,34 @@ const (
HeaderContentTypeOptions = "X-Content-Type-Options"
)

type Handler interface {
Serve(ctx *Context, w http.ResponseWriter, r *http.Request) error
}
var (
_ Handler = (*ContentType)(nil)
_ Handler = (*ContentLength)(nil)
_ Handler = (*ContentDecode)(nil)
_ Handler = (*ContentEncode)(nil)
_ Handler = (*ExecuteSQL)(nil)
)

type Context struct {
Config *Config
In Values
Out Values
}

type Handler interface {
Serve(ctx *Context, w http.ResponseWriter, r *http.Request) error
}

type ContentType struct{}

func (h *ContentType) Serve(ctx *Context, w http.ResponseWriter, r *http.Request) error {
accept := httputil.NegotiateContentType(r, ctx.Config.CodecTypes, ctx.Config.DefaultCodec)

codec, available := ctx.Config.Codecs[accept]
if !available {
return httpError(
w, codec,
http.StatusNotAcceptable,
fmt.Errorf("only able to accept %q", ctx.Config.CodecTypes),
)
if _, available := ctx.Config.Codecs[accept]; !available {
return &Error{
Status: http.StatusNotAcceptable,
Err: fmt.Errorf("only able to accept %q", ctx.Config.CodecTypes),
}
}

w.Header().Set(HeaderContentType, accept)
Expand All @@ -47,22 +54,18 @@ type ContentLength struct {
Max int64
}

func (c *ContentLength) Serve(ctx *Context, w http.ResponseWriter, r *http.Request) error {
codec := ctx.Config.Codecs[w.Header().Get(HeaderContentType)]

func (c *ContentLength) Serve(_ *Context, w http.ResponseWriter, r *http.Request) error {
switch {
case r.ContentLength < c.Min:
return httpError(
w, codec,
http.StatusBadRequest,
fmt.Errorf("payload too small: expected %d byte(s) min, got %d byte(s)", c.Min, r.ContentLength),
)
return &Error{
Status: http.StatusBadRequest,
Err: fmt.Errorf("payload too small: expected %d byte(s) min, got %d byte(s)", c.Min, r.ContentLength),
}
case r.ContentLength > c.Max:
return httpError(
w, codec,
http.StatusRequestEntityTooLarge,
fmt.Errorf("payload too large: expected %d byte(s) max, got %d byte(s)", c.Max, r.ContentLength),
)
return &Error{
Status: http.StatusRequestEntityTooLarge,
Err: fmt.Errorf("payload too large: expected %d byte(s) max, got %d byte(s)", c.Max, r.ContentLength),
}
}

return nil
Expand All @@ -75,17 +78,17 @@ func (h *ContentDecode) Serve(ctx *Context, w http.ResponseWriter, r *http.Reque

err := getHeaderParams(r, ctx.In)
if err != nil && !errors.Is(err, http.ErrBodyNotAllowed) {
return httpError(w, codec, http.StatusBadRequest, err)
return &Error{Status: http.StatusBadRequest, Err: err}
}

err = getQueryParams(r, ctx.In)
if err != nil && !errors.Is(err, http.ErrBodyNotAllowed) {
return httpError(w, codec, http.StatusBadRequest, err)
return &Error{Status: http.StatusBadRequest, Err: err}
}

err = getBodyParams(r, codec, ctx.In)
if err != nil && !errors.Is(err, http.ErrBodyNotAllowed) {
return httpError(w, codec, http.StatusBadRequest, err)
return &Error{Status: http.StatusBadRequest, Err: err}
}

return nil
Expand Down Expand Up @@ -155,19 +158,34 @@ func (h *ContentEncode) Serve(ctx *Context, w http.ResponseWriter, _ *http.Reque
codec := ctx.Config.Codecs[w.Header().Get(HeaderContentType)]

if codec == nil {
return httpError(
w, codec,
http.StatusNotAcceptable,
fmt.Errorf("only able to accept %q", ctx.Config.CodecTypes),
)
return &Error{
Status: http.StatusNotAcceptable,
Err: fmt.Errorf("only able to accept %q", ctx.Config.CodecTypes),
}
}

buf, err := codec.Encode(ctx.Out)
if err != nil && !errors.Is(err, http.ErrBodyNotAllowed) {
return httpError(w, codec, http.StatusInternalServerError, err)
return &Error{
Status: http.StatusInternalServerError,
Err: err,
}
}

w.Write(buf)

return nil
}

type ExecuteSQL struct {
Stmt *sql.Stmt
}

func (h *ExecuteSQL) Serve(ctx *Context, w http.ResponseWriter, r *http.Request) error {
rows, err := h.Stmt.Query()
if err != nil {
return &Error{Status: http.StatusInternalServerError, Err: fmt.Errorf("failed to execute query: %w", err)}
}
_ = rows
return nil
}
43 changes: 42 additions & 1 deletion http.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package flatend
import (
"context"
"github.com/julienschmidt/httprouter"
"github.com/lithdew/bytesutil"
"net"
"net/http"
"sync"
Expand Down Expand Up @@ -89,7 +90,47 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, handler := range s.config.Handlers {
err := handler.Serve(ctx, w, r)
if err != nil {
return
s.writeError(w, err.(*Error))
break
}
}
}

func (s *Server) writeError(w http.ResponseWriter, err *Error) {
if err.Status == http.StatusNoContent {
w.WriteHeader(err.Status)
return
}

values := acquireValues()
defer releaseValues(values)

values["status"] = err.Status
values["error"] = err.Error()

var (
encoded []byte
encodedErr error
)

codec := s.config.Codecs[w.Header().Get(HeaderContentType)]

if codec != nil {
encoded, encodedErr = codec.Encode(values)
}

if codec == nil || encodedErr != nil {
w.Header().Set(HeaderContentType, "text/plain; charset=utf-8")
w.Header().Set(HeaderContentTypeOptions, "nosniff")
w.WriteHeader(err.Status)

encoded = bytesutil.Slice(err.Error())
w.Write(encoded)

return
}

w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(err.Status)
w.Write(encoded)
}
46 changes: 0 additions & 46 deletions http_utils.go

This file was deleted.

0 comments on commit a3699e9

Please sign in to comment.