2017-02-02 12:53:36 +00:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
2017-02-02 14:13:17 +00:00
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2017-02-02 12:53:36 +00:00
|
|
|
"github.com/buaazp/fasthttprouter"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
|
|
"github.com/valyala/fasthttp"
|
2019-02-25 21:04:55 +00:00
|
|
|
"github.com/osuyozora/api/common"
|
2017-02-02 12:53:36 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type router struct {
|
|
|
|
r *fasthttprouter.Router
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r router) Method(path string, f func(md common.MethodData) common.CodeMessager, privilegesNeeded ...int) {
|
2017-02-02 14:13:17 +00:00
|
|
|
r.r.GET(path, wrap(Method(f, privilegesNeeded...)))
|
2017-02-02 12:53:36 +00:00
|
|
|
}
|
|
|
|
func (r router) POSTMethod(path string, f func(md common.MethodData) common.CodeMessager, privilegesNeeded ...int) {
|
2017-02-02 14:13:17 +00:00
|
|
|
r.r.POST(path, wrap(Method(f, privilegesNeeded...)))
|
2017-02-02 12:53:36 +00:00
|
|
|
}
|
|
|
|
func (r router) Peppy(path string, a func(c *fasthttp.RequestCtx, db *sqlx.DB)) {
|
2017-02-02 14:13:17 +00:00
|
|
|
r.r.GET(path, wrap(PeppyMethod(a)))
|
2017-02-02 12:53:36 +00:00
|
|
|
}
|
|
|
|
func (r router) GET(path string, handle fasthttp.RequestHandler) {
|
2017-02-02 14:13:17 +00:00
|
|
|
r.r.GET(path, wrap(handle))
|
|
|
|
}
|
2017-02-19 17:19:59 +00:00
|
|
|
func (r router) PlainGET(path string, handle fasthttp.RequestHandler) {
|
|
|
|
r.r.GET(path, handle)
|
|
|
|
}
|
2017-02-02 14:13:17 +00:00
|
|
|
|
|
|
|
const (
|
|
|
|
// \x1b is escape code for ESC
|
|
|
|
// <ESC>[<n>m is escape sequence for a certain colour
|
|
|
|
// no IP is written out because of the hundreds of possible ways to pass IPs
|
|
|
|
// to a request when using a reverse proxy
|
|
|
|
// this is partly inspired from gin, though made even more simplistic.
|
|
|
|
fmtString = "%s | %15s |\x1b[%sm %3d \x1b[0m %-7s %s\n"
|
|
|
|
// a kind of human readable RFC3339
|
|
|
|
timeFormat = "2006-01-02 15:04:05"
|
|
|
|
// color reference
|
|
|
|
// http://misc.flogisoft.com/bash/tip_colors_and_formatting
|
|
|
|
colorOk = "42" // green
|
|
|
|
colorError = "41" // red
|
|
|
|
)
|
|
|
|
|
|
|
|
// wrap returns a function that wraps around handle, providing middleware
|
|
|
|
// functionality to apply to all API calls, which is to say:
|
|
|
|
// - logging
|
|
|
|
// - panic recovery (reporting to sentry)
|
|
|
|
// - gzipping
|
|
|
|
func wrap(handle fasthttp.RequestHandler) fasthttp.RequestHandler {
|
|
|
|
return func(c *fasthttp.RequestCtx) {
|
|
|
|
start := time.Now()
|
|
|
|
|
2017-04-13 07:23:46 +00:00
|
|
|
doggo.Incr("requests", nil, 1)
|
|
|
|
|
2017-02-02 14:13:17 +00:00
|
|
|
defer func() {
|
|
|
|
if rval := recover(); rval != nil {
|
|
|
|
var err error
|
|
|
|
switch rval := rval.(type) {
|
|
|
|
case string:
|
|
|
|
err = errors.New(rval)
|
|
|
|
case error:
|
|
|
|
err = rval
|
|
|
|
default:
|
|
|
|
err = fmt.Errorf("%v - %#v", rval, rval)
|
|
|
|
}
|
|
|
|
common.Err(c, err)
|
|
|
|
c.SetStatusCode(500)
|
|
|
|
c.SetBodyString(`{ "code": 500, "message": "something really bad happened" }`)
|
|
|
|
}
|
|
|
|
|
|
|
|
// switch color to colorError if statusCode is in [500;600)
|
|
|
|
color := colorOk
|
|
|
|
statusCode := c.Response.StatusCode()
|
|
|
|
if statusCode >= 500 && statusCode < 600 {
|
|
|
|
color = colorError
|
|
|
|
}
|
|
|
|
|
|
|
|
if bytes.Contains(c.Request.Header.Peek("Accept-Encoding"), s2b("gzip")) {
|
|
|
|
c.Response.Header.Add("Content-Encoding", "gzip")
|
|
|
|
c.Response.Header.Add("Vary", "Accept-Encoding")
|
|
|
|
b := c.Response.Body()
|
|
|
|
c.Response.ResetBody()
|
|
|
|
fasthttp.WriteGzip(c.Response.BodyWriter(), b)
|
|
|
|
}
|
|
|
|
|
|
|
|
// print stuff
|
|
|
|
fmt.Printf(
|
|
|
|
fmtString,
|
|
|
|
time.Now().Format(timeFormat),
|
|
|
|
time.Since(start).String(),
|
|
|
|
color,
|
|
|
|
statusCode,
|
|
|
|
c.Method(),
|
|
|
|
c.Path(),
|
|
|
|
)
|
|
|
|
}()
|
|
|
|
|
|
|
|
handle(c)
|
|
|
|
}
|
2017-02-02 12:53:36 +00:00
|
|
|
}
|