How to prevent TIdHTTPServer sending HTML content when KeepAlive = true?
10:35 15 Jun 2026

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?

c++ c++builder indy vcl