在写入文件时使用 IdHTTPServer 提供文件

Serving files with IdHTTPServer when the files are being written

我正在与 TIdHTTPServer 合作,使用 ResponseInfo->ServeFile 函数向客户端提供文件。这适用于 "static": 未被其他进程写入的文件。据我从代码中可以看出,ServeFile 函数在内部使用 TIdReadFileExclusiveStream,这不允许我读取正在写入的文件,但我还需要能够发送其他进程正在写入的文件.

所以,我自己创建了一个 FileStream,并使用 ContentStream 属性 到 return 到客户端,但我在客户端得到了一个 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 个字节。

WriteHeader()WriteContent() 期望 ContentStream 在调用时完整且不变。如果 AResponseInfo->ContentLength 属性 为 -1,WriteHeader() 使用当前 ContentStream->Size 值创建一个 Content-Length header (您实际上是自己设置该值) , 而 WriteContent() 只发送与当前 ContentStream->Size 值一样多的字节。因此,您的客户端收到 0 个字节,因为在您调用 WriteHeader()WriteContent().

时文件 Size 仍为 0

ServeFile()ContentStream 都不适合您的需要。由于文件是实时写入的,因此在创建 HTTP headers 并将其发送到客户端时,您不知道最终文件的大小。所以你必须使用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;
}

最终的工作代码是:

    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 没有 return 任何内容到客户端,但是直接执行 IOHandler->Write(这是 ServeFile 在内部结束时所做的)工作正常。