custom integration question
opened this issue · comments
Hi guys,
Hope you are all well !
I would like to use makeless to administrate users and token for a mini cdn service. The cdn is pretty straight forward as it is using gin + gin-cache contrib.
How can I wrap makeless with the following piece of code ?
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"path"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
"github.com/gin-contrib/cache"
"github.com/gin-contrib/cache/persistence"
)
func main() {
cssRegex, err := regexp.Compile(cssRelativePath)
if err != nil {
log.Fatal(err)
}
router := gin.Default()
store := persistence.NewInMemoryStore(time.Second)
// CORS for https://foo.com and https://github.com origins, allowing:
// - PUT and PATCH methods
// - Origin header
// - Credentials share
// - Preflight requests cached for 12 hours
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET"},
AllowHeaders: []string{"Origin"},
ExposeHeaders: []string{"Content-Length"},
MaxAge: 24 * time.Hour,
})) // Access-Control-Allow-Origin
router.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedExtensions([]string{".eot", ".woff", ".ttf"})))
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
})
// Cached Page
router.GET("/cache_ping", cache.CachePage(store, time.Minute, func(c *gin.Context) {
c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
}))
router.GET("/", cache.CachePage(store, time.Hour*24*36, func(c *gin.Context) {
remoteUrl := c.Query("url")
if remoteUrl == "" {
c.Status(http.StatusServiceUnavailable)
return
}
response, err := http.Get(remoteUrl)
if err != nil || response.StatusCode != http.StatusOK {
c.Status(http.StatusServiceUnavailable)
return
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
c.Status(http.StatusServiceUnavailable)
return
}
// https://github.com/gin-gonic/gin/issues/1222
// c.Header("Cache-Control", "public, max-age=31536000")
now := time.Now()
cacheExpire := now.AddDate(1, 0, 0).Format(http.TimeFormat)
c.Header("Expires", cacheExpire)
c.Header("Cache-Control", "public, max-age=2592000")
c.Header("ETag", getEtag(body))
contentType := response.Header.Get("Content-Type")
c.Data(http.StatusOK, contentType, body)
}))
router.Run(":8080")
}
func getEtag(content []byte) string {
hasher := md5.New()
hasher.Write(content)
return hex.EncodeToString(hasher.Sum(nil))
}
The goal is to get/check a token per asset url and check the domain in order to authorize the caching.
Thanks for any insights or inputs on that.
Cheers,
Luc Michalski
Hey @lucmichalski,
thank you for your question - thats pretty easy to do.
Will get back to this at monday (UTC) 👍
thanks :-)
Hey @lucmichalski,
everything is built up on interfaces to customize the most packages if needed. Mostly there is a basic package and some advanced packages like authenticator and authenticator-in-memory (to use the whole system in microservices for example), mailer and mailer-mailgun or event and event-redis which give you some kind of informations how to customize the packages
to customize the whole http part there is one simple method: makeless.SetRoute - with this method you can replace or create any existing http route
there is also a makeless.SetMail method - with this you can replace or create any existing mail template
There are some examples of that from a side project:
package main
import (
"fmt"
"os"
"strings"
"sync"
"time"
"gorm.io/driver/mysql"
"github.com/makeless/makeless-go"
"github.com/makeless/makeless-go-event-redis"
"github.com/makeless/makeless-go-mailer-mailgun"
"github.com/makeless/makeless-go/authenticator/basic"
"github.com/makeless/makeless-go/config/basic"
"github.com/makeless/makeless-go/database/basic"
"github.com/makeless/makeless-go/event/basic"
"github.com/makeless/makeless-go/http"
"github.com/makeless/makeless-go/http/basic"
"github.com/makeless/makeless-go/logger/basic"
"github.com/makeless/makeless-go/mailer"
"github.com/makeless/makeless-go/security/basic"
"github.com/makeless/makeless-go/tls/basic"
bpcache "github.com/bahn-preisalarm/api/cache"
)
func main() {
// logger
logger := new(makeless_go_logger_basic.Logger)
// mailer
mailer := &makeless_go_mailer_mailgun.Mailer{
Handlers: make(map[string]func(data map[string]interface{}) (makeless_go_mailer.Mail, error)),
ApiBase: os.Getenv("MAILGUN_API_BASE"),
Domain: os.Getenv("MAILGUN_DOMAIN"),
ApiKey: os.Getenv("MAILGUN_API_KEY"),
RWMutex: new(sync.RWMutex),
}
// config
config := &makeless_go_config_basic.Config{
RWMutex: new(sync.RWMutex),
}
// database
database := &makeless_go_database_basic.Database{
Host: os.Getenv("DB_HOST"),
Database: os.Getenv("DB_NAME"),
Port: os.Getenv("DB_PORT"),
Username: os.Getenv("DB_USER"),
Password: os.Getenv("DB_PASS"),
RWMutex: new(sync.RWMutex),
}
// security
security := &makeless_go_security_basic.Security{
Database: database,
RWMutex: new(sync.RWMutex),
}
// bp cache
bpCache := &bpcache.Cache{
Addr: fmt.Sprintf("%s:%s", os.Getenv("REDIS_ADDR"), os.Getenv("REDIS_PORT")),
Password: os.Getenv("REDIS_PASSWORD"),
Db: 0,
RWMutex: new(sync.RWMutex),
}
if err := bpCache.Connect(); err != nil {
logger.Fatal(err)
}
// event hub
hub := &makeless_go_event_basic.Hub{
List: new(sync.Map),
RWMutex: new(sync.RWMutex),
}
// event
event := &makeless_go_event_redis.Event{
Client: bpCache.GetClient(),
BaseEvent: &makeless_go_event_basic.Event{
Hub: hub,
Error: make(chan error),
RWMutex: new(sync.RWMutex),
},
RWMutex: new(sync.RWMutex),
}
// jwt authenticator
authenticator := &makeless_go_authenticator_basic.Authenticator{
Security: security,
Realm: "auth",
Key: os.Getenv("JWT_KEY"),
Timeout: time.Hour,
MaxRefresh: time.Hour,
IdentityKey: "id",
RWMutex: new(sync.RWMutex),
}
// tls
tls := &makeless_go_tls_basic.Tls{
CertPath: os.Getenv("CERT_PATH"),
KeyPath: os.Getenv("KEY_PATH"),
RWMutex: new(sync.RWMutex),
}
// router
router := &makeless_go_http_basic.Router{
RWMutex: new(sync.RWMutex),
}
// http
http := &makeless_go_http_basic.Http{
Router: router,
Handlers: make(map[string]func(http makeless_go_http.Http) error),
Logger: logger,
Event: event,
Authenticator: authenticator,
Security: security,
Database: database,
Mailer: mailer,
Tls: tls,
Origins: strings.Split(os.Getenv("ORIGINS"), ","),
Headers: []string{},
Port: os.Getenv("API_PORT"),
Mode: os.Getenv("API_MODE"),
RWMutex: new(sync.RWMutex),
}
// makeless
makeless := &makeless_go.Makeless{
Config: config,
Logger: logger,
Database: database,
Mailer: mailer,
Http: http,
RWMutex: new(sync.RWMutex),
}
if err := makeless.Init(mysql.Open(database.GetConnectionString()), os.Getenv("MAKELESS_CONFIG")); err != nil {
makeless.GetLogger().Fatal(err)
}
bp := &BP{
Makeless: makeless,
Cache: bpCache,
Mailer: mailer,
RWMutex: new(sync.RWMutex),
}
if err := bp.Init(); err != nil {
logger.Fatal(err)
}
// run
if err := makeless.Run(); err != nil {
makeless.GetLogger().Fatal(err)
}
}
func (bp *BP) testHandler(http makeless_go_http.Http) error {
http.GetRouter().GetEngine().GET(
"/api/test",
http.GetAuthenticator().GetMiddleware().MiddlewareFunc(),
http.EmailVerificationMiddleware(bp.GetMakeless().GetConfig().GetConfiguration().GetEmailVerification()),
bp.GetHttp().AnotherMiddlewareExample(),
func(c *gin.Context) {
http.Response(nil, "test")
},
)
return nil
}
// use it like this
makeless.SetRoute("testHandler", bp.testHandler)
Hope this can help you somehow
Hi @loeffel-io ,
Thanks for this ! I am a little bit confused by all the package loaded,
can you provide a more related example or a full snippet ? plz
Cheers,
Luc Michalski
Hey @lucmichalski,
dont have that time actually but at the end its all about creating a struct for your acme like CDN
.
Into this you put all of your acme related components (your store
for example) and the final makeless
struct
Then you just build up your handlers like: (pseudo code)
func (cdn *CDN) rootHandler(http makeless_go_http.Http) error {
http.GetRouter().GetEngine().GET(
"/",
cdn.TokenMiddleware(), // your custom middleware to verify the token
http.EmailVerificationMiddleware(bp.GetMakeless().GetConfig().GetConfiguration().GetEmailVerification()), // use this middleware if you want to allow only email verified users or just delete it
gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedExtensions([]string{".eot", ".woff", ".ttf"})), // use it here or just do it globally (think its better to use this only for selected routes
cdn.cache.CachePage(store, time.Hour*24*36, func(c *gin.Context) {
remoteUrl := c.Query("url")
if remoteUrl == "" {
c.Status(http.StatusServiceUnavailable)
return
}
response, err := http.Get(remoteUrl)
if err != nil || response.StatusCode != http.StatusOK {
c.Status(http.StatusServiceUnavailable)
return
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
c.Status(http.StatusServiceUnavailable)
return
}
// https://github.com/gin-gonic/gin/issues/1222
// c.Header("Cache-Control", "public, max-age=31536000")
now := time.Now()
cacheExpire := now.AddDate(1, 0, 0).Format(http.TimeFormat)
c.Header("Expires", cacheExpire)
c.Header("Cache-Control", "public, max-age=2592000")
c.Header("ETag", getEtag(body))
contentType := response.Header.Get("Content-Type")
c.Data(http.StatusOK, contentType, body)
})
return nil
}
Can i close this @lucmichalski?
Hi @loeffel-io,
Hope you are all well !
Sorry for the delay but I was busy with another project so now I can focus on makeless again. ^^
I tried to make an executable from the information above but I have an error with a dependency "bpcache" "github.com/bahn-preisalarm/api/cache".
And i saw that you are member of the organisation https://github.com/bahn-preisalarm/, so maybe it was moved.
Is there any way to sort it out ? Do I need to use redis ?
Here is my main.go file:
package main
import (
"fmt"
"os"
"strings"
"sync"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"github.com/makeless/makeless-go"
"github.com/makeless/makeless-go-event-redis"
"github.com/makeless/makeless-go-mailer-mailgun"
"github.com/makeless/makeless-go/authenticator/basic"
"github.com/makeless/makeless-go/config/basic"
"github.com/makeless/makeless-go/database/basic"
"github.com/makeless/makeless-go/event/basic"
"github.com/makeless/makeless-go/http"
"github.com/makeless/makeless-go/http/basic"
"github.com/makeless/makeless-go/logger/basic"
"github.com/makeless/makeless-go/mailer"
"github.com/makeless/makeless-go/security/basic"
"github.com/makeless/makeless-go/tls/basic"
bpcache "github.com/bahn-preisalarm/api/cache"
)
func main() {
// logger
logger := new(makeless_go_logger_basic.Logger)
// mailer
mailer := &makeless_go_mailer_mailgun.Mailer{
Handlers: make(map[string]func(data map[string]interface{}) (makeless_go_mailer.Mail, error)),
ApiBase: os.Getenv("MAILGUN_API_BASE"),
Domain: os.Getenv("MAILGUN_DOMAIN"),
ApiKey: os.Getenv("MAILGUN_API_KEY"),
RWMutex: new(sync.RWMutex),
}
// config
config := &makeless_go_config_basic.Config{
RWMutex: new(sync.RWMutex),
}
// database
database := &makeless_go_database_basic.Database{
Host: os.Getenv("DB_HOST"),
Database: os.Getenv("DB_NAME"),
Port: os.Getenv("DB_PORT"),
Username: os.Getenv("DB_USER"),
Password: os.Getenv("DB_PASS"),
RWMutex: new(sync.RWMutex),
}
// security
security := &makeless_go_security_basic.Security{
Database: database,
RWMutex: new(sync.RWMutex),
}
// bp cache
bpCache := &bpcache.Cache{
Addr: fmt.Sprintf("%s:%s", os.Getenv("REDIS_ADDR"), os.Getenv("REDIS_PORT")),
Password: os.Getenv("REDIS_PASSWORD"),
Db: 0,
RWMutex: new(sync.RWMutex),
}
if err := bpCache.Connect(); err != nil {
logger.Fatal(err)
}
// event hub
hub := &makeless_go_event_basic.Hub{
List: new(sync.Map),
RWMutex: new(sync.RWMutex),
}
// event
event := &makeless_go_event_redis.Event{
Client: bpCache.GetClient(),
BaseEvent: &makeless_go_event_basic.Event{
Hub: hub,
Error: make(chan error),
RWMutex: new(sync.RWMutex),
},
RWMutex: new(sync.RWMutex),
}
// jwt authenticator
authenticator := &makeless_go_authenticator_basic.Authenticator{
Security: security,
Realm: "auth",
Key: os.Getenv("JWT_KEY"),
Timeout: time.Hour,
MaxRefresh: time.Hour,
IdentityKey: "id",
RWMutex: new(sync.RWMutex),
}
// tls
tls := &makeless_go_tls_basic.Tls{
CertPath: os.Getenv("CERT_PATH"),
KeyPath: os.Getenv("KEY_PATH"),
RWMutex: new(sync.RWMutex),
}
// router
router := &makeless_go_http_basic.Router{
RWMutex: new(sync.RWMutex),
}
// http
http := &makeless_go_http_basic.Http{
Router: router,
Handlers: make(map[string]func(http makeless_go_http.Http) error),
Logger: logger,
Event: event,
Authenticator: authenticator,
Security: security,
Database: database,
Mailer: mailer,
Tls: tls,
Origins: strings.Split(os.Getenv("ORIGINS"), ","),
Headers: []string{},
Port: os.Getenv("API_PORT"),
Mode: os.Getenv("API_MODE"),
RWMutex: new(sync.RWMutex),
}
// makeless
makeless := &makeless_go.Makeless{
Config: config,
Logger: logger,
Database: database,
Mailer: mailer,
Http: http,
RWMutex: new(sync.RWMutex),
}
if err := makeless.Init(mysql.Open(database.GetConnectionString()), os.Getenv("MAKELESS_CONFIG")); err != nil {
makeless.GetLogger().Fatal(err)
}
cdn := &CDN{
Makeless: makeless,
Cache: bpCache,
Mailer: mailer,
RWMutex: new(sync.RWMutex),
}
if err := cdn.Init(); err != nil {
logger.Fatal(err)
}
makeless.SetRoute("/", cdn.rootHandler)
// run
if err := makeless.Run(); err != nil {
makeless.GetLogger().Fatal(err)
}
}
func (cdn *CDN) rootHandler(http makeless_go_http.Http) error {
http.GetRouter().GetEngine().GET(
"/",
cdn.TokenMiddleware(), // your custom middleware to verify the token
http.EmailVerificationMiddleware(bp.GetMakeless().GetConfig().GetConfiguration().GetEmailVerification()), // use this middleware if you want to allow only email verified users or just delete it
gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedExtensions([]string{".eot", ".woff", ".ttf"})), // use it here or just do it globally (think its better to use this only for selected routes
cdn.cache.CachePage(store, time.Hour*24*36, func(c *gin.Context) {
remoteUrl := c.Query("url")
if remoteUrl == "" {
c.Status(http.StatusServiceUnavailable)
return
}
response, err := http.Get(remoteUrl)
if err != nil || response.StatusCode != http.StatusOK {
c.Status(http.StatusServiceUnavailable)
return
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
c.Status(http.StatusServiceUnavailable)
return
}
// https://github.com/gin-gonic/gin/issues/1222
// c.Header("Cache-Control", "public, max-age=31536000")
now := time.Now()
cacheExpire := now.AddDate(1, 0, 0).Format(http.TimeFormat)
c.Header("Expires", cacheExpire)
c.Header("Cache-Control", "public, max-age=2592000")
c.Header("ETag", getEtag(body))
contentType := response.Header.Get("Content-Type")
c.Data(http.StatusOK, contentType, body)
}),
)
return nil
}
Do you mind to review it a bit ?
Lot of thanks for any minutes and insights you can provide on that. :-)
Cheers,
Luc Michalski
Hey @lucmichalski,
just remove the cache bpCache
and replace the makeless event redis with the basic event component like in the makeless demo: https://github.com/makeless/makeless-demo/blob/master/backend/main.go#L78
Just to let you know that I used qor admin, and wrote a simple RBAC, as I could not make it ork, sadly, with makeless