A simple web server that can be bound to either the EthernetInterface or the WiflyInterface.

Dependents:   Smart-WiFly-WebServer WattEye X10Svr SSDP_Server

SW_HTTPServer.h

Committer:
WiredHome
Date:
2019-02-27
Revision:
60:d49a346c386f
Parent:
59:9a71ac02c782

File content as of revision 60:d49a346c386f:


#ifndef SW_HTTPSERVER_H
#define SW_HTTPSERVER_H
#include "mbed.h"
#include "TCPSocketServer.h"
#include "TCPSocketConnection.h"

#ifdef MODSERIAL_H
#define PC MODSERIAL
#else
#define PC RawSerial
#endif

/// This is the default buffer size used to send files. You might size
/// this to be equal or less than the payload size of 1460 bytes.
/// For WiFly: see User Manual 3.6.1.
#define FILESEND_BUF_SIZE 500


/// MAX_HEADER_SIZE is the default size to contain the largest header.
/// This is the size of the URL and query string, and also all the
/// other header information about the client. This can be
/// a couple of K, larger if you have big forms as it includes the
/// form data that is submitted.
#define MAX_HEADER_SIZE 1000


/// HTTPServer is a simple web server leveraging a network interface.
///
/// While simple, it is a capable, web server. The basic mode
/// of operation is for it to serve static web pages from an available
/// file system.
///
/// The default page is index.htm (compile time defined)
/// standard support to serve a number of standard file types;
/// gif, jpg, jpeg, ico, png, zip, gz, tar, txt, pdf, htm, html
/// (this list is also compile time defined)
///
/// It can also serve dynamically generated pages, and therefore
/// respond to form submission. Through the dynamic interface it is
/// then quite easy to interact with the hardware, reading the inputs
/// or signaling outputs.
///
/// @code
///     HTTPServer svr(HTTP_SERVER_PORT, "/local", 15, 30, 10, &pc);
///     svr.RegisterHandler("/dyn1", SimpleDynamicPage);
///     while (true) {
///        svr.Poll();   // this is non blocking process, with variable execution
///     }
/// @endcode
///
/// This web server used nweb as a starting point, but expanded well beyond that.
/// http://nmon.sourceforge.net/pmwiki.php?n=Site.Nweb&cm_mc_uid=21286415979014862330090&cm_mc_sid_50200000=1486233009
///
/// Given: scheme://server:port/path?query_string#fragment_id
/// @li scheme is "http"
/// @li server is whatever IP the server has
/// @li port is the registered port
/// @li /path is the reference to the file (actual or logical) on the server
/// @li query_string is any combination of name=value pairs
/// @li fragment_id is a reference to an anchor on the page
///
/// Features:
/// @li Serves static pages from a file system. Many normal filetypes are
///     supported.
/// @li Compile time configurable for the "default" file, typically index.htm.
/// @li Provides a registration interface for dynamically generated pages that
///     can then interact with other hardware.
/// @li Revised to be Non-blocking, however the execution time is variable
///     depending on the actions being performed and can span hundreds of msec
///     when using a WiFly module as the network interface.
/// @li Support for filenames aliases, which permit using long filenames with
///     the LocalFileSystem (which has an 8.3 constraint).
///
/// Limitations:
/// @li When used with Wifly network interface it supports only a single 
///     connection at a time. A web page with served objects (img src=...) 
///     is rarely served properly. It might trace to forcing the connection to 
///     close, but not yet sure. Explore "Set Uart Rx Data Buffer" in
///     WiFly manual 2.3.65. This is a limitation of the Wifly module. 
///     No solution is forthcoming, so a crude workaround is to use javascript 
///     to load the images after the page loads.
/// @li Rapid requests for page objects (e.g. embedded images) are lost. Still
///     working to understand this issue.
///
/// Improvements (TODO):
/// @li Add hook for a custom 404 page, which might show a page, or might redirect.
/// @li Combine the API for accessing parameters for the GET and POST, as they
///     are normally [always?] mutually exclusive.
/// @li Keep only the public functions public, make the rest private/protected.
///     Add another interface for dynamic pages to give them access to private/protected
///     functions.
/// @li Try to reduce the size of the header buffer, since that is quite large.
/// @li General clean-up, refactoring, de-duplication.
/// @li Leverage mbed os, so one receive handler can spawn up to N response handlers.
///     This may improve overall performance.
/// @li Since some transactions can't be easily buffered (the stream may be too
///     large, consider how to shuttle parts of the transaction back and forth
///     between the user-code and the server.
///
/// History (TO-Done):
/// @li 20170408 Added another optional parameter to the constructor to control the
///       blocking time when Poll() is called.
/// @li 20140913 Removed the relationship to the Wifly module, which caused an API change
///       in the constructor by elimination of the first parameter.
/// @li 20140913 parses the header similar to the query string, and then makes
///       those parameters accessible.
/// @li 20140913 Added basic password capability to dynamic web pages.
/// @li 20140913 move part of the POST method handler to the registered handler, so
///       it can decide if it should allocate the needed memory.
/// @li 20140201 hunted down several lengthy operations - the speed of the file system
///       and the "close" operation which requires <delay 0.25s>$$$<delay>close\r.
/// @li 20130530 Initial version
/// @li 20130601 Renamed ip_process to Poll
/// @li 20130617 Cleaned up some of the documentation changes
/// @li 20130623 Make it non-blocking. "Poll" takes a variable amount
///              of time, based on whether it is idle, or how much it
///              has to do.
/// @li 20130911 Lots of incremental changes along this way, this update
///              refreshes the documentation.
///
/// @note Copyright &copy; 2014-2017 by Smartware Computing, all rights reserved.
///     Individuals may use this application for evaluation or non-commercial
///     purposes. Within this restriction, changes may be made to this application
///     as long as this copyright notice is retained. The user shall make
///     clear that their work is a derived work, and not the original.
///     Users of this application and sources accept this application "as is" and
///     shall hold harmless Smartware Computing, for any undesired results while
///     using this application - whether real or imagined.
///
/// @author David Smart, Smartware Computing
///
class HTTPServer
{
public:
    /**
    * name-value pairs for parameters
    */
    typedef struct NAMEVALUE {
        char * name;
        char * value;
    } namevalue;

