Move to fasthttp for improved performance

This commit is contained in:
Morgan Bazalgette
2017-02-02 13:40:28 +01:00
parent ace2fded7e
commit 85e6dc7e5e
26 changed files with 448 additions and 380 deletions

29
common/conversions.go Normal file
View File

@@ -0,0 +1,29 @@
package common
import (
"reflect"
"unsafe"
)
// b2s converts byte slice to a string without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func b2s(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
// s2b converts string to a byte slice without memory allocation.
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func s2b(s string) []byte {
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh := reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
Cap: sh.Len,
}
return *(*[]byte)(unsafe.Pointer(&bh))
}

View File

@@ -2,26 +2,132 @@ package common
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/DataDog/datadog-go/statsd"
"github.com/gin-gonic/gin"
"github.com/getsentry/raven-go"
"github.com/jmoiron/sqlx"
"github.com/valyala/fasthttp"
"gopkg.in/redis.v5"
)
// RavenClient is the raven client to which report errors happening.
// If nil, errors will just be fmt.Println'd
var RavenClient *raven.Client
// MethodData is a struct containing the data passed over to an API method.
type MethodData struct {
User Token
DB *sqlx.DB
RequestData RequestData
C *gin.Context
Doggo *statsd.Client
R *redis.Client
User Token
DB *sqlx.DB
Doggo *statsd.Client
R *redis.Client
Ctx *fasthttp.RequestCtx
}
// Err logs an error into gin.
// ClientIP implements a best effort algorithm to return the real client IP, it parses
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
func (md MethodData) ClientIP() string {
clientIP := strings.TrimSpace(string(md.Ctx.Request.Header.Peek("X-Real-Ip")))
if len(clientIP) > 0 {
return clientIP
}
clientIP = string(md.Ctx.Request.Header.Peek("X-Forwarded-For"))
if index := strings.IndexByte(clientIP, ','); index >= 0 {
clientIP = clientIP[0:index]
}
clientIP = strings.TrimSpace(clientIP)
if len(clientIP) > 0 {
return clientIP
}
return md.Ctx.RemoteIP().String()
}
// Err logs an error. If RavenClient is set, it will use the client to report
// the error to sentry, otherwise it will just write the error to stdout.
func (md MethodData) Err(err error) {
md.C.Error(err)
if RavenClient == nil {
fmt.Println("ERROR!!!!")
fmt.Println(err)
return
}
// Create stacktrace
st := raven.NewStacktrace(0, 3, []string{"zxq.co/ripple", "git.zxq.co/ripple"})
// Generate tags for error
tags := map[string]string{
"endpoint": b2s(md.Ctx.RequestURI()),
"token": md.User.Value,
}
RavenClient.CaptureError(
err,
tags,
st,
generateRavenHTTP(md.Ctx),
&raven.User{
ID: strconv.Itoa(md.User.UserID),
Username: md.User.Value,
IP: md.Ctx.RemoteAddr().String(),
},
)
}
// Err for peppy API calls
func Err(c *fasthttp.RequestCtx, err error) {
if RavenClient == nil {
fmt.Println("ERROR!!!!")
fmt.Println(err)
return
}
// Create stacktrace
st := raven.NewStacktrace(0, 3, []string{"zxq.co/ripple", "git.zxq.co/ripple"})
// Generate tags for error
tags := map[string]string{
"endpoint": b2s(c.RequestURI()),
}
RavenClient.CaptureError(
err,
tags,
st,
generateRavenHTTP(c),
)
}
func generateRavenHTTP(ctx *fasthttp.RequestCtx) *raven.Http {
// build uri
uri := ctx.URI()
// safe to use b2s because a new string gets allocated eventually for
// concatenation
sURI := b2s(uri.Scheme()) + "://" + b2s(uri.Host()) + b2s(uri.Path())
// build header map
// using ctx.Request.Header.Len would mean calling .VisitAll two times
// which can be quite expensive since it means iterating over all the
// headers, so we give a rough estimate of the number of headers we expect
// to have
m := make(map[string]string, 16)
ctx.Request.Header.VisitAll(func(k, v []byte) {
// not using b2s because we mustn't keep references to the underlying
// k and v
m[string(k)] = string(v)
})
return &raven.Http{
URL: sURI,
// Not using b2s because raven sending is concurrent and may happen
// AFTER the request, meaning that values could potentially be replaced
// by new ones.
Method: string(ctx.Method()),
Query: string(uri.QueryString()),
Cookies: string(ctx.Request.Header.Peek("Cookie")),
Headers: m,
}
}
// ID retrieves the Token's owner user ID.
@@ -31,23 +137,16 @@ func (md MethodData) ID() int {
// Query is shorthand for md.C.Query.
func (md MethodData) Query(q string) string {
return md.C.Query(q)
return b2s(md.Ctx.QueryArgs().Peek(q))
}
// HasQuery returns true if the parameter is encountered in the querystring.
// It returns true even if the parameter is "" (the case of ?param&etc=etc)
func (md MethodData) HasQuery(q string) bool {
_, has := md.C.GetQuery(q)
return has
return md.Ctx.QueryArgs().Has(q)
}
// RequestData is the body of a request. It is wrapped into this type
// to implement the Unmarshal function, which is just a shorthand to
// json.Unmarshal.
type RequestData []byte
// Unmarshal json-decodes Requestdata into a value. Basically a
// shorthand to json.Unmarshal.
func (r RequestData) Unmarshal(into interface{}) error {
return json.Unmarshal([]byte(r), into)
// Unmarshal unmarshals a request's JSON body into an interface.
func (md MethodData) Unmarshal(into interface{}) error {
return json.Unmarshal(md.Ctx.PostBody(), into)
}

View File

@@ -19,8 +19,8 @@ func Sort(md MethodData, config SortConfiguration) string {
config.Table += "."
}
var sortBy string
for _, s := range md.C.Request.URL.Query()["sort"] {
sortParts := strings.Split(strings.ToLower(s), ",")
for _, s := range md.Ctx.Request.URI().QueryArgs().PeekMulti("sort") {
sortParts := strings.Split(strings.ToLower(b2s(s)), ",")
if contains(config.Allowed, sortParts[0]) {
if sortBy != "" {
sortBy += ", "

View File

@@ -51,15 +51,15 @@ func (w *WhereClause) And() *WhereClause {
// initial is the initial part, e.g. "users.id".
// Fields are the possible values.
// Sample output: users.id IN ('1', '2', '3')
func (w *WhereClause) In(initial string, fields ...string) *WhereClause {
func (w *WhereClause) In(initial string, fields ...[]byte) *WhereClause {
if len(fields) == 0 {
return w
}
w.addWhere()
w.Clause += initial + " IN (" + generateQuestionMarks(len(fields)) + ")"
fieldsInterfaced := make([]interface{}, 0, len(fields))
for _, i := range fields {
fieldsInterfaced = append(fieldsInterfaced, interface{}(i))
fieldsInterfaced := make([]interface{}, len(fields))
for k, f := range fields {
fieldsInterfaced[k] = string(f)
}
w.Params = append(w.Params, fieldsInterfaced...)
return w