diff --git a/src/iisnode/chttpprotocol.cpp b/src/iisnode/chttpprotocol.cpp
index 64b1c81..bc48c89 100644
--- a/src/iisnode/chttpprotocol.cpp
+++ b/src/iisnode/chttpprotocol.cpp
@@ -270,6 +270,113 @@ Error:
return hr;
}
+HRESULT CHttpProtocol::ParseChunkHeader(CNodeHttpStoredContext* context)
+{
+ HRESULT hr;
+
+ char* data = (char*)context->GetBuffer() + context->GetParsingOffset();
+ char* current;
+ char* chunkHeaderStart;
+ DWORD dataSize = context->GetDataSize() - context->GetParsingOffset();
+ ULONG chunkLength = 0;
+ ULONG totalChunkLength = 0;
+
+ // attempt to parse as many response body chunks as there are buffered in memory
+
+ current = data;
+ do
+ {
+ // parse chunk length
+
+ chunkHeaderStart = current;
+ chunkLength = 0;
+ while (true)
+ {
+ ErrorIf((current - data) >= dataSize, ERROR_MORE_DATA);
+ if (*current >= 'A' && *current <= 'F')
+ {
+ chunkLength <<= 4;
+ chunkLength += *current - 'A' + 10;
+ }
+ else if (*current >= 'a' && *current <= 'f')
+ {
+ chunkLength <<= 4;
+ chunkLength += *current - 'a' + 10;
+ }
+ else if (*current >= '0' && *current <= '9')
+ {
+ chunkLength <<= 4;
+ chunkLength += *current - '0';
+ }
+ else
+ {
+ ErrorIf(current == chunkHeaderStart, ERROR_BAD_FORMAT); // no hex digits found
+ break;
+ }
+
+ current++;
+ }
+
+ // skip optional extensions
+
+ while (true)
+ {
+ ErrorIf((current - data) >= dataSize, ERROR_MORE_DATA);
+ if (*current == 0x0D)
+ {
+ break;
+ }
+
+ current++;
+ }
+
+ // LF
+
+ current++;
+ ErrorIf((current - data) >= dataSize, ERROR_MORE_DATA);
+ ErrorIf(*current != 0x0A, ERROR_BAD_FORMAT);
+ current++;
+
+ // remember total length of all parsed chunks before attempting to parse subsequent chunk header
+
+ // set total chunk length to include current chunk content length, previously parsed chunks (with headers),
+ // plus the CRLF following the current chunk content
+ totalChunkLength = chunkLength + (ULONG)(current - data) + 2;
+ current += chunkLength + 2; // chunk content length + CRLF
+
+ } while (chunkLength != 0); // exit when last chunk has been detected
+
+ // if we are here, current buffer contains the header of the last chunk of the response
+
+ context->SetChunkLength(totalChunkLength);
+ context->SetIsLastChunk(TRUE);
+ context->SetChunkTransmitted(0);
+
+ return S_OK;
+
+Error:
+
+ if (ERROR_MORE_DATA != hr)
+ {
+ context->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
+ L"iisnode failed to parse response body chunk header", WINEVENT_LEVEL_ERROR, context->GetActivityId());
+
+ return hr;
+ }
+ else if (0 < totalChunkLength)
+ {
+ // at least one response chunk has been successfuly parsed, but more chunks remain
+
+ context->SetChunkLength(totalChunkLength);
+ context->SetIsLastChunk(FALSE);
+ context->SetChunkTransmitted(0);
+
+ return S_OK;
+ }
+
+ return hr;
+}
+
HRESULT CHttpProtocol::ParseResponseHeaders(CNodeHttpStoredContext* context)
{
HRESULT hr;
diff --git a/src/iisnode/chttpprotocol.h b/src/iisnode/chttpprotocol.h
index 4ab7b08..353069a 100644
--- a/src/iisnode/chttpprotocol.h
+++ b/src/iisnode/chttpprotocol.h
@@ -15,6 +15,7 @@ public:
static HRESULT SerializeRequestHeaders(CNodeHttpStoredContext* ctx, void** result, DWORD* resultSize, DWORD* resultLength);
static HRESULT ParseResponseStatusLine(CNodeHttpStoredContext* context);
static HRESULT ParseResponseHeaders(CNodeHttpStoredContext* context);
+ static HRESULT ParseChunkHeader(CNodeHttpStoredContext* context);
};
#endif
\ No newline at end of file
diff --git a/src/iisnode/cnodehttpstoredcontext.cpp b/src/iisnode/cnodehttpstoredcontext.cpp
index 9a6ae8d..d84b617 100644
--- a/src/iisnode/cnodehttpstoredcontext.cpp
+++ b/src/iisnode/cnodehttpstoredcontext.cpp
@@ -2,7 +2,7 @@
CNodeHttpStoredContext::CNodeHttpStoredContext(CNodeApplication* nodeApplication, IHttpContext* context)
: nodeApplication(nodeApplication), context(context), process(NULL), buffer(NULL), bufferSize(0), dataSize(0), parsingOffset(0),
- responseContentLength(0), responseContentTransmitted(0), pipe(INVALID_HANDLE_VALUE), result(S_OK),
+ chunkLength(0), chunkTransmitted(0), isChunked(FALSE), pipe(INVALID_HANDLE_VALUE), result(S_OK), isLastChunk(FALSE),
requestNotificationStatus(RQ_NOTIFICATION_PENDING), connectionRetryCount(0), pendingAsyncOperationCount(1),
targetUrl(NULL), targetUrlLength(0), childContext(NULL)
{
@@ -153,24 +153,44 @@ void CNodeHttpStoredContext::SetParsingOffset(DWORD parsingOffset)
this->parsingOffset = parsingOffset;
}
-LONGLONG CNodeHttpStoredContext::GetResponseContentTransmitted()
+LONGLONG CNodeHttpStoredContext::GetChunkTransmitted()
{
- return this->responseContentTransmitted;
+ return this->chunkTransmitted;
}
-LONGLONG CNodeHttpStoredContext::GetResponseContentLength()
+LONGLONG CNodeHttpStoredContext::GetChunkLength()
{
- return this->responseContentLength;
+ return this->chunkLength;
}
-void CNodeHttpStoredContext::SetResponseContentTransmitted(LONGLONG length)
+void CNodeHttpStoredContext::SetChunkTransmitted(LONGLONG length)
{
- this->responseContentTransmitted = length;
+ this->chunkTransmitted = length;
}
-void CNodeHttpStoredContext::SetResponseContentLength(LONGLONG length)
+void CNodeHttpStoredContext::SetChunkLength(LONGLONG length)
{
- this->responseContentLength = length;
+ this->chunkLength = length;
+}
+
+BOOL CNodeHttpStoredContext::GetIsChunked()
+{
+ return this->isChunked;
+}
+
+void CNodeHttpStoredContext::SetIsChunked(BOOL chunked)
+{
+ this->isChunked = chunked;
+}
+
+void CNodeHttpStoredContext::SetIsLastChunk(BOOL lastChunk)
+{
+ this->isLastChunk = lastChunk;
+}
+
+BOOL CNodeHttpStoredContext::GetIsLastChunk()
+{
+ return this->isLastChunk;
}
HRESULT CNodeHttpStoredContext::GetHresult()
diff --git a/src/iisnode/cnodehttpstoredcontext.h b/src/iisnode/cnodehttpstoredcontext.h
index 9f7038c..b0a1a6d 100644
--- a/src/iisnode/cnodehttpstoredcontext.h
+++ b/src/iisnode/cnodehttpstoredcontext.h
@@ -19,14 +19,16 @@ private:
DWORD bufferSize;
DWORD dataSize;
DWORD parsingOffset;
- LONGLONG responseContentTransmitted;
- LONGLONG responseContentLength;
+ LONGLONG chunkTransmitted;
+ LONGLONG chunkLength;
+ BOOL isChunked;
HRESULT result;
REQUEST_NOTIFICATION_STATUS requestNotificationStatus;
long pendingAsyncOperationCount;
PCSTR targetUrl;
DWORD targetUrlLength;
IHttpContext* childContext;
+ BOOL isLastChunk;
public:
@@ -47,8 +49,11 @@ public:
DWORD* GetBufferSizeRef();
DWORD GetDataSize();
DWORD GetParsingOffset();
- LONGLONG GetResponseContentTransmitted();
- LONGLONG GetResponseContentLength();
+ LONGLONG GetChunkTransmitted();
+ LONGLONG GetChunkLength();
+ BOOL GetIsChunked();
+ void SetIsLastChunk(BOOL lastChunk);
+ BOOL GetIsLastChunk();
HRESULT GetHresult();
REQUEST_NOTIFICATION_STATUS GetRequestNotificationStatus();
GUID* GetActivityId();
@@ -66,8 +71,9 @@ public:
void SetBufferSize(DWORD bufferSize);
void SetDataSize(DWORD dataSize);
void SetParsingOffset(DWORD parsingOffet);
- void SetResponseContentTransmitted(LONGLONG length);
- void SetResponseContentLength(LONGLONG length);
+ void SetChunkTransmitted(LONGLONG length);
+ void SetChunkLength(LONGLONG length);
+ void SetIsChunked(BOOL chunked);
void SetHresult(HRESULT result);
void SetRequestNotificationStatus(REQUEST_NOTIFICATION_STATUS status);
LPOVERLAPPED InitializeOverlapped();
diff --git a/src/iisnode/cprotocolbridge.cpp b/src/iisnode/cprotocolbridge.cpp
index 860aa44..651713a 100644
--- a/src/iisnode/cprotocolbridge.cpp
+++ b/src/iisnode/cprotocolbridge.cpp
@@ -758,18 +758,6 @@ void CProtocolBridge::ContinueReadResponse(CNodeHttpStoredContext* context)
WINEVENT_LEVEL_VERBOSE,
&activityId);
}
- else if (context->GetResponseContentLength() == -1)
- {
- // connection termination with chunked transfer encoding indicates end of response
- // since we have sent Connection: close HTTP request header to node from SendHttpRequestHeaders
-
- etw->Log(L"iisnode iniatiated reading http response chunk and synchronously detected the end of the http response",
- WINEVENT_LEVEL_VERBOSE,
- &activityId);
-
- // CR: narrow down this condition to orderly pipe closure
- CProtocolBridge::FinalizeResponse(context);
- }
else
{
// error
@@ -844,7 +832,8 @@ void WINAPI CProtocolBridge::ProcessResponseHeaders(DWORD error, DWORD bytesTran
contentLength = ctx->GetHttpContext()->GetResponse()->GetHeader(HttpHeaderContentLength, &contentLengthLength);
if (0 == contentLengthLength)
{
- ctx->SetResponseContentLength(-1);
+ ctx->SetIsChunked(TRUE);
+ ctx->SetNextProcessor(CProtocolBridge::ProcessChunkHeader);
}
else
{
@@ -858,14 +847,16 @@ void WINAPI CProtocolBridge::ProcessResponseHeaders(DWORD error, DWORD bytesTran
while (i < contentLengthLength && contentLength[i] >= '0' && contentLength[i] <= '9')
length = length * 10 + contentLength[i++] - '0';
- ctx->SetResponseContentLength(length);
+ ctx->SetIsChunked(FALSE);
+ ctx->SetIsLastChunk(TRUE);
+ ctx->SetChunkLength(length);
+ ctx->SetNextProcessor(CProtocolBridge::ProcessResponseBody);
}
ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
L"iisnode finished processing http response headers", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId());
- ctx->SetNextProcessor(CProtocolBridge::ProcessResponseBody);
- CProtocolBridge::ProcessResponseBody(S_OK, 0, ctx->GetOverlapped());
+ ctx->GetAsyncContext()->completionProcessor(S_OK, 0, ctx->GetOverlapped());
return;
Error:
@@ -884,6 +875,43 @@ Error:
return;
}
+void WINAPI CProtocolBridge::ProcessChunkHeader(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped)
+{
+ HRESULT hr;
+ CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped);
+
+ ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
+ L"iisnode starting to process http response body chunk header", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId());
+
+ CheckError(error);
+
+ ctx->SetDataSize(ctx->GetDataSize() + bytesTransfered);
+ CheckError(CHttpProtocol::ParseChunkHeader(ctx));
+
+ ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
+ L"iisnode finished processing http response body chunk header", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId());
+
+ ctx->SetNextProcessor(CProtocolBridge::ProcessResponseBody);
+ CProtocolBridge::ProcessResponseBody(S_OK, 0, ctx->GetOverlapped());
+
+ return;
+
+Error:
+
+ if (ERROR_MORE_DATA == hr)
+ {
+ CProtocolBridge::ContinueReadResponse(ctx);
+ }
+ else
+ {
+ ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
+ L"iisnode failed to process response body chunk header", WINEVENT_LEVEL_ERROR, ctx->GetActivityId());
+ CProtocolBridge::SendEmptyResponse(ctx, 500, _T("Internal Server Error"), hr);
+ }
+
+ return;
+}
+
void WINAPI CProtocolBridge::ProcessResponseBody(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped)
{
HRESULT hr;
@@ -895,64 +923,73 @@ void WINAPI CProtocolBridge::ProcessResponseBody(DWORD error, DWORD bytesTransfe
ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
L"iisnode starting to process http response body", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId());
- if (S_OK != error)
- {
- if (ctx->GetResponseContentLength() == -1)
- {
- // connection termination with chunked transfer encoding indicates end of response
- // since we have sent Connection: close HTTP request header to node from SendHttpRequestHeaders
-
- ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
- L"iisnode detected the end of the http response", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId());
-
- // CR: check the other commend for finalizing response
- CProtocolBridge::FinalizeResponse(ctx);
- }
- else
- {
- ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
- L"iisnode failed to read http response body", WINEVENT_LEVEL_ERROR, ctx->GetActivityId());
- CProtocolBridge::SendEmptyResponse(ctx, 500, _T("Internal Server Error"), error);
- }
-
- return;
- }
+ CheckError(error);
ctx->SetDataSize(ctx->GetDataSize() + bytesTransfered);
if (ctx->GetDataSize() > ctx->GetParsingOffset())
{
- // send body data to client
+ // there is response body data in the buffer
- // CR: consider using malloc here (memory can be released after Flush)
-
- ErrorIf(NULL == (chunk = (HTTP_DATA_CHUNK*) ctx->GetHttpContext()->AllocateRequestMemory(sizeof HTTP_DATA_CHUNK)), ERROR_NOT_ENOUGH_MEMORY);
- chunk->DataChunkType = HttpDataChunkFromMemory;
- chunk->FromMemory.BufferLength = ctx->GetDataSize() - ctx->GetParsingOffset();
- ErrorIf(NULL == (chunk->FromMemory.pBuffer = ctx->GetHttpContext()->AllocateRequestMemory(chunk->FromMemory.BufferLength)), ERROR_NOT_ENOUGH_MEMORY);
- memcpy(chunk->FromMemory.pBuffer, (char*)ctx->GetBuffer() + ctx->GetParsingOffset(), chunk->FromMemory.BufferLength);
-
- ctx->SetDataSize(0);
- ctx->SetParsingOffset(0);
- ctx->SetNextProcessor(CProtocolBridge::SendResponseBodyCompleted);
-
- CheckError(ctx->GetHttpContext()->GetResponse()->WriteEntityChunks(
- chunk,
- 1,
- TRUE,
- ctx->GetResponseContentLength() == -1 || ctx->GetResponseContentLength() > (ctx->GetResponseContentTransmitted() + chunk->FromMemory.BufferLength),
- &bytesSent,
- &completionExpected));
-
- ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
- L"iisnode started sending http response body chunk", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId());
-
- if (!completionExpected)
+ if (ctx->GetChunkLength() > ctx->GetChunkTransmitted())
{
- CProtocolBridge::SendResponseBodyCompleted(S_OK, chunk->FromMemory.BufferLength, ctx->GetOverlapped());
+ // send the smaller of the rest of the current chunk or the data available in the buffer to the client
+
+ DWORD dataInBuffer = ctx->GetDataSize() - ctx->GetParsingOffset();
+ DWORD remainingChunkSize = ctx->GetChunkLength() - ctx->GetChunkTransmitted();
+ DWORD bytesToSend = dataInBuffer < remainingChunkSize ? dataInBuffer : remainingChunkSize;
+
+ // CR: consider using malloc here (memory can be released after Flush)
+
+ ErrorIf(NULL == (chunk = (HTTP_DATA_CHUNK*) ctx->GetHttpContext()->AllocateRequestMemory(sizeof HTTP_DATA_CHUNK)), ERROR_NOT_ENOUGH_MEMORY);
+ chunk->DataChunkType = HttpDataChunkFromMemory;
+ chunk->FromMemory.BufferLength = bytesToSend;
+ ErrorIf(NULL == (chunk->FromMemory.pBuffer = ctx->GetHttpContext()->AllocateRequestMemory(chunk->FromMemory.BufferLength)), ERROR_NOT_ENOUGH_MEMORY);
+ memcpy(chunk->FromMemory.pBuffer, (char*)ctx->GetBuffer() + ctx->GetParsingOffset(), chunk->FromMemory.BufferLength);
+
+ if (bytesToSend == dataInBuffer)
+ {
+ ctx->SetDataSize(0);
+ ctx->SetParsingOffset(0);
+ }
+ else
+ {
+ ctx->SetParsingOffset(ctx->GetParsingOffset() + bytesToSend);
+ }
+
+ ctx->SetNextProcessor(CProtocolBridge::SendResponseBodyCompleted);
+
+ CheckError(ctx->GetHttpContext()->GetResponse()->WriteEntityChunks(
+ chunk,
+ 1,
+ TRUE,
+ !ctx->GetIsLastChunk(),
+ &bytesSent,
+ &completionExpected));
+
+ ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
+ L"iisnode started sending http response body chunk", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId());
+
+ if (!completionExpected)
+ {
+ CProtocolBridge::SendResponseBodyCompleted(S_OK, chunk->FromMemory.BufferLength, ctx->GetOverlapped());
+ }
+ }
+ else if (ctx->GetIsChunked())
+ {
+ // process next chunk of the chunked encoding
+
+ ctx->SetNextProcessor(CProtocolBridge::ProcessChunkHeader);
+ CProtocolBridge::ProcessChunkHeader(S_OK, 0, ctx->GetOverlapped());
+ }
+ else
+ {
+ // response data detected beyond the body length declared with Content-Length
+
+ CheckError(ERROR_BAD_FORMAT);
}
}
- else if (-1 == ctx->GetResponseContentLength() || ctx->GetResponseContentLength() > ctx->GetResponseContentTransmitted())
+ else if (ctx->GetIsChunked() || ctx->GetChunkLength() > ctx->GetChunkTransmitted())
{
// read more body data
@@ -983,11 +1020,15 @@ void WINAPI CProtocolBridge::SendResponseBodyCompleted(DWORD error, DWORD bytesT
BOOL completionExpected = FALSE;
CheckError(error);
- ctx->SetResponseContentTransmitted(ctx->GetResponseContentTransmitted() + bytesTransfered);
+ ctx->SetChunkTransmitted(ctx->GetChunkTransmitted() + bytesTransfered);
- if (ctx->GetResponseContentLength() == -1 || ctx->GetResponseContentLength() > ctx->GetResponseContentTransmitted())
+ if (ctx->GetIsLastChunk() && ctx->GetChunkLength() == ctx->GetChunkTransmitted())
{
- if (ctx->GetResponseContentLength() == -1 && CModuleConfiguration::GetFlushResponse(ctx->GetHttpContext()))
+ CProtocolBridge::FinalizeResponse(ctx);
+ }
+ else
+ {
+ if (ctx->GetIsChunked() && CModuleConfiguration::GetFlushResponse(ctx->GetHttpContext()))
{
// Flushing of chunked responses is enabled
@@ -1002,10 +1043,6 @@ void WINAPI CProtocolBridge::SendResponseBodyCompleted(DWORD error, DWORD bytesT
CProtocolBridge::ContinueProcessResponseBodyAfterPartialFlush(S_OK, 0, ctx->GetOverlapped());
}
}
- else
- {
- CProtocolBridge::FinalizeResponse(ctx);
- }
return;
Error:
@@ -1024,7 +1061,7 @@ void WINAPI CProtocolBridge::ContinueProcessResponseBodyAfterPartialFlush(DWORD
CheckError(error);
ctx->SetNextProcessor(CProtocolBridge::ProcessResponseBody);
- CProtocolBridge::ContinueReadResponse(ctx);
+ CProtocolBridge::ProcessResponseBody(S_OK, 0, ctx->GetOverlapped());
return;
Error:
diff --git a/src/iisnode/cprotocolbridge.h b/src/iisnode/cprotocolbridge.h
index c3c1ca7..e146ad7 100644
--- a/src/iisnode/cprotocolbridge.h
+++ b/src/iisnode/cprotocolbridge.h
@@ -30,7 +30,9 @@ private:
static void ContinueReadResponse(CNodeHttpStoredContext* context);
static void WINAPI ProcessResponseStatusLine(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped);
static void WINAPI ProcessResponseHeaders(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped);
- static void WINAPI ProcessResponseBody(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped);
+
+ static void WINAPI ProcessChunkHeader(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped);
+ static void WINAPI ProcessResponseBody(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped);
static void WINAPI SendResponseBodyCompleted(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped);
static void WINAPI ContinueProcessResponseBodyAfterPartialFlush(DWORD error, DWORD bytesTransfered, LPOVERLAPPED overlapped);
diff --git a/src/iisnode/iisnode.vcxproj b/src/iisnode/iisnode.vcxproj
index b13c6c7..7f5fdb4 100644
--- a/src/iisnode/iisnode.vcxproj
+++ b/src/iisnode/iisnode.vcxproj
@@ -238,6 +238,7 @@ copy /y $(ProjectDir)\..\config\* $(ProjectDir)\..\..\build\$(Configuration)\$(P
+
@@ -273,6 +274,8 @@ copy /y $(ProjectDir)\..\config\* $(ProjectDir)\..\..\build\$(Configuration)\$(P
+
+
diff --git a/src/iisnode/iisnode.vcxproj.filters b/src/iisnode/iisnode.vcxproj.filters
index 605595f..923ccd0 100644
--- a/src/iisnode/iisnode.vcxproj.filters
+++ b/src/iisnode/iisnode.vcxproj.filters
@@ -108,6 +108,9 @@
{6b173a34-4fbb-49ac-9e03-33cde5a6e50f}
+
+ {5e0f95ef-1256-4223-a412-f5146a7713cc}
+
@@ -462,5 +465,14 @@
Tests\performance
+
+ Tests\functional\www\113_encoding
+
+
+ Tests\functional\www\113_encoding
+
+
+ Tests\functional\tests
+
\ No newline at end of file
diff --git a/test/functional/tests/113_encoding.js b/test/functional/tests/113_encoding.js
new file mode 100644
index 0000000..93d4e65
--- /dev/null
+++ b/test/functional/tests/113_encoding.js
@@ -0,0 +1,11 @@
+/*
+Fixed length and chunked transfer encoding responses are received.
+*/
+
+var iisnodeassert = require("iisnodeassert");
+
+iisnodeassert.sequence([
+ iisnodeassert.get(10000, "/113_encoding/hello.js", 200, "content-length response"),
+ iisnodeassert.get(2000, "/113_encoding/hello.js?onechunk", 200, "chunked response"),
+ iisnodeassert.get(4000, "/113_encoding/hello.js?tenchunks", 200, "0123456789")
+]);
\ No newline at end of file
diff --git a/test/functional/www/113_encoding/hello.js b/test/functional/www/113_encoding/hello.js
new file mode 100644
index 0000000..502a36b
--- /dev/null
+++ b/test/functional/www/113_encoding/hello.js
@@ -0,0 +1,28 @@
+var http = require('http');
+
+http.createServer(function (req, res) {
+ var query = require('url').parse(req.url).query;
+ if (query === 'onechunk') { // chunked transfer encoding, one chunk
+ res.writeHead(200, { 'Content-Type': 'text/html' });
+ res.end('chunked response');
+ }
+ else if (query === 'tenchunks') { // chunked transfer encoding, ten chunks
+ res.writeHead(200, { 'Content-Type': 'text/html' });
+ var n = 0;
+ function writeOne() {
+ if (n < 9) {
+ res.write(n.toString());
+ n++;
+ setTimeout(writeOne, 200);
+ }
+ else {
+ res.end(n.toString());
+ }
+ }
+ writeOne();
+ }
+ else { // fixed response length with Content-Length
+ res.writeHead(200, { 'Content-Type': 'text/html', 'Content-Length': '23' });
+ res.end('content-length response');
+ }
+}).listen(process.env.PORT);
\ No newline at end of file
diff --git a/test/functional/www/113_encoding/web.config b/test/functional/www/113_encoding/web.config
new file mode 100644
index 0000000..ae902f5
--- /dev/null
+++ b/test/functional/www/113_encoding/web.config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+