Golang (iris webframework) share between handlers - go

I am currently using the iris web framework and since questions cannot be asked on the issue tracker and the community chat is dead I am asking this here hoping someone helps me out.
I need to pass data to the c.Render function
I have a handler that checks if the user is logged or not. If its not logged I should add an extra button to the html page
iris.Use(userHandler{})
type userHandler struct{
Allow bool
}
func (u userHandler) Serve(c *iris.Context) {
...
if isLogged {
// When I call from another middleware (c.Next) c.Render it should know that the user is logged in
}
c.Next()
}
So is it possible to add some default data to the c.Render function?

// retrieve local storage or previous handler,
// this is how handlers can share values, with the context's Values().
logged := ctx.Values().Get("logged")
// set template data {{.isLogged}}
ctx.ViewData("isLogged", logged)
// and finally, render the mypage.html
ctx.View("mypage.html")
"logged" can be set to your middleware/any previous handler by:
ctx.Values().Set("logged", false)
All these are described to the examples, you're welcome to explore some of those there: https://github.com/kataras/iris/tree/master/_examples
Happy Coding!

Related

Integrating Sentry and Elastic APM in Buffalo

I am trying to integrate Elastic APM and Sentry into my website using Buffalo. The interesting files are as follows:
handlers/sentryHandler.go
package handlers
import (
sentryhttp "github.com/getsentry/sentry-go/http"
"github.com/gobuffalo/buffalo"
)
func SentryHandler(next buffalo.Handler) buffalo.Handler {
handler := buffalo.WrapBuffaloHandler(next)
sentryHandler := sentryhttp.New(sentryhttp.Options{})
return buffalo.WrapHandler(sentryHandler.Handle(handler))
}
handlers/elasticAPMHandler.go
package handlers
import (
"fmt"
"github.com/gobuffalo/buffalo"
"go.elastic.co/apm/module/apmhttp"
)
func ElasticAPMHandler(next buffalo.Handler) buffalo.Handler {
fmt.Println("AAA")
handler := apmhttp.Wrap(buffalo.WrapBuffaloHandler(next))
return buffalo.WrapHandler(handler)
}
actions/app.go
package actions
import (
"github.com/gobuffalo/buffalo"
"github.com/gobuffalo/envy"
forcessl "github.com/gobuffalo/mw-forcessl"
paramlogger "github.com/gobuffalo/mw-paramlogger"
"github.com/unrolled/secure"
"my_website/handlers"
"my_website/models"
"github.com/gobuffalo/buffalo-pop/pop/popmw"
csrf "github.com/gobuffalo/mw-csrf"
i18n "github.com/gobuffalo/mw-i18n"
"github.com/gobuffalo/packr/v2"
)
func App() *buffalo.App {
if app == nil {
app = buffalo.New(buffalo.Options{
Env: ENV,
SessionName: "_my_website_session",
})
// Automatically redirect to SSL
app.Use(forceSSL())
// Catch errors and send them to Sentry.
app.Use(handlers.SentryHandler)
// Get tracing information and send it to Elastic.
app.Use(handlers.ElasticAPMHandler)
// Other Buffalo middleware stuff goes here...
// Routing stuff goes here...
}
return app
}
The problem I'm running into is if I have the Sentry/APM handlers at the top, then I get errors like application.html: line 24: "showPagePath": unknown identifier. However, if I move it to just before I set up the routes, then I get a no transaction found error. So, I'm guessing that the handler wrappers are dropping the buffalo.Context information. So, what would I need to do to be able to integrate Sentry and Elastic in Buffalo asides from trying to reimplement their wrappers?
So, I'm guessing that the handler wrappers are dropping the buffalo.Context information.
That's correct. The problem is that buffalo.WrapHandler (Source) throws away all of the context other than the underlying http.Request/http.Response:
// WrapHandler wraps a standard http.Handler and transforms it
// into a buffalo.Handler.
func WrapHandler(h http.Handler) Handler {
return func(c Context) error {
h.ServeHTTP(c.Response(), c.Request())
return nil
}
}
So, what would I need to do to be able to integrate Sentry and Elastic in Buffalo asides from trying to reimplement their wrappers?
I can see two options:
Reimplement buffalo.WrapHandler/buffalo.WrapBuffaloHandler to stop throwing away the buffalo.Context. This would involve storing the buffalo.Context in the underlying http.Request's context, and then pulling it out again on the other side instead of creating a whole new context.
Implement Buffalo-specific middleware for Sentry and Elastic APM without using the Wrap* functions.
There's an open issue in the Elastic APM agent for the latter option: elastic/apm#39.

List custom resources from caching client with custom fieldSelector

