view packages/net/athttpd/current/src/socket.c @ 2326:bd648d8929f6

* cdl/httpd.cdl: Improve CDL dependencies. * doc/athttpd.sgml: Updated to describe lasted changes and corrected minor typos. * src/http.c: Check for "Content-Type" header. This is needed if we want to support parsing form variables in POST requests. * src/jim.c: Updated with latest release from Jim CVS. * src/cgi.c: streamlined cyg_httpd_exec_cgi_tcl(), now uses the 'source' command of tcl to execute a tcl script. * src/forms.c: Modified cyg_handle_method_POST so that the variables in the payload are scanned only if the request has a Content-Type of 'application/x-www-form-urlencoded' * src/jim-aio.c: Added to package. Now tcl has IO functions to access a file system. * include/httpd.h: Added a new mode, CYG_HTTPD_MODE_FORM_DATA which is set when a POST request has a Content-Type of 'application/x-www-form-urlencoded' * cdl/httpd.cdl: add CYGOPT_NET_ATHTTPD_CLOSE_CHUNKED_CONNECTIONS. Default is set to CLOSE, so it is backward compatible with previous versions of the browser. * src/socket.c: cyg_httpd_process_request() uses a loop to collect at least one full frame (til a header terminator is found), cyg_httpd_start_chunked() only close if configured to do so. * src/httpd.c: Overhaul of cyg_httpd_send_error to avoid the use of inbuffer as temporary storage (conflicts with pipelined frames), removed the option to send a page after calling a C language handler * include/httpd.h: Added a new mode, CYG_HTTPD_MODE_NO_CACHE 2006-10-12 Lars Povlsen <lpovlsen@vitesse.com> and Anthony Tonizzo <atonizzo@gmail.com> * cdl/httpd.cdl: add CYGNUM_ATHTTPD_SERVER_MAX_POST to limit POST'ed data * include/http.h: Added header_end, post_data fields to httpstate, Added "302 Found" for POST handler redirect (CYG_HTTPD_STATUS_MOVED_TEMPORARILY) * src/forms.c: Fixed variable decoding, fixed large POST processing * src/http.c: Fixed some debug ouptuts, cleanup after POST processing, overhaul of the pipelined requests code which can now handle multiple requests per frame. * src/socket.c: Removed assert for socket write failure, Accumulating receiving of requests (Browsers (Firefox) may pass partial headers in separate fragments). Fixed some diagnostics output.
author jlarmour
date Mon, 27 Nov 2006 15:41:55 +0000
parents b5670f3c40f2
children dbff0fb46d3b
line wrap: on
line source

/* =================================================================
 *
 *      socket.c
 *
 *      Opens socket and starts the daemon.
 *
 * ================================================================= 
 * ####ECOSGPLCOPYRIGHTBEGIN####
 * -------------------------------------------
 * This file is part of eCos, the Embedded Configurable Operating
 * System.
 * Copyright (C) 2005 eCosCentric Ltd.
 * 
 * eCos is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 or (at your option)
 * any later version.
 * 
 * eCos is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with eCos; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 * 
 * As a special exception, if other files instantiate templates or
 * use macros or inline functions from this file, or you compile this
 * file and link it with other works to produce a work based on this
 * file, this file does not by itself cause the resulting work to be
 * covered by the GNU General Public License. However the source code
 * for this file must still be made available in accordance with
 * section (3) of the GNU General Public License.
 * 
 * This exception does not invalidate any other reasons why a work
 * based on this file might be covered by the GNU General Public
 * License.
 *
 * -------------------------------------------
 * ####ECOSGPLCOPYRIGHTEND####
 * =================================================================
 * #####DESCRIPTIONBEGIN####
 * 
 *  Author(s):    Anthony Tonizzo (atonizzo@gmail.com)
 *  Contributors: Sergei Gavrikov (w3sg@SoftHome.net), 
 *                Lars Povlsen    (lpovlsen@vitesse.com)
 *  Date:         2006-06-12
 *  Purpose:      
 *  Description:  
 *               
 * ####DESCRIPTIONEND####
 * 
 * =================================================================
 */
