Сервер отправляет события с помощью AJAX: как разрешить SSE GET с помощью XHR POST? - PullRequest
0 голосов
/ 02 января 2019

Я пытаюсь решить проблему между, как я понимаю, AJAX и событиями, отправленными сервером. У меня есть приложение, которое отправляет сообщение с некоторыми инструкциями контроллеру, и я хотел бы, чтобы контроллер отправил некоторый комментарий назад как событие, чтобы сообщить пользователю, что запрошенное действие было выполнено (может иметь ошибки или занять некоторое время) .

Идея состоит в том, что пользователь может отправить пакет различных инструкций через клиента, а сервер сообщит через SSE, когда каждое из этих действий будет выполнено.

Проблема, которую я вижу в Fiddler, заключается в том, что когда сообщение выполняется, ответ, который он возвращает, содержит мое сообщение источника событий, которое я хотел бы использовать. Тем не менее, код источника событий также, по-видимому, вызывает GET, в котором он хочет получить это сообщение источника событий. Поскольку этого не происходит, соединение постоянно закрывается.

В настоящее время у меня есть некоторый код контроллера, например:

    [System.Web.Http.HttpPost]
    public void Stop(ProjectViewModel model)
    {
        ProjectManager manager = new ProjectManager();
        if (model.Servers != null && model.Servers.Count != 0)
        {
            string machine = model.Servers[0];
            foreach (string service in model.Services)
            {
                manager.StopService(service, machine);
                Message("stop", service);
            }
        }
    }

и, на мой взгляд, и Ajax / XHR, и сервер отправляют события, настроенные так:

var form = document.getElementById("submitform");

    form.onsubmit = function (e) {
        // stop the regular form submission
        e.preventDefault();

        // collect the form data while iterating over the inputs
        var data = {};
        for (var i = 0, ii = 2; i < ii; ++i) {
            var input = form[i];
            if (input.name == "Servers") {
                data[input.name] = document.getElementById("ServerSelect").options[document.getElementById("ServerSelect").selectedIndex].text;
            }
            else if (input.name == "Services")
                data[input.name] = document.getElementById("ServiceSelect").options[document.getElementById("ServiceSelect").selectedIndex].text;
        }
        if (action) { data["action"] = action };

        // construct an HTTP request
        var xhr = new XMLHttpRequest();
        if (action == "stop") {
            xhr.open(form.method, '/tools/project/stop', true);
        }

        if (action == "start") {
            xhr.open(form.method, '/tools/project/start', true)
        }

        xhr.setRequestHeader('Content-Type', 'application/json; charset=urf-8');



        // send the collected data as JSON
        xhr.send(JSON.stringify(data));

        xhr.onloadend = function () {
            // done
        };
    };

    function events() {
        if (window.EventSource == undefined) {
            // If not supported  
            document.getElementById('eventlog').innerHTML = "Your browser doesn't support Server Sent Events.";
        } else {
            var source = new EventSource('../tools/project/Stop');
            source.addEventListener("message", function (message) { console.log(message.data) });

            source.onopen = function (event) {
                document.getElementById('eventlog').innerHTML += 'Connection Opened.<br>';
                console.log("Open");
            };

            source.onerror = function (event) {
                if (event.eventPhase == EventSource.CLOSED) {
                    document.getElementById('eventlog').innerHTML += 'Connection Closed.<br>';
                    console.log("Close");
                }
            };

            source.onmessage = function (event) {
                //document.getElementById('eventlog').innerHTML += event.data + '<br>';
                var newElement = document.createElement("li");
                newElement.textContent = "message: " + event.data;
                document.getElementById("eventlog").appendChild(newElement)
                console.log("Message");
            };
        }
    };

Я немного новичок в веб-разработке и не знаю, как решить эту проблему. Есть ли способ, которым я могу прочитать сообщение источника событий из этого POST? Или он был отправлен в GET вместо того, чтобы быть отправленным в качестве ответа на POST? В целом, кажется, что самая ужасная проблема заключается в том, что я не могу получить сообщения о событиях, отправленные в GET, которые запрашиваются API источника событий.

РЕДАКТИРОВАТЬ: После публикации я попытался создать новый метод в контроллере, который специально обрабатывает запросы к источникам событий, но похоже, что ответ на событие все равно каким-то образом заканчивается в теле ответа POST.

        public void Message(string action, string service)
    {
        Response.ContentType = "text/event-stream";
        Response.CacheControl = "no-cache";
        //Response.Write($"event: message\n");
        if (action == "stop")
        {
            Response.Write($"data: <li> {service} has stopped </li>\n\n");
        }
        Response.Flush();
        Thread.Sleep(1000);
        Response.Close();
    }

1 Ответ

0 голосов
/ 03 января 2019

В итоге я решил это.Моя первоначальная идея состояла в том, чтобы передать view-модель в каждом из моих методов назад и вперед с Dictionary<string,string>, чтобы указать каждое событие, которое можно использовать, но view-модель не является постоянной.Я решил эту проблему дальше, внедрив события в Dictionary, сохраненные в Session данных, и использование Sessions for MVC можно найти в ресурсе, который я использовал:

https://code.msdn.microsoft.com/How-to-create-and-access-447ada98

Моя окончательная реализация выглядит следующим образом:

public void Stop(ProjectViewModel model)
    {
        ProjectManager manager = new ProjectManager();
        if (model.Servers != null && model.Servers.Count != 0)
        {
            string machine = model.Servers[0];
            foreach (string service in model.Services)
            {
                manager.StopService(service, machine);
                model.events.Add(service, "stopped");
                this.Session["Events"] = model.events;
            }
        }
        //return View(model);
    }

public void Message(ProjectViewModel model)
    {
        Thread.Sleep(1000);
        Response.ContentType = "text/event-stream";
        Response.CacheControl = "no-cache";
        Response.AddHeader("connection", "keep-alive");
        var events = this.Session["Events"] as Dictionary<string, string>;
        Response.Write($"event: message\n");
        if (events != null && events.Count != 0)
        {
            foreach (KeyValuePair<string, string> message in events)
            {
                Response.Write($"data: {message.Key} has been {message.Value}\n\n");
            }
        }
        Response.Flush();
        Thread.Sleep(1000);
        Response.Close();

    }

Добавление поддержки активности в качестве атрибута соединения в заголовке HTTP-ответа также важно для получения SSE для отправки, а Thread.Sleep(1000)используется из-за одновременного действия остановки и сообщения.Я уверен, что есть некоторые оптимизации, которые могут пойти на это, но пока это функционально и может быть доработано.

...