HTTP2 Form Processing

Microchip TCP/IP Stack

Microchip TCP/IP Stack Help
HTTP2 Form Processing

Many applications need to accept data from a user. A common solution is to present a form to the user in a web page, then have the device process the values submitted via this form. Web forms are usually submitted using one of two methods (GET and POST), and the HTTP2 web server supports both.

The GET Method

The GET method appends the data to the end of the URI. This data follows the question mark (?) in the browser's address bar. (ex: http://mchpboard/form.htm?led1=0&led2=1&led3=0) Data sent via GET is automatically decoded and stored in the curHTTP.data array. Since it is to be stored in memory, this data is limited to the size of curHTTP.data, which by default is 100 bytes. However, it is generally easier to process data received in this manner. 

The callback function HTTPExecuteGet is implemented by the application developer to process this data and perform any necessary actions. The functions HTTPGetArg and HTTPGetROMArg provide an easy method to retrieve submitted values for processing. 

The following example demonstrates a form to control several LEDs.

<form method="get" action="leds.htm">
  LED 1: <input type="checkbox" name="led1" value="1" /><br />
  LED 2: <input type="checkbox" name="led2" value="1" /><br />
  LED 3: <input type="checkbox" name="led3" value="1" /><br />
  <input type="submit" value="Set LEDs" />
</form>

Suppose a user selects the checkboxes for LED 1 and LED3. The following string will be submitted to the server:

GET /leds.htm?led1=1&led3=1 HTTP/1.1

The HTTP2 web server will parse this request and store the following string in curHTTP.data:

"led1\01\0led3\01\0\0"

It will then call HTTPExecuteGet to process this input. To process this data, that callback needs to do several things. First, it should call MPFSGetFilename to verify which form was submitted. (This step may be omitted if only one form is provided by the application.) Next, since a checkbox control was used a default state of unchecked must be assumed. Finally, the callback should search for each argument it expects, compare the value, and set the LED pins accordingly. The following example satisfies all these requirements:

HTTP_IO_RESULT HTTPExecuteGet(void)
{
    BYTE *ptr, filename[20];

    // Load the file name (filename[] must be large enough to hold
    // the longest file name expected)
    MPFSGetFilename(curHTTP.file, filename, 20);

    // Verify the file name
    if(!strcmppgm2ram(filename, (ROM char*)"leds.htm"))
    {
        // Assume a default state of off
        LED1_IO = 0;
        LED2_IO = 0;
        LED3_IO = 0;

        // Search for each LED parameter and process
        ptr = HTTPGetROMArg(curHTTP.data, (ROM BYTE*)"led1");
        if(ptr)
            LED1_IO = (*ptr == '1');

        ptr = HTTPGetROMArg(curHTTP.data, (ROM BYTE*)"led2");
        if(ptr)
            LED2_IO = (*ptr == '1');

        ptr = HTTPGetROMArg(curHTTP.data, (ROM BYTE*)"led3");
        if(ptr)
            LED3_IO = (*ptr == '1');
    }

    // Indicate completion
    return HTTP_IO_DONE;
}
The POST Method

The POST method transmits data after all the request headers have been sent. This data is not visible in the browser's address bar, and can only be seen with a packet capture tool. It does however use the same URL encoding method. 

The HTTP2 server does not perform any pre-parsing of this data. All POST data is left in the TCP buffer, so the custom application will need to access the TCP buffer directly to retrieve and decode it. The functions HTTPReadPostName and HTTPReadPostValue have been provided to assist with these requirements. However, these functions can only be used when at least entire variables are expected to fit in the TCP buffer at once. 

Most POST processing functions will be implemented as state machines in order to use these functions. The variable curHTTP.smPost is available to store the current state. This state machine variable is reset to zero with each new request. Functions should generally implement a state to read a variable name, and another to read an expected value. Additional states may be helpful depending on the application. 

The following example form accepts an e-mail address, a subject, and a message body. Since this data will likely total over 100 bytes, it should be submitted via POST.

<form method="post" action="/email.htm">
  To: <input type="text" name="to" maxlength="50" /><br />
  Subject: <input type="text" name="subject" maxlength="50" /><br />
  Message:<br />
  <textarea name="msg" rows="6"></textarea><br />
  <input type="submit" value="Send Message" /></div>
</form>

Suppose a user enters the following data into this form:

