forked from mirrors/gecko-dev
Automatic update from web-platform-tests HTTP cache: Fix http-cache.js Header Fields Too Large. (#29866) Due to a bug in http-cache.js, sending multiple requests can cause: ``` 431 Request Header Fields Too Large ``` That's because the base64 encoded list of requests was added to ... the list of requests ... iteratively. So the size of request was growing quadratically relatively to the number of requests. This has been fixed by copying the list of request instead of taking a new reference. Explanation: The code was executing iteratively: - config = requests[idx]; - fetchInit(requests, config); - init.headers = config['request_headers']; - init.headers.push(['Test-Request', btoa(JSON.stringify(requests))]); which equivalent to: - requests[idx].requests_headers.push( ['Test-Request'], btoa(JSON.stringify(requests))]), So we stringify "requests" and append the result into "requests" iteratively => Boom. Bug: 1221529 Change-Id: Ib9468d95daca2987dd7c391551387f3107dcb1bc Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3063967 Commit-Queue: Arthur Sonzogni <arthursonzogni@chromium.org> Reviewed-by: Maksim Orlovich <morlovich@chromium.org> Cr-Commit-Position: refs/heads/master@{#908039} Co-authored-by: Arthur Sonzogni <arthursonzogni@chromium.org> -- wpt-commits: 34e20f134472dc923d02206fe13819681ca63155 wpt-pr: 29866
274 lines
8.6 KiB
JavaScript
274 lines
8.6 KiB
JavaScript
/* global btoa fetch token promise_test step_timeout */
|
|
/* global assert_equals assert_true assert_own_property assert_throws_js assert_less_than */
|
|
|
|
const templates = {
|
|
'fresh': {
|
|
'response_headers': [
|
|
['Expires', 100000],
|
|
['Last-Modified', 0]
|
|
]
|
|
},
|
|
'stale': {
|
|
'response_headers': [
|
|
['Expires', -5000],
|
|
['Last-Modified', -100000]
|
|
]
|
|
},
|
|
'lcl_response': {
|
|
'response_headers': [
|
|
['Location', 'location_target'],
|
|
['Content-Location', 'content_location_target']
|
|
]
|
|
},
|
|
'location': {
|
|
'query_arg': 'location_target',
|
|
'response_headers': [
|
|
['Expires', 100000],
|
|
['Last-Modified', 0]
|
|
]
|
|
},
|
|
'content_location': {
|
|
'query_arg': 'content_location_target',
|
|
'response_headers': [
|
|
['Expires', 100000],
|
|
['Last-Modified', 0]
|
|
]
|
|
}
|
|
}
|
|
|
|
const noBodyStatus = new Set([204, 304])
|
|
|
|
function makeTest (test) {
|
|
return function () {
|
|
var uuid = token()
|
|
var requests = expandTemplates(test)
|
|
var fetchFunctions = makeFetchFunctions(requests, uuid)
|
|
return runTest(fetchFunctions, requests, uuid)
|
|
}
|
|
}
|
|
|
|
function makeFetchFunctions(requests, uuid) {
|
|
var fetchFunctions = []
|
|
for (let i = 0; i < requests.length; ++i) {
|
|
fetchFunctions.push({
|
|
code: function (idx) {
|
|
var config = requests[idx]
|
|
var url = makeTestUrl(uuid, config)
|
|
var init = fetchInit(requests, config)
|
|
return fetch(url, init)
|
|
.then(makeCheckResponse(idx, config))
|
|
.then(makeCheckResponseBody(config, uuid), function (reason) {
|
|
if ('expected_type' in config && config.expected_type === 'error') {
|
|
assert_throws_js(TypeError, function () { throw reason })
|
|
} else {
|
|
throw reason
|
|
}
|
|
})
|
|
},
|
|
pauseAfter: 'pause_after' in requests[i]
|
|
})
|
|
}
|
|
return fetchFunctions
|
|
}
|
|
|
|
function runTest(fetchFunctions, requests, uuid) {
|
|
var idx = 0
|
|
function runNextStep () {
|
|
if (fetchFunctions.length) {
|
|
var nextFetchFunction = fetchFunctions.shift()
|
|
if (nextFetchFunction.pauseAfter === true) {
|
|
return nextFetchFunction.code(idx++)
|
|
.then(pause)
|
|
.then(runNextStep)
|
|
} else {
|
|
return nextFetchFunction.code(idx++)
|
|
.then(runNextStep)
|
|
}
|
|
} else {
|
|
return Promise.resolve()
|
|
}
|
|
}
|
|
|
|
return runNextStep()
|
|
.then(function () {
|
|
return getServerState(uuid)
|
|
}).then(function (testState) {
|
|
checkRequests(requests, testState)
|
|
return Promise.resolve()
|
|
})
|
|
}
|
|
|
|
function expandTemplates (test) {
|
|
var rawRequests = test.requests
|
|
var requests = []
|
|
for (let i = 0; i < rawRequests.length; i++) {
|
|
var request = rawRequests[i]
|
|
request.name = test.name
|
|
if ('template' in request) {
|
|
var template = templates[request['template']]
|
|
for (let member in template) {
|
|
if (!request.hasOwnProperty(member)) {
|
|
request[member] = template[member]
|
|
}
|
|
}
|
|
}
|
|
requests.push(request)
|
|
}
|
|
return requests
|
|
}
|
|
|
|
function fetchInit (requests, config) {
|
|
var init = {
|
|
'headers': []
|
|
}
|
|
if ('request_method' in config) init.method = config['request_method']
|
|
// Note: init.headers must be a copy of config['request_headers'] array,
|
|
// because new elements are added later.
|
|
if ('request_headers' in config) init.headers = [...config['request_headers']];
|
|
if ('name' in config) init.headers.push(['Test-Name', config.name])
|
|
if ('request_body' in config) init.body = config['request_body']
|
|
if ('mode' in config) init.mode = config['mode']
|
|
if ('credentials' in config) init.credentials = config['credentials']
|
|
if ('cache' in config) init.cache = config['cache']
|
|
init.headers.push(['Test-Requests', btoa(JSON.stringify(requests))])
|
|
return init
|
|
}
|
|
|
|
function makeCheckResponse (idx, config) {
|
|
return function checkResponse (response) {
|
|
var reqNum = idx + 1
|
|
var resNum = parseInt(response.headers.get('Server-Request-Count'))
|
|
if ('expected_type' in config) {
|
|
if (config.expected_type === 'error') {
|
|
assert_true(false, `Request ${reqNum} doesn't throw an error`)
|
|
return response.text()
|
|
}
|
|
if (config.expected_type === 'cached') {
|
|
assert_less_than(resNum, reqNum, `Response ${reqNum} does not come from cache`)
|
|
}
|
|
if (config.expected_type === 'not_cached') {
|
|
assert_equals(resNum, reqNum, `Response ${reqNum} comes from cache`)
|
|
}
|
|
}
|
|
if ('expected_status' in config) {
|
|
assert_equals(response.status, config.expected_status,
|
|
`Response ${reqNum} status is ${response.status}, not ${config.expected_status}`)
|
|
} else if ('response_status' in config) {
|
|
assert_equals(response.status, config.response_status[0],
|
|
`Response ${reqNum} status is ${response.status}, not ${config.response_status[0]}`)
|
|
} else {
|
|
assert_equals(response.status, 200, `Response ${reqNum} status is ${response.status}, not 200`)
|
|
}
|
|
if ('response_headers' in config) {
|
|
config.response_headers.forEach(function (header) {
|
|
if (header.len < 3 || header[2] === true) {
|
|
assert_equals(response.headers.get(header[0]), header[1],
|
|
`Response ${reqNum} header ${header[0]} is "${response.headers.get(header[0])}", not "${header[1]}"`)
|
|
}
|
|
})
|
|
}
|
|
if ('expected_response_headers' in config) {
|
|
config.expected_response_headers.forEach(function (header) {
|
|
assert_equals(response.headers.get(header[0]), header[1],
|
|
`Response ${reqNum} header ${header[0]} is "${response.headers.get(header[0])}", not "${header[1]}"`)
|
|
})
|
|
}
|
|
return response.text()
|
|
}
|
|
}
|
|
|
|
function makeCheckResponseBody (config, uuid) {
|
|
return function checkResponseBody (resBody) {
|
|
var statusCode = 200
|
|
if ('response_status' in config) {
|
|
statusCode = config.response_status[0]
|
|
}
|
|
if ('expected_response_text' in config) {
|
|
if (config.expected_response_text !== null) {
|
|
assert_equals(resBody, config.expected_response_text,
|
|
`Response body is "${resBody}", not expected "${config.expected_response_text}"`)
|
|
}
|
|
} else if ('response_body' in config && config.response_body !== null) {
|
|
assert_equals(resBody, config.response_body,
|
|
`Response body is "${resBody}", not sent "${config.response_body}"`)
|
|
} else if (!noBodyStatus.has(statusCode)) {
|
|
assert_equals(resBody, uuid, `Response body is "${resBody}", not default "${uuid}"`)
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkRequests (requests, testState) {
|
|
var testIdx = 0
|
|
for (let i = 0; i < requests.length; ++i) {
|
|
var expectedValidatingHeaders = []
|
|
var config = requests[i]
|
|
var serverRequest = testState[testIdx]
|
|
var reqNum = i + 1
|
|
if ('expected_type' in config) {
|
|
if (config.expected_type === 'cached') continue // the server will not see the request
|
|
if (config.expected_type === 'etag_validated') {
|
|
expectedValidatingHeaders.push('if-none-match')
|
|
}
|
|
if (config.expected_type === 'lm_validated') {
|
|
expectedValidatingHeaders.push('if-modified-since')
|
|
}
|
|
}
|
|
testIdx++
|
|
expectedValidatingHeaders.forEach(vhdr => {
|
|
assert_own_property(serverRequest.request_headers, vhdr,
|
|
`request ${reqNum} doesn't have ${vhdr} header`)
|
|
})
|
|
if ('expected_request_headers' in config) {
|
|
config.expected_request_headers.forEach(expectedHdr => {
|
|
assert_equals(serverRequest.request_headers[expectedHdr[0].toLowerCase()], expectedHdr[1],
|
|
`request ${reqNum} header ${expectedHdr[0]} value is "${serverRequest.request_headers[expectedHdr[0].toLowerCase()]}", not "${expectedHdr[1]}"`)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
function pause () {
|
|
return new Promise(function (resolve, reject) {
|
|
step_timeout(function () {
|
|
return resolve()
|
|
}, 3000)
|
|
})
|
|
}
|
|
|
|
function makeTestUrl (uuid, config) {
|
|
var arg = ''
|
|
var base_url = ''
|
|
if ('base_url' in config) {
|
|
base_url = config.base_url
|
|
}
|
|
if ('query_arg' in config) {
|
|
arg = `&target=${config.query_arg}`
|
|
}
|
|
return `${base_url}resources/http-cache.py?dispatch=test&uuid=${uuid}${arg}`
|
|
}
|
|
|
|
function getServerState (uuid) {
|
|
return fetch(`resources/http-cache.py?dispatch=state&uuid=${uuid}`)
|
|
.then(function (response) {
|
|
return response.text()
|
|
}).then(function (text) {
|
|
return JSON.parse(text) || []
|
|
})
|
|
}
|
|
|
|
function run_tests (tests) {
|
|
tests.forEach(function (test) {
|
|
promise_test(makeTest(test), test.name)
|
|
})
|
|
}
|
|
|
|
var contentStore = {}
|
|
function http_content (csKey) {
|
|
if (csKey in contentStore) {
|
|
return contentStore[csKey]
|
|
} else {
|
|
var content = btoa(Math.random() * Date.now())
|
|
contentStore[csKey] = content
|
|
return content
|
|
}
|
|
}
|