View Source OneAndDone.Plug (One and Done v0.1.5)
Easy to use plug for idempoent requests.
getting-started
Getting started
Add
:one_and_done
to your list of dependencies inmix.exs
:def deps do [ {:one_and_done, "~> 0.1.5"} ] end
Add the plug to your router:
pipeline :api do plug OneAndDone.Plug, # Required: must conform to OneAndDone.Cache (Nebulex.Cache works fine) cache: MyApp.Cache, # Optional: How long to keep entries, defaults to 86_400 (24 hours) ttl: 86_400, # Optional: Function reference to generate an idempotence TTL per request. # Takes the current `Plug.Conn` as the first argument and the current # `idempotency_key` as the second. # # When provided, this function is called before falling back to the # `ttl` option. # # Defaults to `nil`. build_ttl_fn: &OneAndDone.Plug.build_ttl/2, # Optional: Which methods to cache, defaults to ["POST", "PUT"] # Used by the default idempotency_key_fn to quickly determine if the request # can be cached. If you override idempotency_key_fn, consider checking the # request method in your implementation for better performance. # `supported_methods` is available in the opts passed to the idempotency_key_fn. supported_methods: ["POST", "PUT"], # Optional: Which response headers to ignore when caching, defaults to ["x-request-id"] # When returning a cached response, some headers should not be modified by the contents of the cache. # # Instead, the ignored headers are returned with the prefix `original-`. # # By default, the `x-request-id` header is not modified. This means that each request will have a # unique `x-request-id` header, even if a cached response is returned for a request. The original request # ID is still available under `original-x-request-id`. # # If you are using a framework that sets a different header for request IDs, you can add it to this list. ignored_response_headers: ["x-request-id"], # Optional: Function reference to generate the idempotency key for a given request. # By default, uses the value of the `Idempotency-Key` header. # Must return a binary or nil. If nil is returned, the request will not be cached. # Default function implementation: # # fn conn, opts -> # Opts is the same as the opts passed to the plug # if Enum.any?(opts.supported_methods, &(&1 == conn.method)) do # conn # |> Plug.Conn.get_req_header("idempotency-key") # Request headers are always downcased # |> List.first() # else # nil # end # end idempotency_key_fn: &OneAndDone.Plug.idempotency_key_from_conn/2, # Optional: Function reference to generate the cache key for a given request. # Given the conn & idempotency key (returned from idempotency_key_fn), this function # should return a term that will be used as the cache key. # By default, it returns a tuple of the module name and the idempotency key. # Default function implementation: fn _conn, idempotency_key -> {__MODULE__, idempotency_key} cache_key_fn: &OneAndDone.Plug.build_cache_key/2 # Optional: Flag to enable request match checking. Defaults to true. # If true, the function given in check_requests_match_fn will be called to determine if the # original request matches the current request. # If false, no such check shall be performed. request_matching_checks_enabled: true, # Optional: Function reference to determine if the original request matches the current request. # Given the current connection and a hash of the original request, this function should return # true if the current request matches the original request. # By default, uses `:erlang.phash2/2` to generate a hash of the current request. If the `hashes` # do not match, the request is not idempotent and One and Done will return a 400 response. # To disable this check, use `fn _conn, _original_request_hash -> true end` # Default function implementation: # # fn conn, original_request_hash -> # request_hash = # Parser.build_request(conn) # |> Request.hash() # # cached_response.request_hash == request_hash # end check_requests_match_fn: &OneAndDone.Plug.matching_request?/2, # Optional: Max length of each idempotency key. Defaults to 255 characters. # If the idempotency key is longer than this, we respond with error 400. # Set to 0 to disable this check. max_key_length: 255 end
That's it! POST and PUT requests will now be cached by default for 24 hours.
response-headers
Response headers
By default, the "x-request-id" header is not modified. This means that each request will have a unique "x-request-id" header, even if a cached response is returned for a request. By default, the "original-x-request-id" header is set to the value of the "x-request-id" header from the original request. This is useful for tracing the original request that was cached. One and Done sets the "idempotent-replayed" header to "true" if a cached response is returned.
telemetry
Telemetry
To monitor the performance of the OneAndDone plug, you can hook into OneAndDone.Telemetry
.
For a complete list of events, see OneAndDone.Telemetry.events/0
.
example
Example
# In your application.ex
# ...
:telemetry.attach_many(
"one-and-done",
OneAndDone.Telemetry.events(),
&MyApp.Telemetry.handle_event/4,
nil
)
# ...
# In your telemetry module:
defmodule MyApp.Telemetry do
require Logger
def handle_event([:one_and_done, :request, :stop], measurements, _metadata, _config) do
duration = System.convert_time_unit(measurements.duration, :native, :millisecond)
Logger.info("Running one_and_done took #{duration}ms")
:ok
end
# Catch-all for unhandled events
def handle_event(_, _, _, _) do
:ok
end
end