To: [email protected]
Subject: Sent by a PIC
Message: I sent this message using my development board!

The HTTPExecutePost function will be called with the following data still in the TCP buffer:

to=joe%40picsaregood.com&subject=Sent+by+a+PIC
&msg=I+sent+this+message+using+my+development+board%21

To use the e-mail module, the application needs to read in the address and the subject, store those in RAM, then send the message. However, since the message is not guaranteed to fit in RAM all at once, it must be read as space is available and passed to the e-mail module. A state machine, coupled with the HTTPReadPostName and HTTPReadPostValue functions can simplify this greatly. 

The following example callback function will properly parse this input. For this example, it is assumed that this is the only form the board accepts, so file name checking is not performed. The address will be stored at curHTTP.data[0:49], and the subject will be stored at curHTTP.data[50:99]. This is not the most optimal solution, but serves as a simple example.

HTTP_IO_RESULT HTTPExecutePost(void)
{
    BYTE *dest, temp[16];

    // Define state machine values
    #define SM_READ_NAME       (0u)
    #define SM_READ_VALUE      (1u)
    #define SM_START_MESSAGE   (2u)
    #define SM_SEND_MESSAGE    (3u)

    switch(curHTTP.smPost)
    {
        case SM_READ_NAME:
            // Read the next variable name.  If a complete name is
            // not found, request more data.  This function will
            // automatically truncate invalid data to prevent
            // buffer overflows.
            if(HTTPReadPostName(temp,16) == HTTP_READ_INCOMPLETE)
                return HTTP_IO_NEED_DATA;

            // Save "to" values to curHTTP.data[0:49]
            if(!strcmppgm2ram((char*)temp, (ROM char*)"to"))
                dest = curHTTP.data;

            // Save "subject" values to curHTTP.data[50:99]
            else if(!strcmppgm2ram((char*)temp, (ROM char*)"subject"))
                dest = curHTTP.data + 50;

            // When a "msg" is encountered, start sending
            else if(!strcmppgm2ram((char*)temp, (ROM char*)"msg"))
            {
                curHTTP.smPost = SM_START_MESSAGE;
                break;
            }

            // Ignore unexpected values
            else
                dest = NULL;

            // Move to the next state, but do not break yet
            curHTTP.smPost = SM_READ_VALUE;

        case SM_READ_VALUE:
            // Read the next value.  If a complete value is
            // not found, request more data.  This function will
            // automatically truncate invalid data to prevent
            // buffer overflows.
            if(HTTPReadPostValue(dest,50) == HTTP_READ_INCOMPLETE)
                return HTTP_IO_NEED_DATA;

            // Return to read a new name
            curHTTP.smPost = SM_READ_NAME;
            break;

        case SM_START_MESSAGE:
            // TODO: Perform necessary tasks to start sending the message.

            // Move on to sending the message body
            curHTTP.smPost = SM_SEND_MESSAGE;
            break;

        case SM_SEND_MESSAGE:
            // The message may be longer than the TCP buffer can hold
            // at once.  To avoid errors, read the data piece by
            // piece and send it to the e-mail module.  This requires
            // using TCP functions directly.

            // Send all remaining data
            while(curHTTP.byteCount > 0)
            {
                // First check if data is ready
                if(TCPIsGetReady(sktHTTP) == 0)
                    return HTTP_IO_NEED_DATA;

                // TODO: Read data with TCPGetArray and send
                //       it to the e-mail module.
            }

            // Process is complete
            return HTTP_IO_DONE;
    }

    // Assume return for state machine convenience.
    // Do not return HTTP_IO_NEED_DATA here by default, because
    // doing so when more data will not arrive is cause for
    // the HTTP2 server to return an error to the user.
    return HTTP_IO_WAITING;
}

The previous example uses the HTTPReadPostName and HTTPReadPostValue functions, and also demonstrates using the need to use TCPIsGetReady, TCPGet, and TCPGetArray when longer values are expected. For applications that will receive and react to parameters immediately and have no need for a state machine, a simple while loop can be written around HTTPReadPostPair to accomplish the callback. The HTTPPostLCD function in the TCPIP Demo App provides a simple example of this. 

For more examples, refer to CustomHTTPApp.c in the TCPIP Demo App project.

Microchip TCP/IP Stack 5.42.08 - June 15, 2013
Copyright © 2012 Microchip Technology, Inc.  All rights reserved.