    /**
    * Indicates the purpose of the Handler callback
    *
    * Application code in a dynamic page uses this to determine the state
    * and therefore the needed operation to be performed.
    *
    * @code
    * bool SimpleDynamicPage(HTTPServer *svr, HTTPServer::CallBackType type, 
    *                        const char * path, const HTTPServer::namevalue *queryParams, 
    *                        int queryParamCount) {
    *     char buf[100];
    *     bool ret = false;
    *
    *     switch (type) {
    *         case HTTPServer::SEND_PAGE:
    *             svr->header(200, "OK", "Content-Type: text/html\r\n");
    *             svr->send("<html><head><title>Dynamic Page</title></head>\r\n");
    *             svr->send("<body>\r\n");
    *             svr->send("This page was generated dynamically. Create your own name=value pairs on the URL "
    *                       "which uses the GET method.<br/>\r\n");
    *             sprintf(buf, "%d parameters passed to {%s}:<br/>\r\n", queryParamCount, path);
    *             svr->send(buf);
    *             for (int i=0; i<queryParamCount; i++) {
    *                 sprintf(buf, "%d: %s = %s<br/>\r\n", i, queryParams[i].name, queryParams[i].value);
    *                 svr->send(buf);
    *             }
    *             svr->send("<br/><a href='/'>back to main</a></body></html>\r\n");
    *             ret = true;
    *             break;
    *         case HTTPServer::CONTENT_LENGTH_REQUEST:
    *             ret = true;
    *             break;
    *         case HTTPServer::DATA_TRANSFER:
    *             ret = true;
    *             break;
    *         default:
    *             ret = false;
    *             break;
    *     }
    *     return ret;
    * }
    * @endcode
    */
    typedef enum CALLBACKTYPE {
        CONTENT_LENGTH_REQUEST, ///< ask the client if they wish to accept the data, typically from a POST event
        DATA_TRANSFER,          ///< used when submitting a file via a form
        DATA_TRANSFER_END,      ///< used when all data has been given to the client (may be used to close the file)
        SEND_PAGE,              ///< the activated method should now send the page
    } CallBackType;

