2019-05-24
Enabling keep-alive when chaining multiple SOAP services with gSoap
stackoverflow
Question

We have a C++ windows project that performs soap method proxying. We use gSoap to implement both input / server services to accept data via SOAP methods and output / client services to proxy the incoming calls to a remote system.

The SOAP services are defined in a fixed legacy specification and we have no control over the client systems that we receive data from or the server that we proxy the data to.

The SOAP services are defined in multiple WSDL specs and our system must implement all services behind one server port / endpoint. The gSoap documentation covers this requirement (section How to Chain C++ Server Classes to Accept Messages on the Same Port) and our code follows the guidance closely.

On the whole the system works well and we have an integrated lightweight solution that is deployed stand-alone.

But the code is deployed in some intensive high volume / high call frequency situations and this is giving us several problems. I believe our problems would be solved if the server-side services would allow HTTP keep-alive but the documented gSoap method for chaining multiple services specifically advises against enabling HTTP keep-alive:

Do not enable keep-alive support, as the socket may stay open indefinitely afterwards as a consequence.

We have made several attempts to ignore this advice by initiating services / gSoap with the SOAP_IO_KEEPALIVE flag but there seems to be code within the gSoap HTTP connection handling that forces connections to be closed after each SOAP transaction is completed.

These are the types of problem that this limitation causes:

  • General concerns over the inefficiency of needing a new HTTP connection for each unique transaction in high volume situations. Concerns over ephemeral port exhaustion.
  • Some clients attempt to invoke SOAP methods with partial data payloads using HTTP Expect: 100-continue. These transactions always fail since the connection is closed by the gSoap server, giving the client no opportunity to continue.
  • It is sometimes necessary to use secure SSL communications. But with no HTTP keep-alive the clients need to negotiate an SSL connection for every transaction, which carries a prohibitively high overhead in high call frequency situations.

Some details on our implementation: We combine services using gSoap WSDL2H:

wsdl2h -NServTest -s -o ServTestWebServices.h Service1.wsdl Service2.wsdl Service3.wsdl

The resulting gSoap definition is then used to implement server-side service classes:

soapcpp2 -S -j -w -qServTestWSIn -x ServTestWebServices.h

Normally when implementing gSoap integrated HTTP connection handling without service chaining the code simply accepts connections and invokes soap_serve to handle the entire lifetime of the connection:

struct soap gsoap;
soap_init2(&gsoap, SOAP_IO_KEEPALIVE, SOAP_IO_KEEPALIVE);
SOAP_SOCKET m = soap_bind(gsoap, NULL, port, backlog);
while (soap_valid_socket(soap_accept(gsoap)))
{
    soap_serve(gsoap); 
    soap_destroy(gsoap); 
    soap_end(gsoap);
}

When chaining multiple services, soap_serve() does not work since it is not capable of directing the call to the correct service. Instead the following approach is used:

struct soap gsoap;

//SOAP_IO_KEEPALIVE does not help here:
soap_init2(&gsoap, SOAP_IO_KEEPALIVE, SOAP_IO_KEEPALIVE); 

Service1 srv1(gsoap);
Service2 srv2(gsoap);
Service3 srv3(gsoap);

SOAP_SOCKET m = soap_bind(gsoap, NULL, port, backlog);
while (soap_valid_socket(soap_accept(gsoap)))
{
    if (soap_begin_serve(gsoap))
        soap_stream_fault(gsoap, std::cerr);
    else
    {
        if (srv1.dispatch() == SOAP_NO_METHOD)
            if (srv2.dispatch() == SOAP_NO_METHOD)
                srv3.dispatch();
            if (soap->error)
                soap_send_fault(gsoap);
    }
}

I realise that this is a documented limitation of the gSoap system but it seems likely to me that others may have experienced similar problems. Has anyone found a solution / work-around that they could share?

Answer
1

I am sharing an answer to my own question here. I eventually got some help from the gSoap people, who confirmed that there should be no problem with the solution that I found. This is the gist of the code I am now using (based on the code in the question):

gsoap->keep_alive = gsoap->max_keep_alive + 1;

do
{
    if ((gsoap->keep_alive > 0) && (gsoap->max_keep_alive > 0))
        gsoap->keep_alive--;

    if (soap_begin_serve(gsoap))
    {
        if (gsoap->error >= SOAP_STOP)
            continue;
        else
            break;
    }

    if ((SoapServeResult = srv1.dispatch()) == SOAP_NO_METHOD)
        if ((SoapServeResult = srv2.dispatch()) == SOAP_NO_METHOD)
            if ((SoapServeResult = srv3.dispatch()) == SOAP_NO_METHOD)
            {
                //no method found - send fault to client
                soap_send_fault(srv3.soap);
            }
} while (m_pSoap->keep_alive);

This is the response from Genivia / gSoap support:

The approach is fine to make sure HTTP keep-alive is enabled while running the server loop.

The example in the documentation is a simplified approach to show the logic of chaining multiple attempted dispatches, but it has the keep-alive limitation you stated.

But that does not prevent the use of the same strategy we use for a non-changed service dispatching. The code you are using is fine, replacing the gist of the loop body by the dispatch attempts.

Wondering if we should say something in the documentation about this limitation, since it does not mention this.

Enabling keep-alive when chaining multiple SOAP services with gSoap
See more ...