1
votes

I'm busy with my own http server implementation on an embedded platform. Technically the server is HTTP 1.0 compliant, and therefore it expects the the client to send the header "Connection: Keep-Alive" to keep the connection open.

The implementation looks like this. I removed the code that parses the HTTP header and performs the request, to keep the post as short as possible:

int Service_Request(int conn) {

    struct ReqInfo reqinfo;
    volatile int resource = 0;
    int retval = 0;
    Req_Result req_result = GOT_REQ;

    InitReqInfo(&reqinfo);

    /* while we still have HTTP requests to process */
    while (req_result == GOT_REQ)
    {

        /*  Get HTTP request, there are 3 different return values:
         * GOT_REQ: we got a valid HTTP request
         * TIMEOUT_REQ we timed out waiting for a request
         * ERROR_REQ there was some error receiving from the socket
         * usually because the connection was closed by the peer*/
        req_result = Get_Request(conn, &reqinfo);
        if ( req_result == TIMEOUT_REQ)
        {
            /* timed out waiting for the client, exit */
            retval = 0;
            break;
        }
        else if (req_result == ERROR_REQ)
        {
            /* some error, exit */
            retval = -1;
            break;
        }
        /* Process the request GET, PUT and POST is supported*/
        if (reqinfo.method == GET) 
        {
            /* code to handle GET*/
        } 
        /* PUT and POST are handled in the same way */
        else if ((reqinfo.method == PUT) || (reqinfo.method == POST) )
        {
            /* Code to handle PUT and POST*/        
        } 
        else 
        {
            /* not supported, code should never get here */
            reqinfo.status = 501;
            Return_Error_Msg(conn, &reqinfo);
        }
        /*Diag_Msg("Client Request: \r\n");
        Diag_Msg(reqinfo.clientRequest);*/

        /*
         * the reqinfo.keep_alive flag will be set to 1 if the 
         * "Connection: Keep-Alive" header was sent by the client
         */
        if(reqinfo.keep_alive == 0)
        {
            break;
        }

        reqinfo.keep_alive_max--;
        if(reqinfo.keep_alive_max <= 0 )
        {
            /*
             * the connection has been reused for the maxmum amount of times, stop
             */
            break;
        }
        /*
         * If we get here, we will clear the memory used for the client request
         * and go to the beginning of the while loop to receive another request
         */
        Writeline(conn,"\r\n",2);
        FreeReqInfo(&reqinfo);

    }
    FreeReqInfo(&reqinfo);
    return (retval);
}

The Get_Request function looks like this:

Req_Result Get_Request(int conn, struct ReqInfo * reqinfo) {

    char   buffer[MAX_REQ_LINE] = {0};
    int    rval;
    fd_set fds;
    struct timeval tv;


    /*  Set timeout to 5 seconds if this is the first request since the client connected, wait 5 seconds
     * Otherwise, wait 5ms */
    if(reqinfo->first_request == 1)
    {
        tv.tv_sec  = 5;
        tv.tv_usec = 0;
        reqinfo->first_request = 0;
    }
    else
    {
        tv.tv_sec  = reqinfo->keep_alive_timeout;
        tv.tv_usec = 0;
    }

    /*  Loop through request headers. If we have a simple request,
    then we will loop only once. Otherwise, we will loop until
    we receive a blank line which signifies the end of the headers,
    or until select() times out, whichever is sooner.                */
    do {

    /*  Reset file descriptor set  */

    FD_ZERO(&fds);
    FD_SET (conn, &fds);


    /*  Wait until the timeout to see if input is ready  */

    rval = select(conn + 1, &fds, NULL, NULL, &tv);


    /*  Take appropriate action based on return from select()  */

    if ( rval < 0 ) 
    {
        Diag_Msg("Error calling select() in get_request()");
        return (ERROR_REQ);
    }
    else if ( rval == 0 ) {

        /*  input not ready after timeout  */

        return (TIMEOUT_REQ);

    }
    else {

        /*  We have an input line waiting, so retrieve it  */
        memset(buffer,0,MAX_REQ_LINE - 1);
        if(Readline(conn, buffer, MAX_REQ_LINE - 1) == -1)
        {
            return (ERROR_REQ);
        }
        if(reqinfo->clientRequest == NULL)
        { 
            reqinfo->clientRequest = calloc(MAX_REQ_LINE - 1, sizeof(char));
            strncpy(reqinfo->clientRequest,buffer,MAX_REQ_LINE - 1);
        }
        else
        {
            strncat(reqinfo->clientRequest,buffer,MAX_REQ_LINE - 1);
        }
        Trim(buffer);

        if ( buffer[0] == '\0' )
        break;

        if ( Parse_HTTP_Header(buffer, reqinfo) )
        break;
    }
    } while ( reqinfo->type != SIMPLE );

    return (GOT_REQ);
}

