【问题标题】:WinInet - can't remove Content-Length from headers to manually chunk an uploadWinInet - 无法从标头中删除 Content-Length 以手动分块上传
【发布时间】:2012-04-20 11:09:48
【问题描述】:

MSDN states WinInet 不支持分块上传(“客户端代码必须执行分块。”)。对我来说,这意味着我可以手动分块传输。我的意图是通过HttpAddRequestHeaders 添加“Transfer-Encoding: chunked”,通过 HttpAddRequestHeaders HTTP_ADDREQ_FLAG_REPLACE 删除 Content-Length,然后在我的 InternetWriteFile 循环中,将数据写入分块编码块中。问题是,我似乎无法说服 WinInet 不发送 Content-Length。即使在删除之后,它最终也会向服务器发送“Content-Length: 0”(除了“Transfer-Encoding: chunked”),这会使服务器感到困惑。

我也尝试在HttpSendRequestEx 中设置 HSR_CHUNKED 标志。

有没有人有让 WinInet 跳过发送 Content-Length 的示例?

我知道 WinHTTP 声称支持分块上传,但我们对 WinInet 有其他依赖项,这就是为什么我希望尽可能在那里解决问题。

这是我尝试过的代码示例:

#include <windows.h>
#include <wininet.h>
#include <tchar.h>
#include <stdio.h>

bool http_putfile(HINTERNET hConnection, TCHAR* resource);

#define HOST  _T("www.website.com")
#define RESOURCE _T("/path/for/resource")

int _tmain(int argc, TCHAR* argv[])
{
    LPCTSTR lpszHostName = HOST;
    INTERNET_PORT nServerPort = INTERNET_DEFAULT_HTTP_PORT;
    DWORD dwService = INTERNET_SERVICE_HTTP;
    DWORD dwFlags = NULL;
    DWORD dwContext = 0;

    HINTERNET hSession = InternetOpen(
        argv[0],
        INTERNET_OPEN_TYPE_DIRECT, 
        NULL,
        NULL,
        NULL);

    if(hSession != NULL)
    {
        HINTERNET hConnection = InternetConnect(
            hSession,
            lpszHostName,
            nServerPort,
            NULL,
            NULL,
            dwService,
            dwFlags,
            dwContext);

        if(hConnection != NULL)
        {
            http_putfile(hConnection, RESOURCE);

            InternetCloseHandle(hConnection);
        }
        else
        {
            printf("InternetConnect failed: %d\n", GetLastError());
        }

        InternetCloseHandle(hSession);
    }
    else
    {
        printf("InternetOpen failed: %d\n", GetLastError());
    }

    return 0;
}

bool http_putfile(HINTERNET hConnection, TCHAR* resource)
{
    bool result = false;

    HINTERNET hRequest = HttpOpenRequest(
        hConnection,
        _T("PUT"),
        resource,
        NULL,
        NULL,
        NULL,
        INTERNET_FLAG_RELOAD | INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_CACHE_WRITE,
        0);

    if(hRequest != NULL)
    {
        HttpAddRequestHeaders(
            hRequest,
            _T("Transfer-Encoding: chunked\r\n"),
            -1L,
            HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);

        // have tried:
        // Content-Length
        // Content-Length:
        // Content-Length\r\n
        // Content-Length:\r\n
        // all with/without HTTP_ADDREQ_FLAG_ADD.  Have even tried adding in a Content-Length
        // and then removing it.  All results show "Content-Length: 0" in the header on the wire.
        if(HttpAddRequestHeaders(
            hRequest,
            _T("Content-Length"),
            -1L,
            HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE) == FALSE)
        {
            DWORD err = GetLastError();
        }

        // have tried both 0 here for flags as documented on msdn
        // http://msdn.microsoft.com/en-us/library/windows/desktop/aa384318%28v=vs.85%29.aspx
        // as well as other combinations of INITIATE/CHUNKED
        if(HttpSendRequestEx(hRequest, NULL, NULL, HSR_INITIATE | HSR_CHUNKED /* 0 */, NULL))
        {
            DWORD wrote = 0;
            char* chunks = "5\r\nCHUNK0\r\n";

            if(InternetWriteFile(
                    hRequest,
                    chunks,
                    strlen(chunks),
                    &wrote) == FALSE)
            {
                printf("InternetWriteFile failed: %d\n", GetLastError());
            }

            HttpEndRequest(hRequest, NULL, 0, NULL);
        }
        else
        {
            printf("HttpSendRequestEx failed: %d\n", GetLastError);
        }

        InternetCloseHandle(hRequest);
    }
    else
    {
        printf("HttpOpenRequest failed: %d\n", GetLastError());
    }

    return result;
}

【问题讨论】:

  • 你的调用顺序是什么样的?
  • 1. InternetOpen(), 2. InternetConnect(), 3. HttpOpenRequest(), 4. HttpAddRequestHeaders(), 5. HttpSendRequestEx(), 6. InternetWriteFile(), 7. HttpEndRequest(), 8. InternetCloseHandle()

标签: winapi http-headers wininet chunked-encoding


【解决方案1】:

嗯,看来你是对的。事实上,我没有看到任何表明 WinINet 支持分块上传的信息。 only WinHTTP does。查看Wine source code(这往往非常准确),“Content-Length”标头总是附加到“GET”以外的任何方法的请求中。

根据HTTP specs,如果存在非身份“Transfer-Encoding”标头,则认为该消息是分块的。如果还存在“Content-Length”标头,则必须将其忽略。如果虚假的“Content-Length: 0”标头导致您出现问题,那么可能会认为这是服务器的问题。我会试试 WinHTTP 看看它是否能解决问题。

【讨论】:

  • 你是对的,针对不同的 HTTP 服务器进行测试,它忽略了 Content-Length 并且只接受了分块上传。 RFC 2616 表明客户端永远不应该这样做,但如果这样做,服务器应该按照你的说法行事。所以在我原来的情况下,双方似乎都是错误的。即使我不能使用 WinHTTP,我也确实对其进行了测试。我认为文档暗示它会直接为您进行分块;它没有,您仍然需要自己对数据进行分块,但它确实允许从标头中删除 Content-Length。然而,这仅在 Vista/2k3 及更高版本上受支持。
  • 已经找到了解决这个问题的方法(尽管是在提出问题后 18 个月),但我发现您的回答有些令人不快,因为这是不可能的。
【解决方案2】:

很好的问题,我自己为此沮丧了好几个小时。

你对HTTP_ADDREQ_FLAG_REPLACE 的直觉证明是正确的......但是,你必须很晚才知道。具体来说你需要register a status callback,当你的回调被INTERNET_STATUS_CONNECTED_TO_SERVER调用时,就是你删除标题的时候:

// Inside InternetStatusCallback
//
if (dwInternetStatus == INTERNET_STATUS_CONNECTED_TO_SERVER)
{
  HttpAddRequestHeaders(Handle, L"Content-Length", -1, HTTP_ADDREQ_FLAG_REPLACE);
}

【讨论】:

  • 我在Connection 标头上尝试过这个(不要问我为什么),但是没有用。
  • @jwalker - 嗯,对于您的情况,我想知道库本身是否稍后会设置该标题?您可能还可以寻找另一个dwInternetStatus
猜你喜欢
  • 2021-06-14
  • 1970-01-01
  • 2011-01-26
  • 2015-07-07
  • 1970-01-01
  • 2016-08-25
  • 2016-11-05
  • 1970-01-01
  • 2012-07-03
相关资源
最近更新 更多