【问题标题】:boost asio async_read header connection closes too earlyboost asio async_read 标头连接过早关闭
【发布时间】:2016-12-15 13:23:10
【问题描述】:

提供MCVE 会很困难,场景如下:

  • 用 c++ 编写的带有 boost asio 的服务器提供了一些服务
  • 用 c++ 编写的带有 boost asio 请求服务的客户端

有自定义标题,并且大多数通信都是使用 multipart/form 完成的。 但是,在服务器返回 401 表示未授权访问的情况下, 客户端收到损坏的管道(系统错误 32)。

当服务器连接过早关闭时会发生这种情况。 因此,运行到 gdb,我可以看到问题确实是从发送请求的 async_write 到读取 HTTP Header 第一行的 async_read_until 的转换:

connect 例程将请求从客户端发送到服务器:

    boost::asio::async_write(*socket_.get(),
                             request_,
                             boost::bind(&asio_handler<http_socket>::write_request, 
                                         this,
                                         boost::asio::placeholders::error,
                                         boost::asio::placeholders::bytes_transferred));

还有write_request回调,检查请求是否发送OK,然后读取第一行(直到第一个换行):

template <class T> 
void asio_handler<T>::write_request(const boost::system::error_code & err,
                                    const std::size_t bytes)
{       
    if (!err) {
        // read until first newline
        boost::asio::async_read_until(*socket_,
                                      buffer_,
                                      "\r\n",
                                      boost::bind(&asio_handler::read_status_line, 
                                                  this, 
                                                  boost::asio::placeholders::error,
                                                  boost::asio::placeholders::bytes_transferred));
    }
    else {
        end(err);       
    }
}

问题是end(err) 总是被一个损坏的管道调用(错误代码32)。据我了解,这意味着服务器关闭了连接。服务器确实关闭了连接,但只有在它发送了一条消息HTTP/1.1 401 Unauthorized

  • curl 与适当的请求一起使用,我们确实会在服务器关闭连接之前收到实际的消息/错误
  • 使用我们用 C++/boost asio 编写的客户端,我们只能得到损坏的管道,没有数据
  • 只有当服务器保持连接打开时,我们才会读取错误 (401) 但这违背了目的,因为现在连接保持打开状态。

我非常感谢任何提示或提示。我知道没有代码很难提供帮助,因此我可以随时添加更多源代码。

编辑:

如果我检查写入请求和读取服务器回复之间的错误,那么我确实会收到实际的 HTTP 401 错误。然而,这似乎违反直觉,我不确定为什么会发生这种情况或是否应该发生这种情况。

【问题讨论】:

  • 服务器是否需要读取整个HTTP请求来判断和响应未经授权的?例如,也许授权可以仅从标头字段确定,从而无需读取任何多部分/表单主体。在这种情况下,服务器可以在客户端完成写入请求之前发送响应并关闭连接。
  • @TannerSansbury 不,它只需要标题值来回复。是的,这就是正在发生的事情,服务器在收到整个请求之前正在回复。我假设(是的,我知道......)在服务器完成接收请求之后会出现回复,显然情况并非如此。非常感谢您的帮助!

标签: c++ boost boost-asio


【解决方案1】:

根据 HTTP 规范允许观察到的行为。

客户端或服务器可以随时关闭套接字。服务器可以在客户端完成传输请求之前提供响应并关闭连接。编写正文时,建议客户端监视套接字是否有错误或关闭通知。来自RFC 7230, HTTP/1.1: Message Syntax and Routing Section 6.5. Failures and Timeouts

6.5。失败和超时

客户端、服务器或代理可以随时关闭传输连接。 [...]

发送消息正文的客户端应该在传输请求时监控网络连接是否有错误响应。如果客户端看到响应表明服务器不希望接收消息正文并正在关闭连接,则客户端应立即停止传输正文并关闭其一侧的连接。

在优雅的连接关闭时,服务器将在关闭底层套接字之前向客户端发送响应:

6.6。拆解

发送“关闭”连接选项的服务器必须在发送包含“关闭”的响应后启动连接的关闭 [...]。 [...]

鉴于上述行为,存在三种可能的情况。 async_write() 操作完成:

  • 成功,表示请求已完整写入。客户端可能尚未收到 HTTP 响应
  • 错误,表示请求未完整写入。如果套接字上有可供读取的数据,那么它可能包含服务器在连接终止之前发送的 HTTP 响应。 HTTP 连接可能已正常终止
  • 错误,表示请求未完整写入。如果套接字上没有可读取的数据,则 HTTP 连接没有正常终止

考虑:

  • 如果async_write() 成功或有数据可供读取,则启动async_read() 操作

     void write_request(
       const boost::system::error_code & error,
       const std::size_t bytes_transferred)
     {
       // The server may close the connection before the HTTP Request finished
       // writing.  In that case, the HTTP Response will be available on the
       // socket.  Only stop the call chain if an error occurred and no data is
       // available.
       if (error && !socket_->available()) 
       {
         return;
       }
    
       boost::asio::async_read_until(*socket_, buffer_, "\r\n", ...);
    }
    
  • 根据 RFC建议,在 async_write() 的同时启动 async_read() 操作。如果服务器指示 HTTP 连接正在关闭,则客户端将关闭其套接字的发送端。额外的状态处理可能无法保证额外的复杂性

【讨论】:

  • 非常感谢您的详尽回复!我最终采用了第二种方法,但我想我会结合第一种方法以防万一。
猜你喜欢
  • 1970-01-01
  • 2013-12-21
  • 2013-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多