I'm using the Operator SDK to build a custom Kubernetes operator. I have created a custom resource definition and a controller using the respective Operator SDK commands:
operator-sdk add api --api-version example.com/v1alpha1 --kind=Example
operator-sdk add controller --api-version example.com/v1alpha1 --kind=Example
Within the main reconciliation loop (for the example above, the auto-generated ReconcileExample.Reconcile method) I have some custom business logic that requires me to query the Kubernetes API for other objects of the same kind that have a certain field value. It's occurred to me that I might be able to use the default API client (that is provided by the controller) with a custom field selector:
func (r *ReconcileExample) Reconcile(request reconcile.Request) (reconcile.Result, error) {
ctx := context.TODO()
listOptions := client.ListOptions{
FieldSelector: fields.SelectorFromSet(fields.Set{"spec.someField": "someValue"}),
Namespace: request.Namespace,
}
otherExamples := v1alpha1.ExampleList{}
if err := r.client.List(ctx, &listOptions, &otherExamples); err != nil {
return reconcile.Result{}, err
}
// do stuff...
return reconcile.Result{}, nil
}
When I run the operator and create a new Example resource, the operator fails with the following error message:
{"level":"info","ts":1563388786.825384,"logger":"controller_example","msg":"Reconciling Example","Request.Namespace":"default","Request.Name":"example-test"}
{"level":"error","ts":1563388786.8255732,"logger":"kubebuilder.controller","msg":"Reconciler error","controller":"example-controller","request":"default/example-test","error":"Index with name field:spec.someField does not exist","stacktrace":"..."}
The most important part being
Index with name field:spec.someField does not exist
I've already searched the Operator SDK's documentation on the default API client and learned a bit about the inner workings of the client, but no detailed explanation on this error or how to fix it.
What does this error message mean, and how can I create this missing index to efficiently list objects by this field value?
The default API client that is provided by the controller is a split client -- it serves Get and List requests from a locally-held cache and forwards other methods like Create and Update directly to the Kubernetes API server. This is also explained in the respective documentation:
The SDK will generate code to create a Manager, which holds a Cache and a Client to be used in CRUD operations and communicate with the API server. By default a Controller's Reconciler will be populated with the Manager's Client which is a split-client. [...] A split client reads (Get and List) from the Cache and writes (Create, Update, Delete) to the API server. Reading from the Cache significantly reduces request load on the API server; as long as the Cache is updated by the API server, read operations are eventually consistent.
To query values from the cache using a custom field selector, the cache needs to have a search index for this field. This indexer can be defined right after the cache has been set up.
To register a custom indexer, add the following code into the bootstrapping logic of the operator (in the auto-generated code, this is done directly in main). This needs to be done after the controller manager has been instantiated (manager.New) and also after the custom API types have been added to the runtime.Scheme:
package main
import (
k8sruntime "k8s.io/apimachinery/pkg/runtime"
"example.com/example-operator/pkg/apis/example/v1alpha1"
// ...
)
function main() {
// ...
cache := mgr.GetCache()
indexFunc := func(obj k8sruntime.Object) []string {
return []string{obj.(*v1alpha1.Example).Spec.SomeField}
}
if err := cache.IndexField(&v1alpha1.Example{}, "spec.someField", indexFunc); err != nil {
panic(err)
}
// ...
}
When a respective indexer function is defined, field selectors on spec.someField will work from the local cache as expected.

Is there a way to create method hooks for structs in Go?

I want to create before save and after save method hooks for my Go structs how can I achieve this?
type Person struct {
FirstName string
LastName string
}
func (p *Person) Save() {
// call beforeSave()
// Save person data
// call afterSave()
}
func (p *Person) Update() {
// call beforeUpdate()
// Update person data
// call afterUpdate()
}
type Order struct {
Number bson.ObjectId
Items []Item
}
func (o *Order) Save() {
// call beforeSave()
// Save order data
// call afterSave()
}
func (o *Order) Update() {
// call beforeUpdate()
// Update order data
// call afterUpdate()
}
For any struct I create as a model I want it to have a beforeSave() and afterSave() hook called automatically and be able to further override if necessary.
Packages like gorm use callback hooks heavily. But if you are writing your own engine (for some specific logic) using interfaces can help greatly (sample).
https://medium.com/#matryer/the-http-handler-wrapper-technique-in-golang-updated-bc7fbcffa702
I would only add to that article above, which I think well answers your question, that you can go further using pipelines (methods that return the associated variable they are bound to). It's particularly interesting as in the above article, as relates to being able to interpose anything between two processes, and you can use this to create tees and journalling/logging systems, and it can go a long way towards compensating for the kinda primitive error handling in Go.
I designed a number of related byte slice processing libraries that were able to be easily chained without needing to break a a line, although obviously more than about 3-4 in a line can get confusing and you can find yourself wanting to pass things in a DAG style pattern, at which point I think channels would make sense.

Fetching Logged in User Info for display