    typedef enum CALLBACKRESULTS {
        ACCEPT_ERROR,           ///< client not accepting the request.
        ACCEPT_COMPLETE,        ///< client accepted the request, the work is done.
        ACCEPT_CONTINUE,        ///< client accepted the request, additional transactions to complete.
    } CallBackResults;
    
    /// This is the set of header codes that are generally recognized. Note that many are
    /// not supported, so may not be listed here.
    typedef enum HEADERCODES {
        // 1xx: Information
        //Continue = 100,         ///< Server has received the headers, client should send the body.
        //Switching = 101,        ///< Request to switch protocols - not supported.
        //Checkpoint = 102,       ///< resume aborted PUT/POST - not supported.
        
        // 2xx: Successful
        OK = 200,               ///< request is OK.
        //Created = 201,          ///< The request has been fulfilled - not supported.
        //Accepted = 202,         ///< The request has been accepted for processing - not supported.
        //Non_Auth = 203,         ///< The request has been successfully processed - not supported.
        //No_Content = 204,       ///< The request has been successfully processed, but is not returning any content
        //Reset_Content = 205,    ///< The request has been successfully processed, but is not returning any content, 
        //                        ///     and requires that the requester reset the document view
        //Partial_Content = 206,  /// The server is delivering only part of the resource due to a range header sent by the client
        
        // 3xx: Redirection
        //Multiple_Choices = 300, /// A link list. The user can select a link and go to that location. Maximum five addresses.
        Moved_Permanently = 301, ///< The requested page has moved to a new URL.
        Found = 302,            /// The requested page has moved temporarily to a new URL 
        //See_Other = 303,        /// The requested page can be found under a different URL
        //Not_Modified = 304,     /// Indicates the requested page has not been modified since last requested
        // 306 Switch Proxy    No longer used
        // 307 Temporary Redirect  The requested page has moved temporarily to a new URL
        // 308 Resume Incomplete   Used in the resumable requests proposal to resume aborted PUT or POST requests

        // 4xx: Client Error
        Bad_Request = 400,      ///< The request cannot be fulfilled due to bad syntax
        Unauthorized = 401,     ///< The request was a legal request, but the server is refusing to respond to it. For use when authentication is possible but has failed or not yet been provided
        // 402 Payment Required    Reserved for future use
        // 403 Forbidden   The request was a legal request, but the server is refusing to respond to it
        Not_Found = 404,        ///< The requested page could not be found but may be available again in the future
        // Method Not Allowed 405   A request was made of a page using a request method not supported by that page
        // Not Acceptable 406   The server can only generate a response that is not accepted by the client
        // Proxy Auth Reqd 407    The client must first authenticate itself with the proxy
        Request_Timeout = 408,  ///< The server timed out waiting for the request
        // 409 Conflict    The request could not be completed because of a conflict in the request
        // 410 Gone    The requested page is no longer available
        // 411 Length Required The "Content-Length" is not defined. The server will not accept the request without it 
        // 412 Precondition Failed The precondition given in the request evaluated to false by the server
        // 413 Request Entity Too Large    The server will not accept the request, because the request entity is too large
        // 414 Request-URI Too Long    The server will not accept the request, because the URL is too long. Occurs when you convert a POST request to a GET request with a long query information 
        Unsupported_Media_Type = 415,       ///< The server will not accept the request, because the media type is not supported 
        // 416 Requested Range Not Satisfiable The client has asked for a portion of the file, but the server cannot supply that portion
        // 417 Expectation Failed  The server cannot meet the requirements of the Expect request-header field
        
        // 5xx: Server Error
        // Message:    Description:
        Server_Error = 500,             ///< A generic error message, given when no more specific message is suitable
        // 501 Not Implemented The server either does not recognize the request method, or it lacks the ability to fulfill the request
        // 502 Bad Gateway The server was acting as a gateway or proxy and received an invalid response from the upstream server
        // 503 Service Unavailable The server is currently unavailable (overloaded or down)
        // 504 Gateway Timeout The server was acting as a gateway or proxy and did not receive a timely response from the upstream server
        // 505 HTTP Version Not Supported  The server does not support the HTTP protocol version used in the request
        // 511 Network Authentication Required The client needs to authenticate to gain network access
    } HeaderCodes;
    
