【问题标题】:Serving files with IdHTTPServer when the files are being written在写入文件时使用 IdHTTPServer 提供文件
【发布时间】:2015-02-09 19:28:09
【问题描述】:

我正在使用TIdHTTPServer 为客户提供文件,使用ResponseInfo->ServeFile 函数。这适用于“静态”文件:不是由其他进程写入的。据我从代码中可以看出,ServeFile 函数内部使用TIdReadFileExclusiveStream,这不允许我读取正在写入的文件,但我还需要能够发送正在由其他进程写入的文件。

所以,我自己创建了一个 FileStream 并使用 ContentStream 属性将其返回给客户端,但是我在客户端中得到了一个 0 字节的文件(对于任何文件,无论是否写入),我可以'看不到我错过了什么或做错了什么。这是我在OnCommandGet 事件处理程序上使用的代码:

AResponseInfo->ContentStream = new TFileStream(path, fmOpenRead | fmShareDenyNone);
AResponseInfo->ContentStream->Position = 0;
AResponseInfo->ContentLength = AResponseInfo->ContentStream->Size;
AResponseInfo->ResponseNo = 200;
AResponseInfo->WriteHeader();
AResponseInfo->WriteContent();

此时的 ContentLength 属性有一个有效值(即调用 ContentStream->Size 时的文件大小),这就是我想发送给客户端的值,即使文件在两者之间发生变化。

我已经尝试删除 WriteContent() 函数、WriteHeader(),但结果是一样的。我搜索了一些示例,但我发现的几个与此代码或多或少相同,所以我不知道出了什么问题。大多数示例不包括 WriteContent() 调用,这就是我尝试删除它们的原因,但似乎没有任何区别。

附带说明:正在写入的文件需要 24 小时才能完成写入,但这是客户端所期望的:我只需要在请求时已经写入的字节(甚至更少是有效的)。这些文件永远不会被删除:它们只会越来越大。

有什么想法吗?

更新

使用 Fiddler,我收到了一些与此相关的协议违规警告。例如,我得到:

Content-Length mismatch: Response Header indicated 111,628,288 bytes, but server sent 41 bytes

内容长度是正确的,它是文件大小,但我不知道我做错了什么导致应用程序只发送了 41 个字节。

【问题讨论】:

    标签: delphi c++builder indy indy10 c++builder-xe5


    【解决方案1】:

    WriteHeader()WriteContent() 期望 ContentStream 在调用它们时是完整且不变的。如果AResponseInfo->ContentLength 属性为-1(实际上是您自己设置值),WriteHeader() 使用当前ContentStream->Size 值创建Content-Length 标头,并且WriteContent() 仅发送与当前ContentStream->Size 一样多的字节数价值说。因此,您的客户端收到 0 个字节,因为在您调用 WriteHeader()WriteContent() 时文件 Size 仍然为 0。

    ServeFile()ContentStream 都不适合您的需求。由于文件是实时写入的,因此在创建 HTTP 标头并将其发送到客户端时,您不知道最终文件的大小。所以您必须使用 HTTP 1.1 的chunked transfer coding 发送文件数据。这将允许您在写入文件时以块的形式发送文件数据,然后在文件完成时向客户端发出信号。

    但是,TIdHTTPServer 本身并不支持发送chunked 响应,因此您必须手动实现它,例如:

    TFileStream *fs = new TFileStream(path, fmOpenRead | fmShareDenyNone);
    try
    {
        AResponseInfo->ResponseNo = 200;
        AResponseInfo->TransferEncoding = "chunked";
        AResponseInfo->WriteHeader();
    
        TIdBytes buffer;
        buffer.Length = 1024;
    
        do
        {
            int NumRead = fs->Read(&buffer[0], 1024);
            if (NumRead == -1) RaiseLastOSError();
            if (NumRead == 0)
            {
                // check for EOF, unless you have another way to detect it...
                Sleep(1000);
                NumRead = fs->Read(&buffer[0], 1024);
                if (NumRead <= 0) break;
            }
    
            // send the current chunk
            AContext->Connection->IOHandler->WriteLn(IntToHex(NumRead));
            AContext->Connection->IOHandler->Write(buffer, NumRead);
            AContext->Connection->IOHandler->WriteLn();
        }
        while (true);
    
        // send the last chunk to signal EOF
        AContext->Connection->IOHandler->WriteLn("0");
    
        // send any trailer headers you need, if any...
    
        // finish the transfer encoding
        AContext->Connection->IOHandler->WriteLn();
    }
    __finally
    {
        delete fs;
    }
    

    【讨论】:

    • 感谢您的详细回答,但是:我设置的大小不是 0。对于请求的文件,它是当时正确的值,例如 100mb,我不在乎文件在两者之间长大(它们永远不会变小也不会被删除)。我发送小于实际尺寸的问题没有问题。而且我发布的代码也不适用于已经完成的文件。客户端无法等待整个文件,因为它们需要 24 小时才能写入。
    • 如果您使用AResponseInfo-&gt;ContentStreamAResponseInfo-&gt;ServeFile(),则无需手动设置AResponseInfo-&gt;ContentLength,它们会为您处理ContentLength。但它们确实有效,所以如果您遇到错误/丢失数据的问题,那么您可能在此问题中未显示的代码中做错了。
    • 我向你保证,如果使用得当,如果文件大小至少为 111628288 字节,那么至少会发送 111628288 字节。它可以发送 41 个字节的唯一方法是文件实际缩小到 41 个字节。但是,WriteHeader()WriteContent() 之间存在竞争条件,因此如果文件增长,WriteContent() 可能发送比WriteHeader() 生成的Content-Length 标头中指示的更多字节,这将对客户不利。这看起来像是 TIdHTTPServer 中需要修复的错误。
    • ... 所以您可能需要绕过 WriteContent() 并手动发送文件数据,以便它与发送的实际 Content-Length 标头匹配。或者至少将源 TFileStream 包装在自定义 TStream 中,以确保一致的 Size 值,即使文件在使用流时增长。
    • 一般来说,TIdHTTPServer 并不是真正为处理实时数据而设计的,因此您可能需要跳过一些额外的环节才能使其正常工作。
    【解决方案2】:

    最终的工作代码是:

        std::unique_ptr< TFileStream >fs(new TFileStream(path, fmOpenRead | fmShareDenyNone));
    
        fs->Position = 0;
        __int64 size = fs->Size;
    
        AResponseInfo->ContentLength = size;
        AResponseInfo->ResponseNo    = 200;
    
        AResponseInfo->WriteHeader();
    
        AContext->Connection->IOHandler->Write(fs.get(), size);
    

    这允许客户端最多接收size 字节的原始文件,即使文件正在同时被写入。

    由于某种原因,传递 ContentStream 没有向客户端返回任何内容,但直接执行 IOHandler-&gt;Write(这是 ServeFile 在内部结束的操作)工作正常。

    【讨论】:

      猜你喜欢
      • 2021-07-26
      • 2019-09-06
      • 1970-01-01
      • 1970-01-01
      • 2011-10-16
      • 1970-01-01
      • 1970-01-01
      • 2023-04-05
      • 1970-01-01
      相关资源
      最近更新 更多