【问题标题】:Error handling over websockets a design dessision错误处理 websockets 一个设计方案
【发布时间】:2016-08-18 08:27:17
【问题描述】:

我目前正在构建一个具有两个明确用例的 web 应用程序。

  1. 传统的客户端从服务器请求数据。
  2. 在服务器开始向客户端推送数据之后,客户端向服务器请求一个流。

目前我使用通过 websocket 传递的 json 消息来实现 1 和 2。然而,这已被证明很难,因为我需要手动编写大量错误处理,因为客户端不等待响应。它只是发送消息,希望它会在某个时候得到回复。 我使用 Js 并在前端做出反应,在后端使用 Clojure。

对此我有两个问题。

  1. 鉴于当前的设计,有哪些替代方案可以通过 websocket 处理错误?
  2. 使用 UC1 的 rest 和 UC2 的 websocket 拆分两个 UC 会更聪明,然后我可以在前端使用类似 fetch 的东西来进行休息调用。

更新。 当前的问题是不知道如何在 websockets 上构建异步发送功能可以匹配发送消息和响应消息。

【问题讨论】:

  • 在 webSocket 消息传递架构中构建请求/响应并非易事。这不是自然的请求/响应。您可能必须在每条消息和响应中使用某种消息 ID 来执行此操作,因此当您发送消息时,您可以根据您在每个消息中输入的 ID 知道哪个响应属于该消息。
  • 所以你会说同时使用rest和websockets更容易?
  • 嗯,http 是自然的请求/响应,并且很容易使用。但是,如果这是您想要做的,您不能使用 HTTP 从服务器到客户端进行请求/响应。如果您的请求是从客户端到服务器的,那么 HTTP 和 REST 就更容易了。

标签: javascript rest clojure architecture websocket


【解决方案1】:

这是一个通过 socket.io 进行请求/响应的方案。您可以通过普通的 webSocket 做到这一点,但您必须自己构建更多的基础设施。同样的库可以在客户端和服务器中使用:

function initRequestResponseSocket(socket, requestHandler) {
    var cntr = 0;
    var openResponses = {};

    // send a request
    socket.sendRequestResponse = function(data, fn) {
        // put this data in a wrapper object that contains the request id
        // save the callback function for this id
        var id = cntr++;
        openResponses[id] = fn;
        socket.emit('requestMsg', {id: id, data: data});
    }

    // process a response message that comes back from a request
    socket.on('responseMsg', function(wrapper) {
        var id = wrapper.id, fn;
        if (typeof id === "number" && typeof openResponses[id] === "function") {
            fn = openResponses[id];
            delete openResponses[id];
            fn(wrapper.data);
        }
    });

    // process a requestMsg
    socket.on('requestMsg', function(wrapper) {
        if (requestHandler && wrapper.id) {
            requestHandler(wrapper.data, function(responseToSend) {
                socket.emit('responseMsg', {id: wrapper.id, data; responseToSend});
            });
        }
    });
}

这通过将发送的每条消息包装在一个包含唯一 id 值的包装对象中来工作。然后,当另一端发送它的响应时,它包含相同的 id 值。然后可以将该 id 值与该特定消息的特定回调响应处理程序匹配。它可以从客户端到服务器或服务器到客户端两种方式工作。

您可以通过在每一端的 socket.io 套接字连接上调用一次 initRequestResponseSocket(socket, requestHandler) 来使用它。如果您希望接收请求,则传递一个 requestHandler 函数,该函数在每次有请求时被调用。如果您只是发送请求和接收响应,那么您不必在连接的那一端传入 requestHandler。

要发送消息并等待响应,请执行以下操作:

socket.sendRequestResponse(data, function(err, response) {
    if (!err) {
        // response is here
    }
});

如果您正在接收请求并发送回响应,那么您可以这样做:

initRequestResponseSocket(socket, function(data, respondCallback) {
   // process the data here

   // send response
   respondCallback(null, yourResponseData);
});

至于错误处理,您可以监控连接丢失,并且可以在此代码中构建超时,这样如果在一定时间内没有响应,您就会收到错误消息。

下面是上述代码的扩展版本,它为在某个时间段内未出现的响应实现超时:

function initRequestResponseSocket(socket, requestHandler, timeout) {
    var cntr = 0;
    var openResponses = {};

    // send a request
    socket.sendRequestResponse = function(data, fn) {
        // put this data in a wrapper object that contains the request id
        // save the callback function for this id
        var id = cntr++;
        openResponses[id] = {fn: fn};
        socket.emit('requestMsg', {id: id, data: data});
        if (timeout) {
            openResponses[id].timer = setTimeout(function() {
                delete openResponses[id];
                if (fn) {
                    fn("timeout");
                }
            }, timeout);
        }
    }

    // process a response message that comes back from a request
    socket.on('responseMsg', function(wrapper) {
        var id = wrapper.id, requestInfo;
        if (typeof id === "number" && typeof openResponse[id] === "object") {
            requestInfo = openResponses[id];
            delete openResponses[id];
            if (requestInfo) {
                if (requestInfo.timer) {
                    clearTimeout(requestInfo.timer);
                }
                if (requestInfo.fn) {
                    requestInfo.fn(null, wrapper.data);
                }
            }
        }
    });

    // process a requestMsg
    socket.on('requestMsg', function(wrapper) {
        if (requestHandler && wrapper.id) {
            requestHandler(wrapper.data, function(responseToSend) {
                socket.emit('responseMsg', {id: wrapper.id, data; responseToSend});
            });

        }
    });
}

【讨论】:

    【解决方案2】:

    您的问题和设计中有一些有趣的事情,我更愿意忽略实现细节并查看高级架构。

    您声明您正在寻找请求数据的客户端和响应某些数据流的服务器。这里需要注意两点:

    1. HTTP 1.1 具有发送流式响应的选项(分块传输编码)。如果您的用例只是发送流式响应,那么这可能更适合您。当您例如想要将不响应某种请求的消息推送到客户端(有时称为服务器端事件)。
    2. 与 HTTP 不同,Websockets 本身并不实现某种请求-响应周期。您可以通过实现自己的机制来使用协议,例如子协议WAMP 正在做。

    正如您所发现的,实现自己的机制会带来一些缺陷,而这正是 HTTP 具有明显优势的地方。鉴于您的问题中所述的要求,我会选择 HTTP 流式传输方法,而不是实现您自己的请求/响应机制。

    【讨论】:

    • 服务器会在没有请求的情况下向客户端推送消息。这就是为什么我考虑使用rest ans ws或实现发送响应逻辑并且只使用ws。
    猜你喜欢
    • 2012-02-21
    • 2014-02-23
    • 1970-01-01
    • 1970-01-01
    • 2011-01-15
    • 1970-01-01
    • 2013-08-27
    • 2016-12-05
    • 1970-01-01
    相关资源
    最近更新 更多