Обработка потокового ответа приводит к противоречивым результатам на разных страницах и в браузерах.
У меня возникли проблемы с обработкой StreamingResponse из FastAPI, в частности, мне нужно добавить каждый ответ из StreamingResponse в модальное тело (или любой другой элемент HTML). Однако по какой-то причине результат противоречив.
у меня есть конечная точка/watcher
и как только действие, которое обслуживает StreamingResponse из конечной точки FastAPI, инициируется приведенным ниже javascript, я получаю ожидаемый результат, и модальное тело заполняется каждый раз, когда отправляется ответ. И это прекрасно обрабатывается в Chrome (Windows 114.0.5735.199), Firefox (Windows 114.0.2) и Firefox (Linux Fedora 107.0).
function tailLog(servername) {
try {
document.getElementsByClassName("modal-dialog modal-dialog-centered modal-xl")[0].classList = "modal-dialog modal-dialog-centered modal-fullscreen"
} catch {
// none
}
var last_response_len = false;
tg = document.getElementById("wfDebugger-body")
tg.innerHTML = "<p>Info msg.</p>"
btnholder = document.getElementById("wfDebugger-btn-holder")
btnholder.innerHTML = ""
modalHeader = document.getElementById("wfDebugger-header")
modalHeader.innerHTML = ""
modalHeaderTitle = document.createElement("h5")
modalHeaderTitle.setAttribute("class", "modal-title")
modalHeaderTitle.innerHTML = "Live Log Viewer"
modalHeader.appendChild(modalHeaderTitle)
rholder = document.createElement("code")
rholder.id = "responseHolder"
rholder.style.whiteSpace = "pre"
tg.appendChild(rholder)
btn = document.createElement("button")
btn.setAttribute("type", "button")
btn.setAttribute("class", "btn btn-secondary ms-3")
btn.setAttribute("onclick", "stopTailLog(this)")
btn.setAttribute("data-servername", servername)
btn.innerHTML = "Stop View"
closebtn = document.createElement("button")
closebtn.setAttribute("id", "wfDebugger-close-btn")
closebtn.setAttribute("type", "button")
closebtn.setAttribute("class", "btn btn-info ms-3")
closebtn.setAttribute("data-bs-dismiss", "modal")
closebtn.setAttribute("data-bs-target", "wfDebugger")
closebtn.setAttribute("onclick", "stopTailLog(this)")
closebtn.setAttribute("data-servername", servername)
closebtn.innerHTML = "Close"
modalCloseDiv = document.createElement("div")
modalCloseDiv.setAttribute("class", "modal-title")
modalCloseDiv.appendChild(btn)
modalCloseDiv.appendChild(closebtn)
modalHeader.appendChild(modalCloseDiv)
$.ajax({
url: "/watcher/" + servername + "?action=wildfly_getlogid",
success: function(data) {
btn.setAttribute("data-logid", data)
closebtn.setAttribute("data-logid", data)
$.ajax({
url: "/watcher/" + servername + "/wildfly_getlog?data=" + data,
method: "POST",
complete: function() {
$.ajax({ url: "/watcher/" + servername + "/wildfly_killlog?data=" + data, method: "POST" })
},
xhrFields: {
onprogress: function(e) {
var this_response, response = e.currentTarget.response;
if(last_response_len === false)
{
this_response = response;
last_response_len = response.length;
}
else
{
this_response = response.substring(last_response_len);
last_response_len = response.length;
}
ccontent = rholder.innerHTML
modalBody = document.getElementById("wfDebugger-body")
if (modalBody.scrollTop == (modalBody.scrollHeight - modalBody.clientHeight)) {
rholder.innerHTML = ccontent + "<br>\n" + this_response.replaceAll("<", "<")
modalBody.scrollTo({
top: (modalBody.scrollHeight - modalBody.clientHeight),
left: 0,
behavior: 'instant'
})
} else {
rholder.innerHTML = ccontent + "<br>\n" + this_response.replaceAll("<", "<")
}
}
}
})
}
})
}
Однако, когда я пробую то же самое на другой странице, которая вызывает другую конечную точку, с аналогичным кодом JavaScript, я получаю полный ответ только в Chrome (Windows 114.0.5735.199) только после его завершения, но отлично работает как в Firefox в Windows, так и в Linux. Понятия не имею почему. Это код, который вызывает проблемы в Chrome.
function startStopInstance(action, instanceId, instanceName, btn, refreshBtn) {
modal = initModal()
var last_response_len = false;
rholder = document.createElement("code")
rholder.id = "responseHolder"
rholder.style.whiteSpace = "pre"
DevOpsModalBody.appendChild(rholder)
var title
if (action == 'start') {
title = `Starting instance: '${instanceName}'...`
}
else if (action == 'stop') {
title = `Stopping instance: '${instanceName}'...`
}
else if (action == 'restart') {
title = `Restarting instance: '${instanceName}'...`
}
else if (action == "increase_volume_by_") {
title = `Increasing volume size for instance: '${instanceName}'...`
const _size = document.getElementById(`size_${instanceId}`).value
action = `increase_volume_by_${_size}`
}
const spinner = createSpinner()
const modalSpinner = createSpinner()
// Disable btn while request is running
btn.disabled = true
btn.appendChild(spinner)
DevOpsModalTitle.textContent = title
DevOpsModalTitle.appendChild(modalSpinner)
DevOpsModalCloseBtn.hidden = true
DevOpsModalSaveBtn.hidden = true
modal.show()
$.ajax({
url: `/instance-action/${instanceId}/${action}`,
method: "POST",
complete: function() {
btn.disabled = false;
spinner.remove();
modalSpinner.remove();
DevOpsModalCloseBtn.hidden = false;
},
xhrFields: {
onprogress: function(e) {
console.log(e)
var this_response, response = e.currentTarget.response;
if(last_response_len === false)
{
this_response = response;
last_response_len = response.length;
}
else
{
this_response = response.substring(last_response_len);
last_response_len = response.length;
}
ccontent = rholder.innerHTML
rholder.innerHTML = ccontent + "<br>\n" + this_response.replaceAll("<", "<")
}
}
})
}
Выше вы увидитеcreateElement
иcreateSpinner
но эти функции — не что иное, как ярлык парыdocument.createElement
с определенными атрибутами, чтобы получить нужный мне элемент. Обратный вызов xhrFields изменен, чтобы не проверять, просматривает ли пользователь уже последнюю строку для прокрутки, вместо этого просто добавляет данные - так довольно просто, но все еще не работает в Chrome.
Обе конечные точки имеют одинаковый оператор возврата в FastAPI:
return StreamingResponse(getwflogs(serverip, data), media_type="text/plain") # The one that works on all three browsers
return StreamingResponse(func(instance_id), media_type='text/plain') # The one that works only in firefox
async def getwflogs(serverip, logid):
async with AsyncClient(headers=api_auth, timeout=None) as client:
req = client.build_request("POST", f"http://{serverip}:3322/api/service/getlog/{logid}")
r = await client.send(req, stream=True)
if r.status_code == 200:
async for i in r.aiter_text():
yield i
async def func(instance_id: str, size: int):
cfg = load_settings()
session = aioboto3.Session(aws_access_key_id=cfg.aws.key, aws_secret_access_key=cfg.aws.secret_key, region_name=cfg.aws.region)
partition_number = 0
async with session.resource('ec2') as res, session.client('ec2') as client:
yield "Loading instance information\n"
instance = await res.Instance(instance_id)
async for v in instance.volumes.all():
partition_number += 1
yield f"Processing volume id: {v.id}. Increasing size from {await v.size} to {await v.size + size}...\n"
_res = await client.modify_volume(DryRun=False, VolumeId=v.id, Size=await v.size + size)
if jpsearch("VolumeModification.ModificationState", _res) == 'modifying':
_res = await client.describe_volumes_modifications(VolumeIds=[v.id])
while not jpsearch("VolumesModifications[].ModificationState", _res) == ['completed']:
_res = await client.describe_volumes_modifications(VolumeIds=[v.id])
yield f"Size of volume id: {v.id} successfully increased. Please go to server and execute:\nsudo growpart /dev/nvme0n1 {partition_number}\nsudo resize2fs /dev/nvme0n1p{partition_number}\n"
Любая идея, что может быть причиной такого поведения. Почему Chrome получает полный ответ потоковой передачи после того, как он полностью завершен для одной из двух страниц/конечных точек, но Firefox каждый раз работает нормально?