Iris: [BUG] View: If errors are returned by the ViewRenderer - do not output a "half template"

Created on 24 Jul 2020  ·  15Comments  ·  Source: kataras/iris

Describe the bug
I am currently using the latest version from the Master Branch. If I use a template with Jet that uses variables that are not present, an error is returned, but the template is already sent to the browser as far as it has come, this should be prevented.

To Reproduce
We have tried with and without iris.WithResetOnFireErrorCode but without ctx.Record()

app.Get("/bug-test", func(ctx iris.Context) {
    ctx.ViewData("demo", "123")
    if err := ctx.View("bugtest.jet"); err != nil {
        ctx.StopWithText(iris.StatusInternalServerError, "Templates not rendered!")
    }
})

Expected behavior
Only the StopWithText should returned or the OnAnyErrorCode should be triggered. Not the half rendered Template.

I would expect, if a ViewEngine throws an error, that there will be no normal output, even if the template system has already sent it to the output.

An alternative would be to document it more clearly, in connection with the ViewEngines.

external resolved bug

All 15 comments

Another point is that if you use ctx.Record() and iris.Compression then you can't send errors because the compression already uses a recorder and therefore the actual interception with ctx.Record() is impossible.

@Dexus This has to do with the CloudyKit/jet template, not iris' view engine. There is a relative issue at: https://github.com/CloudyKit/jet/issues/110. And the code, I can edit and push a PR to add an option to fix that is there: https://github.com/CloudyKit/jet/blob/e02d8822d3974184cce9f3c6b5b7a7a27389f408/eval.go#L534 and: https://github.com/CloudyKit/jet/blob/cc9978f14830540788c1b68bd4fe02dd6cb9a9e9/template.go#L309. However I am not sure if this is possible there, it's a runtime error by processing blocks, so the previous are rendered, the result is written one by one, it is not cached until all blocks are parsed. Even if I push a fix there, it will require adding a buffer and parse the template, and if success then write to the main writer (render to client), but you can already do that with ctx.Record() and WithResetOnFireErrorCode. So I am not sure if we must do something about that or just add a ctx.Record() -> ctx.View -> err!=nil { ctx.StatusCode(errCode) or ctx.StopWithXXX to a jet example. What's your opinion?

Another point is that if you use ctx.Record() and iris.Compression then you can't send errors because the compression already uses a recorder and therefore the actual interception with ctx.Record() is impossible.

Yes, but it supposed that the following fixes it: https://github.com/kataras/iris/blob/75d7fe7e8bf1641e48b3a460dd1232dc90d48a45/core/router/handler.go#L461-L488

Did you try the iris.WithResetOnFireErrorCode setting? It's added after this issue (which describes the opposite) and it's described on the HISTORY's Breaking Changes section.

Hi @kataras

Another point is that if you use ctx.Record() and iris.Compression then you can't send errors because the compression already uses a recorder and therefore the actual interception with ctx.Record() is impossible.

here my example:

package main

import (
    "bytes"
    "encoding/base64"
    "fmt"
    "reflect"

    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/middleware/logger"
    "github.com/kataras/iris/v12/middleware/recover"
    "github.com/kataras/iris/v12/view"
)

var tmpl *view.JetEngine

// GetView return the view.JetEngine for later use
func GetView() *view.JetEngine {
    return tmpl
}

// RegisterView for Iris and all View Helper
func RegisterView(app *iris.Application) *view.JetEngine {
    tmpl = iris.Jet("views", ".jet") // <--
    tmpl.Reload(true)                // remove in production.
    tmpl.AddFunc("base64", func(a view.JetArguments) reflect.Value {
        a.RequireNumOfArguments("base64", 1, 1)

        buffer := bytes.NewBuffer(nil)
        fmt.Fprint(buffer, a.Get(0))

        return reflect.ValueOf(base64.URLEncoding.EncodeToString(buffer.Bytes()))
    })
    app.RegisterView(tmpl) // <--

    return tmpl
}

func main() {
    app := iris.New()
    app.Logger().SetLevel("info") //"debug"
    // Optionally, add two built'n handlers
    // that can recover from any http-relative panics
    // and log the requests to the terminal.
    app.Use(recover.New())
    app.Use(logger.New())

    RegisterView(app)

    // BUG: The OnAnyErrorCode will work if this is commented
    // but as soon as you uncomment it, the webpage get rendered and 
    // delivered unfinished to the client

    //app.Use(iris.Compression)

    app.OnAnyErrorCode(func(ctx iris.Context) {
        err := ctx.GetErr()
        if err != nil {
            ctx.Text(err.Error())
            return
        }
        ctx.Text(iris.StatusText(ctx.GetStatusCode()))
    })

    // Method:   GET
    // Resource: http://localhost:8080/plans
    app.Get("/plans", func(ctx iris.Context) {
        ctx.Record()
        ctx.ViewData("demo", "123")
        if err := ctx.View("pages/plans.jet"); err != nil {
            ctx.SetErr(err)
            ctx.StopWithStatus(iris.StatusInternalServerError)
        }
    })

    app.Run(
        iris.Addr(":8080"),
        iris.WithoutServerError(iris.ErrServerClosed),
        iris.WithCharset("utf-8"),
        iris.WithOptimizations,
        iris.WithResetOnFireErrorCode,
        iris.WithPostMaxMemory(1024*1024*3),
    )
}

Template:

<html>
<head><title>broken page</title></head>
<body>
<p>some text... some text... some text... some text... some text... some text... some text... some text... some text... some text... some text... some text... some text... some text... some text... some text... </p>
{{ .demo }}<br>
<p>some text that never rendered even not the error that should trigger the code below</p>
<pre><code>
app.OnAnyErrorCode(func(ctx iris.Context) {
        err := ctx.GetErr()
        if err != nil {
            ctx.Text(err.Error())
            return
        }
        ctx.Text(iris.StatusText(ctx.GetStatusCode()))
    })
</code></pre>
</body>
</html>

ctx.Record() and WithResetOnFireErrorCode works, but only if you not have app.Use(iris.Compression) in use.

@Dexus This has to do with the CloudyKit/jet template, not iris' view engine. ..... Even if I push a fix there, it will require adding a buffer and parse the template, and if success then write to the main writer (render to client), but you can already do that with ctx.Record() and WithResetOnFireErrorCode. So I am not sure if we must do something about that or just add a ctx.Record() -> ctx.View -> err!=nil { ctx.StatusCode(errCode) or ctx.StopWithXXX to a jet example. What's your opinion?

This works, but still causes problems if a ctx.Record() is already running elsewhere. For an example see above, with app.Use(iris.Compression)

Yes @Dexus, I see, the problem was that ctx.Record had a type check which allowed only *responseWriter to be wrapped (in order to not wrap twice the recorder) and also in the error handler there was no check for the response recorder's underline iris writer (e.g. compress response writer). This is fixed now: https://github.com/kataras/iris/blob/39a1f00d724e391bc0a2b55cca94a127fe84d7bd/_examples/routing/http-errors/reset-body/main.go#L1-L42

Is that the solution you were looking for?

@kataras
after your change in 39a1f00 i don't get any response now together with app.Use(iris.Compression) and ctx.Record() and iris.WithResetOnFireErrorCode. I only get a blank page. If i remove the app.Use(iris.Compression) everything works fine again.

@Dexus that's strange, why you get an empty page? The error message doesn't shown up? Can you please create a repo that I can reproduce it? Thanks!!

I will try to create a repo that have the exact same problem but without our code, this take some time, but try to finish it today.

I will try to create a repo that have the exact same problem but without our code, this take some time, but try to finish it today.

You can just create a simple jet view, with a runtime error, put the code you use, and the middlewares, I dont need your whole code base, just a small example that I can run and run until I fix the issue. Thanks @Dexus !

@kataras here your example: https://github.com/Dexus/iris-bug-examples

please loog to the main.go and comment app.Use(iris.Compression) or uncomment it, to check what is wrong...

Oh is this the simplest example you could give me? :P I see many things like translation and view functions but it doesn't work it panics because locale files are missing, I will try to clean up, and get back to you soon. Also I want to know why you select to not use the Iris' i18n for localization which you can use on context methods and views as well, if there is something we can do to improve the iris i18n experience please let me know

Just copy paste the most of it 😅 to not remove something that may have an impact.

Von meinem iPhone gesendet

Am 26.07.2020 um 18:17 schrieb Gerasimos (Makis) Maropoulos notifications@github.com:


Oh is this the simplest example you could give me? :P I see many things like translation and view functions, I will take a look of it, and get back to you soon.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.

OK @Dexus give it a try

It works now. Thanks @kataras

I thank you @Dexus, will take a look at the assesment for the certification of yours when I finish the documentation, I didnt forget it. I am freezing new features for v12.2.0.

Was this page helpful?
0 / 5 - 0 ratings