Skip to content

[enhancement/idea] @CacheControl / @ConditionalRequest annotation #667

@cbarlin

Description

@cbarlin

Hi there!

I noticed that sometimes I'm needing to repeat a bunch of code to handle sending/processing ETag / Last-Modified / If-Match / If-Unmodified-Since headers and I was wondering if that's something that could potentially be generated instead?

I did take a look around the issues/documentation and couldn't see anything for this already but I did notice that this is almost the opposite of #632 😆

An example could be:

@Controller
@Path("/contacts")
class ContactController {

  @Get("/{id}")
  @CacheControl("getByIdCacheControl") // The name of the method on the controller that handles the cache control
  Contact getById(long id) {
    ...
  }

  // Mirrors the arguments of the original method
  CacheManagement getByIdCacheControl(long id) {
    return CacheManagement.builder()
       .lastModified(...) // OffsetDateTime maybe?
       .etag(...) // String maybe? Or perhaps building up a hash internally?
       .maxAge(..) // For `Cache-Control` - `Duration` maybe? And could be used to automatically create the `Expires` header?
       // Other methods could exist for the `Cache-Control` header like `privateOnly`, `sMaxAge`/`sharedCacheMaxAge`,`staleIfError`
       .build();
  }

  @Patch
  @ConditionalRequest("saveConditional")
  // @ConditionalRequest(methodName = "saveConditional", acceptIfMissingHeaders = false)
  void save(Contact contact) {
    ...
  }

  // Mirrors the arguments of the original method
  ConditionalRequestManagement saveConditional(Contact contact) {
    return ConditionalRequestManagement.builder()
      .etag(..) // named to reflect what the server sent on a GET or HEAD request, even though this doesn't reflect the inbound header...
      .lastModified(..)
      .build();
  }
}

And then the generated controller automatically handles setting the headers correctly, as well as replying with the appropriate 304 or 412 statuses by calling the other method first. So the flow would be:

For @CacheControl (GET):

  1. Call the management method to get the policy (ETag / Last-Modified).
  2. Check request headers (If-None-Match / If-Modified-Since).
  3. Hit: Return 304 Not Modified immediately.
  4. Miss: Execute the controller method, and apply the Cache-Control, ETag, and Last-Modified headers.

For @ConditionalRequest (PUT/PATCH/etc):

  1. Call the management method to get the current resource state.
  2. Check request headers (If-Match / If-Unmodified-Since).
  3. Mismatch: Return 412 Precondition Failed immediately.
  4. Match: Execute the controller method (save/update).

It could have just the one annotation and "Management" object, and the names could definitely be improved from my example, but is this idea (once fleshed out a bit more) something that could be in-scope for avaje-http?

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions