super-graph/serv/serv.go

310 lines
6.2 KiB
Go
Raw Normal View History

2019-03-24 14:57:29 +01:00
package serv
import (
2019-05-13 01:27:26 +02:00
"context"
2019-03-24 14:57:29 +01:00
"errors"
"flag"
"fmt"
"net/http"
"os"
2019-05-13 01:27:26 +02:00
"os/signal"
2019-03-24 14:57:29 +01:00
"strings"
2019-05-13 01:27:26 +02:00
"time"
2019-03-24 14:57:29 +01:00
"github.com/dosco/super-graph/psql"
"github.com/dosco/super-graph/qcode"
"github.com/go-pg/pg"
2019-03-25 05:43:14 +01:00
"github.com/gobuffalo/flect"
"github.com/rs/zerolog"
2019-03-24 14:57:29 +01:00
"github.com/spf13/viper"
)
//go:generate esc -o static.go -ignore \\.DS_Store -prefix ../web/build -private -pkg serv ../web/build
2019-03-24 14:57:29 +01:00
const (
2019-05-13 01:27:26 +02:00
serverName = "Super Graph"
2019-03-24 14:57:29 +01:00
authFailBlockAlways = iota + 1
authFailBlockPerQuery
authFailBlockNever
)
var (
logger *zerolog.Logger
2019-04-08 08:47:59 +02:00
conf *config
2019-03-24 14:57:29 +01:00
db *pg.DB
qcompile *qcode.Compiler
2019-05-13 01:27:26 +02:00
pcompile *psql.Compiler
2019-03-24 14:57:29 +01:00
authFailBlock int
)
func initLog() *zerolog.Logger {
logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).
2019-06-17 07:58:00 +02:00
With().
Timestamp().
Caller().
Logger()
return &logger
/*
log := logrus.New()
logger.Formatter = new(logrus.TextFormatter)
logger.Formatter.(*logrus.TextFormatter).DisableColors = false
logger.Formatter.(*logrus.TextFormatter).DisableTimestamp = true
logger.Level = logrus.TraceLevel
logger.Out = os.Stdout
*/
2019-03-24 14:57:29 +01:00
}
2019-04-08 08:47:59 +02:00
func initConf() (*config, error) {
vi := viper.New()
2019-03-24 14:57:29 +01:00
2019-04-08 08:47:59 +02:00
path := flag.String("path", "./", "Path to config files")
2019-03-24 14:57:29 +01:00
flag.Parse()
2019-04-08 08:47:59 +02:00
vi.SetEnvPrefix("SG")
vi.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
vi.AutomaticEnv()
2019-03-24 14:57:29 +01:00
2019-04-08 08:47:59 +02:00
vi.AddConfigPath(*path)
2019-04-09 14:43:42 +02:00
vi.AddConfigPath("./config")
2019-04-08 08:47:59 +02:00
vi.SetConfigName(getConfigName())
2019-03-24 14:57:29 +01:00
2019-04-08 08:47:59 +02:00
vi.SetDefault("host_port", "0.0.0.0:8080")
vi.SetDefault("web_ui", false)
vi.SetDefault("enable_tracing", false)
2019-04-11 07:10:51 +02:00
vi.SetDefault("auth_fail_block", "always")
2019-03-24 14:57:29 +01:00
2019-04-08 08:47:59 +02:00
vi.SetDefault("database.type", "postgres")
vi.SetDefault("database.host", "localhost")
vi.SetDefault("database.port", 5432)
vi.SetDefault("database.user", "postgres")
2019-03-24 14:57:29 +01:00
2019-04-08 08:47:59 +02:00
vi.SetDefault("env", "development")
vi.BindEnv("env", "GO_ENV")
2019-07-30 07:38:05 +02:00
vi.BindEnv("HOST", "HOST")
vi.BindEnv("PORT", "PORT")
2019-03-24 14:57:29 +01:00
2019-04-11 07:10:51 +02:00
vi.SetDefault("auth.rails.max_idle", 80)
vi.SetDefault("auth.rails.max_active", 12000)
2019-03-24 14:57:29 +01:00
2019-04-08 08:47:59 +02:00
if err := vi.ReadInConfig(); err != nil {
return nil, err
}
2019-03-24 14:57:29 +01:00
2019-04-08 08:47:59 +02:00
c := &config{}
2019-03-24 14:57:29 +01:00
2019-04-08 08:47:59 +02:00
if err := vi.Unmarshal(c); err != nil {
return nil, fmt.Errorf("unable to decode config, %v", err)
}
2019-04-01 14:55:46 +02:00
2019-04-08 08:47:59 +02:00
for k, v := range c.Inflections {
flect.AddPlural(k, v)
2019-03-24 14:57:29 +01:00
}
2019-05-13 01:27:26 +02:00
if len(c.DB.Tables) == 0 {
c.DB.Tables = c.DB.Fields
}
for i := range c.DB.Tables {
t := c.DB.Tables[i]
t.Name = flect.Pluralize(strings.ToLower(t.Name))
}
2019-04-08 08:47:59 +02:00
authFailBlock = getAuthFailBlock(c)
//fmt.Printf("%#v", c)
2019-03-24 14:57:29 +01:00
2019-04-08 08:47:59 +02:00
return c, nil
}
2019-03-24 14:57:29 +01:00
2019-04-08 08:47:59 +02:00
func initDB(c *config) (*pg.DB, error) {
2019-03-24 14:57:29 +01:00
opt := &pg.Options{
2019-04-11 07:10:51 +02:00
Addr: strings.Join([]string{c.DB.Host, c.DB.Port}, ":"),
User: c.DB.User,
Password: c.DB.Password,
Database: c.DB.DBName,
ApplicationName: c.AppName,
2019-03-24 14:57:29 +01:00
}
2019-04-08 08:47:59 +02:00
if c.DB.PoolSize != 0 {
opt.PoolSize = conf.DB.PoolSize
2019-03-24 14:57:29 +01:00
}
2019-04-08 08:47:59 +02:00
if c.DB.MaxRetries != 0 {
opt.MaxRetries = c.DB.MaxRetries
2019-03-24 14:57:29 +01:00
}
2019-04-11 07:10:51 +02:00
if len(c.DB.Schema) != 0 {
opt.OnConnect = func(conn *pg.Conn) error {
_, err := conn.Exec("set search_path=?", c.DB.Schema)
if err != nil {
return err
}
return nil
}
}
2019-04-08 08:47:59 +02:00
db := pg.Connect(opt)
if db == nil {
return nil, errors.New("failed to connect to postgres db")
2019-03-24 14:57:29 +01:00
}
2019-04-08 08:47:59 +02:00
return db, nil
2019-03-24 14:57:29 +01:00
}
2019-04-08 08:47:59 +02:00
func initCompilers(c *config) (*qcode.Compiler, *psql.Compiler, error) {
schema, err := psql.NewDBSchema(db, c.getAliasMap())
2019-05-13 01:27:26 +02:00
if err != nil {
return nil, nil, err
2019-04-08 08:47:59 +02:00
}
2019-03-24 14:57:29 +01:00
2019-04-08 08:47:59 +02:00
qc, err := qcode.NewCompiler(qcode.Config{
2019-05-13 01:27:26 +02:00
DefaultFilter: c.DB.Defaults.Filter,
FilterMap: c.getFilterMap(),
Blacklist: c.DB.Defaults.Blacklist,
2019-06-02 01:48:42 +02:00
KeepArgs: false,
2019-04-08 08:47:59 +02:00
})
2019-03-24 14:57:29 +01:00
if err != nil {
2019-04-08 08:47:59 +02:00
return nil, nil, err
2019-03-24 14:57:29 +01:00
}
2019-04-08 08:47:59 +02:00
pc := psql.NewCompiler(psql.Config{
Schema: schema,
2019-07-29 07:13:33 +02:00
Vars: c.getVariables(),
2019-04-08 08:47:59 +02:00
})
2019-03-24 14:57:29 +01:00
2019-04-08 08:47:59 +02:00
return qc, pc, nil
2019-03-24 14:57:29 +01:00
}
2019-05-13 01:27:26 +02:00
func Init() {
2019-04-08 08:47:59 +02:00
var err error
logger = initLog()
conf, err = initConf()
if err != nil {
logger.Fatal().Err(err).Msg("failed to read config")
2019-04-08 08:47:59 +02:00
}
2019-06-17 07:58:00 +02:00
logLevel, err := zerolog.ParseLevel(conf.LogLevel)
if err != nil {
logger.Error().Err(err).Msg("error setting log_level")
}
zerolog.SetGlobalLevel(logLevel)
2019-04-08 08:47:59 +02:00
db, err = initDB(conf)
if err != nil {
logger.Fatal().Err(err).Msg("failed to connect to database")
2019-04-08 08:47:59 +02:00
}
qcompile, pcompile, err = initCompilers(conf)
if err != nil {
logger.Fatal().Err(err).Msg("failed to connect to database")
2019-04-08 08:47:59 +02:00
}
2019-03-24 14:57:29 +01:00
if err := initResolvers(); err != nil {
logger.Fatal().Err(err).Msg("failed to initialized resolvers")
}
2019-05-13 01:27:26 +02:00
2019-07-29 07:13:33 +02:00
initAllowList()
initPreparedList()
2019-05-13 01:27:26 +02:00
startHTTP()
}
func startHTTP() {
2019-07-30 07:38:05 +02:00
hp := strings.SplitN(conf.HostPort, ":", 2)
if len(conf.Host) != 0 {
hp[0] = conf.Host
}
if len(conf.Port) != 0 {
hp[1] = conf.Port
}
hostPort := fmt.Sprintf("%s:%s", hp[0], hp[1])
2019-05-13 01:27:26 +02:00
srv := &http.Server{
2019-07-30 07:38:05 +02:00
Addr: hostPort,
2019-05-13 01:27:26 +02:00
Handler: routeHandler(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
idleConnsClosed := make(chan struct{})
go func() {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt)
<-sigint
if err := srv.Shutdown(context.Background()); err != nil {
logger.Error().Err(err).Msg("shutdown signal received")
2019-05-13 01:27:26 +02:00
}
close(idleConnsClosed)
}()
srv.RegisterOnShutdown(func() {
if err := db.Close(); err != nil {
logger.Error().Err(err).Msg("db closed")
2019-05-13 01:27:26 +02:00
}
})
2019-07-30 07:38:05 +02:00
fmt.Printf("%s listening on %s (%s)\n", serverName, hostPort, conf.Env)
2019-05-13 01:27:26 +02:00
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
logger.Error().Err(err).Msg("server closed")
2019-05-13 01:27:26 +02:00
}
<-idleConnsClosed
}
2019-03-24 14:57:29 +01:00
2019-05-13 01:27:26 +02:00
func routeHandler() http.Handler {
mux := http.NewServeMux()
mux.Handle("/api/v1/graphql", withAuth(apiv1Http))
2019-04-08 08:47:59 +02:00
if conf.WebUI {
2019-05-13 01:27:26 +02:00
mux.Handle("/", http.FileServer(_escFS(false)))
2019-03-24 14:57:29 +01:00
}
2019-05-13 01:27:26 +02:00
fn := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", serverName)
mux.ServeHTTP(w, r)
}
2019-04-08 08:47:59 +02:00
2019-05-13 01:27:26 +02:00
return http.HandlerFunc(fn)
2019-04-08 08:47:59 +02:00
}
func getConfigName() string {
ge := strings.ToLower(os.Getenv("GO_ENV"))
switch {
case strings.HasPrefix(ge, "pro"):
return "prod"
case strings.HasPrefix(ge, "sta"):
return "stage"
case strings.HasPrefix(ge, "tes"):
return "test"
}
return "dev"
}
func getAuthFailBlock(c *config) int {
switch c.AuthFailBlock {
case "always":
return authFailBlockAlways
case "per_query", "perquery", "query":
return authFailBlockPerQuery
case "never", "false":
return authFailBlockNever
}
2019-03-24 14:57:29 +01:00
2019-04-08 08:47:59 +02:00
return authFailBlockAlways
2019-03-24 14:57:29 +01:00
}