Обработка потокового ответа приводит к противоречивым результатам на разных страницах и в браузерах.

У меня возникли проблемы с обработкой 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("<", "&lt;")
                                modalBody.scrollTo({
                                    top: (modalBody.scrollHeight - modalBody.clientHeight),
                                    left: 0,
                                    behavior: 'instant'
                                })
                            } else {
                                rholder.innerHTML = ccontent + "<br>\n" + this_response.replaceAll("<", "&lt;")
                            }
                        }
                    }
                })
            }
        })
    }

Однако, когда я пробую то же самое на другой странице, которая вызывает другую конечную точку, с аналогичным кодом 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("<", "&lt;")
                    }
                }
            })
        }

Выше вы увидите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 каждый раз работает нормально?

0 ответов

Другие вопросы по тегам