Rack middleware ensuring at most once requests for mutating endpoints.
Go to file
2018-04-24 16:55:17 +02:00
bin initial commit 2018-01-22 16:29:33 +01:00
lib release v0.1.2 2018-04-24 16:55:17 +02:00
spec release v0.1.2 2018-04-24 16:55:17 +02:00
.gitignore initial commit 2018-01-22 16:29:33 +01:00
.rspec initial commit 2018-01-22 16:29:33 +01:00
.travis.yml update .travis.yml 2018-01-23 10:45:49 +01:00
CHANGELOG.md release v0.1.2 2018-04-24 16:55:17 +02:00
CODE_OF_CONDUCT.md initial commit 2018-01-22 16:29:33 +01:00
Gemfile initial commit 2018-01-22 16:29:33 +01:00
idempotent-request.gemspec update readme 2018-01-22 18:58:30 +01:00
LICENSE.txt initial commit 2018-01-22 16:29:33 +01:00
Rakefile initial commit 2018-01-22 16:29:33 +01:00
README.md add travis status 2018-01-23 10:54:19 +01:00

Idempotent Request Build Status

Rack middleware ensuring at most once requests for mutating endpoints.

Installation

Add this line to your application's Gemfile:

gem 'idempotent-request'

And then execute:

$ bundle

Or install it yourself as:

$ gem install idempotent-request

How it works

  1. Front-end generates a unique key then a user goes to a specific route (for example, transfer page).
  2. When user clicks "Submit" button, the key is sent in the header idempotency-key and back-end stores server response into redis.
  3. All the consecutive requests with the key won't be executer by the server and the result of previous response (2) will be fetched from redis.
  4. Once the user leaves or refreshes the page, front-end should re-generate the key.

Configuration

# application.rb
config.middleware.use IdempotentRequest::Middleware,
  storage: IdempotentRequest::RedisStorage.new(::Redis.current, expire_time: 1.day),
  policy: YOUR_CLASS

To define a policy, whether a request should be idempotent, you have to provider a class with the following interface:

class Policy
  attr_reader :request

  def initialize(request)
    @request = request
  end

  def should?
    # request is Rack::Request class
  end
end

Example of integration for rails

# application.rb
config.middleware.use IdempotentRequest::Middleware,
  storage: IdempotentRequest::RedisStorage.new(::Redis.current, expire_time: 1.day),
  policy: IdempotentRequest::Policy

config.idempotent_routes = [
  { controller: :'v1/transfers', action: :create },
]
# lib/idempotent-request/policy.rb
module IdempotentRequest
  class Policy
    attr_reader :request

    def initialize(request)
      @request = request
    end

    def should?
      route = Rails.application.routes.recognize_path(request.path, method: request.request_method)
      Rails.application.config.idempotent_routes.any? do |idempotent_route|
        idempotent_route[:controller] == route[:controller].to_sym &&
          idempotent_route[:action] == route[:action].to_sym
      end
    end
  end
end

Custom options

# application.rb
config.middleware.use IdempotentRequest::Middleware,
  header_key: 'X-Qonto-Idempotency-Key', # by default Idempotency-key
  policy: IdempotentRequest::Policy,
  callback: IdempotentRequest::RailsCallback,
  storage: IdempotentRequest::RedisStorage.new(::Redis.current, expire_time: 1.day, namespace: 'idempotency_keys')

Policy

Custom class to decide whether the request should be idempotent.

See Example of integration for rails

Storage

Where the response will be stored. Can be any class that implements the following interface:

def read(key)
  # read from a storage
end

def write(key, payload)
  # write to a storage
end

Callback

Get notified when the client sends a request with the same idempotency key:

class RailsCallback
  attr_reader :request
  
  def initialize(request)
    @request = request
  end
  
  def detected(key:)
    Rails.logger.warn "IdempotentRequest request detected, key: #{key}"
  end
end

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/idempotent-request. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Idempotent::Request projects codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.