#include <pkgconf/hal.h>
#include <pkgconf/kernel.h>
#include <cyg/kernel/kapi.h>           // Kernel API.
#include <cyg/kernel/ktypes.h>         // base kernel types.
#include <cyg/infra/diag.h>            // For diagnostic printing.
#include <network.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <stdio.h>                     // sprintf().
#include <time.h>                      // sprintf().

#include <cyg/athttpd/http.h>
#include <cyg/athttpd/socket.h>
#include <cyg/athttpd/cgi.h>

#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
#define CYG_HTTPD_DAEMON_STACK_SIZE (CYGNUM_HAL_STACK_SIZE_MINIMUM + \
                                          CYGNUM_NET_ATHTTPD_THREADOPT_STACKSIZE)
static cyg_int32    cyg_httpd_initialized = 0;
cyg_thread   cyg_httpd_thread_object;
cyg_handle_t cyg_httpd_thread_handle;
cyg_uint8    cyg_httpd_thread_stack[CYG_HTTPD_DAEMON_STACK_SIZE]     
                                       __attribute__((__aligned__ (16)));
CYG_HTTPD_STATE httpstate;
                                         
__inline__ ssize_t
cyg_httpd_write(char* buf, int buf_len)
{
    // We are not going to write anything in case
    ssize_t sent = send(httpstate.sockets[httpstate.client_index].descriptor, 
                        buf, 
                        buf_len,
                        0);
    return sent;
}

__inline__ ssize_t
cyg_httpd_writev(cyg_iovec *iovec_bufs, int count)
{
    int i;
    ssize_t sent = writev(httpstate.sockets[httpstate.client_index].descriptor, 
                          iovec_bufs, 
                          count);
    ssize_t buf_len = 0;
    for (i = 0; i < count; i++)
        buf_len += iovec_bufs[i].iov_len;
#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 1
    if (sent != buf_len)
        diag_printf("writev() did not send out all bytes (%ld of %ld)\n", 
                    sent,
                    buf_len);
#endif    
    return sent;
}
    
// The need for chunked transfers arises from the fact that with dinamic
//  pages it is not always possible to know the packet size upfront, and thus
//  it is not possible to fill the 'Content-Length:' field in the header.
// Today's web browser use 'Content-Length:' when present in the header and 
//  when not present they read everything that comes in up to the last 2 \r\n
//  and then figure it out. The HTTP standard _mandates_ 'Content-Length:' to
//  be present in the header with a correct value, and whenever that is not
//  possible, chunked transfers must be used.
//
// A chunked transer takes the form of:
// -----------------------------------------------------------------------------
//    cyg_httpd_start_chunked("html");
//    sprintf(phttpstate->payload, ...);             
//    cyg_httpd_write_chunked(phttpstate->payload, 
//                             strlen(phttpstate->payload));
//    ...                         
//    cyg_httpd_end_chunked();
// -----------------------------------------------------------------------------
ssize_t
cyg_httpd_start_chunked(char *extension)
{
    httpstate.status_code = CYG_HTTPD_STATUS_OK;

#if defined(CYGOPT_NET_ATHTTPD_CLOSE_CHUNKED_CONNECTIONS)
     // I am not really sure that this is necessary, but even if it isn't, the
    //  added overhead is not such a big deal. In simple terms, I am not sure 
    //  how much I can rely on the client to understand that the frame has ended 
    //  with the last 5 bytes sent out. In an ideal world, the data '0\r\n\r\n'
    //  should be enough, but several posting on the subject I read seem to
    //  imply otherwise, at least with early generation browsers that supported
    //  the "Transfer-Encoding: chunked" mechanism. Things might be getting 
    //  better now but I snooped some sites that use the chunked stuff (Yahoo!
    //  for one) and all of them with no exception issue a "Connection: close" 
    //  on chunked frames even if there is nothing in the HTTP 1.1 spec that
    //  requires it.
    httpstate.mode |= CYG_HTTPD_MODE_CLOSE_CONN;
#endif
    
    // We do not cache chunked frames. In case they are used to display dynamic
    //  data we want them to be executed any every time they are requested.
    httpstate.mode |= 
              (CYG_HTTPD_MODE_TRANSFER_CHUNKED | CYG_HTTPD_MODE_NO_CACHE);
    
    httpstate.last_modified = -1;
    httpstate.mime_type = cyg_httpd_find_mime_string(extension);
    cyg_int32 header_length = cyg_httpd_format_header();
    return cyg_httpd_write(httpstate.outbuffer, header_length);
}