    /**
    * This is the prototype for custom handlers that are activated via a callback
    *
    * This callback gets overloaded for a few purposes, which can be identified by the 
    * \see CallBackType parameter.
    *
    * @li CONTENT_LENGTH_REQUEST - the server is asking the callback if it wants to receive the message,
    *       which may require significant memory. If the request is accepted, true should be returned.
    *       If the request is denied, false should be returned.
    * @li DATA_TRANSFER - the server is handing off a large body of data, which was accepted based
    *       on the CONTENT_LENGTH_REQUEST callback. The data is now available for processing.
    *       The callback should return true to continue the processing.
    * @li SEND_PAGE - the callback should now send the html page, using as many svr->send() as needed.
    *       When the callback returns, it should always indicate true that it has sent the page.
    *
    * @note The queryParams pointer purpose depends on the callback type.
    *       For CONTENT_LENGTH_REQUEST, the pointer points to the name=value pairs from the 
    *           header.
    *       For DATA_TRANSFER, the pointer points to the start of the actual data.
    *       For SEND_PAGE, ... <to be determined>
    *
    * @param[in] svr is a handle to this class, so the callback has access to member functions
    * @param[in] type is the callback type @see CallBackType
    * @param[in] path is the pointer to a large block of information being transferred. This pointer
    *        references a dynamically managed resource, so any information of value must be 
    *        extracted from here, and not referenced into this memory space.
    * @param[in] queryParams is a pointer based on the callback type.
    * @param[in] count is the number of items - for type = CONTENT_LENGTH_REQUEST this is the number of 
    *        name=value pars in the queryParams parameter, and for the DATA_TRANSFER this is the 
    *        number of bytes being passed in the path parameters.
    * @return one of the @see CallBackResults signals indicating error or successes
    */
    typedef CallBackResults (* Handler)(HTTPServer * svr, CallBackType type, char *path, 
        const namevalue *queryParams, int queryParamCount);

    /**
    * Create the HTTPServer object.
    *
    * @param[in] port is the optional parameter for the port number to use, default is 80.
    * @param[in] webroot is a file system path to the root folder for the web space. If any trailing '/'
    *        is included (e.g. "/web/path/") it will be removed (to "/web/path").
    * @param[in] maxheaderParams defines the maximum number of parameters to extract from a header 
    *        (Host: 192..\r\nConnection: keep-alive\r\n...)
    * @param[in] maxqueryParams defines the maximum number of query parameters to a dynamic function 
    *        (and the memory to support them).
    * @param[in] maxdynamicpages defines the maximum number of dynamic pages that can be registered.
    * @param[in] blockingtime is the time that the Poll process will pause, waiting for input.
    * @param[in] pc is the serial port for debug information (I should transform this to a log interface)
    * @param[in] allocforheader is the memory allocation to support the largest expected header from a client
    * @param[in] allocforfile is the memory allocation to support sending a file to the client. This is 
    *        typically sized to fit an ethernet frame.
    */
    HTTPServer(int port = 80, const char * webroot = "/", int maxheaderParams = 15, 
        int maxqueryParams = 30, int maxdynamicpages = 10,
        int blockingtime = 10,
        PC * pc = NULL, int _allocforheader = MAX_HEADER_SIZE, int _allocforfile = FILESEND_BUF_SIZE
        );

    /**
    * Destructor, which can clean up memory.
    */
    ~HTTPServer();