I am using https://github.com/kataras/iris Go web framework. I have:
User Registered
User Verified & Logged in
Session created and set with key username with user (table & struct) username
Now, here is my code for logged in user:
// Loaded All DB and other required value above
allRoutes := app.Party("/", logThisMiddleware, authCheck) {
allRoutes.Get("/", func(ctx context.Context) {
ctx.View("index.html");
});
}
In authcheck middleware
func authcheck(ctx context.Context) {
// Loaded session.
// Fetched Session key "isLoggedIn"
// If isLoggedIn == "no" or "" (empty)
// Redirected to login page
// else
ctx.Next()
}
My Session function
func connectSess() *sessions.Sessions {
// Creating Gorilla SecureCookie Session
// returning session
}
Now, my problem is, how do I share Logged User value to all routes in template. My Current option is:
// Loaded all DB and required value
allRoutes := app.Party("/", logThisMiddleware, authCheck) {
allRoutes.Get("/", func(ctx context.Context) {
// Load Session again
// Fetch username stored in session
// Run Query against DB
// Share the user struct value.
// Example ctx.ViewData("user", user)
ctx.View("index.html");
});
allRoutes.Get("dashboard", func(ctx context.Context) {
// Load Session again
// Fetch username stored in session
// Run Query against DB
// Share the user struct value.
// Example ctx.ViewData("user", user)
ctx.View("index.html");
});
}
But problem with above code is, I will have to write session for each route and run query again for each route I run and than share.
I feel, there must be better way of doing it , rather than loading session twice for each route one in authCheck middleware and second inside allRoutes.Get route.
I need ideas on how this can be optimised and user data can be shared to template by just writing code one time and not repeating below for each route
// Load Session again
// Fetch username stored in session
// Run Query against DB
// Share the user struct value.
// Example ctx.ViewData("user", user)
it's easy you can use the ctx.Values().Set/Get to make something shareable between your route's handlers or middleware(s).
// load session manager once
sess := connectSess()
func authCheck(ctx context.Context) {
session := sess.Start(ctx)
// Load your user here.
// [...]
// Save the returning user to the local storage of this handlers chain, once.
ctx.Values().Set("user", user) // <-- IMPORTANT
}
app.Get("/", func(ctx context.Context) {
// Get the user from our handlers chain's local storage.
user := ctx.Values().Get("user") // <-- IMPORTANT
// Bind the "{{.user}}" to the user instance.
ctx.ViewData("user", user)
// Render the template file.
ctx.View("index.html")
})
app.Get("dashboard", func(ctx context.Context) {
// The same, get the user from the local storage...
user := ctx.Values().Get("user") // <-- IMPORTANT
ctx.ViewData("user", user)
ctx.View("index.html")
})
That's all, pretty simple, right?
But I have some notes for you, read them if you have more time.
When you're on root "/" you don't have to create a party for it(.Party) in order to add middlewares (begin(Use) or finish(Done)), use just the iris.Application instance, app.Use/Done.
Don't write this:
allRoutes := app.Party("/", logThisMiddleware, authCheck) {
allRoutes.Get("/", myHandler)
}
Do that instead:
app.Use(logThisMiddleware, authCheck)
app.Get("/", myHandler)
It's easier to read and understand.
I've also noticed that you're using ; at the end of your functions, your editor and gocode tool will remove those, when you write a program using the Go Programming Language you shouldn't do that, remove all ;.
Last, please read the documentation and the examples, we have many of them at https://github.com/kataras/iris/tree/master/_examples , hopes you the best!

gin gonic middleware, how to get failure/success status for next handler in chain

I am writing a middleware, using gin gonic golang framework. I want to know within my middleware, if the call to next handler failed and take action based on that.
func foo() gin.HandlerFunc {
//do something..
c.Next()
//do something based on result of c.Next()
}
How can I do that? Documentation for next doesnt give much information https://godoc.org/github.com/gin-gonic/gin#Context.Next
Can you offer any suggestions. I basically want to check for non-existing api endpoints, for example, someone enters a URL, for which we don't have any api endpoint, then can I detect it here.
I am going to answer my question since I found a way to handle this kind of issues with route not found / method not found etc. The idea is to set an error in the gin context using context.Error() from the NoRoute/NoMethod handlers. This adds an error to the slice of errors in the c.Errors, which can then later on be used inside your middleware function.
func MyMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
//do something here
c.Next()
//do something based on error in c.
if c.Errors.Error() != nil {
//error case, get out
return
}
//else continue regular processing.
}
}
engine.NoRoute(func(c *gin.Context) {
c.JSON(404,
gin.H{"error": gin.H{
"key": "not allowed",
"description": "not allowed",
}})
c.Error(errors.New("Failed to find route")) //Set the error here.
})
Hope it helps.
There is a method on Engine called NoRoute that you can pass route handlers to handle 404's and such.

Resources