ssize_t
cyg_httpd_write_chunked(char* buf, int len)
{
    char leader[16], trailer[] = {'\r', '\n'};

    cyg_iovec iovec_bufs[] = { {leader, 0}, {buf, 0}, {trailer, 2} };
    sprintf(leader, "%x\r\n", len);
    iovec_bufs[0].iov_len = strlen(leader);
    iovec_bufs[1].iov_len = len;
    iovec_bufs[2].iov_len = 2;
    if (httpstate.mode & CYG_HTTPD_MODE_SEND_HEADER_ONLY)
        return (iovec_bufs[0].iov_len + iovec_bufs[1].iov_len + 
                                                  iovec_bufs[2].iov_len);
    return cyg_httpd_writev(iovec_bufs, 3);
}

void
cyg_httpd_end_chunked(void)
{
    if (httpstate.mode & CYG_HTTPD_MODE_SEND_HEADER_ONLY)
        return;
    strcpy(httpstate.outbuffer, "0\r\n\r\n");
    cyg_httpd_write(httpstate.outbuffer, 5);
    httpstate.mode &= ~CYG_HTTPD_MODE_TRANSFER_CHUNKED;
}    

// This function builds and send out a standard header. It is likely going to
//  be used by a c language callback function, and thus followed by one or
//  more calls to cyg_httpd_write(). Unlike cyg_httpd_start_chunked(), this
//  call requires prior knowledge of the final size of the frame (browsers
//  _will_trust_ the "Content-Length:" field when present!), and the user 
//  is expected to make sure that the total number of bytes (octets) sent out
//  via 'cyg_httpd_write()' matches the number passed in the len parameter.
// Its use is thus more limited, and the more flexible chunked frames should 
//  be used whenever possible.
void
cyg_httpd_create_std_header(char *extension, int len)
{
    httpstate.status_code = CYG_HTTPD_STATUS_OK;
    httpstate.mode |= CYG_HTTPD_MODE_NO_CACHE;

    // We do not want to send out a "Last-Modified:" field for c language
    //  callbacks.
    httpstate.last_modified = -1;
    httpstate.mime_type = cyg_httpd_find_mime_string(extension);
    httpstate.payload_len = len;
    cyg_int32 header_length = cyg_httpd_format_header();
    cyg_httpd_write(httpstate.outbuffer, header_length);
}

void
cyg_httpd_process_request(cyg_int32 index)
{
    httpstate.client_index = index;
    cyg_int32 descr = httpstate.sockets[index].descriptor;
    
    // By placing a terminating '\0' not only we have a safe stopper point
    //  for our parsing, but also we can detect if we have a split header.
    // Since headers always end with an extra '\r\n', if we find a '\0'
    //  before the terminator than we can safely assume that the header has
    //  not been received completely and more is following (i.e. split headers.)
    httpstate.inbuffer[0] = '\0';
    httpstate.inbuffer_len = 0;
    while ((strstr(httpstate.inbuffer, "\r\n\r\n") == 0) &&
                (strstr(httpstate.inbuffer, "\n\n") == 0))
    {            
        int len = recv(descr,
                       httpstate.inbuffer + httpstate.inbuffer_len,
                       CYG_HTTPD_MAXINBUFFER - httpstate.inbuffer_len,
                       0);
        if (len == 0)
        {
            // This is the client that has closed its TX socket, possibly as
            //  a response from a shutdown() initiated by the server. Another
            //  possibility is that the client was closed altogether, in
            //  which case the client sent EOFs on each open sockets before 
            //  dying.
            close(descr);
            FD_CLR(descr, &httpstate.rfds);
            httpstate.sockets[index].descriptor = 0;
#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 0
            printf("EOF received on descriptor: %d. Closing it.\n", descr);
#endif    
            return;
        }    
        
        if (len < 0)
        {
            // There was an error reading from this socket. Play it safe and
            //  close it. This will force the client to generate a shutdown
            //  and we will read a len = 0 the next time around.
            shutdown(descr, SHUT_WR);
#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 0
            diag_printf("ERROR reading from socket. read() returned: %d\n", 
                        httpstate.inbuffer_len);
#endif    
            return;
        }  
    
        httpstate.inbuffer_len += len;
    }
    
    httpstate.inbuffer[httpstate.inbuffer_len] = '\0';

    // Timestamp the socket. 
    httpstate.sockets[index].timestamp = time(NULL);
        
    // This is where it all happens.
    cyg_httpd_process_method();
        
    if (httpstate.mode & CYG_HTTPD_MODE_CLOSE_CONN)
        // There are 2 cases we can be here:
        // 1) chunked frames close their connection by default
        // 2) The client requested the connection be terminated with a
        //     "Connection: close" in the header
        // In any case, we close the TX pipe and wait for the client to
        //  send us an EOF on the receive pipe. This is a more graceful way
        //  to handle the closing of the socket, compared to just calling
        //  close() without first asking the opinion of the client, and 
        //  running the risk of stray data lingering around.
        shutdown(descr, SHUT_WR);
}

