зеркало из https://github.com/Azure/iisnode.git
fix #152: enable request messages with chunked transfer encoding
This commit is contained in:
Родитель
0f65ec94f0
Коммит
79aabc55e5
|
@ -114,6 +114,28 @@ DWORD CNodeHttpStoredContext::GetBufferSize()
|
|||
return this->bufferSize;
|
||||
}
|
||||
|
||||
void* CNodeHttpStoredContext::GetChunkBuffer()
|
||||
{
|
||||
// leave room in the allocated memory buffer for a chunk transfer encoding header that
|
||||
// will be calculated only after the entity body chunk had been read
|
||||
|
||||
return (void*)((char*)this->GetBuffer() + this->GetChunkHeaderMaxSize());
|
||||
}
|
||||
|
||||
DWORD CNodeHttpStoredContext::GetChunkBufferSize()
|
||||
{
|
||||
// leave room in the buffer for the chunk header and the CRLF following a chunk
|
||||
|
||||
return this->GetBufferSize() - this->GetChunkHeaderMaxSize() - 2;
|
||||
}
|
||||
|
||||
DWORD CNodeHttpStoredContext::GetChunkHeaderMaxSize()
|
||||
{
|
||||
// the maximum size of the chunk header
|
||||
|
||||
return 64;
|
||||
}
|
||||
|
||||
void** CNodeHttpStoredContext::GetBufferRef()
|
||||
{
|
||||
return &this->buffer;
|
||||
|
|
|
@ -48,6 +48,9 @@ public:
|
|||
DWORD GetConnectionRetryCount();
|
||||
void* GetBuffer();
|
||||
DWORD GetBufferSize();
|
||||
void* GetChunkBuffer();
|
||||
DWORD GetChunkBufferSize();
|
||||
DWORD GetChunkHeaderMaxSize();
|
||||
void** GetBufferRef();
|
||||
DWORD* GetBufferSizeRef();
|
||||
DWORD GetDataSize();
|
||||
|
|
|
@ -688,6 +688,19 @@ void CProtocolBridge::SendHttpRequestHeaders(CNodeHttpStoredContext* context)
|
|||
CheckError(request->DeleteHeader(HttpHeaderExpect));
|
||||
}
|
||||
|
||||
// determine if the request body had been chunked; IIS decodes chunked encoding, so it
|
||||
// must be re-applied when sending the request entity body
|
||||
|
||||
USHORT encodingLength;
|
||||
PCSTR encoding = request->GetHeader(HttpHeaderTransferEncoding, &encodingLength);
|
||||
if (NULL != encoding && 0 == strnicmp(encoding, "chunked;", encodingLength > 8 ? 8 : encodingLength))
|
||||
{
|
||||
context->SetIsChunked(TRUE);
|
||||
context->SetIsLastChunk(FALSE);
|
||||
}
|
||||
|
||||
// serialize and send request headers
|
||||
|
||||
CheckError(CHttpProtocol::SerializeRequestHeaders(context, context->GetBufferRef(), context->GetBufferSizeRef(), &length));
|
||||
|
||||
context->SetNextProcessor(CProtocolBridge::SendHttpRequestHeadersCompleted);
|
||||
|
@ -794,7 +807,15 @@ void CProtocolBridge::ReadRequestBody(CNodeHttpStoredContext* context)
|
|||
if (0 < context->GetHttpContext()->GetRequest()->GetRemainingEntityBytes())
|
||||
{
|
||||
context->SetNextProcessor(CProtocolBridge::ReadRequestBodyCompleted);
|
||||
CheckError(context->GetHttpContext()->GetRequest()->ReadEntityBody(context->GetBuffer(), context->GetBufferSize(), TRUE, &bytesReceived, &completionPending));
|
||||
|
||||
if (context->GetIsChunked())
|
||||
{
|
||||
CheckError(context->GetHttpContext()->GetRequest()->ReadEntityBody(context->GetChunkBuffer(), context->GetChunkBufferSize(), TRUE, &bytesReceived, &completionPending));
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckError(context->GetHttpContext()->GetRequest()->ReadEntityBody(context->GetBuffer(), context->GetBufferSize(), TRUE, &bytesReceived, &completionPending));
|
||||
}
|
||||
}
|
||||
|
||||
if (!completionPending)
|
||||
|
@ -817,7 +838,17 @@ Error:
|
|||
{
|
||||
context->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
|
||||
L"iisnode detected the end of the http request body", WINEVENT_LEVEL_VERBOSE, context->GetActivityId());
|
||||
CProtocolBridge::StartReadResponse(context);
|
||||
|
||||
if (context->GetIsChunked() && !context->GetIsLastChunk())
|
||||
{
|
||||
// send the terminating zero-length chunk
|
||||
|
||||
CProtocolBridge::ReadRequestBodyCompleted(S_OK, 0, context->GetOverlapped());
|
||||
}
|
||||
else
|
||||
{
|
||||
CProtocolBridge::StartReadResponse(context);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -843,7 +874,17 @@ void WINAPI CProtocolBridge::ReadRequestBodyCompleted(DWORD error, DWORD bytesTr
|
|||
{
|
||||
ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
|
||||
L"iisnode detected the end of the http request body", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId());
|
||||
CProtocolBridge::StartReadResponse(ctx);
|
||||
|
||||
if (ctx->GetIsChunked() && !ctx->GetIsLastChunk())
|
||||
{
|
||||
// send the zero-length last chunk to indicate the end of a chunked entity body
|
||||
|
||||
CProtocolBridge::SendRequestBody(ctx, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
CProtocolBridge::StartReadResponse(ctx);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -861,9 +902,62 @@ void CProtocolBridge::SendRequestBody(CNodeHttpStoredContext* context, DWORD chu
|
|||
GUID activityId;
|
||||
memcpy(&activityId, context->GetActivityId(), sizeof GUID);
|
||||
|
||||
DWORD length;
|
||||
char* buffer;
|
||||
|
||||
if (context->GetIsChunked())
|
||||
{
|
||||
// IIS decodes chunked transfer encoding of request entity body. Chunked encoding must be
|
||||
// re-applied here around the request body data IIS provided in the buffer before it is sent to node.exe.
|
||||
// This is done by calculating and pre-pending the chunk header to the data in the buffer
|
||||
// and appending a chunk terminating CRLF to the data in the buffer.
|
||||
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6
|
||||
|
||||
// Generate the chunk header (from last byte to first)
|
||||
|
||||
buffer = (char*)context->GetChunkBuffer(); // first byte of entity body chunk data
|
||||
*(--buffer) = 0x0A; // LF
|
||||
*(--buffer) = 0x0D; // CR
|
||||
|
||||
if (0 == chunkLength)
|
||||
{
|
||||
// this is the end of the request entity body - generate last, zero-length chunk
|
||||
*(--buffer) = '0';
|
||||
context->SetIsLastChunk(TRUE);
|
||||
}
|
||||
else
|
||||
{
|
||||
length = chunkLength;
|
||||
while (length > 0)
|
||||
{
|
||||
DWORD digit = length % 16;
|
||||
*(--buffer) = (digit < 10) ? ('0' + digit) : ('a' + digit - 10);
|
||||
length >>= 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Append CRLF to the entity body chunk
|
||||
|
||||
char* end = (char*)context->GetChunkBuffer() + chunkLength; // first byte after the chunk data
|
||||
*end = 0x0D; // CR
|
||||
*(++end) = 0x0A; // LF
|
||||
|
||||
// Calculate total length of the chunk including framing
|
||||
|
||||
length = end - buffer + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
length = chunkLength;
|
||||
buffer = (char*)context->GetBuffer();
|
||||
}
|
||||
|
||||
// send the entity body data to the node.exe process
|
||||
|
||||
context->SetNextProcessor(CProtocolBridge::SendRequestBodyCompleted);
|
||||
|
||||
if (WriteFile(context->GetPipe(), context->GetBuffer(), chunkLength, NULL, context->InitializeOverlapped()))
|
||||
if (WriteFile(context->GetPipe(), (void*)buffer, length, NULL, context->InitializeOverlapped()))
|
||||
{
|
||||
// completed synchronously
|
||||
|
||||
|
@ -1137,6 +1231,7 @@ void WINAPI CProtocolBridge::ProcessResponseHeaders(DWORD error, DWORD bytesTran
|
|||
if (0 == contentLengthLength)
|
||||
{
|
||||
ctx->SetIsChunked(TRUE);
|
||||
ctx->SetIsLastChunk(FALSE);
|
||||
ctx->SetNextProcessor(CProtocolBridge::ProcessChunkHeader);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -256,6 +256,7 @@ copy /y $(ProjectDir)\..\config\* $(ProjectDir)\..\..\build\$(Configuration)\$(P
|
|||
<None Include="..\..\test\functional\tests\120_dev_errors_exception.js" />
|
||||
<None Include="..\..\test\functional\tests\121_watchedFiles.js" />
|
||||
<None Include="..\..\test\functional\tests\122_multipleResponseHeaders.js" />
|
||||
<None Include="..\..\test\functional\tests\123_upload.js" />
|
||||
<None Include="..\..\test\functional\tests\200_samples.bat" />
|
||||
<None Include="..\..\test\functional\tests\node_modules\iisnodeassert.js" />
|
||||
<None Include="..\..\test\functional\tests\parts\106_autoupdate_first.js" />
|
||||
|
@ -313,6 +314,8 @@ copy /y $(ProjectDir)\..\config\* $(ProjectDir)\..\..\build\$(Configuration)\$(P
|
|||
<None Include="..\..\test\functional\www\121_watchedFiles\web.config" />
|
||||
<None Include="..\..\test\functional\www\122_multipleResponseHeaders\hello.js" />
|
||||
<None Include="..\..\test\functional\www\122_multipleResponseHeaders\web.config" />
|
||||
<None Include="..\..\test\functional\www\123_upload\hello.js" />
|
||||
<None Include="..\..\test\functional\www\123_upload\web.config" />
|
||||
<None Include="..\..\test\performance\client.bat" />
|
||||
<None Include="..\..\test\performance\localRun.bat" />
|
||||
<None Include="..\..\test\performance\readme.txt" />
|
||||
|
|
|
@ -135,6 +135,9 @@
|
|||
<Filter Include="Tests\functional\www\122_multipleResponseHeaders">
|
||||
<UniqueIdentifier>{53b5e674-69d5-4bae-9629-49cd8b7c7c45}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Tests\functional\www\123_upload">
|
||||
<UniqueIdentifier>{18d24dd3-6582-41ad-856d-949ed0626792}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp">
|
||||
|
@ -588,6 +591,15 @@
|
|||
<None Include="..\..\test\functional\tests\122_multipleResponseHeaders.js">
|
||||
<Filter>Tests\functional\tests</Filter>
|
||||
</None>
|
||||
<None Include="..\..\test\functional\www\123_upload\hello.js">
|
||||
<Filter>Tests\functional\www\123_upload</Filter>
|
||||
</None>
|
||||
<None Include="..\..\test\functional\www\123_upload\web.config">
|
||||
<Filter>Tests\functional\www\123_upload</Filter>
|
||||
</None>
|
||||
<None Include="..\..\test\functional\tests\123_upload.js">
|
||||
<Filter>Tests\functional\tests</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="iisnode.rc" />
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Uploading data with and without chunked transfer encoding works
|
||||
*/
|
||||
|
||||
var iisnodeassert = require("iisnodeassert");
|
||||
|
||||
iisnodeassert.sequence([
|
||||
iisnodeassert.post(10000, "/123_upload/hello.js", { body: 'abc', chunked: true }, 200, 'true-abc'),
|
||||
iisnodeassert.post(2000, "/123_upload/hello.js", { body: 'def' }, 200, 'false-def'),
|
||||
iisnodeassert.post(2000, "/123_upload/hello.js", { body: '', chunked: true, headers: { 'transfer-encoding': 'chunked'} }, 200, 'true-'),
|
||||
iisnodeassert.post(2000, "/123_upload/hello.js", { body: '' }, 200, 'false-'),
|
||||
function (next) {
|
||||
// test for multi-chunk upload
|
||||
|
||||
var net = require('net')
|
||||
, assert = require('assert');
|
||||
|
||||
var timeout = setTimeout(function () {
|
||||
console.error('Timeout occurred');
|
||||
assert.ok(false, 'request timed out');
|
||||
next();
|
||||
}, 2000);
|
||||
|
||||
var host = process.env.IISNODETEST_HOST || 'localhost';
|
||||
var port = process.env.IISNODETEST_PORT || 31415;
|
||||
|
||||
var client = net.connect(port, host, function () {
|
||||
client.setEncoding('utf8');
|
||||
client.write('POST /123_upload/hello.js HTTP/1.1\r\nHost: ' + host + ':' + port + '\r\nTransfer-Encoding: chunked\r\n\r\n'
|
||||
+ '3\r\nabc\r\n4\r\ndefg\r\n0\r\n\r\n');
|
||||
});
|
||||
|
||||
client.on('data', function (data) {
|
||||
clearTimeout(timeout);
|
||||
console.log('Received response: ' + data);
|
||||
assert.ok(data.indexOf('true-abcdefg') > 0, 'Request contains two chunks of data in the entity body');
|
||||
client.end();
|
||||
next();
|
||||
});
|
||||
}
|
||||
]);
|
|
@ -37,7 +37,9 @@ function request(timeout, path, message, verb, expectedStatusCode, expectedBody,
|
|||
}
|
||||
|
||||
if (body) {
|
||||
options.headers["Content-Length"] = body.length;
|
||||
if (!message.chunked) {
|
||||
options.headers["Content-Length"] = body.length;
|
||||
}
|
||||
}
|
||||
else if (verb === "POST" || verb === "PUT") {
|
||||
options.headers["Content-Length"] = 0;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
var http = require('http');
|
||||
|
||||
http.createServer(function (req, res) {
|
||||
var response = req.headers['transfer-encoding'] === 'chunked' ? 'true-' : 'false-';
|
||||
|
||||
req.on('data', function (chunk) {
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
req.on('end', function () {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end(response);
|
||||
});
|
||||
}).listen(process.env.PORT);
|
|
@ -0,0 +1,7 @@
|
|||
<configuration>
|
||||
<system.webServer>
|
||||
<handlers>
|
||||
<add name="iisnode" path="hello.js" verb="*" modules="iisnode" />
|
||||
</handlers>
|
||||
</system.webServer>
|
||||
</configuration>
|
Загрузка…
Ссылка в новой задаче