【问题标题】:Server-initiated requests服务器发起的请求
【发布时间】:2015-08-30 09:46:43
【问题描述】:

我知道 HTTP 是一种请求-响应协议。简而言之,我的问题是客户端向服务器发出请求以启动一个长时间运行的进程,我想通过包含进度信息的简单 JSON 消息通知客户端进度。

在 HTTP/1.1 中,我知道我可以使用 WebSocket 或服务器发送事件 (SSE) 或长轮询。

现在我知道 HTTP/2 还不支持 WebSocket。

我的问题是,通过 HTTP/2 处理此类事情的最佳方式是什么?

在处理 HTTP/2 中服务器发起的请求时,有什么我不知道的新东西吗?

我正在使用 Go 语言,如果这很重要的话。

【问题讨论】:

    标签: http go http2


    【解决方案1】:

    在使用 websockets 之前,我们进行了轮询。这实际上意味着让客户端定期(每隔几秒,或任何对您的应用程序有意义的时间段)向服务器发出请求以了解作业的状态。

    许多人使用的优化是“长”轮询。这涉及让服务器接受请求,并在服务器内部检查更改,并在没有更改时休眠,直到达到特定超时或发生所需事件,然后将其返回给客户端。

    如果达到超时,连接将关闭,客户端需要发出另一个请求。服务器代码如下所示,假设函数根据其名称和签名执行合理的操作:

    import (
        "net/http"
        "time"
    )
    
    func PollingHandler(w http.ResponseWriter, r *http.Request) {
        jobID := getJobID(r)
        for finish := 60; finish > 0; finish-- { // iterate for ~1 minute
            status, err := checkStatus(jobID)
            if err != nil {
                writeError(w, err)
                return
            }
            if status != nil {
                writeStatus(w, status)
                return
            }
            time.Sleep(time.Second) // sleep 1 second
        }
        writeNil(w) // specific response telling client to request again.
    }
    

    处理超时的更好方法是使用context package 并创建一个带有超时的上下文。这看起来像:

    import (
        "net/http"
        "time"
        "golang.org/x/net/context"
    )
    
    func PollingHandler(w http.ResponseWriter, r *http.Request) {
        jobID := getJobID(r)
        ctx := context.WithTimeout(context.Background(), time.Second * 60)
        for {
            select{
            case <-ctx.Done():
                writeNil(w)
            default: 
                status, err := checkStatus(jobID)
                if err != nil {
                    writeError(w, err)
                    return
                }
                if status != nil {
                    writeStatus(w, status)
                    return
                }
                time.Sleep(time.Second) // sleep 1 second
            }
        }
    
    }
    

    第二个版本将在更可靠的时间内返回,尤其是在 checkStatus 调用速度可能较慢的情况下。

    【讨论】:

    • 谢谢,这很有帮助,我想过轮询,但它真的是通过 http2 实现实时应用程序的方式吗?因为如果状态没有改变,为什么还要发出请求?在没有请求的情况下,我找不到任何关于向客户端发送请求的信息。
    • 一般来说,HTTP 服务器不应该能够向客户端发出请求。很多时候,您会发现客户端有防火墙、在 NAT 之后或无法访问。 HTTP2 很酷,但除非你使用它的一些新特性(这似乎不太可能,因为 Go 的实现目前还很原始),你可以坚持使用 HTTP 1.1 并使用好的旧 websockets。在 Go 中准备好后升级到 HTTP2 几乎肯定会非常简单。
    【解决方案2】:

    您可以考虑使用 HTML5 文本/事件流,也就是服务器端事件 (SSE)。问题中提到了 SSE,这不适合 http2 吗?

    关于 SSE 的一般文章

    (IE是目前唯一不支持SSE的浏览器)

    在下面的文章中,http2 push 与 SSE 相结合。文档被推送到客户端缓存中,并且 SSE 用于通知客户端可以从其缓存中检索哪些文档(= 服务器通过单个 http2 连接发起的请求):

    SSE 基础知识:在服务器端,您可以从:

    Content-Type: text/event-stream\n\n
    

    然后每次您想向您发送的客户端发送更新

    data: { "name": "value", "othername": "othervalue" }\n\n
    

    完成后,在关闭连接之前,您可以选择发送:

    retry: 60000\n\n
    

    指示浏览器在 60000 毫秒后重试新连接

    在浏览器中的连接是这样的:

    var URL = "http://myserver/myeventstreamer"
    if (!!window.EventSource) {
        source = new EventSource(URL);
    } else {
        // Resort to xhr polling :(
        alert ("This browser does not support Server Sent Events\nPlease use another browser") 
    }
    
    source.addEventListener('message', function(e) {
      console.log(e.data);
    }, false);
    
    source.addEventListener('open', function(e) {
      // Connection was opened.
    }, false);
    
    source.addEventListener('error', function(e) {
      if (e.readyState == EventSource.CLOSED) {
        // Connection was closed.
      }
    }, false);
    

    【讨论】:

    • 请注意,服务器总是会在 SSE 中收到 HTTP 请求。如果您检查您的 HTTP 服务器或浏览器的网络日志中的访问日志,那么您就会知道。
    【解决方案3】:

    如果您想以文本形式发送 JSON 消息,server-sent event (SSE) 是一个不错的方法。 SSE 旨在发送文本。所有事件数据都以 UTF-8 字符编码。缺点是这使得通过 SSE 发送二进制数据效率低。

    如果你想发送二进制数据,你可能会对 HTTP/2 引入的Server Push 机制感兴趣。服务器推送允许 HTTP/2 服务器主动向客户端发送任何类型的文件。它被称为服务器推送“响应”,即使它是在客户端请求之前发送的。客户端自动将通过服务器推送响应发送的文件存储在其缓存中。对文件的后续请求会立即从缓存中完成,而无需往返服务器。

    这是一种将二进制数据推送到 Web 浏览器的有效方式。问题是当服务器推送响应到达时,浏览器的文档对象模型 (DOM) 不会得到通知。浏览器只有在发出请求时才发现数据在其缓存中。我们可以通过以下方式解决这个问题。使用 Server Push 发送二进制数据后,服务器立即向客户端发送 SSE,通知其数据已被推送到其缓存。现在客户端可以通过请求从其缓存中检索数据。

    但是,只要您使用的是 SSE,为什么不首先通过 SSE 发送文件呢?因为如果您正在处理二进制数据,您可以受益于 Server Push 允许您实现的更小的文件大小。对于简短的 JSON 消息,使用服务器推送可能没有意义。在您推送二进制数据并且必须节省带宽的情况下,请考虑通过服务器推送发送数据,然后发送 SSE 通知。

    与轮询不同,这种方法不需要来自客户端的定期请求。服务器可以随时发送服务器推送响应。

    【讨论】:

      猜你喜欢
      • 2014-07-23
      • 2016-02-27
      • 1970-01-01
      • 1970-01-01
      • 2012-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-07
      相关资源
      最近更新 更多