I have started developing web application where the back end is Go. I'm using beego framework to develop this application.Previously i used to program in java. Java has a filter function to filter the request by url. I came to know that we can implement it in beego after reading the documentation. There they have given the following example code
var FilterUser = func(ctx *context.Context) {
if strings.HasPrefix(ctx.Input.URL(), "/login") {
return
}
_, ok := ctx.Input.Session("uid").(int)
if !ok {
ctx.Redirect(302, "/login")
}
}
beego.InsertFilter("/*", beego.BeforeRouter, FilterUser)
The problem is I don't know where to use this block of code.
You can do something like the following:
Set the URL you want to protect in router and the corresponding filter
Create a filter function which will be called by the router and check the user
In more detail:
// A URL set in router.go
beego.InsertFilter("/admin/", beego.BeforeRouter, controllers.ProtectAdminPages)
// A Filter that runs before the controller
// Filter to protect admin pages
var ProtectAdminPages = func(ctx *context.Context) {
sess, _ := beego.GlobalSessions.SessionStart(ctx.ResponseWriter, ctx.Request)
defer sess.SessionRelease(ctx.ResponseWriter)
// read the session from the request
ses := sess.Get("mysession")
if ses != nil {
s := ses.(map[string]string)
// get the key identifying the user id
userId, _ := strconv.Atoi(s["id"])
// a utility function that searches the database
// gets the user and checks for admin privileges
if !utils.UserIsAdmin(userId) {
ctx.Redirect(301, "/some-other-page")
}
} else {
ctx.Redirect(301, "/")
}
}
Related
I'm new to Gin and Gin sessions, and I have a weird problem I cannot explain: It seems that session data I write in my controller can't be accessed from my middlware.
Allow me to demonstrate:
I do the following in my controller:
func OidcCallBack(c *gin.Context) {
...
// store user info and redirect to original location
userJsonData, err := json.Marshal(*user)
if err != nil {
log.Errorf("error encoding user data: %s", err.Error())
}
session.Set(SessionUserInfo, string(userJsonData))
session.Save()
}
In my middleware, I want to read this session data:
func OidcMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// get our session variables
session := sessions.Default(c)
// some test code, this works btw
session.Set("bla", "test")
session.Save()
blavar := session.Get("bla")
// get our userinfo
userinfo := session.Get(controllers.SessionUserInfo) // this results in nil
log.Debug(blavar)
if userinfo == nil {
log.Debugf("filter incoming url: %s", c.Request.URL.String())
session.Set(controllers.SessionRedirectTarget, c.Request.URL.String())
session.Save()
c.Redirect(http.StatusFound, controllers.AuthLogin)
c.Abort()
}
c.Next()
}
}
When I look in my debugger, I notice that the data set in the controller, ended up in a different place of session.store.MemStore.cache.data:
session data set in the middleware ends up in data[0], and the data set in the controller ends up in data1.
Can someone explain me what I need to do to make sure all data ends up in the same spot, and also explain me why this happens? I can't find in any info on this in the readme file.
I'm trying to write a "Binder" middleware that will validate any request query using a struct type with gin bindings/validators
So for example, let's say I have an endpoint group called /api/subject which requires the query string to have a subject code and an ID that will be validated using the following struct (called entity.Subject):
type Subject struct {
Code string `binding:"required,alphanum"`
ID string `binding:"required,alphanum,len=4"`
}
That's just one example, but I'd like to be able to pass any struct type to this middleware, because I'd like to access the query data on future handlers without worrying about query validation.
So I tried something like this:
func Binder(t reflect.Type) gin.HandlerFunc {
return func(c *gin.Context) {
obj := reflect.New(t).Elem().Interface()
if err := c.BindQuery(&obj); err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}
c.Set(t.Name(), obj)
}
}
And added this middleware like so:
apiGroup := router.Group("/api")
{
// other subgroups/endpoints
// ...
subjectGroup := apiGroup.Group("/subject", middleware.Binder(reflect.TypeOf(entity.Subject{})))
}
And later on, in another handler function, let's say GetSubject, I want to access the subject data passed by doing c.MustGet("Subject").(entity.Subject)
But this isn't working =(, when I print obj, it's just an empty interface, how would I do this?
I managed to do something similar!
I created the following middleware
var allowedTypes = []binding.Binding{
binding.Query,
binding.Form,
binding.FormPost,
binding.FormMultipart,
}
func Bind(name string, data interface{}, bindingType binding.Binding) gin.HandlerFunc {
return func(ctx *gin.Context) {
ok := false
for _, b := range allowedTypes {
if b == bindingType {
ok = true
}
}
if !ok {
ctx.AbortWithError(
http.StatusInternalServerError,
fmt.Errorf("Bind function only allows %v\n", allowedTypes),
)
}
_ = ctx.MustBindWith(data, bindingType)
ctx.Set(name, data)
}
}
Remember to pass a pointer to your desired type in the call, like so:
router.GET("/something", Bind("Object", &myObject, binding.Query))
I restricted only to a few binding types because they allow ShouldBind to be called multiple times, whereas JSON, XML and others consume the Request body.
This way you can pass multiple Bind middlewares and if the validation fails it automatically aborts with http.StatusBadRequest
Suppose I have an api having two routes one is for saving the user and another one is for getting the user given below:-
router.go
package main
import (
"github.com/gin-gonic/gin"
"go-training/postAPI/controller"
)
type Route struct {
Name string
Method string
Pattern string
HandlerFunc func(*gin.Context)
}
type Routes []Route
var routes = Routes{
Route{"SaveUser", "POST", "/post", controller.SaveUser},
Route{"GetUser", "GET", "/post/:id", controller.GetUser},
}
func NewRouter() {
router := gin.Default()
v1 := router.Group("/api/v1")
for _, route := range routes {
switch route.Method {
case "GET":
v1.GET(route.Pattern, route.HandlerFunc)
case "POST":
v1.POST(route.Pattern, route.HandlerFunc)
case "PUT":
v1.PUT(route.Pattern, route.HandlerFunc)
case "DELETE":
v1.DELETE(route.Pattern, route.HandlerFunc)
default:
v1.GET(route.Pattern, func(c *gin.Context) {
c.JSON(200, gin.H{
"result": "Specify a valid http method with this route.",
})
})
}
}
router.Run(":8080")
}
By hitting these url localhost:8080/api/v1/post method:- POST by Postman it will take the json data from postman and enters into the database and while getting the user url localhost:8080/api/v1/post/:id method:- GET by postman then it will return the user matching with the id entered. Now, I want to take load test(vegeta) these api's because I want to see that how many request it would be take in one second. I read this link and implement the program but I don't know how will I take load test of my api's separately.
Can anyone tell me that how will I take the load test of these two api's or any refrence?
Edited
main.go
package main
func main() {
NewRouter()
GetVegeta()
}
vegeta.go
// Panic if there is an error
func check(err error) {
if err != nil {
panic(err)
}
}
func GetVegeta() {
var (
users int
)
// The Go random number generator source is deterministic, so we need to seed
// Configure our command line app
app := cli.NewApp()
app.Name = "Pokemon User Data Generator"
app.Usage = "generate a stream of test data for vegeta. Type 'pokemon help' for details"
// Add -users flag, which defaults to 5
app.Flags = []cli.Flag{
cli.IntFlag{
Name: "users",
},
}
// Our app's main action
app.Action = func(c *cli.Context) error {
// Combine verb and URL to a target for Vegeta
verb := c.Args().Get(0)
url := c.Args().Get(1)
target := fmt.Sprintf("%s %s", verb, url)
fmt.Println(verb)
fmt.Println(url)
fmt.Println(target)
if len(target) > 1 {
for i := 1; i < users; i++ {
fmt.Println(users)
}
} else {
// Return an error if we're missing the required command line arguments
return cli.NewExitError("You must specify the target in format 'VERB url'", 1)
}
return nil
}
app.Run(os.Args)
}
These files are in same folder name Template. I'm running this whole folder with the command ./Template -users=10 GET https://localhost:8080/api/v1/customer | vegeta attack -rate=10 -duration=30s | vegeta report
by running this all api's run and when I will hit any api from the postman then it will give me the error of bad method: [GIN] and encode: can't detect encoding of "stdin" How will I solve this to make a report of my api's.
Thanks for your precious time!
Vegeta takes concurrency and time parameters and you can create report out of it.
you could use
https://linux.die.net/man/1/siege and it allow you to test APIs and having report
Upon getting session information of type map[string]interface{} with this.GetSession("session_key"), I did have to explicitly set the context and type assert the session like this in order to explicitly pass the data to the template.
// Get the session
profile := this.GetSession("profile")
// Have to add data to the template's context
this.Data["nickname"] = profile.(map[string]interface{})["nickname"].(string)
this.Data["picture"] = profile.(map[string]interface{})["picture"].(string)
// Render template
this.TplNames = "user.html"
The session data (type map[string]interface{}) looks like this:
{"nickname": "joe", "picture": "urltotheimg"}
However, according to the Beego's session doc, it looks like the session is passed implicitly without any need of type assertions or context passing (the template has immediate access to session values i.e. {{.nickname}} and {{.picture}})
This is the controller setting the session before redirecting to /user
// Inherit beego's base controller
type MyController struct {
beego.Controller
}
func (this *MyController) Get() {
// code for getting token here
// Getting the User information
client := conf.Client(oauth2.NoContext, token)
resp, err := client.Get("https://" + domain + "/userinfo")
if err != nil {
this.Redirect("/error", 500)
return
}
// Reading the body for user's information
raw, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
this.Redirect("/error", 500)
return
}
// Unmarshalling the JSON of the Profile
var profile map[string]interface{}
if err := json.Unmarshal(raw, &profile); err != nil {
this.Redirect("/error", 500)
return
}
// Saving the information to the session.
this.SetSession("profile", profile)
// redirect to /user
this.Redirect("/user", 301)
}
This is the controller of "/user"
type UserController struct {
beego.Controller
}
func (this *UserController) Get() {
// get the saved session
profile := this.GetSession("profile")
// without setting the template data here, the session data won't be
// available in user.html
this.Data["nickname"] = profile.(map[string]interface{})["nickname"].(string)
this.Data["picture"] = profile.(map[string]interface{})["picture"].(string)
this.TplNames = "user.html"
}
Only this then I can map the template to the data like this:
<img src="{{ .picture }}">
<p>Hello, {{ .nickname }}</p>
I'm quite sure it's necessary to set the template data. I'm just not sure why the above doc didn't do that.
Any help would be appreciated.
I just tried running the Beego quickstart project and ran it successfully.
Make sure you have both the beego and bee installed. After creating a new project with bee new projectname make sure you edit the projectname/conf/app.conf file and add the sessionon = true:
appname = quickstart
httpport = 8080
runmode = dev
sessionon = true
I created a redirect controller like:
type RedirectController struct {
beego.Controller
}
func (c *RedirectController) Get() {
profile := make(map[string]interface{})
profile["nickname"] = "User's Nickname"
profile["picture"] = "/path/to/img.jpg"
c.SetSession("profile", profile)
c.Redirect("/", 301)
}
The main controller:
type MainController struct {
beego.Controller
}
func (c *MainController) Get() {
profile := c.GetSession("profile")
c.Data["nickname"] = profile.(map[string]interface{})["nickname"]
c.Data["picture"] = profile.(map[string]interface{})["picture"]
c.TplNames = "index.tpl"
}
My index.tpl file:
<p>Nickname: {{.nickname}}</p>
<p>Picture: {{.picture}}</p>
And the router:
func init() {
beego.Router("/", &controllers.MainController{})
beego.Router("/redirect", &controllers.RedirectController{})
}
I would also recommend you to use a structure to store the profile values like:
// Define struct.
type Profile struct{
Nickname string
Picture string
}
// Save it for template rendering.
this.Data["profile"] = &Profile{Nickname:"astaxie", Picture:"img.jpg"}
// And render it like this:
Nickname: {{.profile.Nickname}}
Picture: {{.profile.Picture}}
Make sure to read this to understand how template rendering is done. I hope this is what you were asking for, if not, please edit your question and add more helpful information and I will edit this answer.
Right now i am using Gorilla context package to pass data around in my middlewares & controllers, but what i want to do is pass the data directly to my Pongo2 template so later in my controller i don't have to get the data from the Gorilla context and manually pass it to the template context, for those of you familiar with express.js it would be like
var user = {
name: "Name",
age: 0
}
response.locals = user
Edit: So every pongo2 template needs access to a User object, right now i fetch the user from database using middleware and using Gorilla context pass the data to my controller, from there on to my template on each controller but what i want to do is pass the User object to template from my middleware instead of using Gorilla context.
func UserMiddleware(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
user := &User{} // user will normally be fetched from database
context.Set(req, "user", user)
next.ServeHTTP(res, req)
})
}
Then In My Request Handler
func Handler(res http.ResponseWriter, req *http.Request) {
tpl, _ := pongo2.FromFile("view/template.html")
user := context.Get(req, "user").(*User)
data := pongo2.Context{
"user": user,
}
out, _ := tpl.Execute(data)
res.Write([]byte(out))
}
For all of my handlers i have to pass in user to template like that, but i want to pass it in from my middleware so that i don't have to do it in each of my handlers.
invoke MyExecute(req, tpl) instead of tpl.Execute(data)
func MyExecute(req *http.Request, tpl TemplateSet) (string, error){
gorillaObj := context.GetAll(req)
pongoObj := make(map[string]interface{})
for key, value := range gorillaObj {
if str, ok := key.(string); ok{
pongoObj[str] = value
}
}
return tpl.Execute(pongo2.Context(pongoObj))
}
not tested, it should work.
the most problem is that gorilla use map[interface{}]interface{} as store, but pongo use map[string]interface{}, note not to use non-string as key in gorilla context.