    /**
    * Get the path to the webroot, for applications that need to 
    * reference the file system relative to that point.
    *
    * @note The returned value may not be exactly as set at instantiation
    *       as trailing '/' were removed (unless the web root == "/").
    *       e.g. "/msc/web/" becomes "/msc/web"
    *
    * @returns pointer to the webroot string.
    */
    const char * GetWebRoot() {
        return (const char *)webroot;
    };

    
    /**
    * Search a haystack of name:value pairs for the needle.
    *
    * This is a case-sensitive search. If it cannot find an alias, 
    * it returns the needle.
    *
    * This is used in the web server for conveniently mapping long filenames
    * to short filenames if you are using the LocalFileSystem which only
    * supports short filenames.
    *
    * @code
    * char * ptr;
    * namevalue list[] = {
    *   {"/local/longfilename.ext",  "/local/short.ext"},
    *   {"/local/verylongname2.ext", "/local/verylo~1.ext"},
    *   {NULL, NULL}
    * };
    *
    * ptr = FindAlias(list, "/local/verylongname2.ext");
    * // ptr now references "/local/verylo~1.ext"
    * @endcode
    *
    * @param[in] haystack is the NULL terminated namevalue pair list.
    * @param[in] needle is a pointer to the name to find.
    * @returns the alias (value) if the needle is found, otherwise
    *       returns the needle.
    */
    const char * FindAlias(const namevalue * haystack, const char * needle);


    /**
    * Register a list of filename aliases to webroot.
    *
    * Some uses of this could be on the LocalFileSystem, which only supports
    * an 8.3 naming convention. This API lets you register a list of
    * name:value pairs, where the name is the long filename and the
    * value is the corresponding short filename.
    *
    * @param[in] namevaluelist is a pointer to a NULL terminated long
    *           to short filename list.
    */
    void RegisterFilenameAliasList(const namevalue * namevaluelist);

    /**
    * The process to call whenever there is free time, as this basically does
    * all the work to monitor for connections and handle replies.
    *
    * Activate this API as often as you can, typically from the loop in main.
    *
    * @note This API will pause execution for the "blocking time" value configured
    *       in the constructor.
    */
    void Poll();

    /**
    * Send typical header data, and some optional data back to the client.
    *
    * This forms and sends the typical header back to the client. It may also send
    * optional data (which must end with "\r\n"). It then sends the second newline
    * sequence that signals the end of the header.
    *
    * @code
    * svr->header(200, "OK", "Content-Type: text/html\r\n");
    * ...
    * svr->header(200, "OK", "Content-Type: text/html\r\n", "SpecialParam: 13\r\n");
    * svr->header("NextParam: 14\r\n");
    * svr->header("");  // sends the final \r\n to end the header
    * @endcode
    *
    * @param[in] code is the optional return code; 200 = OK, if not provided then 404 = Not found is returned
    * @param[in] code_text is the text to align with the code (e.g. 404, "Not Found")
    * @param[in] content_type is a pointer to "Content-Type: text/html\r\n" (for example). The string
    *               must have a \r\n termination. If this parameter is NULL, no alternate is substituted.
    * @param[in] optional_text is a pointer to any other text that is part of the header, which must
    *               have \r\n termination. It is permissible to string several header items together, 
    *               each with the \r\n termination (which includes \r\n termination at the end).
    *               If this parameter is NULL, a standard template response will be sent consisting
    *               of "Max-age: 0\r\nServer: Smart_Server v0.2\r\nConnection: close\r\n\r\n"
    *               If this parameter is not NULL, the user must call header(""), or ensure that
    *               the termination double (\r\n\r\n) ends the optional_text.
    */
    void header(HeaderCodes code = Not_Found, const char * code_text = "Not Found", const char * content_type = NULL, 
        const char * optional_text = NULL);

    /**
    * Send a fragment of a header to the client.
    *
    * @code
    * ...
    * svr->header("NextParam: 14\r\n");
    * svr->header("");  // sends the final \r\n to end the header
    * @endcode
    *
    * This API lets you send header information a fragment at a time. A fragment can be a single
    * line of text, or it can be several strung together with \r\n.
    *
    * @param[in] partialheader is a pointer to \r\n terminated text. If partial is a pointer to NULL,
    *           then a terminating \r\n is sent, which ends the header record.
    */
    void header(const char * partialheader);

    /**
    * Send text to the client
    *
    * This sends the specified text to the client. If the number of bytes is not set,
    * then it calculates the number of bytes as a string. For binary transfers, the
    * number of bytes to send is required for proper operation.
    *
    * @param[in] msg is the text string to send
    * @param[in] bytes is the number of bytes to send. If not set, then strlen is calculated.
    * @returns # of bytes sent, and < 0 for various error codes.
    */
    int send(const char * msg, int bytes = -1);

