I wrote a REST server using C++Builder XE6 and Indy 10.6.0.5122. The server uses TIdHTTPServer and TIdServerIOHandlerSSLOpenSLL.
The REST client is configured to send a GET request every 5000 ms and a POST request every 60000 ms. When the server receives a GET request, it sends a '200 OK' response and JSON data in the content. When the client sends a POST request, the content contains JSON data and the server sends a '204 No Content' response without content.
When KeepAlive = false, everything works fine for both HTTP and HTTPS. When KeepAlive = true, the REST client log file shows that it receives HTML content from the server approximately every 60000 ms:
Recv 15-06-2026 15:40:02.851: 204 No Content
When I configure the REST client to send a POST request every 10000 ms, the HTML content is also received approximately every 10000 ms.
The '204 No Content' in the HTML looks like a leftover from the last POST response sent by the server and is only sent to the client when KeepAlive = true on the server.
This is the server's OnCommandGet event handler:
void __fastcall TMainForm::HTTPCommandGet (TIdContext *AContext, TIdHTTPRequestInfo *ARequestInfo, TIdHTTPResponseInfo *AResponseInfo)
{
TCommandHandler Handler;
// Initialize.
Handler.Context = AContext;
Handler.RequestInfo = ARequestInfo;
Handler.ResponseInfo = AResponseInfo;
Handler.ListHistory = ListHistory;
Handler.EditVehiclesURI = EditVehiclesURI;
Handler.ListVehiclesResponse = ListVehiclesResponse;
Handler.ListVehiclesAction = ListVehiclesAction;
Handler.ListVehiclesCondition = ListVehiclesCondition;
Handler.BtnVehiclesLimitsAsText = BtnVehiclesLimitsAsText;
Handler.ListVehicles = ListVehicles;
Handler.EditEquipmentURI = EditEquipmentURI;
Handler.ListEquipmentResponse = ListEquipmentResponse;
Handler.TreeEquipment = TreeEquipment;
Handler.ListEquipment = ListEquipment;
Handler.EditStatusURI = EditStatusURI;
Handler.ListStatusResponse = ListStatusResponse;
Handler.ListStatus = ListStatus;
Handler.EditResultsURI = EditResultsURI;
Handler.ListResultsResponse = ListResultsResponse;
Handler.ListResults = ListResults;
// Add request to 'History' list-view.
TThread::Synchronize(NULL, &Handler.LogRequest);
// Process request.
TThread::Synchronize(NULL, &Handler.ProcessRequest);
// Check if request should be ignored.
if (AResponseInfo->ResponseNo <= 0)
{
// Prevent sending response.
AResponseInfo->HeaderHasBeenWritten = true;
return;
}
// Generate headers.
if (!AResponseInfo->HeaderHasBeenWritten)
AResponseInfo->WriteHeader();
// If only headers requested, clear content.
if (ARequestInfo->CommandType == hcHEAD)
AResponseInfo->ContentText = _D("");
// Send response.
String S = AResponseInfo->ContentText; // NOTE: WriteContent() clears ContentText and ContentStream properties.
AResponseInfo->WriteContent();
AResponseInfo->ContentText = S;
// Add response to 'History' list-view.
TThread::Synchronize(NULL, &Handler.LogResponse);
// Clear content.
AResponseInfo->ContentText = _D("");
}
And this is the ProcessRequest() method of my command handler:
void __fastcall TCommandHandler::ProcessRequest ()
{
// Check if request matches 'Vehicles' URI.
if (SameText(EditVehiclesURI->Text, RequestInfo->URI))
{
// Validate HTTP method.
if (RequestInfo->CommandType == hcGET ||
RequestInfo->CommandType == hcHEAD) ResponseInfo->ResponseNo = 200; // 200 OK
else ResponseInfo->ResponseNo = 405; // 405 Method not allowed
// If custom response code specified, set response code.
if (ListVehiclesResponse->ItemIndex != 0)
ResponseInfo->ResponseNo = (ListVehiclesResponse->ItemIndex < 0 ? ListVehiclesResponse->Text.ToInt() :
int(ListVehiclesResponse->Items->Objects[ListVehiclesResponse->ItemIndex]));
switch (ResponseInfo->ResponseNo)
{
case 200:
// Set 'Content-Type' header.
ResponseInfo->ContentType = _D("application/json");
// Set content.
ResponseInfo->ContentText = VehiclesToJSON();
break;
case 405:
// Add 'Allow' header.
ResponseInfo->CustomHeaders->AddValue(_D("Allow"), _D("GET, HEAD"));
}
return;
}
// Check if request matches 'Equipment' URI.
if (SameText(EditEquipmentURI->Text, RequestInfo->URI))
{
// Validate HTTP method.
if (RequestInfo->CommandType == hcPOST)
{
// Validate content type.
if (SameText(RequestInfo->ContentType, _D("application/json")))
{
// Parse content of POST request.
RequestInfo->PostStream->Position = 0;
ResponseInfo->ResponseNo = ParseEquipment(ReadStringFromStream(RequestInfo->PostStream));
}
else
ResponseInfo->ResponseNo = 415; // 415 Unsupported media type
} else ResponseInfo->ResponseNo = 405; // 405 Method not allowed
// If custom response code specified, set response code.
if (ListEquipmentResponse->ItemIndex != 0)
ResponseInfo->ResponseNo = (ListEquipmentResponse->ItemIndex < 0 ? ListEquipmentResponse->Text.ToInt() :
int(ListEquipmentResponse->Items->Objects[ListEquipmentResponse->ItemIndex]));
goto Post;
}
// Check if request matches 'Status' URI.
if (StartsText(EditStatusURI->Text, RequestInfo->URI))
{
// Validate HTTP method.
if (RequestInfo->CommandType == hcPOST)
{
// Validate content type.
if (SameText(RequestInfo->ContentType, _D("application/json")))
{
// Parse content of POST request.
RequestInfo->PostStream->Position = 0;
ResponseInfo->ResponseNo = ParseStatus(ReadStringFromStream(RequestInfo->PostStream));
}
else
ResponseInfo->ResponseNo = 415; // 415 Unsupported media type
} else ResponseInfo->ResponseNo = 405; // 405 Method not allowed
// If custom response code specified, set response code.
if (ListStatusResponse->ItemIndex != 0)
ResponseInfo->ResponseNo = (ListStatusResponse->ItemIndex < 0 ? ListStatusResponse->Text.ToInt() :
int(ListStatusResponse->Items->Objects[ListStatusResponse->ItemIndex]));
goto Post;
}
// Check if request matches 'Results' URI.
if (StartsText(EditResultsURI->Text, RequestInfo->URI))
{
// Validate HTTP method.
if (RequestInfo->CommandType == hcPOST)
{
// Validate content type.
if (SameText(RequestInfo->ContentType, _D("application/json")))
{
// Parse content of POST request.
RequestInfo->PostStream->Position = 0;
ResponseInfo->ResponseNo = ParseResults(ReadStringFromStream(RequestInfo->PostStream));
}
else
ResponseInfo->ResponseNo = 415; // 415 Unsupported media type
} else ResponseInfo->ResponseNo = 405; // 405 Method not allowed
// If custom response code specified, set response code.
if (ListResultsResponse->ItemIndex != 0)
ResponseInfo->ResponseNo = (ListResultsResponse->ItemIndex < 0 ? ListResultsResponse->Text.ToInt() :
int(ListResultsResponse->Items->Objects[ListResultsResponse->ItemIndex]));
goto Post;
}
// Set default response code.
ResponseInfo->ResponseNo = 404; // 404 Not found
Post:
switch (ResponseInfo->ResponseNo)
{
case 200:
// Set 'Content-Type' header.
ResponseInfo->ContentType = _D("application/json");
// Set content.
ResponseInfo->ContentText = _D("{\"result\":\"OK\"}");
break;
case 201:
// Add 'Location' header.
ResponseInfo->Location = RequestInfo->URI;
break;
case 204:
case 205:
break;
case 405:
// Add 'Allow' header.
ResponseInfo->CustomHeaders->AddValue(_D("Allow"), _D("POST"));
}
}
The HTML content is often discarded by the client, but sometimes it's received right after a POST request, which causes an EIdHTTPProtocolException in the client:
Error 15-06-2026 16:11:52.643: PostEquipment(): EIdHTTPProtocolException: POST /equipment 204 No Content
How do I prevent the server from sending the (undesired) HTML content when KeepAlive = true?