![]() |
|
| << Previous | Index | Next >> | |
| | |
This chapter is intended to be a detailed description of the HTTP server, and how it interfaces to other libraries, such as Zserver and TCP/IP. For an overview of how these libraries interface with one another and with your application, please see Chapter 2. "Web-Enabling Your Application."
An HTTP (Hypertext Transfer Protocol) server makes HTML (Hypertext Markup Language) pages and other resources available to clients (that is, web browsers). HTTP is implemented by
HTTP.LIB, thus you need to write#use "http.lib"near the top of your program. HTTP depends on the Dynamic C networking suite, which is included in your program by writing#use "dcrtcp.lib".Setting up the network subsystem is a necessary pre-requisite for use of HTTP. This is described in the Dynamic C TCP/IP User's Manual, Vol. 1. However, it can be quite simple for test applications and samples to initialize the network subsystem. In the file
tcp_config.libare predefined configurations that may be accessed by a #define of the macroTCPCONFIG. For instructions on how to set up different configurations, please see the Dynamic C TCP/IP User's Manual, Vol. 1 or look in the file \LIB\TCPIP\TCP_CONFIG.LIB.HTTP makes use of the Zserver library to manage resources and access control. The previous chapter discusses Zserver. When reading this chapter on the HTTP server, it will help if you are familiar with Zserver, its interfaces and capabilities.
Much of this chapter contains material that could be considered advanced usage. There is also some material of a historical nature, with relevant sections marked as such.
4.1 HTTP Server Data Structures
The single data structure in
HTTP.LIBof interest to developers of CGI functions is discussed in this section.4.1.1 HttpState
Use of the
HttpStatestructure is necessary for CGI functions (whether or not they were written prior to Dynamic C 8.50). Some of the fields are off-limits to developers. The field that are available for use are described in the next section.Historical note: prior to Dynamic C 8.50, it was sometimes necessary for CGI functions to access directly the fields of this structure. New programs should not directly access the fields, since it reduces the chance of upward compatibility. There is a new suite of macros (see
http_getAction()and related macros) that should be used instead. Where applicable, the equivalent macro is documented with the field. Some fields do not have an equivalent macro (such as the cookie field); for now, use read-only access to such fields.A pointer to
HttpStateis the first (and only) parameter to all CGI functions. Most of the time, this pointer should be passed on to other HTTP library functions.Note that the
HttpStatestructure is only valid within a CGI function that has been called from the HTTP server. Outside of this (for example, in yourmain()function) none of the fields are guaranteed to be meaningful or consistent.4.1.1.1 HttpState Fields
The fields discussed here are available for developers to use in their CGI functions.
s
- This is the socket associated with the given HTTP server. A developer can use this in a CGI function to output dynamic data (although there are better, safer ways of doing this: see the section on "Writing a CGI Function"). Any of the TCP functions can be used; however, you should not use any functions that may wait for long periods, or may change the state or mode of the socket (since the HTTP server depends on it being a normal ASCII mode TCP socket).
- It is recommended that you use the
http_getSocket()macro instead of directly accessing this field.substate
subsubstate
- Intended for holding the current state of a state machine for a CGI function. That is, if a CGI function relinquishes control back to the HTTP server, then the values in these variables will be preserved for the next
http_handler()call, in which the CGI function will be called again. These variables are initialized to 0 before the CGI function is called for the first time. Hence, the first state of a state machine using substate should be 0.- It is recommended that you use the macros
http_getState()andhttp_setState()to manipulate thesubstatefield instead of directly accessing it.subsubstateis not accessible via these macros, but there are better alternatives.timeout
- This value can be used by the CGI function to implement an internal time-out.
main_timeout
- This value holds the timeout that is used by the web server. The web server checks against this timeout on every call of
http_handler(). When the web server changes states, it resetsmain_timeout. When it has stayed in one state for too long, it cancels the current processing for the server and goes back to the initial state. Hence, a CGI function may want to reset this timeout if it needs more processing time (but care should be taken to make sure that the server is not locked up forever). This can be achieved like this:state->main_timeout=set_timeout(HTTP_TIMEOUT);HTTP_TIMEOUTis the number of seconds until the web server will time out. It is 16 seconds by default.buffer[]
- A buffer that the developer can use to put data to be transmitted over the socket. It is of size
HTTP_MAXBUFFER(defaults to 256 bytes).- Note: It is not recommended to directly access "buffer" or "p" (below). Use the new-style CGI functions and the
http_write(),http_getData()andhttp_getDataLength()functions instead. These create a much easier-to-use and safer method of reading/writing data to the client.p
- Pointer into the buffer given above. See above note.
method
- This should be treated as read-only. It holds the method by which the web request was submitted. The value is either
HTTP_METHOD_GETorHTTP_METHOD_POST, for the GET and POST request methods, respectively.- Use
http_getHTTPMethod()for new code.url[]
- This should be treated as read-only. It holds the URL by which the current web request was submitted. If there is GET-style form information, then that information will follow the first
NULLbyte in the url array. The form information will itself beNULL-terminated. If the information in the url array is truncated toHTTP_MAXURLbytes, the truncated information is alsoNULL-terminated.
Use
http_getURL()for new code.version
- This should be treated as read-only. This holds the version of the HTTP request that was made. It can be
HTTP_VER_09,HTTP_VER_10, orHTTP_VER_11for 0.9, 1.0, or 1.1 requests, respectively.- Use
http_getHTTPVersion()for new code.content_type[]
This should be treated as read-only. This buffer holds the value from the Content-Type header sent by the client.
Use
http_getContentType()for new code.content_length
This should be treated as read-only. This variable holds the length of the content sent by the client. It matches the value of the Content-Length header sent by the client.
Use
http_getContentLength()for new code.has_form
This should be treated as read-only. If the value is 1 there is a GET style form, after the \0 byte in
url[].abort_notify
Set to
!0in user-definedformprolog()function to indicate that theformepilog()function needs to be called on an abort condition. If the epilog function is reached normally, this field must be set to zero. This prevents the formepilog function from being called one more time on a connection abort.cancel
This should be treated as read-only. It is intended for when the user-defined functions, which may be called before and after an HTML form is submitted, are used for locking resources.
If the formprolog function was called and then the connection is aborted before the formepilog function can be called,
cancelis set to 1 and the formepilog function is called exactly once. If the epilog function was already called but returned zero (not finished yet), then it is called again if the connection is aborted, except ifcgi_redirectto()has been called from the epilog function. In that case the epilog function is not called after an abort.username[]
Read-only buffer has username of the user making the request, if authentication took place.
Note: New code should use the
http_getContext()macro, then use the results to look up the user details using thesauth_*functions. See the documentation for the ServerContext Structure in the previous chapter.password[]
Read-only buffer has password of the user making the request, if authentication took place. See the above note.
cookie[]
Read-only buffer contains the value of the cookie "DCRABBIT" (see
http_setcookie()for more information).headerlen
headeroff
These variables can be used together to cause the web server to flush data from the
buffer[]array in theHttpStatestructure.headerlenshould be set to the amount of data inbuffer[], andheaderoffshould be set to 0 (to indicate the offset into the array). The next time the CGI function is called the data inbuffer[]will be flushed to the socket.For new code, consider writing a new-style CGI function, which obviates the need to manipulate these fields.
cond[]
Support for conditional SSI (error feedback etc.).
New code should use the macros
http_getCond()andhttp_setCond().userdata[]
This field is included if
HTTP_USERDATA_SIZEis defined. It is an optional user data area. The area is cleared to zero when the structure is initialized, otherwise it is not touched. Its size must be greater than zero.New code should use the
http_getUserData()macro to obtain a pointer to user-defined storage in this structure.4.2 Configuration Macros
The following macros are specified in
HTTP.LIB. Unless otherwise noted, you can override the default values by defining the macro (same name, different value) before you #use "http.lib".HTTP_HOMEDIR
Specify the "home directory" for the server. This is the root directory to which all URLs are appended. The default is "
/", which means that all resources are accessible. If this is set to, say, "/htdocs", then an incoming URL of "foo/bar.html" gets turned into "/htdocs/foo/bar.html". You can use this to restrict the HTTP server's access to all but a specific "branch" of resources.Note: the string value for this macro must start and end with a "
/" character.HTTP_DFLTFILE
Specify the default file name to append to the URL if the URL refers to a directory. This is only applicable if the URL is "
/", or is in a filesystem (not the static or dynamic resource tables). The default setting is "index.html". The value must not start or end with a "/" character.HTTP_SOCK_BUF_SIZE
This macro is not defined by default. If you define it, then it specifies the amount of extended memory to allocate (
xalloc()) for each HTTP server instance. If you do not define it, then socket buffers are allocated from the usual pool. Seetcp_extopen()for more details.HTTP_DIGEST_NONCE_TIMEOUT
This macro is used when
USE_HTTP_DIGEST_AUTHENTICATIONis set to one. Nonces that are generated by the server are valid for this many seconds (900 by default). If set to 0, nonces are good forever. Setting this to a smaller value can possibly result in higher security, although internal use of the nonce-count facility offsets this. Setting it to a larger value reduces the negotiation between the browser and the server, since when a nonce times out, the browser must be told that it is using a stale nonce value and provided with a new one. Since Mozilla and Netscape ignore the stale parameter, the user must reenter the username and password when a nonce times out. Internet Explorer and Opera respect the stale parameter, so they automatically try the username and password with the new nonce without asking the user.HTTP_MAXBUFFER
This is the size of the buffer accessible through the
HttpSpecstructure. It defaults to 256 bytes. The size of this buffer affects the speed of the HTTP server; the larger the buffer (up to a point), the faster the server will run. The buffer size is also important for use in CGI functions because it is a work space the programmer can use.HTTP_MAXBUFFERmust be at least 180 bytes for CGI functionality.HTTP_MAX_COND
Support for conditional SSI (error feedback etc.). It defaults to 4. This is the maximum number of state variables that may be accessed using the
http_getCond()orhttp_setCond()macros.HTTP_MAX_NONCES
This macro is used when
USE_HTTP_DIGEST_AUTHENTICATIONis set to one. Defined to 5 by default, it specifies the number of nonces the HTTP server will allow as valid at any one time. This value should be somewhat larger than the maximum number of clients expected to be accessing the server simultaneously. Otherwise performance could suffer as clients are forced to retry authorization in order to acquire a fresh nonce.HTTP_MAXSERVERS
This is the maximum number of HTTP servers listening on port 80. The default is 2. You may increase this value to the maximum number of independent entities on your page. For example, for a Web page with four pictures, two of which are the same, set
HTTP_MAXSERVERSto 4: one for the page, one for the duplicate images, and one for each of the other two images. By default, each server takes 2500 bytes of RAM. This RAM usage can be changed by the macroSOCK_BUF_SIZE(ortcp_MaxBufSizewhich is deprecated as of Dynamic C ver. 6.57). Another option is to use thetcp_reserveport()function and a smaller number of sockets.HTTP_MAXURL
This macro defines the maximum incoming URL. This could be important if someone is allowing GET requests with a large number of parameters.
HTTP_PORT
This macro allows the user to override the default port of 80.
HTTP_IFACE
This macro allows the user to override the default listening network interface. The default is
IF_ANY, meaning that the HTTP server(s) will listen for incoming network connections on all interfaces which are up. You can restrict the HTTP servers to a single interface by overriding this macro to the specific interface number (for example,IF_ETH0).HTTP_TIMEOUT
Defines the number of seconds of no activity that can elapse before the HTTP server closes a connection. The default is 16 seconds.
HTTP_USERDATA_SIZE
This macro causes "char userdata[]" to be added to the
HttpStatestructure. Define your structure before the statement#use HTTP.LIB.struct UserStateData {char name[50]; int floor; int model;};
#define HTTP_USERDATA_SIZE (sizeof(struct UserStateData))
#use http.libIn your own CGI function code, access it using:
mystate = (struct UserStateData *)http_getUserData(state);USE_HTTP_DIGEST_AUTHENTICATION
Set to 1 to enable digest authentication, 0 to disable digest authentication. Set to 0 by default.
USE_HTTP_BASIC_AUTHENTICATION
Set to 1 to enable basic authentication, 0 to disable basic authentication. Set to 1 by default.
4.2.1 Sending Customized HTTP Headers to the Client
The callback macro,
HTTP_CUSTOM_HEADERS, will be called whenever HTTP headers are being sent. It must be defined as a function with the following prototype:void my_headers(HttpState *state, char *buffer, int bytes);
state
Pointer to the state structure for the calling web server.
buffer
The buffer in which the header(s) can be written.
bytes
The number of bytes available in the buffer.
Typically, the macro would be defined by the user before the
#use "http.lib"statement, like in the following:#define HTTP_CUSTOM_HEADERS(state, buffer, bytes) \
my_headers(state, buffer, bytes)Then, for the above to work,
my_headers()must be defined by the user, like so:void my_headers(HttpState *state, char *buffer, int bytes)
{
strcpy(buffer, Hello Rabbit!\r\n");
printf("bytes: %d\n", bytes);
}In the real world, the user may need to check the number of bytes available to be sure they don't overwrite the buffer. The buffer must end with "\r\n" and be NULL-terminated.
4.2.2 Saving Custom Headers from the Client
Customers may want to save some specific headers that a web client sends to the server as part of a request. One possibility for this is to check the browser version of the client and display a different page depending on that value. This is mostly useful for CGI functions.
The user can create a structure like the following to indicate to the web server that it should save the specified tags:
const HttpHeader http_headers[] = {
"Host",
"Content-Length",
"User-Agent",
END_HTTP_HEADERS
};
END_HTTP_HEADERSis simply a macro (NULL) that indicates the end of the structure. These headers will be saved in an internal buffer of a user-specified size:#define HTTP_CUSTOM_HEADERS_SIZE 1024By default,
HTTP_CUSTOM_HEADERS_SIZEis undefined, which disables the custom header functionality (since, in most cases, it will not need to be used). This buffer will be located in xmem, and there will be one per HTTP server. A define will also be provided to limit the maximum size of a single header (to keep one very long header from monopolizing all of the buffer space):#define HTTP_CUSTOM_HEADER_MAX_SIZE 128By default, this is undefined and there is no limit.
The user will also need functions that look up the data:
int http_getheader(HttpState *state, char *header, char *dest, int destlen);int http_xgetheader(HttpState *state, char *header, long *destptr);The first function requires the user to provide a root buffer to place the header. The
HttpStatestate structure must be passed so that the server knows which set of headers to access. The header parameter is, of course, the name of the header the user wants to retrieve.destis a pointer to the destination buffer.destlenis the length of the destination buffer (provided by the user). The function returns -1 on error, and the number of bytes in the header on success.The second function,
http_xgetheader(), simply returns a long pointer into the internal header buffer for the given header. It returns -1 on error, and the number of bytes in the header on success.Note that some headers are saved by the HTTP server by default into the HTTP state structure, such as "Content-Length." We will also begin saving the "Host" header, which is useful in performing CGI redirection. Hence, we can change the semantics of the
cgi_redirectto()function:int cgi_redirectto(HttpState *state, char *url);such that the
urlparameter no longer needs to be an absolute URL.4.3 Authentication Methods
HTTP/1.0 Basic Authentication is used by default. This scheme is not a secure method of user authentication across an insecure network (e.g., the Internet). HTTP/1.0 does not, however, prevent additional authentication schemes and encryption mechanisms from being employed to increase security.
Starting with Dynamic C version 8.01, HTTP Digest Authentication as specified in RFC 2617 is supported. Instead of sending the password in cleartext as is done using Basic Authentication, MD5 is used to perform a cryptographic hash.
In general, adding a query string to the end of a GET request is poorly supported by digest authentication. Users should be aware that older browsers (e.g., IE6 and earlier) do not consider the query string to be part of the URL that is included in the cryptographic hash, whereas newer browsers (e.g., IE7 and later) are just the opposite. This means that digest authentication can work with one or the other, but not both. Therefore, it is only "safe" to use digest authentication without a query string at the end of the URL.
To use HTTP Digest Authentication, define
USE_HTTP_DIGEST_AUTHENTICATIONas 1. When thisUSE_*macro is defined, the macrosHTTP_MAX_NONCESandHTTP_DIGEST_NONCE_TIMEOUTare available; they affect negotiation time between server and client. For more details see Section 4.2 "Configuration Macros."In either case (basic or digest), you will need to add the appropriate rules and/or permissions to the appropriate tables. See the previous chapter for details on protecting resources. The HTTP server applies the strongest applicable authentication mechanism depending on the information it retrieves from the resource manager. Typically, in addition to defining user IDs and groups, you also need to associate an authentication mechanism with the resource using e.g. the
SSPEC_MM_RULEmacro, or thesspec_setpermissions()function.Starting with Dynamic C 8.50, Secure Socket Layer (SSL) as specified in RFC 2818, is supported. It is also known by its newer official name, TLS (Transport Layer Security). To use SSL, you must create a secure HTTP server, known as an HTTPS server. To do this you must define some macros and import the SSL certificate.
#define USE_HTTP_SSL
#define HTTP_SSL_SOCKETS 1#ximport "cert\mycert.dcc" SSL_CERTIFICATEFor complete documentation on the Dynamic C implementation of SSL, see the Dynamic C Module document entitled, "Rabbit Embedded Security Pack." Another good source of information are the sample programs that demonstrate using SSL. They are located in the
/Samples/tcpip/sslfolder that will be created when the Rabbit Embedded Security Pack is installed.4.4 Setting the Time Zone
The HTTP specification requires the server to indicate its current clock time in the response to any request. The HTTP implementation performs this function by consulting the
rtc_timezone()library function (inRTCLOCK.LIB). The server uses the returned time zone to adjust the local real-time clock (RTC) value so that it is always returned to the client in UTC (Co-ordinated Universal Time).There are several macros which you can set to define
TIMEZONE: The local timezone offset from UTC.
RTC_IS_UTC: Whether the RTC is already running on UTC.The local timezone offset may be defined using the
TIMEZONEmacro, or it may be obtained automatically from a DHCP server if you are using DHCP to configure the network interface. Failing that, it defaults to zero.If the RTC is already set to UTC (not local time), then you must define the macro
RTC_IS_UTC, in which case the local timezone offset will be ignored.For many reasons, including the fact that daylight savings transitions are more manageable, it is better to set the RTC to UTC, however some users prefer the clock to run in local time.
See the documentation for
rtc_timezone()for more details. To do this, use the function lookup feature in Dynamic C or refer to the Dynamic C Function Reference Manual.4.5 Sample Programs
Sample programs demonstrating HTTP are in the
Samples\Tcpip\Httpdirectory. There is a configuration block at the beginning of each sample program. The macros in this block need to be changed to reflect your network settings.Starting with Dynamic C 7.30, setting up the network addresses is both more complex and more simple. The complexity lies in the added support for multiple interfaces. Luckily for us, the simplicity is in the interface to this more intricate implementation. In the file
tcp_config.libare predefined configurations that may be accessed by a #define of the macroTCPCONFIG. For instructions on how to set the configuration, please see volume 1 of the manual orLIB\TCPIP\TCP_CONFIG.LIB.4.5.1 Serving Static Web Pages
The sample program,
Static.c, initializesHTTP.LIBand then sets up a basic static web page. It is assumed you are on the same subnet as the controller. The code forStatic.cis explained in the following pages.From Dynamic C, compile and run the program. You will see the LNK light on the board come on after a couple of seconds. Point your internet browser at the controller (e.g., http://10.10.6.100/). The ACT light will flash a couple of times and your browser will display the page.
Program Name: \Samples\tcpip\http\Static.c
The program serves the
static.htmlfile and therabbit1.giffile to any user contacting the controller. If you want to change the file that is served by the controller, find and modify this line inStatic.c:
#ximport "samples/tcpip/http/pages/static.html" index_htmlReplace
static.htmlwith the name of the file you want the controller to serve.4.5.1.1 Adding Files to Display
Adding additional files to the controller to serve as web pages is slightly more complicated. First, add an
#ximportline with the filename as the first parameter, and a symbol that references it in Dynamic C as the second parameter.
#ximport "samples/tcpip/http/pages/static.html" index_html
#ximport "samples/tcpip/http/pages/newfile.html" newfile_htmlNext, find these lines in
Static.c:
SSPEC_RESOURCETABLE_START
SSPEC_RESOURCE_XMEMFILE("/index.html", index_html),
SSPEC_RESOURCE_XMEMFILE("/rabbit1.gif", rabbit1_gif)
SSPEC_RESOURCETABLE_ENDInsert the name of your new file, preceded by "
/", into this structure, using the same format as the other lines.Compile and run the program. Open up your browser to the new page (for example, "http://10.10.6.100/newfile.html"), and your new page will be displayed by the browser.
4.5.1.2 Adding Files with Different Extensions
If you are adding a file with an extension that is not html or gif, you need to use the appropriate macros to make an entry in the
MIMETypeMapstructure for the new extension. The first field is the extension and the second field describes the MIME type for that extension. You can find a list of MIME types at:ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/media-typesIn the media-types document located there, the text in the type column would precede the "
/", and the subtype column would directly follow. Find the type subtype entry that matches your extension and add it to thehttp_typestable.
SSPEC_MIMETABLE_START
SSPEC_MIME(".html", "text/html"),
SSPEC_MIME(".pdf", "application/pdf"), //added this one
SSPEC_MIME(".gif", "image/gif")
SSPEC_MIMETABLE_END
4.5.1.3 Handling of Files With No Extension
The entry "
/" and files without an extension are dealt with by the handler specified in the first entry in the MIME table. If you use theSSPEC_MIMEmacro, the default handler is used. It passes the information verbatim. You can also use the macroSSPEC_MIME_FUNCto specify a non-default text processor; this is necessary for SSI and RabbitWeb scripts (described later).4.5.2 Dynamic Web Pages Without HTML Forms
Serving a dynamic web page without the use of HTML forms is done by sample program
ssi.c. This program displays four "lights" and four buttons to toggle them. Users can browse to the device and change the status of the lights.The sample code follows, but it has been edited for brevity. Open
ssi.cin Dynamic C to see the fully-commented source.
void main(){
strcpy(led1,"ledon.gif");
strcpy(led2,"ledon.gif");
strcpy(led3,"ledoff.gif");
strcpy(led4,"ledon.gif");sock_init();
http_init();tcp_reserveport(80);
while (1) http_handler();
}
When you compile and run
ssi.c, you see the LNK light on the board come on. Point your browser at the controller (e.g., http://10.10.6.100/). The ACT light will flash a couple of times and your browser will display the page.This program displays pictures of LEDs. Their state is toggled by pressing the image of a button. This program uses Server Side Includes (SSI) and the old style of CGI (
SSPEC_RESOURCE_FUNCTION). Use of SSI is explained in greater detail below.4.5.2.1 SSI Feature
SSI commands are an extension of the HTML comment command (<!--This is a comment -->). They allow dynamic changes to HTML files and are resolved at the server side, so the client never sees them. HTML files that need to be parsed because they contain SSI commands, are conventionally recognized by the HTTP server by the resource name extension .shtml.1
The supported SSI commands are:
They are used by inserting the command into an HTML file:
<!--#include file="anyfile" -->The server replaces the command,
#include file, with the contents ofanyfile.
#exec cmdexecutes a command i.e. and old-style CGI and replaces the SSI command with the output.Dynamically Changing the Display of a Variable on a Web Page
The
Ssi.shtmlfile, located in\Samples\Tcpip\Http\Pages, gives an example of dynamically changing a variable on a web page using#echo var.
<img SRC="<!--#echo var="led1" -->">
In an shtml file, the "
<!--#echo var="led1" -->" is replaced by the value of the variableled1from the static resource table.
shtml_handler(which is the built-in script processor for SSI) looks upled1and replaces it with the text output from:
printf("%s",(char*)led1);
The
led1variable is eitherledon.giforledoff.gif.When the browser loads the page, it replaces
<img SRC="<!--#echo var="led1"-->">This causes the browser to load the appropriate image file.
SSI string variables are only appropriate for relatively short strings. (In the above example, the SSI string variables are "ledon.gif" and "ledoff.gif.") The size that can be output is limited to the size
HTTP_MAXBUFFER. If you need larger strings, you should either increaseHTTP_MAXBUFFER(which will use more root RAM) or switch to using a CGI function.4.5.2.2 CGI Feature
Ssi.calso demonstrates the Common Gateway Interface. CGI is a standard for interfacing external applications with HTTP servers. Each time a client requests an URL corresponding to a CGI program, the server will execute the CGI program in real-time.For increased flexibility, a CGI function is responsible for outputting its own HTTP headers. Information about HTTP headers can be found at:
http://deesse.univ-lemans.fr:8003/Connected/RFC/1945/and many other web sites and books. In the
Ssi.shtmlfile, the following line creates the clickable button viewable from the browser.
<TD> <A HREF="/led1tog.cgi"> <img SRC="button.gif"> </A> </TD>
When the user clicks on the button, the browser will request the
/led1tog.cgientity. This causes the HTTP server to examine the contents of thehttp_flashspecstructure looking for/led1tog.cgi. It finds it and notices thatled1toggle()needs to be called.The
led1togglefunction changes the value of theled1variable, then redirects the browser back to the original page. When the original page is reloaded by the browser, the LED image will have changed states to reflect the user's action.This sample demonstrates the so-called "old-style" CGI. New-style CGIs are easier to write (especially when they are doing something non-trivial). They are described in Section 4.6 "HTTP File Upload."
Connection Abort Condition
There are two fields in the
HttpStatestructure that allow a CGI function to appropriately respond to a connection abort condition. The user may set the fieldabort_notifyto a non-zero value in a CGI function to request that the CGI function be called one more time with thecancelfield set to one if a connection abort occurs.4.5.3 Web Pages With HTML Forms
With a web browser, HTML forms enable users to input values. With a CGI program, those values can be sent back to the server and processed. The FORM and INPUT tags are used to create forms in HTML.
The FORM tag specifies which elements constitute a single form and what CGI program to call when the form is submitted. The FORM tag has an option called ACTION. This option defines what CGI program is called when the form is submitted (when the "Submit" button is pressed). The FORM tag also has an option called METHOD that defines the method used to return the form information to the web server. In Section 4.5.3.1, the POST method is used, which will be described later. All of the HTML between the <FORM> and </FORM> tags define what is contained within a form.
Starting with Dynamic C 8.50, you can also use the enctype option inside the FORM tag. This specifies a return encoding type for the form's information. If you did not specify this option, then you can use old-style CGIs (as described in this section). If you specify enctype="multipart/form-data" then you should specify a new-style CGI instead. See Section 4.6 describing the HTTP upload feature for more details on writing a new-style CGI.
The INPUT tag defines a specific form element, the individual input fields in a form. For example, a text box in which the user may type in a value, or a pull-down menu from which the user may choose an item. The TYPE parameter defines what type of input field is being used. In the following example, in the first two cases, it is the text input field, which is a single-line text entry box. The NAME parameter defines what the name of that particular input variable is, so that when the information is returned to the server, then the server can associate it with a particular variable. The VALUE parameter defines the current value of the parameter. The SIZE parameter defines how long the text entry box is (in characters).
At the end of the HTML page in our example, the Submit and Reset buttons are defined with the INPUT tag. These use the special types "submit" and "reset," since these buttons have special purposes. When the submit button is pressed, the form is submitted by calling the CGI program "
myform."4.5.3.1 Sample HTML Page
An HTML page that includes a form may look like the following:
<HTML><HEAD><TITLE>ACME Thermostat Settings</TITLE></HEAD>
<BODY>
<H1>ACME Thermostat Settings</H1>
<FORM ACTION="myform.html" METHOD="POST">
<TABLE BORDER>
<TR>
<TD>Name</TD> <TD>Value</TD> <TD>Description</TD></TR>
<TR>
<TD>High Temp</TD>
<TD><INPUT TYPE="text" NAME="temphi" VALUE="80"
SIZE="5">
</TD>
<TD>Maximum in temperature range (°F)</TD></TR>
<TR>
<TD>Low Temp</TD>
<TD><INPUT TYPE="text" NAME="templo" VALUE="65"
SIZE="5">
</TD>
<TD>Minimum in temperature range (°F)</TD></TR>
</TABLE>
<P>
<INPUT TYPE="submit" VALUE="Submit">
<INPUT TYPE="reset" Value="Reset">
</FORM></BODY>
</HTML>The form might display as shown here:
![]()
When the form is displayed by a browser, the user can change values in the form. But how does this changed data get back to the HTTP server? By using the HTTP
POSTcommand. When the user presses the "Submit" button, the browser connects to the HTTP server and makes the following request:POST myform HTTP/1.0
.
. (some header information)
.
Content-Length: 19where "
myform" is the CGI program that was specified in the ACTION attribute of the FORM tag andPOSTis the METHOD attribute of the FORM tag. "Content-Length" defines how many bytes of information are being sent to the server (not including the request line and the headers).Then, the browser sends a blank line followed by the form information in the following manner:
temphi=80&templo=65That is, it sends back name and value pairs, separated by the `&' character. (There can be some further encoding done here to represent special characters, but we will ignore that in this explanation.) The server must read in the information, decode it, parse it, and then handle it in some fashion. It will examine the new values, and assign them to the appropriate C variables if they are valid.
4.5.3.2 POST-Style Form Submission
If an HTML file specifies a POST-style form submission (that is,
METHOD="POST"), the form will still be waiting on the socket when the old-style CGI handler is called. Therefore, it is the job of the CGI handler to read this data off the socket and parse it in a meaningful way. The sample filesPost.candPost2.cin the\Samples\Tcpip\Httpfolder show how to do this.The HTTP
POSTcommand can put any kind of data onto the network. There are many encoding schemes currently used, but we will only look at URL-encoded data in this document. Other encoding schemes can be handled in a similar manner.4.5.3.3 URL-Encoded Data
URL-encoded data is of the form "name1=value1&name2=value2," and is similar to the CGI form submission type passed in normal URLs. This has to be parsed to
name=valuepairs. The rest of this section details an extensible way to do this.
Reading & Storing URL-encoded Data
parse_post()is called from the CGI function (submit()) to read URL-encoded data off the network. It callshttp_scanpost()to store the data inFORMSpec[]. These code snippets are fromSamples\tcpip\http\post.c.
4.5.3.4 Sample of a CGI Handler
This next function is the CGI handler that calls
parse_post(). It is a state machine-based handler that generates the page. It callsparse_post()and references the structure that is now filled with the parsed data we wanted.This function is from
Samples\tcpip\http\post.c.int submit(HttpState *state){
auto int i;if(state->length) { // buffer to write out
if(state->offset < state->length) {state->offset += sock_fastwrite(&state->s, state->buffer +
(int)state->offset,(int)state->length -
(int)state->offset);} else {
state->offset = 0;
state->length = 0;
}} else {
switch(state->substate) {case 0:
strcpy(state->buffer, "HTTP/1.0 200 OK\r\n\r\n");
state->length = strlen(state->buffer);
state->offset = 0;
state->substate++;
break;case 1:strcpy(state->buffer,"<html><head><title>Results</title>
</head><body>\r\n");state->length = strlen(state->buffer);
state->substate++;
break;case 2: // initialize the FORMSpec data
FORMSpec[0].value[0] = '\0';
FORMSpec[1].value[0] = '\0';
state->p = state->buffer;
state->substate++;
break;case 3: // parse the POST information
if(parse_post(state)) {sprintf(state->buffer, "<p>Username: %s<p>\r\n<p>Email:
%s<p>\r\n", FORMSpec[0].value, FORMSpec[1].value);state->length = strlen(state->buffer);
state->substate++;
}
break;case 4:strcpy(state->buffer,"<p>Go <a href=\"/\">home</a></body> </html>\r\n");state->length = strlen(state->buffer);
state->substate++;
break;default:
state->substate = 0;
return 1;
}
}
return 0;
}4.5.4 HTML Forms Using Zserver.lib
In this section, we will step through a sample program,
Samples\tcpip\http\form1.c, that uses HTML forms. Through this step-by-step explanation, the method of using the functions inzserver.libwill become clear. (As of Dynamic C 8.50, you have the option of using the RabbitWeb server, with its easier-to-use interface and completely flexible ZHTML page layout capabilities.Defining
FORM_ERROR_BUFis required in order to use the HTML form functionality inZserver.lib. The value represents the number of bytes that will be reserved in root memory for the buffer that will be used for form processing. This buffer must be large enough to hold the name and value for each variable, plus four bytes for each variable. Since we are building a small form, 256 bytes is sufficient.
#define FORM_ERROR_BUF 256
Since we will not be using the static resource table, we can define the following macro, to remove some code for handling this table from Zserver.
#define HTTP_NO_FLASHSPEC
These lines are part of the standard TCP/IP and MIME table configuration.
#memmap xmem
#use "dcrtcp.lib"
#use "http.lib"SSPEC_MIMETABLE_START
SSPEC_MIME(".html", "text/html")
SSPEC_MIMETABLE_END
These are the declarations of the variables that will be included in the form.
int temphi;
int tempnow;
int templo;
float humidity;
char fail[21];
void main(void)
{
An array of type
FormVarmust be declared to hold information about the form variables. Be sure to allocate enough entries in the array to hold all of the variables that will go in the form. If more forms are needed, then more of these arrays can be allocated.
FormVar myform[5];
These variables will hold the indices in the TCP/IP servers' object list for the form and the form variables.
int var;int form;
This array holds the possible values for the fail variable. The fail variable will be used to make a pulldown menu in the HTML form.
const char *const fail_options[] = {"Email","Page","Email and page","Nothing"
};
These lines initialize the form variables.
temphi = 80;tempnow = 72;templo = 65;humidity = 0.3;strcpy(fail, "Page");
The next line adds a form to the dynamic resource table. The first parameter gives the name of the form. When a browser requests the page "
myform.html" the HTML form is generated and presented to the browser. The second parameter gives the developer-declared array in which form information will be saved. The third parameter gives the number of entries in themyformarray (this number should match the one given in themyformdeclaration above). The fourth parameter indicates that this form should only be accessible to the HTTP server, and not the FTP server.SERVER_HTTPshould always be given for HTML forms. The return value is the index of the newly created form in the dynamic resource table.
form = sspec_addform("myform.html", myform, 5, SERVER_HTTP);
This line sets the title of the form. The first parameter is the form index (the return value of
sspec_addform()), and the second parameter is the form title. This title will be displayed as the title of the HTML page and as a large heading in the HTML page.
sspec_setformtitle(form, "ACME Thermostat Settings");
The following line adds a variable to the resource table. It must be added to this table before being added to the form. The first parameter is the name to be given to the variable, the second is the address of the variable, the third is the type of variable (this can be
INT8,INT16,INT32,FLOAT32, orPTR16), the fourth is a printf-style format specifier that indicates how the variable should be printed, and the fifth is the server for which this variable is accessible. The return value is the handle of the variable in the resource table.
var = sspec_addvariable("temphi", &temphi, INT16, "%d",SERVER_HTTP);
The following line adds a variable to a form. The first parameter is the index of the form to add the variable to (the return value of
sspec_addform()), and the second parameter is the index of the variable (the return value ofsspec_addvariable()). The return value is the index of the variable within the developer-declaredFormVararray,myform.
var = sspec_addfv(form, var);
This function sets the name of a form variable that will be displayed in the first column of the form table. If this name is not set, it defaults to the name for the variable in the resource table ("temphi", in this case). The first parameter is the form in which the variable is located, the second parameter is the variable index within the form, and the third parameter is the name for the form variable.
sspec_setfvname(form, var, "High Temp");
This function sets the description of the form variable, which is displayed in the third column of the form table.
sspec_setfvdesc(form, var, "Maximum in temperature range
(60 - 90 °F)");
This function sets the length of the string representation of the form variable. In this case, the text box for the form variable in the HTML form will be 5 characters long. If the user enters a value longer than 5 characters, the extra characters will be ignored.
sspec_setfvlen(form, var, 5);
This function sets the range of values for the given form variable. The variable must be within the range of 60 to 90, inclusive, or an error will be generated when the form is submitted.
sspec_setfvrange(form, var, 60, 90);
This concludes setting up the first variable. The next five lines set up the second variable, which represents the current temperature.
Since the value of the second variable should not be modifiable via the HTML form (by default variables are modifiable,) the following line is necessary and makes the given form variable read-only when the third parameter is 1. The variable will be displayed in the form table, but can not be modified within the form.
sspec_setfvreadonly(form, var, 1);
These lines set up the low temperature variable. It is set up in much the same way as the high temperature variable.
This code begins setting up the string variable that specifies what to do in case of air conditioning failure. Note that the variable is of type
PTR16, and that the address of the variable is not given tosspec_addvariable(), since the variablefailalready represents an address.
This line associates an option list with a form variable. The third parameter gives the developer-defined option array, and the fourth parameter gives the length of the array. The form variable can now only take on values listed in the option list.
sspec_setfvoptlist(form, var, fail_options, 4);
This function sets the type of form element that is used to represent the variable. The default is
HTML_FORM_TEXT, which is a standard text entry box. This line sets the type toHTML_FORM_PULLDOWN, which is a pull-down menu.
sspec_setfventrytype(form, var, HTML_FORM_PULLDOWN);
Finally, this code sets up the last variable. Note that it is a float, so
FLOAT32is given in thesspec_addvariable()call. The last function call issspec_setfvfloatrange()instead ofsspec_setfvrange(), since this is a floating point variable.
These calls create aliases in the dynamic resource table for the HTML form. That is, the same form can now be generated by requesting "
index.html" or "/". Note thatsspec_aliasspec()should be called after the form has already been set up. The aliasing is done by creating a new entry in the resource table and copying the original entry into the new entry. Note that aliasing can also be done for files and other types of server objects.
sspec_aliasspec(form, "index.html");sspec_aliasspec(form, "/");
These lines complete the sample program. They initialize the TCP/IP stack and web server, and run the web server.
sock_init();http_init();while (1) {
http_handler();}
}
This is the form that is generated:
![]()
4.6 HTTP File Upload
This section describes the HTTP file upload feature available starting with Dynamic C 8.50. The enhanced CGI capabilities of this version of Dynamic C allow files of unlimited size to be uploaded using a web interface. It has always been possible to upload files using FTP; however, it is usually more convenient to use a browser-based upload.
4.6.1 What is a CGI Function and Why is It Useful?
The HTTP library provided with Dynamic C allows the association of C functions with web page URLs. When the user, via their web browser, retrieves a specified resource, the C function may be called from the HTTP server. Such a function is called a Common Gateway Interface (CGI) function, and it is responsible for generating a response to the user's request.
The advantage of using a CGI is that it can generate web page content on-the-fly, and cause the browser to display or do anything that it is capable of. In addition, the CGI is able to read data that was sent by the browser.
Previous to this release of Dynamic C, the CGI was limited to handling relatively small amounts of data sent from the browser. This is satisfactory for processing simple forms, but does not allow large data sets to be uploaded. This release of Dynamic C supports upload of one or more files from the browser. The files can be of unlimited size. In conjunction with the latest Zserver (resource manager) enhancements introduced in Dynamic C 8.50, the uploaded files may be stored in the FS2 or FAT file systems, or even processed dynamically.
The new CGI file upload facility enables a range of convenient firmware features. Possibilities include:
Remote firmware updates.
Web page content updates (i.e. "publishing").
Executable (interpreter) scripts.
Remote hardware updates (if using an FPGA or other configurable logic device).
Firmware configuration.
NOTE Throughout this document the FAT file system is the destination for the uploaded file. The FAT uses a variety of storage media, from the onboard serial flash, to NAND flash or SD cards. 4.6.2 How Do I Use the New CGI Facility?
There are a number of steps, some of which will be familiar to users of CGIs in previous releases. They are listed here and described in more detail in the following pages. The steps, if coding from scratch, are:
#use "dcrtcp.lib", and specify network configuration options.
#use <filesystem(s) of choice>, and specify the file system configuration.
#define USE_HTTP_UPLOAD
#use "http.lib"
Create an initial web page with a form asking for the file(s) to be uploaded. The main requirement is that you specify
enctype="multipart/form-data"inside the <FORM> tag(s).Write a CGI function (if not using the default one provided).
Create an initial resource table containing at least an entry for each of the above two resources (the web page and the CGI).
Create a list of content type mappings, i.e., the MIME table.
Create rules which limit the upload facility to select user groups.
Create a set of user IDs
In the main program, call
http_handler()in a loop.Step 1: Specify Network Configuration
To make use of HTTP upload, you need to perform the usual inclusion and configuration of the networking library,
dcrtcp.lib. At its simplest, it is two lines of code at the top of your main program:#define TCPCONFIG 1
#use "dcrtcp.lib"This specifies that the default TCP (networking) configuration is to be used. If you want to change the default networking configuration, first read the comments at the top of
tcp_config.lib.HTTP upload usually requires at least two additional libraries to be included: a file system library, and
http.libitself. A file system is required, otherwise the uploaded file has nowhere to go (although you can write a CGI which processes the file as it is uploaded, in which case you do not need to store it permanently, and thus you do not need to include a file system; the following discussion assumes that you are using a file system).Steps 2, 3 and 4: Specify File system and Web Server
You need to include the file system library (or libraries)
beforeincludinghttp.lib. This is because the HTTP library needs to know about the filesystem(s) it is going to support. In addition, you need to tell the HTTP library to use the upload facility. For example, if you want to use the FAT file system, then you would write the following:#define TCPCONFIG 1
#use "dcrtcp.lib"
#use "fat.lib" // Step 2: the filesystem
#define USE_HTTP_UPLOAD // Step 3: enable upload feature
#use "http.lib" // Step 4: HTTP server code
The order of the above statements is important. A possible exception is that the order of
dcrtcp.libandfat.libmay be interchanged, since these libraries are independent. However, it is recommended you use the given ordering since future releases of the FAT may be able to use networking services.Step 5: Create a Web Page
When using HTTP upload, there needs to be a way to prompt the user (web browser) to enter a file name to upload. This is done by using an HTML form. The form specifies input fields that may be filled out by the user, and one or more "submit" buttons that the user presses to start the upload process.
If you have an existing web-based application to which you want to add a file upload facility, you probably already have a web page with a form on it; in this case, you can add an extra input field to an existing form on that page, or create a new form on the same page. You may already have a CGI function that processes the results of the form submission. This will need to be rewritten to process data that is not URL encoded.
If you are creating a new application, you need to construct an initial page to contain the necessary form elements. As a starting point you can use the sample page in
samples\tcpip\http\pages\upload.html. Click onupload.htmland the browser will display something like this:![]()
The construction of this page is outlined below, but it has been simplified and reformatted slightly. A blow-by-blow description of each line is added in italics.
<html>This introduces the page as an HTML document.
<head><title>HTTP Upload Form</title></head>This ("HTTP Upload Form") gets displayed at the top of the browser window. You can change this to whatever is appropriate for describing the overall purpose of this page.
<body>Introduce the main content of this page.
<FORM ACTION="upload.cgi" METHOD="POST" enctype="multipart/form-data">Start a form definition. The parameters are
action="upload.cgi": this refers to the CGI function that will process the results of the form submission. This is a URL name, which is mapped to a C function on the server.method=post: this is required, since a post-type request must be sent to the server.
enctype="multipart/form-data": this is also required, and is the part that is different from the old style of processing. The old style did not specify an encoding type, thus the default of "URL encoded" was used.
<TABLE BORDER=0 CELLSPACING=2 CELLPADDING=1>For neatness of screen layout, we put everything in an HTML table. The following <TR>...</TR> sections delimit each row of the table, and the data for each cell is delimited by <TD>...</TD>.
<TR>
<TD WIDTH=130 ALIGN=RIGHT><B>Name</B></TD>
<TD WIDTH=500><INPUT TYPE="TEXT" NAME="user_name" SIZE=50></TD>This is the first input field. It is not a file to upload, but it is information that the server may nevertheless be interested in. This shows that not every form field needs to be a file to upload. The order is important. Browsers will send back the form fields in the same order that they are defined in the HTML, however it is probably best not to rely on this if you can help it.
</TR>
<TR>
<TD ALIGN=RIGHT><B>File to upload<BR>(to /A/new.htm)</B></TD>
<TD><INPUT TYPE="FILE" NAME="/A/new.htm" SIZE=50></TD>This is the file-to-upload input field. The browser displays this as a text input field, with an additional "browse" button so that the user can easily navigate his local filesystem to find the appropriate file. The critical distinction is that it contains a type=file parameter (as opposed to, for example, type=text in the previous field). The name="/A/new.htm" parameter specifies the name of the input field, not the name of the file on the user's system! As it happens, this looks like a file name, and indeed the server may use it as the name of a local file, but this is a convention only. The size=50 parameter specifies the number of characters that the browser will display for file name selection.
<TR>
</TABLE>
<INPUT TYPE="SUBMIT" VALUE="Upload">It is necessary to supply a type=submit form element. The user presses this button to start to post (upload) process. Note that this is another input field, however if you leave out the name= parameter (as in this example) then the browser will not send the value of this button back with the form submission. If there is only one submit button, then there is no need to name it.
</FORM></body></html>Close and complete the form, body, and entire page.
If you have an existing application, you can take out the relevant parts of the above, and insert them in your existing web page. The relevant parts are the enctype="multipart/form-data" parameter in the <FORM> element, and the <INPUT type=file> element.
If you have an existing application that processes the form data submission, you will need to rewrite the CGI function that handles the submitted data. This is because the
enctypeparameter changes the syntax that the browser uses to encode the data. In short, you will need to rewrite the CGI as a "new-style" CGI as described in Step 6: Writing a CGI Function.Having created the HTML file with the upload form, it is necessary to import it into your main program, so that the HTTP server can present it to the user. This can be done using
#ximport, or you can write it directly to the filesystem (although, initially at least, this presents a chicken-and-egg type problem since you might not have established an upload procedure in the first place!)Step 6: Writing a CGI Function
The CGI function is responsible for processing the form submission data as it comes in from the client (browser). In addition, it generally needs to write some sort of response back to the client indicating whether or not the submission was acceptable.
If you start reading the following, and start feeling somewhat overwhelmed, please be aware that there is a default CGI function in the HTTP library that is very useful. The default CGI, called
http_defaultCGI(), automatically saves uploaded files into the filesystem. If that is all you need to do, then you do not need to fully comprehend this section on first reading.Note that all of this section is describing
new-styleCGIs. Old-style CGIs are covered in Section 4.5.3.CGI Syntax
All CGI functions are C functions with the following prototype:
int my_CGI(HttpState * s);The
HttpStateparameter is a pointer to the internal state variables of the HTTP server instance that is handling the current request. You can have one or more server instances. If there is more than one, the same CGI may be invoked at the same time for more than one client (if both happen to press the submit button at about the same time). Thus, it is important to write the CGI function so that it is re-entrant. This basically means that the function should not update global or static variables. The CGI should not attempt to modify directly any of the fields in theHttpStatestructure, otherwise the server may become inoperable.API Functions
The HTTP library provides a set of API functions that can be called safely from the CGI. The list of safe functions is in the index under "Function Reference, CGI."
It is unwise to make direct calls to TCP/IP functions, especially functions that may not return for a long time such as
sock_read().How to Transfer Form Submission Data
To understand how to write a CGI function, it is necessary to have some understanding of the protocol used to transfer the form submission data. Since the data can consist of one or more files and/or form fields, there needs to be a way of separating them within the one, sequential, stream of data that is sent by the client.
The way this is done is that the client specifies a unique string that separates each item of data. The following text is a dump of the actual data sent by a client (with some irrelevant details omitted, and with comments added in
italics):POST /upload.cgi HTTP/1.1This indicates that it is POSTed form data, and the target handler is upload.cgi.
Content-length: 277This gives the
totalnumber of bytes of data following the initial header.Content-Type: multipart/form-data; boundary=3vAL1QsFOUg2GsY3p6n3YQThe multipart/form-data type indicates that this is a multipart form data submission. The boundary parameter specifies a unique character sequence that separates each part. The boundary is deliberately chosen as a long, random, string of characters so that it is unlikely to be confused with the actual data content.
The above blank line is significant; it indicates the end of the initial header lines, and the start of data.
--3vAL1QsFOUg2GsY3p6n3YQThis is the first boundary. Boundary strings are always prefixed by an additional -- sequence. The following lines are header lines for the individual part. The actual data follows the first empty line.
Content-Disposition: form-data; name="/A/new.htm"; filename="test.txt"The Content-Disposition header indicates the presentation of the data. The only type which is relevant is "form-data". The name= parameter indicates the field name (which was originally part of the name= parameter of the <input> element). The filename= parameter is only set if this is an uploaded file. It gives the name of the file on the remote (client) side. This is not usually relevant to the server. The name of the file as it is stored on the server is
notspecified (since the browser does not know it or have control over where the file is stored). We are using the convention that the field name indicates the local file name, but this is just a convention!Content-Type: text/plainContent-Type indicates the type of information. The default is plain (i.e. ascii) text, however it could also be set to image/gif for a GIF file, text/html for HTML etc. The following blank line indicates the end of headers for this part.
test file contents, first lineThis is the actual file or form field content.
--3vAL1QsFOUg2GsY3p6n3YQThe boundary string terminates the data for the previous part. Headers for the next part immediately follow.
Content-Disposition: form-data; name="submit"This is form field data, in this case the submit button itself.
<