Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rest] Add caching for static resources #3335

Merged
merged 9 commits into from
Jun 15, 2023
Merged

Conversation

ghys
Copy link
Member

@ghys ghys commented Jan 24, 2023

Closes #3329.

This implements a new optional cacheable parameter for these REST endpoints:

  • /rest/items
  • /rest/things
  • /rest/rules

When this parameter is set, a flat list of all elements excluding non-cacheable fields (e.g. "state", "transformedState", "stateDescription", "commandDescription" for items, "statusInfo", "firmwareStatus", "properties" for things, "status" for rules) will be retrieved along with a Last-Modified HTTP response header. When unknown, the Last-Modified header will be set to the date of the request.

Also only when this parameter is set, and a If-Modified-Since header is found in the request, that header will be compared to the last known modified date for the corresponding cacheable list. The last modified date will be reset when any change is made on the elements of the underlying registry. If the If-Modified-Since date is equal or more recent than the last modified date, then a 304 Not Modified response with no content will be served instead of the usual 200 OK, informing the client that its cache is still valid at the provided date.

All other request parameters will be ignored except for "metadata" in the /rest/items endpoint. When a metadata selector is set, the resulting item list will be considered like a completely different resource, i.e. it will have its own last modified date. Regarding metadata, the approach to invalidating last modified dates is very conservative: when any metadata is changed, all cacheable lists of items will have their last modified date reset even if the change was in a metadata namespace that wasn't requested.

This also implements the abovedescribed behavior for the /rest/ui/components/{namespace} endpoint, but no cacheable parameter is necessary. The last modified date is tracked by namespace.

Signed-off-by: Yannick Schaus github@schaus.net

This implements a new optional `cacheable` parameter for these REST endpoints:
- `/rest/items`
- `/rest/things`
- `/rest/rules`

When this parameter is set, a flat list of all elements excluding
non-cacheable fields (e.g. "state", "transformedState", "stateDescription",
"commandDescription" for items, "statusInfo", "firmwareStatus",
"properties" for things, "status" for rules) will be retrieved along with
a `Last-Modified` HTTP response header. When unknown, the Last-Modified
header will be set to the date of the request.

Also only when this parameter is set, and a `If-Modified-Since` header is
found in the request, that header will be compared to the last known
modified date for the corresponding cacheable list. The last modified date
will be reset when any change is made on the elements of the underlying
registry. If the `If-Modified-Since` date is equal or more recent than the
last modified date, then a 304 Not Modified response with no content will
be served instead of the usual 200 OK, informing the client that its
cache is still valid at the provided date.

All other request parameters will be ignored except for "metadata" in the
`/rest/items` endpoint. When a metadata selector is set, the resulting
item list will be considered like a completely different resource, i.e.
it will have its own last modified date. Regarding metadata, the approach
to invalidating last modified dates is very conservative: when any metadata
is changed, all cacheable lists of items will have their last modified date
reset even if the change was in a metadata namespace that wasn't requested.

This also implements the abovedescribed behavior for the
`/rest/ui/components/{namespace}` endpoint, but no `cacheable` parameter
is necessary. The last modified date is tracked by namespace.

Signed-off-by: Yannick Schaus <github@schaus.net>
@ghys ghys requested a review from a team as a code owner January 24, 2023 09:04
@ghys
Copy link
Member Author

ghys commented Jan 24, 2023

Examples (using HTTPie):

The first request at 08:24:14 GMT responds with a flat list of items retrieved with the cacheable parameter. Note the Last-Modified response header:

$ http 'http://localhost:8080/rest/items?cacheable=true'
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 24 Jan 2023 08:24:14 GMT
Last-Modified: Tue, 24 Jan 2023 08:24:14 GMT
Server: Jetty(9.4.46.v20220331)
Transfer-Encoding: chunked

[
    {
        "editable": false,
        "groupNames": [
            "DemoSwitchGroup"
        ],
        "link": "http://localhost:8080/rest/items/DemoSwitch",
        "name": "DemoSwitch",
        "tags": [],
        "type": "Switch"
    },
    ...
]

Supposing the client has cached this list, a second request is made with the If-Modified-Since set to the Last-Modified of the previous request (could also be the current date):

$ http 'http://localhost:8080/rest/items?cacheable=true' 'If-Modified-Since: Tue, 24 Jan 2023 08:24:14 GMT'
HTTP/1.1 304 Not Modified
Content-Length: 0
Date: Tue, 24 Jan 2023 08:25:26 GMT
Server: Jetty(9.4.46.v20220331)

Of course, requests without the header still return the list (with or without Last-Modified depending on the cacheable parameter):

$ http HEAD 'http://localhost:8080/rest/items?cacheable=true'
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 24 Jan 2023 08:27:32 GMT
Last-Modified: Tue, 24 Jan 2023 08:24:14 GMT
Server: Jetty(9.4.46.v20220331)
Transfer-Encoding: chunked

$ http HEAD 'http://localhost:8080/rest/items'
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 24 Jan 2023 08:27:56 GMT
Server: Jetty(9.4.46.v20220331)
Transfer-Encoding: chunked

If any item (or metadata) was changed, then the list will be sent again, with a new Last-Modified header:

$ http 'http://localhost:8080/rest/items?cacheable=true' 'If-Modified-Since: Tue, 24 Jan 2023 08:24:14 GMT'
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 24 Jan 2023 08:29:03 GMT
Last-Modified: Tue, 24 Jan 2023 08:29:03 GMT
Server: Jetty(9.4.46.v20220331)
Transfer-Encoding: chunked

[
    {
        "editable": false,
        "groupNames": [
            "DemoSwitchGroup"
        ],
        "link": "http://localhost:8080/rest/items/DemoSwitch",
        "name": "DemoSwitch",
        "tags": [],
        "type": "Switch"
    },
    ...
]

(note: the last modified date is the date when the first request was made after the actual change, not the real date of the change; since it's only precise to the second in those headers, it's to mitigate race conditions if changes and requests are made within the same second).

Requests for UI components are cacheable by default:

$ http 'http://localhost:8080/rest/ui/components/ui:page'
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 24 Jan 2023 09:13:39 GMT
Last-Modified: Tue, 24 Jan 2023 08:26:32 GMT
Server: Jetty(9.4.46.v20220331)
Transfer-Encoding: chunked

[
    {
        "component": "oh-layout-page",
        "config": {
            "label": "Overview"
        },
    ...
]

$ http 'http://localhost:8080/rest/ui/components/ui:page' 'If-Modified-Since: Tue, 24 Jan 2023 09:00:00 GMT'
HTTP/1.1 304 Not Modified
Content-Length: 0
Date: Tue, 24 Jan 2023 09:15:05 GMT
Server: Jetty(9.4.46.v20220331)

If used carefully with client-side caching by UIs and other clients, this has the potential of saving a ton of bandwidth.

@ghys ghys changed the title "Cacheability" option for critical REST resources [WIP] "Cacheability" option for critical REST resources Jan 24, 2023
Signed-off-by: Yannick Schaus <github@schaus.net>
@ghys ghys changed the title [WIP] "Cacheability" option for critical REST resources "Cacheability" option for critical REST resources Jan 24, 2023
ghys added a commit to ghys/openhab-webui that referenced this pull request Jan 24, 2023
Depends on openhab/openhab-core#3335.

Will fetch items, things and rules with the `cacheable` parameter set
when appropriate.

Closes openhab#1650.

Signed-off-by: Yannick Schaus <github@schaus.net>
ghys added a commit to ghys/openhab-webui that referenced this pull request Jan 24, 2023
Depends on openhab/openhab-core#3335.

Will fetch items, things and rules with the `cacheable` parameter set
when appropriate.

Closes openhab#1650.

Signed-off-by: Yannick Schaus <github@schaus.net>
@J-N-K J-N-K added the enhancement An enhancement or new feature of the Core label Jan 24, 2023
@QueryParam("tags") final @Nullable List<String> tags,
@QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary) {
@QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary,
@DefaultValue("false") @QueryParam("cacheable") @Parameter(description = "provides a cacheable list and honors the If-Modified-Since header, all other parameters are ignored") boolean cacheable) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's a good naming. It is not clear what is actually returned if cachable=true is used. It would be better to add a parameter to return structural/static content only (i.e. without status/state). If that is set (which means, this is data that is likely not going to change), the If-Modified-Since header should be processed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to add a parameter to return structural/static content only (i.e. without status/state)

That's basically that this parameter does, I'll admit it was not my first choice either, but after thinking long and hard this is the best I could come up with. It conveys that it will only output information that is ready to be cached (and nothing more) along with the headers to let the browsers do it.

I'm open to suggestions to improve the name 🙂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about "suppressRuntimeData"? Or "limitToStaticData"?

Signed-off-by: Yannick Schaus <github@schaus.net>
Signed-off-by: Yannick Schaus <github@schaus.net>
@florian-h05
Copy link
Contributor

@ghys @J-N-K What‘s the state here? This looks like a great improvement and I‘d really like to have it for openHAB 4, so sorry for pinging!

@ghys
Copy link
Member Author

ghys commented May 7, 2023

I've switched OSes and I don't have a working Java development environment anymore, at least at the moment. I think the debate was mostly about the name of the parameter so it could be decided and the code could be updated.

@florian-h05
Copy link
Contributor

Okay, thanks for the status.
No hurries, openHAB 4 is coming closer but I think there is enough time left to finish this PR and the UI part of it.

Regarding the naming, something like staticDataOnly would IMO be a good option.

@J-N-K
Copy link
Member

J-N-K commented May 29, 2023

@ghys I like the naming suggested by @florian-h05. Would you update this PR and rebase, so we can merge?

@florian-h05
Copy link
Contributor

DCO is failing, but other then this it seems finished to me, ping @J-N-K.

Copy link
Member

@J-N-K J-N-K left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@J-N-K
Copy link
Member

J-N-K commented Jun 14, 2023

@ghys Can you rebase to solve the merge conflict and check the sign-off of you last commit?

Signed-off-by: Yannick Schaus <github@schaus.net>
@ghys
Copy link
Member Author

ghys commented Jun 15, 2023

Yeah please bear with me, I'm using this PR and made the changes "in the blind" to check out the GitHub Web IDE (https://github.dev/openhab/openhab-core/pull/3335) as a contributor.
It didn't sign-off that one commit, maybe you can set it to pass this one time, the sign-off line is the same as the others, you know the commit is from me.

As for the rebase this is also a good opportunity to test https://github.blog/changelog/2022-02-03-more-ways-to-keep-your-pull-request-branch-up-to-date/, I've activated it on the webui repo for a while (it's under "General" now, they've designed the settings) so you can rebase yourself if needed, it's always nice to have the option instead of asking the contributor when merging stale PRs.

-- 
Signed-off-by: Yannick Schaus <github@schaus.net>
…nhab/core/automation/rest/internal/RuleResource.java

Signed-off-by: J-N-K <github@klug.nrw>
…nhab/core/automation/rest/internal/RuleResource.java

Signed-off-by: J-N-K <github@klug.nrw>
@J-N-K J-N-K merged commit 6e83d3f into openhab:main Jun 15, 2023
@J-N-K J-N-K added the REST/SSE label Jun 15, 2023
@J-N-K J-N-K changed the title "Cacheability" option for critical REST resources [rest] Add caching for static resources Jun 15, 2023
@J-N-K J-N-K added this to the 4.0 milestone Jun 15, 2023
florian-h05 pushed a commit to openhab/openhab-webui that referenced this pull request Jul 2, 2023
Closes #1650.
Depends on openhab/openhab-core#3335.

Will fetch items, things and rules with the `staticDataOnly` parameter set when appropriate.

Also-by: Florian Hotze <florianh_dev@icloud.com>
Signed-off-by: Yannick Schaus <github@schaus.net>
lolodomo added a commit to lolodomo/openhab-core that referenced this pull request Jul 5, 2023
Fix openhab#3679

If not, calls of method limitToFields by PR openhab#3335 fails when the list of
fields does not contain firmwareStatus.

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
J-N-K pushed a commit that referenced this pull request Jul 5, 2023
Fix #3679

If not, calls of method limitToFields by PR #3335 fails when the list of
fields does not contain firmwareStatus.

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
splatch pushed a commit to ConnectorIO/copybara-hab-core that referenced this pull request Jul 12, 2023
* Closes openhab#3329.

This implements a new optional `cacheable` parameter for these REST endpoints:
- `/rest/items`
- `/rest/things`
- `/rest/rules`

When this parameter is set, a flat list of all elements excluding
non-cacheable fields (e.g. "state", "transformedState", "stateDescription",
"commandDescription" for items, "statusInfo", "firmwareStatus",
"properties" for things, "status" for rules) will be retrieved along with
a `Last-Modified` HTTP response header. When unknown, the Last-Modified
header will be set to the date of the request.

Also only when this parameter is set, and a `If-Modified-Since` header is
found in the request, that header will be compared to the last known
modified date for the corresponding cacheable list. The last modified date
will be reset when any change is made on the elements of the underlying
registry. If the `If-Modified-Since` date is equal or more recent than the
last modified date, then a 304 Not Modified response with no content will
be served instead of the usual 200 OK, informing the client that its
cache is still valid at the provided date.

All other request parameters will be ignored except for "metadata" in the
`/rest/items` endpoint. When a metadata selector is set, the resulting
item list will be considered like a completely different resource, i.e.
it will have its own last modified date. Regarding metadata, the approach
to invalidating last modified dates is very conservative: when any metadata
is changed, all cacheable lists of items will have their last modified date
reset even if the change was in a metadata namespace that wasn't requested.

This also implements the abovedescribed behavior for the
`/rest/ui/components/{namespace}` endpoint, but no `cacheable` parameter
is necessary. The last modified date is tracked by namespace.

Signed-off-by: Yannick Schaus <github@schaus.net>
GitOrigin-RevId: 6e83d3f
splatch pushed a commit to ConnectorIO/copybara-hab-core that referenced this pull request Jul 12, 2023
Fix openhab#3679

If not, calls of method limitToFields by PR openhab#3335 fails when the list of
fields does not contain firmwareStatus.

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
GitOrigin-RevId: e9719eb
CacheControl cc = new CacheControl();
cc.setMustRevalidate(true);
cc.setPrivate(true);
thingStream = dtoMapper.limitToFields(thingStream, "UID,label,bridgeUID,thingTypeUID,location,editable");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ghys @J-N-K Is is possible that channels change without a notification to the registry?

If not, we should add channels here to fix openhab/openhab-webui#1996 - the alternative would be to not use the cached list there.

Copy link
Contributor

@florian-h05 florian-h05 Aug 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@J-N-K Sorry for pinging again, but I think you can answer my question above.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically it might be possible, but I would consider it a bug in the binding. You might be able to edit the thing, but if you do it right (using ThingBuilder thingBuilder = editThing(); and updateThing(thingBuilder.build);) you'll get a notification about an updated thing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ghys @J-N-K Is is possible that channels change without a notification to the registry?

If not, we should add channels here to fix openhab/openhab-webui#1996 - the alternative would be to not use the cached list there.

it's IMO way better to just change the channeltype-picker in the UI to not use cached lists. There is no need to add channels (a huge chunk of the response payload, some things have dozens if not hundreds of channels), they're not used apart from that picker and there is only one instance of them in the entire UI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement An enhancement or new feature of the Core REST/SSE
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support Last-Modified or ETag headers on critical REST API endpoints
3 participants