Skip to content

Commit

Permalink
cmd/flatend, codec, config, handler, http: temporarily disable csv co…
Browse files Browse the repository at this point in the history
…dec, remove default handlers, add sqlite example
  • Loading branch information
lithdew committed May 12, 2020
1 parent 8cdb103 commit 57166ff
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 86 deletions.
50 changes: 34 additions & 16 deletions cmd/flatend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package main
import (
"errors"
"github.com/lithdew/flatend"
_ "github.com/mattn/go-sqlite3"
"github.com/xo/dburl"
"io"
"log"
"net"
Expand All @@ -11,6 +13,38 @@ import (
"os/signal"
)

func main() {
ln, err := net.Listen("tcp", ":44444")
check(err)
defer wrap(ln.Close)

db, err := dburl.Open("sqlite://:memory:")
check(err)
defer wrap(db.Close)

stmt, err := db.Prepare("SELECT sqlite_version()")
check(err)
defer wrap(stmt.Close)

srv := &flatend.Server{}
srv.Register(
&flatend.ContentType{},
&flatend.ContentLength{Max: 10 * 1024 * 1024},
&flatend.ContentDecode{},
&flatend.QuerySQL{MaxNumRows: 1000, Stmt: stmt},
&flatend.ContentEncode{},
)

go func() { check(srv.Serve(ln)) }()
defer wrap(srv.Shutdown)

log.Printf("Listening for connections on %q.", ln.Addr())

ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt)
<-ch
}