void
cyg_httpd_handle_new_connection(cyg_int32 listener)
{
    cyg_int32 i;

    int fd_client = accept(listener, NULL, NULL);
    CYG_ASSERT(listener != -1, "accept() failed");
    if (fd_client == -1) 
        return;
    
#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 0
    diag_printf("Opening descriptor: %d\n", fd_client);
#endif    
    // Timestamp the socket and process the frame immediately, since the accept
    //  guarantees the presence of valid data on the newly opened socket.
    for (i = 0; i < CYGPKG_NET_MAXSOCKETS; i++)
        if (httpstate.sockets[i].descriptor == 0)
        {
            httpstate.sockets[i].descriptor = fd_client;
            httpstate.sockets[i].timestamp  = time(NULL);
            cyg_httpd_process_request(i);
            return;
        }    
}

// This is the "garbage collector" (or better, the "garbage disposer") of
//  the server. It closes any socket that has been idle for a time period
//  of CYG_HTTPD_SELECT_TIMEOUT seconds.
void
cyg_httpd_close_unused_sockets(cyg_int32 listener)
{
    cyg_int32 i;
    
#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 0
    diag_printf("Garbage collector called\r\n");
#endif    
    httpstate.fdmax = listener;
    for (i = 0; i < CYGPKG_NET_MAXSOCKETS; i++)
    {
        if (httpstate.sockets[i].descriptor != 0)
        {
            if (time(NULL) - httpstate.sockets[i].timestamp > 
                                          CYG_HTTPD_SOCKET_IDLE_TIMEOUT)
            {           
#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 0
                diag_printf("Closing descriptor: %d\n", 
                            httpstate.sockets[i].descriptor);
#endif    
                shutdown(httpstate.sockets[i].descriptor, SHUT_WR);
            }
            else
                httpstate.fdmax = MAX(httpstate.fdmax, 
                                      httpstate.sockets[i].descriptor);
        }                              
    }
}

