2020-04-10 08:27:43 +02:00
|
|
|
package serv
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
|
2020-04-25 02:45:03 +02:00
|
|
|
"github.com/dosco/super-graph/core"
|
2020-04-16 06:26:32 +02:00
|
|
|
"github.com/dosco/super-graph/internal/serv/internal/auth"
|
2020-04-10 08:27:43 +02:00
|
|
|
"github.com/rs/cors"
|
2020-05-24 08:24:24 +02:00
|
|
|
"go.opencensus.io/plugin/ochttp"
|
|
|
|
"go.opencensus.io/trace"
|
2020-04-10 08:27:43 +02:00
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
maxReadBytes = 100000 // 100Kb
|
|
|
|
introspectionQuery = "IntrospectionQuery"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
errUnauthorized = errors.New("not authorized")
|
|
|
|
)
|
|
|
|
|
|
|
|
type gqlReq struct {
|
|
|
|
OpName string `json:"operationName"`
|
|
|
|
Query string `json:"query"`
|
|
|
|
Vars json.RawMessage `json:"variables"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type errorResp struct {
|
2020-04-23 02:51:14 +02:00
|
|
|
Error string `json:"error"`
|
2020-04-10 08:27:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func apiV1Handler() http.Handler {
|
|
|
|
h, err := auth.WithAuth(http.HandlerFunc(apiV1), &conf.Auth)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("ERR %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(conf.AllowedOrigins) != 0 {
|
|
|
|
c := cors.New(cors.Options{
|
|
|
|
AllowedOrigins: conf.AllowedOrigins,
|
|
|
|
AllowCredentials: true,
|
|
|
|
Debug: conf.DebugCORS,
|
|
|
|
})
|
2020-05-24 08:24:24 +02:00
|
|
|
return c.Handler(h)
|
2020-04-10 08:27:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
|
|
|
func apiV1(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ct := r.Context()
|
2020-05-12 03:09:12 +02:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2020-04-10 08:27:43 +02:00
|
|
|
|
|
|
|
//nolint: errcheck
|
|
|
|
if conf.AuthFailBlock && !auth.IsAuth(ct) {
|
2020-04-23 02:51:14 +02:00
|
|
|
renderErr(w, errUnauthorized)
|
2020-04-10 08:27:43 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := ioutil.ReadAll(io.LimitReader(r.Body, maxReadBytes))
|
|
|
|
if err != nil {
|
2020-04-23 02:51:14 +02:00
|
|
|
renderErr(w, err)
|
2020-04-10 08:27:43 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
|
|
|
req := gqlReq{}
|
|
|
|
|
|
|
|
err = json.Unmarshal(b, &req)
|
|
|
|
if err != nil {
|
2020-04-23 02:51:14 +02:00
|
|
|
renderErr(w, err)
|
2020-04-10 08:27:43 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-04-20 08:06:58 +02:00
|
|
|
doLog := true
|
2020-04-10 08:27:43 +02:00
|
|
|
res, err := sg.GraphQL(ct, req.Query, req.Vars)
|
|
|
|
|
2020-05-24 08:24:24 +02:00
|
|
|
if conf.telemetryEnabled() {
|
|
|
|
span := trace.FromContext(ct)
|
|
|
|
|
|
|
|
span.AddAttributes(
|
|
|
|
trace.StringAttribute("operation", res.OperationName()),
|
|
|
|
trace.StringAttribute("query_name", res.QueryName()),
|
|
|
|
trace.StringAttribute("role", res.Role()),
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
span.AddAttributes(trace.StringAttribute("error", err.Error()))
|
|
|
|
}
|
|
|
|
|
|
|
|
ochttp.SetRoute(ct, apiRoute)
|
|
|
|
}
|
|
|
|
|
2020-05-01 08:20:13 +02:00
|
|
|
if !conf.Production && res.QueryName() == introspectionQuery {
|
2020-04-20 08:06:58 +02:00
|
|
|
doLog = false
|
|
|
|
}
|
|
|
|
|
|
|
|
if doLog && logLevel >= LogLevelDebug {
|
2020-04-19 08:50:09 +02:00
|
|
|
log.Printf("DBG query %s: %s", res.QueryName(), res.SQL())
|
2020-04-10 08:27:43 +02:00
|
|
|
}
|
|
|
|
|
2020-04-25 02:45:03 +02:00
|
|
|
if err == nil {
|
2020-06-15 16:16:47 +02:00
|
|
|
if conf.CacheControl != "" && res.Operation() == core.OpQuery {
|
2020-04-25 02:45:03 +02:00
|
|
|
w.Header().Set("Cache-Control", conf.CacheControl)
|
|
|
|
}
|
2020-05-01 08:20:13 +02:00
|
|
|
//nolint: errcheck
|
2020-04-23 02:51:14 +02:00
|
|
|
json.NewEncoder(w).Encode(res)
|
2020-04-25 02:45:03 +02:00
|
|
|
|
2020-05-04 02:52:26 +02:00
|
|
|
if doLog && logLevel >= LogLevelInfo {
|
|
|
|
zlog.Info("success",
|
|
|
|
zap.String("op", res.OperationName()),
|
|
|
|
zap.String("name", res.QueryName()),
|
|
|
|
zap.String("role", res.Role()),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-04-25 02:45:03 +02:00
|
|
|
} else {
|
|
|
|
renderErr(w, err)
|
2020-04-10 08:27:43 +02:00
|
|
|
|
2020-05-04 02:52:26 +02:00
|
|
|
if doLog && logLevel >= LogLevelInfo {
|
|
|
|
zlog.Error("error",
|
|
|
|
zap.String("op", res.OperationName()),
|
|
|
|
zap.String("name", res.QueryName()),
|
|
|
|
zap.String("role", res.Role()),
|
|
|
|
zap.Error(err),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-04-10 08:27:43 +02:00
|
|
|
}
|
2020-05-04 02:52:26 +02:00
|
|
|
|
2020-04-10 08:27:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//nolint: errcheck
|
2020-04-23 02:51:14 +02:00
|
|
|
func renderErr(w http.ResponseWriter, err error) {
|
2020-04-10 08:27:43 +02:00
|
|
|
if err == errUnauthorized {
|
|
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
|
|
}
|
|
|
|
|
2020-04-23 02:51:14 +02:00
|
|
|
json.NewEncoder(w).Encode(errorResp{err.Error()})
|
2020-04-10 08:27:43 +02:00
|
|
|
}
|