QNetworkReply 返回不完整的 XML 数据

Posted

技术标签:

【中文标题】QNetworkReply 返回不完整的 XML 数据【英文标题】:QNetworkReply returning incomplete XML data 【发布时间】:2017-11-08 11:01:00 【问题描述】:

我正在向远程服务器发送 HTTP GET 请求。我使用各种参数定义我感兴趣的内容。特别是我确保output=xml 在查询中,因为它使服务器以 XML 形式返回回复。

我的班级HttpRetriever 与各自的QNetworkReplyQNetworkAccessManager 之间有以下联系(对于QNetworkRequest,请参阅slotStartRequest()):

connect(this->reply, SIGNAL(error(QNetworkReply::NetworkError)), this,
        SLOT(slotErrorRequest(QNetworkReply::NetworkError)));
connect(this->reply, &QNetworkReply::finished, this, &HttpRetriever::slotFinishRequest);
connect(this->manager, &QNetworkAccessManager::finished, this->reply, &QNetworkReply::deleteLater);
connect(this->reply, &QIODevice::readyRead, this, &HttpRetriever::slotReadyReadRequest);

这里感兴趣的槽有以下声明:

slotFinishRequest():

void HttpRetriever::slotFinishRequest()

    LOG(INFO) << "Finishing HTTP GET request from URL \"" << this->url.toString() << "\"";
    this->reply = Q_NULLPTR;

    // Reset validity of reply from a previous request
    this->validReply = false;
    // Skip validation if it's disabled
    if (!this->validateReply)
    
        LOG(WARNING) << "Validation disabled. In order to enable it see the \"validate\" and \"validationMode\" in \"settings.ini\"";
        this->validReply = true;
    
    else
    
        // Validate reply
        this->validReply = validateReply();
    

    if (!this->validReply)
    
        return;
    

    processReply(); // Parsing

    this->processingRequest = false;

slotReadyReadRequest():

void HttpRetriever::slotReadyReadRequest()

    LOG(INFO) << "Received data from \"" << this->url.toString() << "\"";
    this->bufferReply = this->reply->readAll();

slotFinishRequest()里面我叫processReply()

void HttpRetriever::processReply()

    LOG(INFO) << "Processing reply for request \"" << this->url.toString() << "\"";
    LOG(DEBUG) << QString(this->bufferReply);
    // Process the XML from the reply and extract necessary data

    QXmlStreamReader reader;
    reader.addData(this->bufferReply);

    // Read the XML reply and extract required data
    // TODO
    while (!reader.atEnd())
    
        LOG(DEBUG) << "Reading XML element";
        reader.readNextStartElement();

        QXmlStreamAttributes attributes = reader.attributes();
        foreach (QXmlStreamAttribute attrib, attributes)
        
            LOG(DEBUG) << attrib.name();
        
    
    if (reader.hasError())
    
        LOG(ERROR) << "Encountered error while parsing XML data:" << reader.errorString();
    

    LOG(INFO) << "Sending data to data fusion handler";
    // TODO

我通过以下槽触发HTTP get请求:

void HttpRetriever::slotStartRequest(quint32 id)

    if (this->processingRequest)
    
        this->reply->abort();
    

    this->processingRequest = false;

    // The first request also instantiates the manager. If the slot is called after the instance of HafasHttpRetriever
    // is moved to a new thread this will ensure proper parenting
    if (!this->manager)
    
        this->manager = new QNetworkAccessManager(this);
    

    quint32 requestId = generateRequestId(stopId);
    if (!this->url.hasQuery())
    
        LOG(WARNING) << "Attempting to start request without parameters";
    

    // Part of the filters applied to the request to reduce the data received (for more see slotSetRequestParams())
    QUrlQuery query(this->url.query());
    query.addQueryItem("input", QString::number(requestId));
    // TODO Add more filters; see documentation

    this->url.setQuery(query);

    LOG(INFO) << "Requesting data from \"" << this->url.toString() << "\" with request ID:" << requestId;

    QNetworkRequest request(this->url);
    this->reply = this->manager->get(request);

    // Establish connections from/to the reply and the network access manager
    connect(this->reply, SIGNAL(error(QNetworkReply::NetworkError)), this,
            SLOT(slotErrorRequest(QNetworkReply::NetworkError)));
    connect(this->reply, &QNetworkReply::finished, this, &HttpRetriever::slotFinishRequest);
    connect(this->manager, &QNetworkAccessManager::finished, this->reply, &QNetworkReply::deleteLater);
    connect(this->reply, &QIODevice::readyRead, this, &HttpRetriever::slotReadyReadRequest);

如您所见,到目前为止,我已经为我的班级和服务器之间的网络通信奠定了基础,我还没有开始着手解析 XML 回复并从中提取我需要的信息。

问题是我(非常、非常频繁地)要么

解析 XML 数据时遇到错误:需要开始标记。

解析 XML 数据时遇到错误:文档过早结束

在我的processReply() 函数中。每次我收到大量回复(几百到几千行)时都会发生这种情况。当我得到一个小的(30-40 行给或取)时,它永远不会发生。

所以问题显然出在我收到的数据量上,QNetworkAccessManager(或所有这些缓冲接收到的数据块的任何 Qt 组件)组合在一起的方式和/或方式我已经在课堂上设置了网络相关组件的实例。我还必须在这里做一个重要的说明,即在我的浏览器(带有 HttpRequester 附加组件的最新 Firefox)中,无论它有多大,我总是会收到完整的 XML。所以这似乎是我的应用程序独有的问题,与我系统上的网络设置无关。

【问题讨论】:

我怀疑问题出在这里:this->bufferReply = this->reply->readAll();您不应该分配而是追加,然后在处理缓冲区时清理它。 确实,现在我得到了大量数据。但是我仍然收到Encountered error while parsing XML data: Premature end of document 错误。我查看了浏览器中的输出和 Qt 中的输出,似乎没有缺少标签(根标签在最后关闭)。 我想我找到了解决方案here。如果可行,将在此处尝试并发表评论,以便您可以发布更完整的答案供我标记。 是的,它解决了这个问题。请发布答案(包括指向我在之前评论中发布的答案的链接)。 :) 【参考方案1】:

由于@Marco 没有写出答案...

问题是我一直在通过分配来自QNetworkReply::readAll() 的结果来重写我的缓冲区。正如建议使用QByteArray::append() 解决问题。

为了防止此解决方案可能出现的问题,即您不断附加收到的每一个下一个回复,QByteArray::clear() 需要在某个时间点被调用,例如在发出finished() 信号时。当然,在将其排入下水道之前,需要先对其内容进行处理。

【讨论】:

以上是关于QNetworkReply 返回不完整的 XML 数据的主要内容,如果未能解决你的问题,请参考以下文章

连接丢失时未检测到 QNetworkReply 错误信号

使用 QNetworkAccessManager 从 Web 检索数据:文件已下载但 QNetworkReply::readAll 返回 null

如何制作自己的自定义 QNetworkReply?

Qt QNetworkReply readAll 在再次请求相同的 url 时返回空

从 QNetworkReply 读取未解码的数据

QNetworkReply:在 QWebView 中禁用了网络访问