Skip to content

🚀 [Feature]: Idempotency Middleware #2163

@GalvinGao

Description

@GalvinGao

Feature Description

Idempotency is a guarantee that enables the client to have safe retries if no response (or a 500 response) is received from the server, by using an "idempotency key" that is supplied by client to denote the initial request and its retry requests as a singular set of intention (instead of multiple intentions).

Production examples of idempotency:

Additional Context (optional)

When the middleware received a request...

  • ...without an idempotency key
    • It will just call the Next() handler and that's all.
  • ...with an idempotency key, but such key does not exist/already expired
    • Lock the idempotency mutex lock with idempotency key as the key. The lock is typically a distributed redis lock. We should just provide an interface for outer libraries/users to implement.
      • After acquired lock, check the existence of idempotency key again in storage.
    • The middleware will call Next() handler, and
      • If returned non-nil error, return the error and skip idempotency saving.
    • Save the response status, body, and all headers specified in PersistResponseHeaders
    • Unlock the idempotency mutex lock with idempotency key as the key.
  • ...with an idempotency key, and such key exists/not expired
    • Return the saved response (status, body, and all headers saved) for such key.

Code Snippet (optional)

package main

import (
	"log"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/idempotency"
)

func main() {
	app := fiber.New()

	app.Use(idempotency.New(idempotency.Config{
		// LifeTime is the backtrack time window of the presence of an idempotency key. Default is 24h.
		LifeTime: time.Hour * 24,

		// KeyHeader is the header for looking up the idempotency key from. Default is "X-Idempotency-Key".
		KeyHeader: "X-Idempotency-Key",

		// Storage is the storage of idempotency responses. Default is a in-memory fiber.Storage.
		Storage: fiberstore.NewRedis(), // this is to just illustrate a storage that implements fiber.Storage

		// PersistResponseHeaders is a set of headers to persist. Default is nil.
		PersistResponseHeaders: []string{
			fiber.HeaderContentType,
			fiber.HeaderContentLength,
			fiber.HeaderSetCookie,
		},
	}))

	app.Post("/hello", func(ctx *fiber.Ctx) error {
		return ctx.SendString(time.Now().String())
	})

	log.Fatal(app.Listen(":3000"))
}

Checklist:

  • I agree to follow Fiber's Code of Conduct.
  • I have checked for existing issues that describe my suggestion prior to opening this one.
  • I understand that improperly formatted feature requests may be closed without explanation.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions