agrestio / agrest

Server-side Java REST Framework for easy access to data graphs from various backends
https://agrest.io
Apache License 2.0
80 stars 34 forks source link

DataResponse<T> to support "304 Not Modified" #640

Closed andrus closed 1 year ago

andrus commented 1 year ago

Let's add a mechanism to return "304 Not Modified" versions of DataResponse<T>. While it is always possible to control the response status via JAX-RS Response object, there's a benefit to preserving DataResponse<T> in the API method signature, as it allows documentation tools like Swagger to guess the response type.

So while we are not (yet) building full cache control support in Agrest, let's at least allow the users to implement conditional requests without changing the endpoint method signatures.

Changes required:

Here is a simplied example of a custom endpoint that recognizes ETags. Real-life example would need to provide a smarter etag calculation function, and also set a reasonable Cache-Control header.

public static class Resource {

    @Context
    private Configuration config;

    @GET
    public DataResponse<P1> conditional(@HeaderParam("If-None-Match") String eTag) {
        return AgJaxrs
                .select(P1.class, config)
                .routingStage(SelectStage.APPLY_SERVER_PARAMS, c -> stopIfMatch(c, eTag))
                .stage(SelectStage.ENCODE, this::cacheControl)
                .get();
    }

    ProcessorOutcome stopIfMatch(SelectContext<?> context, String eTag) {
        if (!"my-tag".equals(eTag)) {
            return ProcessorOutcome.CONTINUE;
        }

        context.setResponseStatus(HttpStatus.NOT_MODIFIED);
        // even though we are not modified, per HTTP standards, we must return the original ETag
        context.addResponseHeader("etag", eTag);
        return ProcessorOutcome.STOP;
    }

    void cacheControl(SelectContext<?> context) {
        // calculate response etag 
        context.addResponseHeader("etag", "my-tag");
    }
}