diff --git a/app.yaml b/app.yaml deleted file mode 100644 index 1e30fe4..0000000 --- a/app.yaml +++ /dev/null @@ -1,5 +0,0 @@ -runtime: custom -env: flex - -includes: - - prod-secrets.yaml diff --git a/cmd/merp/main.go b/cmd/merp/main.go index ce71ced..4fced82 100644 --- a/cmd/merp/main.go +++ b/cmd/merp/main.go @@ -1,13 +1,13 @@ -//3456789112345676892123456789312345678941234567895123456789612345678971234567898 package main -import "time" import "os" +import "sync" +import "time" import "github.com/rs/zerolog" import "github.com/rs/zerolog/log" -import "golang.org/x/crypto/ssh/terminal" import "github.com/sneak/merp" +import "golang.org/x/crypto/ssh/terminal" //revive:disable var Version string @@ -21,7 +21,16 @@ var Appname string func main() { initLogging() identify() - merp.ServeForever() + var wg sync.WaitGroup + + wg.Add(1) + go func() { + ms := merp.NewMerpServer() + ms.ServeForever() + wg.Done() + }() + + wg.Wait() } func identify() { diff --git a/models/db.go b/db.go similarity index 67% rename from models/db.go rename to db.go index 723adcf..8d958ed 100644 --- a/models/db.go +++ b/db.go @@ -1,62 +1,48 @@ -package models +package merp import "os" import "time" import "github.com/astaxie/beego/orm" +import "github.com/sneak/merp/models" import "github.com/rs/zerolog/log" import _ "github.com/lib/pq" //revive:disable-line -var ormObject orm.Ormer - -func initialize() { +func GetDB() orm.Ormer { if os.Getenv("DEBUG") != "" { orm.Debug = true } - connectToDb() - syncDB() + o := connectDB() + syncDB(o) + + return o } // ConnectToDb - Initializes the ORM and Connection to the postgres DB -func connectToDb() { - +func connectDB() orm.Ormer { orm.DefaultTimeLoc = time.UTC - dbURL := os.Getenv("POSTGRES_DB_URL") - orm.RegisterDriver("postgres", orm.DRPostgres) orm.RegisterDataBase("default", "postgres", dbURL) orm.SetMaxIdleConns("default", 1) orm.SetMaxOpenConns("default", 5) - - orm.RegisterModel(new(Merp)) - ormObject = orm.NewOrm() - ormObject.Using("default") - + orm.RegisterModel(new(models.Merp)) + o := orm.NewOrm() + o.Using("default") + return o } // SyncDB() is responsible for creating the schema in the database -func syncDB() { +func syncDB(o orm.Ormer) { // Database alias. name := "default" - // Drop table and re-create. force := false - // Print log. verbose := true - // Error. err := orm.RunSyncdb(name, force, verbose) if err != nil { log.Fatal().Msg(err.Error()) } } - -// GetOrmObject - Getter function for the ORM object with which we can query the database -func GetOrmObject() orm.Ormer { - if ormObject == nil { - initialize() - } - return ormObject -} diff --git a/merp.go b/merp.go index 5b619c0..a514319 100644 --- a/merp.go +++ b/merp.go @@ -2,7 +2,6 @@ package merp import "encoding/json" import "net/http" -import "regexp" import "time" import "github.com/astaxie/beego/orm" @@ -11,34 +10,21 @@ import "github.com/google/uuid" import "github.com/rs/zerolog/log" import "github.com/sneak/merp/models" -func thingRegex() *regexp.Regexp { - ThingRegex, e := regexp.Compile(`^[a-zA-Z0-9\_\-]+$`) - if e != nil { - panic(e) - } - return ThingRegex -} - -func decodeJSON(in []byte) interface{} { +func decodeJSON(in []byte) (interface{}, error) { var out interface{} err := json.Unmarshal(in, &out) if err != nil { log.Error().Msg("error decoding json") - return nil + return nil, err } - return out + return out, nil } -func getLatestMerps() gin.HandlerFunc { - ThingRegex := thingRegex() - o := models.GetOrmObject() - - h := func(c *gin.Context) { - +func (ms *MerpServer) getLatestMerps() gin.HandlerFunc { + return func(c *gin.Context) { thing := c.Param("thing") - if thing != "" { - if ThingRegex.MatchString(thing) == false { + if ms.thingRegex.MatchString(thing) == false { c.JSON(http.StatusPreconditionFailed, gin.H{ "this": "failed", "status": http.StatusPreconditionFailed, @@ -50,9 +36,9 @@ func getLatestMerps() gin.HandlerFunc { var qs orm.QuerySeter if thing == "" { - qs = o.QueryTable("merp").OrderBy("-created").Limit(50) + qs = ms.db.QueryTable("merp").OrderBy("-created").Limit(50) } else { - qs = o.QueryTable("merp").Filter("thing", thing).OrderBy("-created").Limit(50) + qs = ms.db.QueryTable("merp").Filter("thing", thing).OrderBy("-created").Limit(50) } var merps []*models.Merp @@ -62,8 +48,8 @@ func getLatestMerps() gin.HandlerFunc { for _, merp := range merps { outelem := make(map[string]interface{}) outelem["thing"] = merp.Thing - outjs := decodeJSON([]byte(merp.Content)) - if outjs == nil { + outjs, err := decodeJSON([]byte(merp.Content)) + if err != nil { outelem["content"] = gin.H{} } else { outelem["content"] = outjs @@ -79,40 +65,13 @@ func getLatestMerps() gin.HandlerFunc { "with": output, }) } - return h } -func getLatestMerp() gin.HandlerFunc { - ThingRegex := thingRegex() - - h := func(c *gin.Context) { - thing := c.Param("thing") - if ThingRegex.MatchString(thing) == false { - c.JSON(http.StatusPreconditionFailed, gin.H{ - "this": "failed", - "status": http.StatusPreconditionFailed, - "because": "invalid thing format, try [a-zA-Z0-9-_]", - }) - return - } - c.JSON(http.StatusOK, gin.H{ - "this": "succeeded", - }) - } - return h -} - -func handleNewMerp() gin.HandlerFunc { - // server startup time - - ThingRegex := thingRegex() - // establish db connection *first*, before requests - orm := models.GetOrmObject() - - h := func(c *gin.Context) { +func (ms *MerpServer) handleNewMerp() gin.HandlerFunc { + return func(c *gin.Context) { // request time thing := c.Param("thing") - if ThingRegex.MatchString(thing) == false { + if ms.thingRegex.MatchString(thing) == false { c.JSON(http.StatusPreconditionFailed, gin.H{ "this": "failed", "status": http.StatusPreconditionFailed, @@ -159,7 +118,7 @@ func handleNewMerp() gin.HandlerFunc { UUID: u.String(), } - _, err := orm.Insert(&merp) + _, err := ms.db.Insert(&merp) if err != nil { c.JSON( @@ -185,6 +144,4 @@ func handleNewMerp() gin.HandlerFunc { }, }) } - - return h } diff --git a/server.go b/server.go index 6819649..1267836 100644 --- a/server.go +++ b/server.go @@ -1,27 +1,78 @@ package merp -//3456789112345676892123456789312345678941234567895123456789612345678971234567898 - import "encoding/json" import "fmt" import "net/http" import "os" +import "regexp" +import "strconv" import "time" -//import "github.com/rs/zerolog/log" import "github.com/didip/tollbooth" import "github.com/didip/tollbooth_gin" import "github.com/gin-gonic/gin" import "github.com/dn365/gin-zerolog" import "github.com/thoas/stats" -// ServeForever causes merp to serve http forever -func ServeForever() { - s := getServer() - s.ListenAndServe() +import "github.com/astaxie/beego/orm" +import _ "github.com/lib/pq" //revive:disable-line + +type MerpServer struct { + db orm.Ormer + debug bool + gin *gin.Engine + port uint + server *http.Server + stats *stats.Stats + thingRegex *regexp.Regexp } -func getHealthCheckHandler() http.HandlerFunc { +func NewMerpServer() *MerpServer { + ms := new(MerpServer) + ms.init() + return ms +} + +func (ms *MerpServer) init() { + + ms.thingRegex = regexp.MustCompile(`^[a-zA-Z0-9\_\-]+$`) + + if os.Getenv("DEBUG") != "" { + ms.debug = true + } + + ms.port = 8080 + var s uint64 + var err error + if os.Getenv("PORT") != "" { + if s, err = strconv.ParseUint(os.Getenv("PORT"), 10, 64); err == nil { + } else { + panic("invalid PORT in environment") + } + ms.port = uint(s) + } + ms.connectDB() + ms.setupRoutes() + + ms.server = &http.Server{ + Addr: fmt.Sprintf(":%s", ms.port), + Handler: ms.gin, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } +} + +func (ms *MerpServer) connectDB() { + ms.db = GetDB() +} + +// ServeForever causes merp to serve http forever +func (ms *MerpServer) ServeForever() { + ms.server.ListenAndServe() +} + +func (ms *MerpServer) HealthCheckHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { result := gin.H{ "status": "ok", @@ -38,24 +89,23 @@ func getHealthCheckHandler() http.HandlerFunc { } } -func getStatsHandler(middleware *stats.Stats) http.HandlerFunc { +func (ms *MerpServer) StatsHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - stats := middleware.Data() + stats := ms.stats.Data() b, _ := json.Marshal(stats) w.Write(b) } } -func getRouter() *gin.Engine { - - if os.Getenv("DEBUG") == "" { +func (ms *MerpServer) setupRoutes() { + if !ms.debug { gin.SetMode(gin.ReleaseMode) } limiter := tollbooth.NewLimiter(5, nil) - statsMiddleware := stats.New() + ms.stats = stats.New() // empty router r := gin.New() @@ -67,41 +117,19 @@ func getRouter() *gin.Engine { r.Use(ginzerolog.Logger("gin")) r.Use(func(c *gin.Context) { - beginning, recorder := statsMiddleware.Begin(c.Writer) + beginning, recorder := ms.stats.Begin(c.Writer) c.Next() - statsMiddleware.End(beginning, stats.WithRecorder(recorder)) + ms.stats.End(beginning, stats.WithRecorder(recorder)) }) - r.GET("/.well-known/healthcheck.json", gin.WrapF(getHealthCheckHandler())) - r.GET("/admin/healthcheck.json", gin.WrapF(getHealthCheckHandler())) - r.GET("/admin/stats.json", gin.WrapF(getStatsHandler(statsMiddleware))) - r.GET("/admin/other.json", gin.WrapF(getStatsHandler(statsMiddleware))) + r.GET("/.well-known/healthcheck.json", gin.WrapF(ms.HealthCheckHandler())) + r.GET("/admin/healthcheck.json", gin.WrapF(ms.HealthCheckHandler())) + r.GET("/admin/stats.json", gin.WrapF(ms.StatsHandler())) + r.GET("/admin/other.json", gin.WrapF(ms.StatsHandler())) + r.GET("/merp/for/:thing", tollbooth_gin.LimitHandler(limiter), ms.handleNewMerp()) + r.GET("/get/latest/merp/for/:thing", tollbooth_gin.LimitHandler(limiter), ms.getLatestMerps()) + r.GET("/get/latest/merps", tollbooth_gin.LimitHandler(limiter), ms.getLatestMerps()) + r.GET("/get/merps/for/:thing", tollbooth_gin.LimitHandler(limiter), ms.getLatestMerps()) - // call it, it returns the appropriate handler function - // so we can execute some code at startup time - // and not just request time - r.GET("/merp/for/:thing", tollbooth_gin.LimitHandler(limiter), handleNewMerp()) - r.GET("/get/latest/merp/for/:thing", tollbooth_gin.LimitHandler(limiter), getLatestMerp()) - r.GET("/get/latest/merps", tollbooth_gin.LimitHandler(limiter), getLatestMerps()) - r.GET("/get/merps/for/:thing", tollbooth_gin.LimitHandler(limiter), getLatestMerps()) - - return r -} - -func getServer() *http.Server { - r := getRouter() - - port := "8080" - if os.Getenv("PORT") != "" { - port = os.Getenv("PORT") - } - - s := &http.Server{ - Addr: fmt.Sprintf(":%s", port), - Handler: r, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - } - return s + ms.gin = r }