void
cyg_httpd_daemon(cyg_addrword_t data)
{
    cyg_int32 rc;
    init_all_network_interfaces();

#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 0
#ifdef CYGHWR_NET_DRIVER_ETH0
    if (eth0_up)
    {
        struct bootp* bps = &eth0_bootp_data;
        diag_printf("ETH0 is up. IP address: %s\n", inet_ntoa(bps->bp_yiaddr));
    }
#endif
#endif

#ifdef CYGOPT_NET_ATHTTPD_USE_CGIBIN_TCL
    cyg_httpd_init_tcl_interpreter();
#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 0
    diag_printf("Tcl interpreter has been initialized...\n");
#endif
#endif    
    
    cyg_httpd_initialize();

    // Get the network going. This is benign if the application has
    //  already done this.
    cyg_int32 listener = socket(AF_INET, SOCK_STREAM, 0);
    CYG_ASSERT(listener > 0, "Socket create failed");
    if (listener < 0)
        return;

    cyg_int32 yes = 1;
    rc = setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
    if (rc == -1) 
        return;
    
    memset(&(httpstate.server_conn), 0, sizeof(struct sockaddr_in));
    httpstate.server_conn.sin_family = AF_INET;
    httpstate.server_conn.sin_addr.s_addr = INADDR_ANY;
    httpstate.server_conn.sin_port = htons(CYGNUM_NET_ATHTTPD_SERVEROPT_PORT);
    rc = bind(listener,
              (struct sockaddr *)&httpstate.server_conn, 
              sizeof(struct sockaddr)); 
    CYG_ASSERT(rc == 0, "bind() returned error");
    if (rc != 0)
        return;

    rc = listen(listener, SOMAXCONN);
    CYG_ASSERT(rc == 0, "listen() returned error");
    if (rc != 0)
        return;

#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 0
    diag_printf("Web server Started and listening...\n");
#endif
    cyg_int32 i;
    for (i = 0; i < CYGNUM_FILEIO_NFILE; i++)
    {
        httpstate.sockets[i].descriptor  = 0;
        httpstate.sockets[i].timestamp   = (time_t)0;
    }
    
    FD_ZERO(&httpstate.rfds);
    httpstate.fdmax = listener;
    while (1)
    {
        // The listener is always added to the select() sensitivity list.
        FD_SET(listener, &httpstate.rfds); 
        struct timeval tv = {CYG_HTTPD_SOCKET_IDLE_TIMEOUT, 0};
        rc = select(httpstate.fdmax + 1, &httpstate.rfds, NULL, NULL, &tv);
        if (rc > 0)
        {
            if (FD_ISSET(listener, &httpstate.rfds))
                // If the request is from the listener socket, then 
                //  this must be a new connection.
                cyg_httpd_handle_new_connection(listener);

            httpstate.fdmax = listener;
            
            // The sensitivity list returned by select() can have multiple
            //  socket descriptors that need service. Loop through the whole
            //  descriptor list to see if one or more need to be served.
            for (i = 0; i < CYGPKG_NET_MAXSOCKETS; i ++)
            {
                cyg_int32 descr = httpstate.sockets[i].descriptor;
                if (descr != 0)
                {
                    // If the descriptor is set in the descriptor list, we
                    //  service it. Otherwise, we add it to the descriptor list
                    //  to listen for. The rfds list gets rewritten each time
                    //  select() is called and after the call it contains only
                    //  the descriptors that need be serviced. Before calling
                    //  select() again we must repopulate the list with all the
                    //  descriptors that must be listened for.
                    if (FD_ISSET(descr, &httpstate.rfds))
                        cyg_httpd_process_request(i);
                    else       
                        FD_SET(descr, &httpstate.rfds); 
                    if (httpstate.sockets[i].descriptor != 0)
                        httpstate.fdmax = MAX(httpstate.fdmax, descr);
                }
            }
        }
        else if (rc == 0)
        {
            cyg_httpd_close_unused_sockets(listener);
        }
        else
        {
#if CYGOPT_NET_ATHTTPD_DEBUG_LEVEL > 0
            cyg_int8 *ptr = (cyg_int8*)&httpstate.rfds;
            diag_printf("rfds: %x %x %x %x\n", ptr[0], ptr[1], ptr[2], ptr[3] );
            for (i = 0; i < CYGPKG_NET_MAXSOCKETS; i++)
                if (httpstate.sockets[i].descriptor != 0)
                     diag_printf("Socket in list: %d\n", 
                                 httpstate.sockets[i].descriptor);
#endif                                 
            CYG_ASSERT(rc != -1, "Error during select()");                 
        }
    }
}

void
cyg_httpd_start(void)
{
    if (cyg_httpd_initialized)
        return;
    cyg_httpd_initialized = 1;
    
    cyg_thread_create(CYGNUM_NET_ATHTTPD_THREADOPT_PRIORITY,
                      cyg_httpd_daemon,
                      (cyg_addrword_t)0,
                      "HTTPD Thread",
                      (void *)cyg_httpd_thread_stack,
                      CYG_HTTPD_DAEMON_STACK_SIZE,
                      &cyg_httpd_thread_handle,
                      &cyg_httpd_thread_object);
    cyg_thread_resume(cyg_httpd_thread_handle);
}