    /**
    * Get the size of the file
    *
    * This returns the size of the file. If the specified file is not found, it returns zero.
    *
    * @param[in] filename is the file to read.
    * @returns the size of the file, or zero if the file was not found/opened.
    */
    uint32_t FileSize(const char * filename);

    /**
    * Send a referenced file to the client, including the header
    *
    * This sends a file from the filesystem to the client. It must be of a supported type
    * in order to properly create the header.
    *
    * @param[in] filename is the fully qualified path and filename
    * @param[in] filetype is the header information (e.g. "Content-Type: application/pdf")
    * @return true if it thinks it sent ok, false otherwise.
    */
    bool SendFile(const char * filename, const char * filetype);

    /**
    * register a handler for a specific URL.
    *
    * This api lets you register a dynamic handler in the web server. This is
    * most useful for interactive web pages, rather than simply serving static
    * pages.
    *
    * @code
    *
    *   ...
    *   svr.RegisterHandler("/dyn1", SimpleDynamicPage);
    *   ...
    *
    *   HTTPServer::CallBackResults SimpleDynamicPage(HTTPServer *svr, HTTPServer::CallBackType type, char * path, 
    *       const HTTPServer::namevalue *queryParams, int queryParamCount) {
    *       char buf[100];
    *       HTTPServer::CallBackResults ret = HTTPServer::ACCEPT_ERROR;
    *
    *       switch (type) {
    *           case HTTPServer::SEND_PAGE:
    *               svr->header(HTTPServer::OK, "OK", "Content-Type: text/html\r\n");
    *               svr->send("<html><head><title>Dynamic Page</title></head>\r\n");
    *               svr->send("<body>\r\n");
    *               svr->send("This page was generated dynamically. Create your own name=value pairs on the URL "
    *                         "which uses the GET method.<br/>\r\n");
    *               sprintf(buf, "%d parameters passed to {%s}:<br/>\r\n", queryParamCount, path);
    *               svr->send(buf);
    *               for (int i=0; i<queryParamCount; i++) {
    *                   sprintf(buf, "%d: %s = %s<br/>\r\n", i, queryParams[i].name, queryParams[i].value);
    *                   svr->send(buf);
    *               }
    *               svr->send("Stats:<br/>\r\n");
    *               sprintf(buf,"Free memory space: %d<br/>\r\n", Free());
    *               svr->send(buf);
    *               sprintf(buf,"Max Header size: %d<br/>\r\n", svr->GetMaxHeaderSize());
    *               svr->send(buf);
    *               svr->send("<br/><a href='/'>back to main</a></body></html>\r\n");
    *               ret = HTTPServer::ACCEPT_COMPLETE;
    *               break;
    *           case HTTPServer::CONTENT_LENGTH_REQUEST:
    *               ret = HTTPServer::ACCEPT_COMPLETE;
    *               break;
    *           case HTTPServer::DATA_TRANSFER:
    *               ret = HTTPServer::ACCEPT_COMPLETE;
    *               break;
    *           default:
    *               ret = HTTPServer::ACCEPT_ERROR;
    *               break;
    *       }
    *   return ret;
    *   }
    * @endcode
    *
    * @param[in] path to register
    * @param[in] callback of type Handler
    * @return true if successfully registered
    */
    bool RegisterHandler(const char * path, Handler callback);


    /**
    * Get the path for the active handler.
    * 
    * Useful from a callback to find out the registered path being serviced.
    *
    * @returns pointer to the string containing the registered path.
    * @returns NULL if it is invalid, e.g. called from a non-callback.
    */
    const char * GetActiveHandlerPath(void);

    /**
    * determine if the named file is a supported type (htm, html, jpg, etc)
    *
    * if you pass in a filename, it will attempt to extract the extension
    * and compare that to the list of supported file types. If it finds a
    * match, then it will return a pointer to the content-type string.
    *
    * @code
    *   fType = GetSupportedType("mypix.jpg");
    *   if (fType) {
    *       ...
    * @endcode
    *
    * @param[in] filename is the filename to test, based on the extension
    * @return pointer to a Content-Type string if supported, or NULL if not.
    */
    const char * GetSupportedType(const char * filename);

    /**
    * search the available query parameters for 'name' and if found, return the 'value'
    *
    * After the querystring is parsed, the server maintains an array of
    * name=value pairs. This Get function will search for the passed in name
    * and provide access to the value.
    *
    * @code
    * BusOut leds(LED1,LED2,LED3,LED4);
    * ...
    * leds = atoi(svr->GetParameter("leds"));
    * @endcode
    *
    * @param[in] name is the name to search for
    * @return pointer to the value, or NULL
    */
    const char * GetParameter(const char * name);

    /**
    * get a pointer to a name-value pair based on the index.
    *
    * @param[in] index is the item being referenced
    * @return pointer to the namevalue, or NULL
    */
    const namevalue * GetParameter(int index);

    /**
    * Get the count of query parameters from the active transaction.
    *
    * @returns count of parameters.
    */
    int GetParameterCount(void) 
        {
        return queryParamCount;
        };
    
    /**
    * search the available post parameters for 'name' and if found, return the 'value'
    *
    * After the post parameter string is parsed, the server maintains an array of
    * name=value pairs. This Get function will search for the passed in name
    * and provide access to the value.
    *
    * @code
    * BusOut leds(LED1,LED2,LED3,LED4);
    * ...
    * leds = atoi(svr->GetPostParameter("leds"));
    * @endcode
    *
    * @param[in] name is the name to search for
    * @return pointer to the value, or NULL
    */
    const char * GetPostParameter(const char * name);

    /**
    * get a pointer to a post parameter name-value pair based on the index.
    *
    * @param[in] index is the item being referenced
    * @return pointer to the namevalue, or NULL
    */
    namevalue * GetPostParameter(int index);

    /**
    * Get the count of post parameters from the active transaction.
    *
    * @returns count of parameters.
    */
    int GetPostParameterCount(void) 
        {
        return postParamCount;
        };


    /**
    * Parse the provided buffer as a method='Post' process.
    *
    * This is called from the user code if they want the parameters to
    * be parsed. If it is a bulk transfer, the user will not call this.
    * But if it is a form submitted with method='post', and if the total
    * number of parameters is small, then we can let the common code
    * process it.
    *
    * @param[in] pPostString is a pointer to the string to be processed.
    * @returns count of parameters parsed.
    */
    int ParsePostParameters(char * pPostString);

    /**
    * Parse the text string into name=value parameters.
    *
    * This will directly modify the referenced string. If there is a
    * #fragment_id on the end of the string, it will be removed.
    *
    * @param[in] qP is a pointer to a namevalue set
    * @param[in] qpCount is a pointer to a counter of what is in the set
    * @param[in] maxP is the maximum number of parameters for which space has been allocated.
    * @param[in,out] pName is a pointer to the string.
    * @returns The total number of items that have been parsed, 
    *       which can include a count from a url query string.
    */
    int ParseParameters(namevalue * qP, int * qpCount, int maxP, char * pName);

    /**
    * Unescape string converts a coded string "in place" into a normal string.
    *
    * A query string will have a number of characters replaced for communication
    * which includes spaces, quotes, question marks and more. Most of them
    * will be replaced with a %xx format, where xx is the hex code for the
    * character. Since the string will only get shorter when this happens
    * the operation is performed in place.
    *
    * this    "This%20is%20a%20question%3F%20and%20an%20answer."
    *
    * becomes "This is a question? and an answer."
    *
    * @note '+' is another form of space, so is converted to a space before the %xx
    *
    * @param[in,out] encoded string to be converted
    */
    void UnescapeString(char * encoded);

    /**
    * This is used to force a connection to close.
    *
    * @note When WiFly is the interface, this switches the module into 
    * command mode, performs the close, and then switches it back to data mode. 
    * So, this is a time-expensive command.
    *
    * @returns true if successful
    */
    bool close_connection();

    /**
    * Diagnostic to get the size of the largest header.
    *
    * This is a diagnostic function, so you can resize the allocated
    * buffer for your application. With proper sizing, more of the
    * system memory is available for your application.
    *
    * @code
    * sprintf(buf,"Max Header size: %d<br/>\r\n", svr->GetMaxHeaderSize());
    * svr->send(buf);
    * @endcode
    *
    * @returns size in bytes of the larger header measured.
    */
    int GetMaxHeaderSize();

