Skip to content

Instantly share code, notes, and snippets.

@rstrlcpy
Last active July 28, 2020 07:16
Show Gist options
  • Save rstrlcpy/82ca2c01826fb2f210b10ed50fd91d51 to your computer and use it in GitHub Desktop.
Save rstrlcpy/82ca2c01826fb2f210b10ed50fd91d51 to your computer and use it in GitHub Desktop.
go gin-gonic help-wrapper
package api
import (
"context"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
type ApiHandler interface {
RegisterMethods()
}
type ServiceApi struct {
httpSrv *http.Server
*gin.Engine
}
func NewApi(developMode bool) *ServiceApi {
if !developMode {
gin.SetMode(gin.ReleaseMode)
}
gin.DisableConsoleColor()
gin.DefaultWriter = log.StandardLogger().Writer()
serviceApi := &ServiceApi{
Engine: gin.New(),
}
return serviceApi
}
func (serviceApi *ServiceApi) Start(listeningPort int) {
serviceApi.httpSrv = &http.Server{
Addr: fmt.Sprintf(":%d", listeningPort),
Handler: serviceApi.Engine,
ReadHeaderTimeout: 10 * time.Second,
WriteTimeout: 60 * time.Second,
MaxHeaderBytes: 100 * 1024,
}
go func() {
if err := serviceApi.httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.WithFields(log.Fields{
"error": err.Error(),
}).Fatal("Listen error")
}
}()
}
func (serviceApi *ServiceApi) Shutdown(ctx context.Context) error {
return serviceApi.httpSrv.Shutdown(ctx)
}
func (serviceApi *ServiceApi) BuildApi(apiHandler ApiHandler) {
serviceApi.Use(apiLogger())
serviceApi.Use(gin.Recovery())
serviceApi.NoRoute(NotFoundHandler)
apiHandler.RegisterMethods()
}
func apiCtxGetError(ctx *gin.Context) error {
err, exist := ctx.Get("error")
if exist {
return err.(error)
}
return nil
}
/* Used to log all API-calls */
func apiLogger() gin.HandlerFunc {
return func(ctx *gin.Context) {
path := ctx.Request.URL.Path
query := ctx.Request.URL.RawQuery
method := ctx.Request.Method
log.WithFields(log.Fields{
"method": method,
"path": path,
"query": query,
}).Debug("API Call - START")
ctx.Next()
clientIP := ctx.ClientIP()
statusCode := ctx.Writer.Status()
err := apiCtxGetError(ctx)
logFields := log.Fields{
"method": method,
"path": path,
"query": query,
"clientAddress": clientIP,
"statusCode": statusCode,
}
if err != nil {
type causer interface {
Cause() error
}
c, ok := err.(causer)
if ok {
logFields["error"] = fmt.Sprintf("%s", c.Cause())
} else {
logFields["error"] = err.Error()
}
}
log.WithFields(logFields).Debug("API Call - END")
}
}
func NotFoundHandler(ctx *gin.Context) {
ctx.JSON(http.StatusNotFound, &gin.H{
"error": "The requested resource does not exist",
})
}
func AbortCtxSetError(ctx *gin.Context, statusCode int, err error) {
ctx.AbortWithStatusJSON(statusCode, gin.H{"error": err.Error()})
ctx.Set("error", err)
}
import (
"net/http"
"os"
"strings"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/thedevsaddam/govalidator"
"project/internal/pkg/api" // the api.go
)
type Service interface {
GetName() string
GetComposeFileName() string
GetDataToken() string
GetValidationOpts(ctx *gin.Context) govalidator.Options
Configure(ctx *gin.Context)
UpdateConfig(ctx *gin.Context)
}
type ServiceApiHandler struct {
*api.ServiceApi
}
func NewServiceApiHandler(developMode bool) *ServiceApiHandler {
service := &ServiceApiHandler{}
service.ServiceApi = api.NewApi(developMode)
service.BuildApi(service)
return service
}
func (service *ServiceApiHandler) RegisterMethods() {
apiV1 := service.Group("/api/v1")
/* XXXX Service API endpoints */
etcd := XXXXSvc{}
registerSubSvcMethods(&xxxx, apiV1.Group("/xxxx"))
}
func registerSubSvcMethods(svc Service, r *gin.RouterGroup) {
r.Use(SvcCheckState(svc, r.BasePath()))
validateGroup := r.Group("")
{
validateGroup.Use(SvcValidateInputData(svc))
validateGroup.POST("", svc.Configure)
validateGroup.PUT("", svc.UpdateConfig)
}
r.GET("", SvcGetStatus(svc))
r.DELETE("", SvcUnconfigure(svc))
r.POST("/start", SvcStart(svc))
r.POST("/stop", SvcStop(svc))
}
func SvcValidateInputData(svc Service) gin.HandlerFunc {
svcDataToken := svc.GetDataToken()
return func(ctx *gin.Context) {
validationOpts := svc.GetValidationOpts(ctx)
validateInputData(ctx, validationOpts, false)
if ctx.IsAborted() {
return
}
ctx.Set(svcDataToken, validationOpts.Data)
}
}
func SvcCheckState(svc Service, basePath string) gin.HandlerFunc {
svcComposeFile := svc.GetComposeFileName()
return func(ctx *gin.Context) {
switch ctx.Request.Method {
case http.MethodPost:
if ctx.FullPath() == basePath {
if checkFileExists(svcComposeFile) {
api.AbortCtxSetError(ctx, http.StatusConflict,
errors.New("Service is already configured"))
}
return
}
fallthrough
case http.MethodPut, http.MethodDelete:
if !checkFileExists(svcComposeFile) {
api.AbortCtxSetError(ctx, http.StatusGone,
errors.New("Service is not configured"))
}
}
}
}
func SvcGetStatus(svc Service) gin.HandlerFunc {
svcName := svc.GetName()
return func(ctx *gin.Context) {
//common functionality that used the above variables
ctx.JSON(http.StatusOK, gin.H{"resp": cinfo})
}
}
func SvcUnconfigure(svc Service) gin.HandlerFunc {
svcName := svc.GetName()
svcComposeFile := svc.GetComposeFileName()
return func(ctx *gin.Context) {
//common functionality that used the above variables
ctx.JSON(http.StatusOK, gin.H{})
}
}
func SvcStart(svc Service) gin.HandlerFunc {
svcName := svc.GetName()
svcComposeFile := svc.GetComposeFileName()
return func(ctx *gin.Context) {
//common functionality that used the above variables
ctx.JSON(http.StatusOK, gin.H{})
}
}
func SvcStop(svc Service) gin.HandlerFunc {
svcName := svc.GetName()
svcComposeFile := svc.GetComposeFileName()
return func(ctx *gin.Context) {
//common functionality that used the above variables
ctx.JSON(http.StatusOK, gin.H{})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment