Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timeout middleware #1684

Closed
3 tasks done
jeyldii opened this issue Nov 13, 2020 · 4 comments · Fixed by #1743
Closed
3 tasks done

Timeout middleware #1684

jeyldii opened this issue Nov 13, 2020 · 4 comments · Fixed by #1743

Comments

@jeyldii
Copy link

jeyldii commented Nov 13, 2020

Hi! I want to create timeout middleware

Checklist

  • Dependencies installed
  • No typos
  • Searched existing issues and docs

Expected behaviour

middleware.Timeout()

Actual behaviour

middleware package do not produce timeout middleware

Working code to debug

I try something like this, yes i know it's wrong way:)

func timeout(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		ctx, _ := context.WithTimeout(context.Background(), time.Second*2)

		go func() {
			select {
			case <-ctx.Done():
				_ = c.JSON(http.StatusGatewayTimeout, ctx.Err())
			}
		}()

		c.SetRequest(c.Request().WithContext(ctx))
		return next(c)
	}
}

Version/commit

latest

@jeyldii
Copy link
Author

jeyldii commented Nov 13, 2020

I migrate my app from gin to echo, because gin has timeout middleware, but it panics.

@pafuent
Copy link
Contributor

pafuent commented Nov 15, 2020

Hi, I hope this example helps you on your Timeout middleware journey

package main

import (
	"context"
	"net/http"
	"time"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/labstack/gommon/log"
)

func main() {
	// Echo instance
	e := echo.New()
	e.Logger.SetLevel(log.INFO)

	// Middleware
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	// Route
	e.GET("/sleep", func(c echo.Context) (err error) {
		// This select make the trick of finish this request when the middleware timeouts
		select {
		case <-time.After(5 * time.Second):
			c.Logger().Info("Done")
			return c.JSON(http.StatusOK, "Done")
		case <-c.Request().Context().Done():
			c.Logger().Info("Timeout")
			return nil
		}
	}, timeoutMiddleware)

	// Start server
	e.Logger.Fatal(e.Start(":8080"))
}

func timeoutMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		// Just to play easily with the middleware using a query parameter
		timeout := 2 * time.Second
		if t, err := time.ParseDuration(c.QueryParam("timeout")); err == nil {
			timeout = t
		}

		// This is the context that controls the timeout. Its parent is the original
		// http.Request context
		ctx, cancel := context.WithTimeout(c.Request().Context(), timeout)
		defer cancel() // releases resources if next(c) completes before timeout elapses

		// A channel and a goroutine to run next(c) and know if its ends
		done := make(chan error, 1)
		go func() {
			// This goroutine will not stop even this middleware timeouts,
			// unless someone in the next(c) call chain handle ctx.Done() properly
			c.SetRequest(c.Request().Clone(ctx))
			done <- next(c)
		}()

		// The real timeout logic
		select {
		case <-ctx.Done():
			return c.JSON(http.StatusGatewayTimeout, ctx.Err())
		case err := <-done:
			return err
		}
	}
}

Here are some curl that you can use to play with the example

# Timeout
curl -i --request GET --url 'http://127.0.0.1:8080/sleep'
# No timeout
curl -i --request GET --url 'http://127.0.0.1:8080/sleep?timeout=6s'

Please, when you have your Timeout middleware woking don't forget to submit a PR whit it 😉

@pafuent
Copy link
Contributor

pafuent commented Nov 22, 2020

Hi @jeyldii, Was my comment helpfull?

@jeyldii
Copy link
Author

jeyldii commented Nov 23, 2020

Hi @jeyldii, Was my comment helpfull?

Hey! Thank you for your answer! I make something like this:

func Timeout(timeout time.Duration) func(next echo.HandlerFunc) echo.HandlerFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			ctx, cancel := context.WithTimeout(context.Background(), timeout)
			go func() {
				<-ctx.Done()
				cancel()
			}()
			c.SetRequest(c.Request().WithContext(ctx))
			return next(c)
		}
	}
}

And i manage context.Deadline in my handlers. I think your decision better. I think i can make a PR with middleware soon. Sorry, for late answer :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants