Repository navigation
Send the client-side caching headers, and one ETag for the same evidence - #44
Merged
Merged
Conversation
The tests ask for the Cache-Control, Vary and weak ETag headers of the web integration specification's cache header detail on both client-side endpoints, the same ETag for the same evidence in two processes, a 304 for a matching If-None-Match on a GET and a 412 on a POST, as RFC 9110 section 13.1.2 requires, and private, no-cache with no ETag on a response to a request that recorded errors, while a matching If-None-Match still gets its 304.
Node's client-side JSON and JavaScript endpoints are written in each web example, and none sent Cache-Control, Vary or an ETag, or answered If-None-Match, so they had none of the web integration specification's cache header detail. Helpers.writeClientSideResponse writes either endpoint's response with those headers. The ETag is a SHA-256 of the evidence the request carries, read from the request again, so evidence processing adds, such as the new session id the sequence element makes, takes no part, and leaving out the request's precondition and cache headers, so that a revalidation can match. The same evidence therefore gives the same ETag in every process. It is weak, as RFC 9110 section 8.8.3 asks of a validator that can stay the same when the response changes. A matching If-None-Match gets a 304 with the same Cache-Control, Vary and ETag to a GET or HEAD, and a 412 to any other method, as RFC 9110 sections 13.1.2 and 15.4.5 require. A response to a request whose processing recorded errors carries Cache-Control private, no-cache and no ETag, and a matching If-None-Match still gets its 304. The web examples of Device Detection, IP Intelligence, location and the pipeline's custom element example use it for every client-side endpoint.
…sing A browser can stop waiting while the pipeline processes, and the request's socket then has no addresses. Reading the request again for the ETag then threw a TypeError, which stopped the Device Detection cloud web example during the Selenium contract tests.
writeClientSideResponse read the request again for its ETag after processing. A browser can stop waiting while the pipeline processes, and the request's socket then has no addresses, so the read threw a TypeError. In the Selenium contract tests on macOS this stopped the Device Detection cloud web example. addFromRequest now keeps every value it reads, under its evidence key, in requestEvidence, and the ETag is built from that, hashing only the evidence an element accepts, so it no longer touches the request. Evidence added by hand has no such record, so its ETag is built from the flow data's evidence.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
For 51Degrees/specifications#53.
The problem
The web integration specification's cache header detail asks the client-side JSON and JavaScript endpoints for
Cache-Control,Varyand anETag, and a 304 whenIf-None-Matchmatches. Specifications#53 adds that the same evidence gives the same ETag in every process, and that a response to a request whose processing recorded errors carriesCache-Control: private, no-cacheand no ETag, while a matchingIf-None-Matchstill gets its 304.Node has no web integration package, so its client-side endpoints are written in each web example, and none of them did any of this. On
rc(21fa119) they set onlyContent-Typeand the body.gettingstarted-web,/jsonand/51Degrees.core.jsgettingstarted-web, bothmixed/gettingstarted-web, bothwebIntegration.js,/jsonclientSideEvidenceFlowElement.js,/jsonThe
/jsonof IP Intelligence's on-premisemixed/gettingstarted-apistands in for the cloud service rather than being a client-side endpoint, so it is unchanged.What the specifications say
Read in full, with RFC 9110 and RFC 9111 checked as current at rfc-editor.org (neither is obsoleted or updated).
If-None-Matchthere gets a 412, not a 304.W/. A strong validator "changes value whenever a change occurs to the representation data" (section 8.8.1). A hash of the evidence stays the same when the answer changes, for example after a data file update, so it is weak.no-cacheandprivate, as specifications#53 cites.The open draft 51Degrees/specifications#81 writes the RFC 9110 rules above into the cache header detail. This change follows RFC 9110 whether or not that draft is merged, as was decided for the 412 on 7 November.
What changed
Helpers.writeClientSideResponse(request, response, flowData, contentType, body, options)infiftyone.pipeline.corewrites either endpoint's response.W/"<32 hex digits>", the start of a SHA-256 of the evidence the request carries, keys sorted. It is built from whataddFromRequestread from the request, whichaddFromRequestkeeps inevidence.requestEvidence, hashing only the keys an element accepts, rather than from the flow data's evidence, because processing adds evidence of its own. Evidence added by hand has no such record, so its ETag comes from the flow data's evidence. The sequence element makes a new random session id for every request without one, and with that in the hash no two responses had the same ETag, which a first attempt at the fix showed (run 42397140186). The request's precondition headers (RFC 9110 section 13.1) and cache directives (Cache-ControlandPragma, RFC 9111 sections 5.2.1 and 5.4) are left out, because they say how to answer rather than what to answer, and the JavaScript builder takes everyheader.key as evidence, so a revalidation's ownIf-None-Matchwould otherwise change the ETag it is checked against. Nothing in the hash is chosen per process, so the same evidence gives the same ETag in every process.private, max-age=1800, with the age set byoptions.maxAgeSecondsas the specification's "Max-age SHOULD be configurable" asks. After errors,private, no-cache.header.evidence keys of every element whose filter lists its keys, as the Java pipeline does, such asuser-agent.If-None-Match. A GET or HEAD gets a 304 with no body and the Cache-Control, Vary and ETag a 200 would carry, which after errors meansprivate, no-cacheand no ETag. Any other method gets a 412 with no caching headers.Content-Type,Content-Lengthin bytes and the body, and no body for a HEAD.The seven endpoints above call it in place of setting the status, the content type and the body.
How it was verified
Red, the tests alone on
rc(run 42397458626, at 28f0425), and green, with the fix (run 42397637050, at a1048b8). Both ran onubuntu-latestwith Node 24 from a temporary branch holding this branch plus one workflow, which has been deleted. The Device Detection cloud example ran with the organisation's Bespoke resource key.tests/clientSideCaching.test.jsinfiftyone.pipeline.coreserves both endpoints from a pipeline with one element that reads the User-Agent and can be made to fail.private, max-age=1800,Vary: user-agent, a weak ETag and the rightContent-Length, on both endpointsHelpers.writeClientSideResponse is not a functiontests/clientSideETag.jsIf-None-Matchwith the ETag, its strong form, a list holding it, and*each get a 304 with no body and the same ETag, Cache-Control and Vary, and another tag gets a 200If-None-Matchmatches gets a 412private, no-cacheand no ETag, a matchingIf-None-Matchstill gets a 304 withprivate, no-cacheand no ETag, and once it works again the ETag is the one it wasThe Device Detection cloud
gettingstarted-webtest gainscloud getting started web caching headers, which asks the real example for the script, then for it again with its ETag inIf-None-Match, and posts to/json. Onrcit failed withExpected: "private, max-age=1800"andReceived: undefined. With the fix it passed, the second request getting a 304 with the same ETag.Both runs also ran the IP Intelligence cloud mixed web example test and the location tests (7 passed), every other
fiftyone.pipeline.coretest (234 passed on red, all 239 on green), and eslint on every changed folder, which passed. The committedtypes/helpers.d.tsis whatnpm run tscgave in the green run.A later fix: the ETag no longer reads the request after processing
The full build of #45, which holds this branch, failed on macOS with Node 24 (run 42418797979). The Selenium contract tests stopped the Device Detection cloud web example with
TypeError: Cannot read properties of undefined (reading 'toString')atrequest.connection.localAddress.toString(), called fromclientSideETag. The ETag was then built by reading the request again after processing, and a browser can stop waiting while the pipeline asks the cloud, which leaves the request's socket with no addresses.Commit 6554046 adds
the ETag does not read the request again after processing, which processes a request and then takes its connection away before writing the response. It failed on run 42420811997, withTests: 1 failed, 5 passed, 6 total. Commit 1d4caad keeps every valueaddFromRequestreads inrequestEvidenceand builds the ETag from that. All six core caching tests, both example tests and all 240 core tests passed on run 42420786946.What remains
51D_cookie orserver.host-ip, no longer change it.rc, and passed on all eight entries (run 42539224557). It had also passed on Accept no evidence in usage sharing, and share the web request it finds #45, which holds this branch, Selenium contract tests included (run 42425263121). The Device Detection on-premise web example, which needs its native module, has no test that asks for these headers.compute_etag) and the Java pipeline an unquoted number (ClientsidePropertyServiceCore.java). The specification does not say which, and RFC 9110 section 8.8.3 asks for weak.