To describe the workings of this server in English: The server receives the first request. It parses the headers, if it finds the "Connection: Keep-Alive" header, it sets a flag. The server proceeds to process this request. WHen it is done it checks the keep-alive flag. If it is cleared, the server closes the connection. If set the server performs a clean-up operation and the proceeds to wait for another request over the same connection. And so on.

I tested this with curl:

C:\curl>curl -v -H "Connection: Keep-Alive" --data-binary @vid1.bin 10.84.67.129/s1p0:1/vid[1-2].bin

[1/2]: 10.84.67.129/s1p0:1/vid1.bin --> <stdout>
--_curl_--10.84.67.129/s1p0:1/vid1.bin
* About to connect() to 10.84.67.129 port 80 (#0)
*   Trying 10.84.67.129...
* connected
* Connected to 10.84.67.129 (10.84.67.129) port 80 (#0)
> POST /s1p0:1/vid1.bin HTTP/1.1
> User-Agent: curl/7.28.1
> Host: 10.84.67.129
> Accept: */*
> Connection: Keep-Alive
> Content-Length: 51200
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
>
* HTTP 1.0, assume close after body
< HTTP/1.0 100 Continue
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: DTSVU v0.1
< Content-Type: text/html
* HTTP/1.0 connection set to keep alive!
< Connection: Keep-Alive
< Keep-Alive: timeout=5, max=10
<
* Connection #0 to host 10.84.67.129 left intact

[2/2]: 10.84.67.129/s1p0:1/vid2.bin --> <stdout>
--_curl_--10.84.67.129/s1p0:1/vid2.bin
* Connection #0 seems to be dead!
* Closing connection #0
* About to connect() to 10.84.67.129 port 80 (#0)
*   Trying 10.84.67.129...
* connected
* Connected to 10.84.67.129 (10.84.67.129) port 80 (#0)
> POST /s1p0:1/vid2.bin HTTP/1.1
> User-Agent: curl/7.28.1
> Host: 10.84.67.129
> Accept: */*
> Connection: Keep-Alive
> Content-Length: 51200
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
>
* HTTP 1.0, assume close after body
< HTTP/1.0 100 Continue
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: DTSVU v0.1
< Content-Type: text/html
* HTTP/1.0 connection set to keep alive!
< Connection: Keep-Alive
< Keep-Alive: timeout=5, max=10
<
* Connection #0 to host 10.84.67.129 left intact
* Closing connection #0

As you can see, curl says: Connection #0 seems to be dead! afterthefirstrequest is completed. It then proceeds to close the connection and opens a new one. I'm sure I implemented the HTTP 1.0 keep-alive functionality correctly. SO my question is: what does curl expect over the connection after the first request is completed? Why does it decide the connection is dead?

PS the above code was adapted from http://www.paulgriffiths.net/program/c/webserv.php

1

1 Answers

5
votes

I solved it. If the server replies with HTTP/1.1 and "Content-Length: 0", curl reuses the connection. My server reply lookes like this

< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Server: DTSVU v0.1
< Content-Type: text/html
< Connection: Keep-Alive
< Keep-Alive: timeout=1, max=95
< Content-Length: 0

after the 5th reuse of the connection.