func isEOF(err error) bool {
if errors.Is(err, io.EOF) {
return true
Expand All @@ -37,19 +71,3 @@ func check(err error) {
func wrap(fn func() error) {
check(fn())
}

func main() {
ln, err := net.Listen("tcp", ":44444")
check(err)
defer wrap(ln.Close)

srv := &flatend.Server{}
go func() { check(srv.Serve(ln)) }()
defer wrap(srv.Shutdown)

log.Printf("Listening for connections on %q.", ln.Addr())

ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt)
<-ch
}
82 changes: 42 additions & 40 deletions codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ package flatend

import (
"bytes"
"encoding/csv"
"encoding/gob"
"encoding/json"
"fmt"
"github.com/BurntSushi/toml"
"gopkg.in/yaml.v3"
)
Expand Down Expand Up @@ -37,7 +35,7 @@ func init() {
registerCodec(newCodec(tomlEncoder, tomlDecoder,
"application/x-toml", "text/x-toml", "application/toml", "text/x-toml",
))
registerCodec(newCodec(csvEncoder, csvDecoder, "application/csv", "text/csv"))
//registerCodec(newCodec(csvEncoder, csvDecoder, "application/csv", "text/csv"))
registerCodec(newCodec(gobEncoder, gobDecoder, "application/x-gob", "text/x-gob"))
}

Expand Down Expand Up @@ -68,43 +66,47 @@ func marshalEncoder(f func(src interface{}) ([]byte, error)) EncodeFunc {
}
}

func csvEncoder(values Values) ([]byte, error) {
records := [][]string{make([]string, 0, len(values)), make([]string, 0, len(values))}

for k, v := range values {
records[0] = append(records[0], k)
records[1] = append(records[1], fmt.Sprint(v))
}

var b bytes.Buffer
if err := csv.NewWriter(&b).WriteAll(records); err != nil {
return nil, err
}

return b.Bytes(), nil
}

func csvDecoder(src []byte, values Values) error {
r := csv.NewReader(bytes.NewReader(src))

keys, err := r.Read()
if err != nil {
return fmt.Errorf("csv: failed to read keys: %w", err)
}

r.FieldsPerRecord = len(keys)

vals, err := r.Read()
if err != nil {
return fmt.Errorf("csv: failed to read values: %w", err)
}

for i := 0; i < len(keys); i++ {
values[keys[i]] = vals[i]
}

return nil
}
//func csvEncoder(values Values) ([]byte, error) {
// records := [][]string{make([]string, 0, len(values)), make([]string, 0, len(values))}
//
// for k, v := range values {
// s, ok := v.(fmt.Stringer)
// if !ok {
// continue
// }
// records[0] = append(records[0], k)
// records[1] = append(records[1], s.String())
// }
//
// var b bytes.Buffer
// if err := csv.NewWriter(&b).WriteAll(records); err != nil {
// return nil, err
// }
//
// return b.Bytes(), nil
//}
//
//func csvDecoder(src []byte, values Values) error {
// r := csv.NewReader(bytes.NewReader(src))
//
// keys, err := r.Read()
// if err != nil {
// return fmt.Errorf("csv: failed to read keys: %w", err)
// }
//
// r.FieldsPerRecord = len(keys)
//
// vals, err := r.Read()
// if err != nil {
// return fmt.Errorf("csv: failed to read values: %w", err)
// }
//
// for i := 0; i < len(keys); i++ {
// values[keys[i]] = vals[i]
// }
//
// return nil
//}

func gobEncoder(values Values) ([]byte, error) {
var b bytes.Buffer
Expand Down
7 changes: 0 additions & 7 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,5 @@ func NewDefaultConfig() *Config {
return &Config{
Codecs: Codecs,
CodecTypes: CodecTypes,

Handlers: []Handler{
&ContentType{},
&ContentLength{Max: 10 * 1024 * 1024},
&ContentDecode{},
&ContentEncode{},
},
}
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ go 1.14

require (
github.com/BurntSushi/toml v0.3.1
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.1
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/mattn/go-sqlite3 v2.0.3+incompatible
github.com/stretchr/testify v1.5.1
github.com/xo/dburl v0.0.0-20200124232849-e9ec94f52bc3
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ github.com/lithdew/bytesutil v0.0.0-20200409052507-d98389230a59 h1:CQpoOQecHxhvg
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 h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
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=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/xo/dburl v0.0.0-20200124232849-e9ec94f52bc3 h1:NC3CI7do3KHtiuYhk1CdS9V2qS3jNa7Fs2Afcnnt+IE=
github.com/xo/dburl v0.0.0-20200124232849-e9ec94f52bc3/go.mod h1:A47W3pdWONaZmXuLZgfKLAVgUY0qvfTRM5vVDKS40S4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
53 changes: 32 additions & 21 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,6 @@ func getBodyParams(r *http.Request, codec *Codec, values map[string]interface{})
type ContentEncode struct{}

func (h *ContentEncode) Serve(ctx *Context, w http.ResponseWriter, _ *http.Request) error {
ctx.Out = ctx.In

codec := ctx.Config.Codecs[w.Header().Get(HeaderContentType)]

if codec == nil {
Expand All @@ -177,15 +175,7 @@ func (h *ContentEncode) Serve(ctx *Context, w http.ResponseWriter, _ *http.Reque
return nil
}

type QuerySQL struct {
MinNumRows int
MaxNumRows int

Stmt *sql.Stmt
Params []string
}

func (h *QuerySQL) handlePagination(ctx *Context) {
func HandlePagination(ctx *Context, min, max int) {
offset, provided := ctx.In["offset"].(int)
if !provided || offset < 0 {
ctx.In["offset"] = 0
Expand All @@ -194,15 +184,23 @@ func (h *QuerySQL) handlePagination(ctx *Context) {
limit, provided := ctx.In["limit"].(int)

switch {
case !provided || limit < h.MinNumRows || limit < 0:
ctx.In["limit"] = h.MinNumRows
case limit > h.MaxNumRows:
ctx.In["limit"] = h.MaxNumRows
case !provided || limit < min || limit < 0:
ctx.In["limit"] = min
case limit > max:
ctx.In["limit"] = max
}
}

func (h *QuerySQL) Serve(ctx *Context, w http.ResponseWriter, _ *http.Request) error {
h.handlePagination(ctx)
type QuerySQL struct {
MinNumRows int
MaxNumRows int

Stmt *sql.Stmt
Params []string
}

func (h *QuerySQL) Serve(ctx *Context, _ http.ResponseWriter, _ *http.Request) error {
HandlePagination(ctx, h.MinNumRows, h.MaxNumRows)

params := acquireValueBuffer(len(h.Params))
for _, param := range h.Params {
Expand All @@ -212,18 +210,27 @@ func (h *QuerySQL) Serve(ctx *Context, w http.ResponseWriter, _ *http.Request) e
releaseValueBuffer(params)

if err != nil {
return &Error{Status: http.StatusInternalServerError, Err: fmt.Errorf("failed to execute query: %w", err)}
return &Error{
Status: http.StatusInternalServerError,
Err: fmt.Errorf("failed to execute query: %w", err),
}
}

defer rows.Close()

keys, err := rows.Columns()
if err != nil {
return &Error{Status: http.StatusInternalServerError, Err: fmt.Errorf("failed to fetch columns: %w", err)}
return &Error{
Status: http.StatusInternalServerError,
Err: fmt.Errorf("failed to fetch columns: %w", err),
}
}

if len(keys) == 0 {
return &Error{Status: http.StatusInternalServerError, Err: errors.New("zero columns resultant from sql query")}
return &Error{
Status: http.StatusInternalServerError,
Err: errors.New("zero columns resultant from sql query"),
}
}

vals := acquireValueBuffer(len(keys))
Expand All @@ -234,7 +241,10 @@ func (h *QuerySQL) Serve(ctx *Context, w http.ResponseWriter, _ *http.Request) e
for i := 0; rows.Next() && i < h.MaxNumRows; i++ {
err := rows.Scan(vals...)
if err != nil {
return &Error{Status: http.StatusInternalServerError, Err: fmt.Errorf("got an error while scanning: %w", err)}
return &Error{
Status: http.StatusInternalServerError,
Err: fmt.Errorf("got an error while scanning: %w", err),
}
}

result := make(map[string]interface{}, len(keys))
Expand All @@ -245,6 +255,7 @@ func (h *QuerySQL) Serve(ctx *Context, w http.ResponseWriter, _ *http.Request) e
results = append(results, result)
}

ctx.Out["status"] = http.StatusOK
ctx.Out["results"] = results

return nil
Expand Down
7 changes: 6 additions & 1 deletion http.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ func (s *Server) init() {
}
}

func (s *Server) Register(handlers ...Handler) {
s.once.Do(s.init)
s.config.Handlers = append(s.config.Handlers, handlers...)
}

func (s *Server) Serve(ln net.Listener) error {
s.once.Do(s.init)
return s.srv.Serve(ln)
Expand Down Expand Up @@ -129,7 +134,7 @@ func (s *Server) writeError(w http.ResponseWriter, err *Error) {
return
}

w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set(HeaderContentTypeOptions, "nosniff")
w.WriteHeader(err.Status)
w.Write(buf)
}

0 comments on commit 57166ff

Please sign in to comment.