    /**
    * Get a value from the http header, if it exists.
    *
    * @param[in] hdr is the string to search for (e.g. "Content-Length")
    * @returns pointer to the value associated with that header.
    * @returns NULL if the header is not found.
    */
    const char * GetHeaderValue(const char * hdr);

    /**
    * Performance parameter
    */
    typedef struct SW_PERFPARAM {
        unsigned long long TotalTime_us;
        unsigned long Samples;
        unsigned long MaxTime_us;
    } SW_PerformanceParam;

    /**
    * Performance metrics
    */
    typedef struct SW_PERFDATA {
        SW_PerformanceParam ConnectionAccepted;
        SW_PerformanceParam HeaderParsed;
        SW_PerformanceParam ResponseSent;
        SW_PerformanceParam ConnectionClosed;
        //SW_PerformanceParam SendFile;
    } SW_PerformanceData;

    /**
    * Get performance metrics from the web server.
    *
    * This is a diagnostic function, and gathers data on the internal
    * performance of the server, as it works various actions.
    *
    * @param[in] p is a pointer to a SW_PerformanceData structure to be populated
    */
    void GetPerformanceData(SW_PerformanceData * p);

    /**
    * Reset performance metrics.
    */
    void ResetPerformanceData();
    
    /**
    * Get performance clock
    */
    unsigned int GetPerformanceClock();

private:
    char * webroot;
    PC * pc;
    TCPSocketServer * server;
    TCPSocketConnection client;
    char * rewriteWithDefaultFile(char * queryString);
    char * rewritePrependWebroot(char * queryString);
    
    namevalue *queryParams;          // Query Parameters from the URL this=that&sky=blue&...
    int maxqueryParams;
    int queryParamCount;
    
    namevalue *postParams;          // Same as Query params, but for post method
    int maxPostParams;
    int postParamCount;
    
    namevalue *headerParams;    // Header params Host: 192.168...\r\nConnection: keep-alive\r\n...
    int maxheaderParams;
    int headerParamCount;
    
    int maxheaderbytes;
    char * headerbuffer;
    int headerbuffersize;

    Timer PerformanceTimer;
    /**
    * Records performance data
    *
    * This will take a pointer to a SW_PerformanceParam, and it will
    * take the time when the performance measurement started. It locally
    * accesses the current time to measure the elapsed.
    *
    * @param[in] param is the performance parameter to update
    * @param[in] value is the reference time.
    * @returns the current time which may be used as the reference time
    *          for further measurements.
    */
    unsigned int RecordPerformanceData(SW_PerformanceParam * param, unsigned int value);
    SW_PerformanceData perfData;

    typedef struct HANDLER {
        char * path;
        Handler callback;
    } handler;
    int maxdynamicpages;
    handler *handlers;
    int handlercount;
    int ndxActiveHandler;       // Contains the index into the list of handlers for the currently active callback

    char * queryType;
    char * queryString;             // the query string [and 'GET' data] passed on the URL (e.g. ?name1=value1&name2=value2...)
    char * postQueryString;         // the post data
    const namevalue * filenameAliasList;  // pointer to a filename alias list
    
    /**
    *  Extract the parameter from the record, by searching for the needle in the haystack.
    *
    *  The parameter of interest follows the needle, and may be ' ' delimited
    *  Can damage haystack while processing it.
    *
    * @param[in] haystack is the record to search
    * @param[in] needle is the text to search for, which precedes the text to return
    * @param[out] string is the text following the needle
    * @return true if it extracted something successfully
    */
    bool Extract(char * haystack, char * needle, char ** string);

    void SendResponse();
    HTTPServer::CallBackResults ParseHeader(char * bPtr);
    bool CheckDynamicHandlers();

    int HexCharToInt(char c);
    char HexPairToChar(char * p);
    
    #ifdef DEBUG
    void * MyMalloc(int x, int y);
    char toP(void * x);
    #endif
};
#endif //SW_HTTPSERVER_H