Skip to content

Add Support for Caching 422 (Unprocessable Entity) Responses#93

Open
devesh-shetty wants to merge 1 commit intomainfrom
add-422-support
Open

Add Support for Caching 422 (Unprocessable Entity) Responses#93
devesh-shetty wants to merge 1 commit intomainfrom
add-422-support

Conversation

@devesh-shetty
Copy link

Summary

This PR extends ResponseBank's caching capabilities to include 422 (Unprocessable Entity) responses alongside the existing support for 200, 301, and 404 status codes. This allows API validation error responses to be cached, reducing redundant processing for frequently repeated invalid requests.

Motivation

422 responses are commonly used in REST APIs to indicate that the request was well-formed but contains semantic errors (e.g., validation failures). These responses are typically:

  • Deterministic: The same invalid input produces the same validation errors
  • Expensive to compute: Validation logic may involve multiple checks, database queries, or complex business rules
  • Frequently repeated: Users or automated systems may repeatedly submit similar invalid requests

By caching 422 responses, we can:

  • Reduce server load from repeated validation of the same invalid inputs
  • Improve API response times for validation errors
  • Maintain consistency with existing caching behavior for other static responses (301, 404)

@devesh-shetty devesh-shetty requested a review from a team as a code owner October 9, 2025 00:28
@devesh-shetty devesh-shetty self-assigned this Oct 9, 2025
@Edouard-chin
Copy link
Member

Edouard-chin commented Oct 9, 2025

👋 It's been a while I didn't look at this gem, so my comment may be wrong.

I think this is not going to work as is, and I'm not sure if it's a good idea to make it work. The issue is that 422 are sent back when a POST request is submitted and post requests are not idempotent.

The first issue is that this gem only accepts GET and HEAD request:

def cacheable_request?
(request.get? || request.head?) && (request.params[:cache] != 'false')

The second issue is that the computation of the cache key only include the query string and the path

{ 'request' => { 'env' => request.env.slice('PATH_INFO', 'QUERY_STRING') } }

A user that tries so submit a form with invalid input will be presented with the cache the next time it submits the form with valid input.

@devesh-shetty
Copy link
Author

@Edouard-chin

couldn't I override these two methods to be method and body aware🤔

def cacheable_request?
    return false if params[:cache] == "false"
    request.get? || request.head? ||
      request.post?
  end

  # Make the cache key body-aware so a corrected submission won’t hit a 422 cache for a different body.
  def cache_key_data
    
  end

@Edouard-chin
Copy link
Member

We could, but I worry that we may end up in many pitfalls. For instance how do we cache multipart form where a user upload a gigantic file that ends up being invalid server side (e.g. not a pdf or whatever), the computation of the key is going to be expensive.

It's also not compliant with the RFC where POST request can't be cached https://datatracker.ietf.org/doc/html/rfc2616#section-9.5 unless specific headers like the Expiry are set which this gems don't do explicitly.

* No explicit expiry

I think the tricky part is the cache invalidation (as always 😅), because the "success" of a request is not necessarily tied to the form data. It's rare but submitting the same request with the same form data may result in different outcome (blips, race condition).


Those are the thing I can think of that make this change non trivial but there may be others, which is one of the reason why it's usually not advised to cache POST requests.

@matthutchinson
Copy link

matthutchinson commented Oct 13, 2025

I think @edouardchin makes some good points above..

422 are sent back when a POST request is submitted and post requests are not idempotent

and

the gem only currently accepts GET and HEAD requests

Since this this gem is intended for those purposes (i.e. GETs) to make it work with our 422s (POSTs) for OOS errors, we'd have to make it more customisable with...

  • configurable cacheable statuses to incl. the 422
  • a setting to toggle on the cache for POSTs
  • some toggle to hide the x-cache header on responses (so bots don't figure it out)

Just adding 422 to the cacheable statuses in the gem (without making it optional) may have unintended effects on other places in SFR where we currently use this cache...

While it would be nice to have our cache handled at middleware level (and with compression), I think our proxy cache use-case is quite specific and unique (aiming to protect making reqs to core) - rather than a generic cache (that this gem is solving)


I think it does make sense for 401s tho, as shown in @soules older draft PR

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 this pull request may close these issues.

3 participants

Comments