![]() |
|
| << Previous | Index | Next >> | |
| | |
This chapter, and the next three, describe how to add web browser control to your application. Web-enabling is a logical and appealing choice for adding a user interface to your application, since the necessary hardware (an Ethernet or serial port) is available on all Rabbit core modules and SBCs. Most users of your application will be familiar with at least one web browser (Netscape, Mozilla, Internet Explorer, Opera), with its graphical user interface, so they will be ready to start controlling your application with minimal training.
This chapter provides an overview of the steps you will need to take to web-enable an application. Knowledge of browsers, and something of their capability, is assumed. With this knowledge, you can understand the concepts described in this chapter. The following chapters go into more detail about the specific libraries; but for simple programs, you may be able to use just the information in this chapter along with the sample code to write a working application.
Dynamic C provides libraries that implement most of the functions required to implement a web server, more formally known as an HTTP (HyperText Transfer Protocol) server. (The browser is formally called an HTTP client). You only need to write code specific to your application, such as dealing with I/Os and the Rabbit peripheral devices, and possibly some code to help the HTTP server generate the appropriate responses back to the user's web browser. In addition, there is a small amount of "boilerplate" that needs to be written to include and configure the HTTP server and any ancillary libraries such as the TCP/IP suite and filesystems.
2.1 Designing Your Application
Should you decide to web-enable your application, you probably already have some idea of the format and layout of the web pages that will be presented to the browser. Unless the application only returns information and does not allow any updates (such as a data logger), you will probably need to lay out some forms. Forms, in web parlance, allow the browser's user to fill in some information then submit it to the server. The server then performs the requested actions and sends a confirmation back to the browser. This is the most common means for implementing control of the server as opposed to merely querying it.
There are several other things to consider. Answers to the following list of questions will determine the pieces of software that need to be gathered into your application, and how they link together.
Does access to some or all resources need to be limited to a select set of users?
If so, how confident does your application need to be that the user's credentials are valid?
Do you need to be able to upload large amounts of data (over, say, 250 bytes)?
Do you want to update the web pages themselves, or maybe even the entire application firmware?
Is the application small, medium, or large?
Do you want to use this same (web) interface to configure all aspects of the application including, for example, the network settings? In other words, is the web interface going to be the only interface once the unit leaves the factory?
The first and second questions relate to user authentication and access control. The next two questions relate to the HTTP upload facility. The last two questions concern the overall design of your application; in particular, a large application may necessitate more storage than is usually available for a given Rabbit product, and may require a sophisticated filesystem to manage the large number of resources.
Since the terms small, medium and large are rather vague, we shall define them by example. A small application would be limited to less than 10 different web pages, and up to about 30 different "controls" (buttons to press, dials to twiddle, options to select etc.). A large application may have upwards of 100 pages, and more than 10KB of configurable data. A medium application sits, as you might expect, near the middle of these.
Note that we are not considering the size of the application other than the web interface part. For example, you may have a sophisticated G-code interpreter and motion control system, where the web interface is limited to simply enabling/disabling the actuators and showing an error log to maintenance personnel. For the purposes of our discussion, this would be a small application.
The next section describes a "smaller-than-small" application, that is, a toy, which we use to show the bare essentials of a web-enabled application.
2.2 The Smallest Web Server in the WWW
Before moving on to real applications, the following sample code shows how to create the simplest possible web server. It does nothing but show "Hello WWW" on the browser. There are two files needed for this. The first is the Dynamic C code to be loaded to the target board (which must support TCP/IP). The second is the web page content itself, written in a syntax known as HTML (HyperText Markup Language). The second file is effectively included in the program, using the #ximport directive.
// toy_http.c#define TCPCONFIG 1
#use "dcrtcp.lib"
#use "http.lib"#ximport "hellowww.html" hellowww_htmlSSPEC_MIMETABLE_START
SSPEC_MIME(".html", "text/html")
SSPEC_MIMETABLE_ENDSSPEC_RESOURCETABLE_START
SSPEC_RESOURCE_XMEMFILE("/hellowww.html", hellowww_html)
SSPEC_RESOURCETABLE_ENDvoid main() {
sock_init();
http_init();for (;;) http_handler();
}The second file, named
hellowww.htmlis coded as follows:<HTML>
<HEAD><TITLE>Hello, WWW</TITLE></HEAD>
<BODY><H1>Hello, WWW</H1></BODY></HTML>That's all there is to it. However, there is actually a lot of activity going on beneath the covers. For a start, the #use "dcrtcp.lib" directive and the
TCPCONFIGmacro definition bring in the TCP/IP networking suite and configure it. Unless you have a private test network, you probably have to modify the default setting - how to do that is beyond the scope of this chapter; it is described in volume 1 of the manual. The #use "http.lib" statement is required in order to bring in the web server. The next lines down to the start of themain()function are setting up tables that are consulted by the HTTP server and other libraries in order to "do the right thing." Finally, themain()function calls the necessary runtime initialization of the network and the HTTP server. It then calls the HTTP server in an endless loop, which drives the entire system into motion.The .html file is ASCII text, in HTML syntax, which is transferred back to the browser when it is requested. Apart from the server adding some header lines, the .html file is transferred verbatim. This markup is merely telling the browser to display "Hello, WWW" as a 1st level heading, i.e., big bold text. This is specified by the second line. The first line adds a title to the page, which most browsers display in the window bar.
To see this web page on screen, the user needs to tell their browser what to get. If doing it manually, they would need to enter something like "http://10.10.6.100/hellowww.html" in the browser's URL entry field. The browser strips off the http://10.10.6.100 part of it, and sends the rest to the specified host address (10.10.6.100) using a TCP connection to port 80 (interpreted from the http:// part). The server gets the /hellowww.html part, which it knows about since it has a page of that name, and returns the contents of that file as a response. The browser interprets the HTML it receives, and generates a nice visual rendition of the contents.
2.3 Web Server Architecture
Before describing a real application, it is useful to know how such an application is organized. The following diagram shows all of the relevant components of a web-enabled application. There may seem to be a large number of components, however keep in mind that not all components need to be used by your application.
2.3.1 Application Block
At the top of this diagram is a block, called "Application," consisting of five sub-blocks. The Application block represents the code that you have to create. Everything below this is provided by the libraries, although you will need to specify some parts of the interface to these components. This will be described in detail in the following sections.
The application block is subdivided into 5 parts:
Compile-time initialization. This includes things like selection of the appropriate library modules; initialization of static (constant) data structures and tables; selecting default network configuration; and inclusion of static resources (external files) via the
#ximportor#zimportdirectives. The arrows leading from the "Compile-Time Initialization" sub-block indicate the tables that may be set up at compile time; namely:
The MIME type mapping table. This mandatory table indicates to the browser how the content is to be presented to the user. This is necessary for the browser, and needs to be specified by the server, however the server does not need to be particularly aware of the details.
The rule table. This is only necessary if a filesystem is in use. It is used by the resource manager to apply access permissions to the resources contained in a filesystem. This is necessary because not all filesystems can associate file ownership and access rights with individual files.
The static resource table. This is the classic method of defining resources in Dynamic C. This table is optional, since all necessary resources may be loaded in a filesystem, or in the dynamic resource table. Most applications will contain at least a few static resources, as an initial default or fallback, or for data that will never change such as a logo image.
Program flash. This really represents the loading of resource files into program memory via the
#ximportdirective. There will almost always need to be a few#ximportfiles, but this can be limited to a few kilobytes total.Runtime initialization. Your
main()function needs to call some specific library functions, once only, when it starts:
sock_init(). This is always mandatory. It initializes the TCP/IP networking system.
sspec_automount(). This is optional. It initializes the available filesystems (FS2 and/or FAT) for use by the resource manager, Zserver.
http_init(). This is mandatory. It initializes the HTTP server.Various functions for setting up a user ID table, the rule table and/or the dynamic resource table. These are optional, but would be used in the majority of applications. The user ID table can only be initialized at run time, unlike the other tables that may, at least partially, be initialized at compile-time.
Main loop. The final code in the
main()function continuously callshttp_handler()and possibly other functions. This is mandatory, since it allows the HTTP server to process requests from the network. Other functions may be specific to your application. For example, you may need to poll an I/O device in order to obtain best performance.Application specifics and I/O. This is really
yourpart of the application or, if you like, the "back end" to the HTTP server. There are a number of ways that your application can communicate with the HTTP server. (These are not all shown on the diagram since it would add needless complexity.) Your application can directly call functions in the HTTP server, in the resource manager (Zserver), in TCP/IP, and just about anywhere else. One very clean and powerful interface is provided via#webvariables, provided by RabbitWeb software which was introduced in Dynamic C 8.50.CGI functions. CGI stands for "Common Gateway Interface," however this acronym has a more specific use in Dynamic C--it refers to a C function that is called by the HTTP server to generate some dynamic content for the browser. This is the only truly optional block. Many applications can be written without resorting to CGI functions; however, there are some cases where the power and flexibility of a CGI will be required. Prior to Dynamic C 8.50, writing a robust CGI was the most difficult part of the process. Starting with Dynamic C 8.50, there is a new style of CGI writing that simplifies the process and reduces the chances of error. The old-style CGI is still supported for backwards compatibility.
2.3.2 HTTP Block
Let us now progress to the HTTP server itself. In the diagram, this is the block with two circles inside. The server is responsible for fielding requests from the outside world. Each request is analyzed to determine the resource that is being requested, the user who is making the request, and whether the user is authorized to obtain that resource. If the resource is available, the user is known and has the proper permissions, then the resource is transmitted back to the browser.
Following the above steps in more detail, we have:
Analyze the request: obtain the resource name. Part of the information provided by the browser is a request header that contains a URL (Uniform Resource Locator). The URL is simply the name of the resource to retrieve. URLs typically look like a file name in a Unix-style filesystem, that is, component directory and file names separated by slash (/) characters.
Obtain the user ID. The browser has the option of sending the username and password of its user. If it does not do this, then the userid is "anonymous." If this is not good enough, then the browser can always try again when it is denied a protected resource. On receipt of user credentials (name and password), the HTTP server consults the resource manager (which in turn looks up the rule table) to see if the user's credentials are OK. If they are, then the resource manager also determines the group(s) of which this user is a member. Thereafter, all access and permission checking is based on the group, not the individual user.1
Return the resource. Having verified the group access rights (if necessary), the resource is transmitted back to the user. The resource may be an HTML or image file obtained from program memory or a filesystem, or it may be a script file that is processed "on the fly" to generate markup language. It may even represent a CGI function that will be called to generate all the necessary response. Note that a complete response requires a small amount of header information to be prefixed to the actual resource. The HTTP server usually takes care of this, however CGIs sometimes need to generate the header themselves.
Referring to the diagram in Figure 2.1, you can see that there are several arrows leading in and out of the HTTP server block. These represent lines of communication, and the arrow heads indicate the usual direction of data flow or, for function calls, "who calls whom."
2.3.3 HTTP Block Subcomponents
The inner circles represent subcomponents of the server. The first of these, RabbitWeb, was introduced in Dynamic C 8.50. RabbitWeb is an extension to C language syntax to simplify presentation of C language objects (variables, structures) to a browser. RabbitWeb allows you to write web pages in a special scripting language. The script makes it easy to generate HTTP, which is the format expected by the browser. In addition, the script allows the contents of your C language objects to be turned into HTML fragments for presentation by the browser. See Chapter 5 for details about RabbitWeb.
The small block named "#web Variables," between the Application block and the RabbitWeb circle, indicates that the
#webvariables are the means of communication between your application and the server. Since#webvariables are really just ordinary C variables, arrays or structures, they are extremely easy to manipulate by your application. Since they also have the property of being registered with the web server, the server has easy access too. (Registering an object with the web server is discussed in Chapter 5.)The second circle in the HTTP server block represents the classic way of generating dynamic content. SSI (Server Side Includes) is also a scripting language. It is not nearly as easy to use SSI as it is to use RabbitWeb; however, an SSI can generate the same content as a RabbitWeb script. It is just that you will need to write CGI functions, and such functions can get large and complicated fairly quickly! In fact, SSI has the ability to invoke CGI functions whereas RabbitWeb does not. In addition, SSIs have the ability to include other resources directly in the primary returned resource much like how #include works in ANSI C.
The server also communicates with lower layers in the diagram. On the right hand side is the TCP/IP block. This is the pipeline to the outside world, i.e., the browser. Usually only the server needs to talk directly to TCP/IP (via a TCP socket). Prior to Dynamic C 8.50, it was often necessary for the application's CGI functions to call TCP/IP functions. This is no longer recommended. Instead, there are functions in the HTTP server that should be called to mediate all networking calls.
2.3.4 Zserver Block
Directly under the HTTP server block is the Zserver, or resource manager, block. This is the "central telephone exchange" of the entire application. It controls access to many of the other blocks in the diagram. In spite of its importance and central placing, you do not usually need to be aware of its inner workings. Zserver has applicability to other types of servers, such as FTP, because it provides a consistent interface to the various different types of resource. As indicated in the diagram, Zserver is architected as a resource manager and a virtual filesystem. The virtual filesystem is basically a notational convenience for accessing all resources using a uniform naming scheme. The external appearance of the virtual filesystem is modelled on the Unix approach. In Unix, all storage devices, and the filesystems contained therein, are accessed from a single starting point known as the root directory, written as a single slash (/) character. Under the root directory may be any number of files and directories. Some of these directories may actually refer to a completely different device and filesystem. The term for such directory entries is mount-point.
Note the distinction between this naming convention and the one used by (PC) DOS and similar operating systems. In DOS, you have to explicitly indicate the device by prefixing the file name. For example, C:\index.htm and A:\index.htm are different files, on different devices. On Unix you create two mount points in the root directory; /backup and /production for example. Then, the above mentioned files are known as /backup/index.htm and /production/index.htm. This may seem like a fine distinction, however it matches better with the naming convention used by HTTP, i.e., the URL. It also offers greater flexibility with regards to naming devices.
Zserver does not currently allow arbitrary mount-point names like Unix. Instead, there is an established convention for each filesystem. If FS2 is in use, then there is a mount-point called "/fs2." If the FAT filesystem is in use, then one or more mount points called "/A," "/B," "/C" etc. are created.
Since Zserver is the resource manager, it takes responsibility for mapping the various filesystems and resource types into a single unified API. This API not only takes care of the detailed differences between the various filesystem APIs, but also allows some functions to be emulated that are not natively supported by the underlying filesystem.
In addition to the resource storage and filesystem, the resource manager needs to be able to associate other data with each resource. This other data is divided into two categories, which are listed in the blocks on the left of the diagram.
The two categories are "metadata" and "authorization." Metadata consists of two tables: the MIME table and the Rule table. The authorization data is currently just a single table of userids. The reason for the split into two categories is this: the metadata is logically associated with individual resources, whereas the authorization data is a mapping from external entities ("users") to the unit in which authorization is performed, namely user groups. The Rule table has some overlap, since it associates groups with individual resource permissions.
The lowest blocks in the diagram are divided into two groups, with a dashed outline. The upper group is labelled "filesystems," and the lower "storage." Both of these groups are indefinitely extensible, meaning that new classes of storage and their organization (filesystems) may be added in future releases of Dynamic C, or by you. The arrows between these groups are indicative of the most common patterns of communication; others may be defined.
2.4 Architecture of a Toy Application
Using the diagram of the previous section as a basis, we now focus on writing a simple web-enabled application. The following diagram is the same as the one above, except that the relevant parts have been visually emphasized. This diagram is essentially the toy application that was described at the start of this chapter. It shows the mandatory components for all web-enabled applications. Later, we introduce the other elements of the diagram to show how a fully featured application is built up.
Let us work again from left to right in the Application block. To reiterate, the Application block represents the coding that you have to do. First, there is the compile-time initialization. Taking the super-simple example illustrated in Figure 2.2, Dynamic C code is given with the relevant part highlighted in
boldface.#define TCPCONFIG 1
#use "dcrtcp.lib"
#use "http.lib"#ximport "hellowww.html" hellowww_htmlSSPEC_MIMETABLE_START
SSPEC_MIME(".html", "text/html")
SSPEC_MIMETABLE_ENDSSPEC_RESOURCETABLE_START
SSPEC_RESOURCE_XMEMFILE("/hellowww.html", hellowww_html)
SSPEC_RESOURCETABLE_ENDvoid main() {
sock_init();
http_init();
for (;;) http_handler();
}The first boldface line is the
#ximportdirective. This tells the compiler to include the specified file in the program flash, and make it accessible via thehellowww_htmlconstant. In the diagram, the arrow from compile-time initialization to program flash represents this inclusion. In most cases you would be including more than just one file.The three lines starting with
SSPEC_MIMETABLE_STARTare initialization statements for the MIME table. In this case, there is a single mapping from resources that end with ".html" to a MIME type of "text/html." All MIME types are registered with the relevant standards body, and must be entered correctly so that the browser does not get confused. "text/html" is the registered MIME type for HTML.The next three lines, starting with
SSPEC_RESOURCETABLE_START, set up the static resource table. Again, this contains a single entry that associates the resource name "/hellowww.html" with the file that was #ximported on the first line. Note that the resource name suffix (.html) matches the first parameter of theSSPEC_MIMEentry.Although not directly indicated on the diagram, the other compile-time initialization that is always required is the
#useof the appropriate libraries. In this case, the first three lines create a default TCP/IP configuration (TCPCONFIG = 1) and bring in the networking and HTTP libraries. Note thathttp.libautomatically includeszserver.lib.Back in the Application block of the diagram, we move right and consider the runtime initialization block. This is contained in the
main()function.sock_init()comes first, to initialize the TCP/IP network library and bring up the necessary interface(s).http_init()resets the HTTP library to a known state.The last statement embodies the Main Loop sub-block. This is always required. Typically, only
http_handler()needs to be called; however, your application can insert calls to its own polling and event handling code. Since this is such a simple example, there is not even any application-specific code.2.5 A Simple but Realistic Application
To turn the above toy example into something more realistic, we need to add some application specifics, and the ability to customize the resource returned to the browser depending on the relevant state of the application. The following diagram shows the necessary parts.
The easiest way to introduce dynamic content is to use RabbitWeb and the associated scripting language. SSI can be used instead (described in Section 4.5.2.1). This example, illustrated in Figure 2.3, assumes that you have RabbitWeb. Chapter 5 describes RabbitWeb and the scripting language, ZHTML, in detail.
The following code is a simplification of
Samples\tcpip\rabbitweb\web.c.#define TCPCONFIG 1#define USE_RABBITWEB 1#use "dcrtcp.lib"
#use "http.lib"#ximport"my_app.zhtml" my_app_zhtmlSSPEC_MIMETABLE_START
SSPEC_MIME_FUNC(".html", "text/html",zhtml_handler),
SSPEC_MIMETABLE_ENDSSPEC_RESOURCETABLE_START
SSPEC_RESOURCE_XMEMFILE("/index.html", my_app_zhtml)
SSPEC_RESOURCETABLE_ENDint io_state;
#web io_statevoid my_io_polling(void);void main()
{
sock_init();
http_init();for (;;) {
my_io_polling();http_handler();
}
}void my_io_polling()
{
io_state = read_that_io_device();
}The differences between the above code and the toy example in the previous section are in
boldface. All the differences relate to the use of RabbitWeb. The first addition is a#defineofUSE_RABBITWEB. This is necessary in order to include the necessary library code.Next, there is a modification to the MIME table. The
SSPEC_MIME_FUNCmacro defines an entry that says that if the resource name ends with ".html" then the MIME type is text/html (as before),andthere is a special scripting function that must be run by the HTTP server. This scripting function is calledzhtml_handler; it is provided by the HTTP library. ZHTML is the unique embedded scripting language that converts script files into ordinary HTML so the browser can understand it.2The
int io_stateand#webstatements define and register a web variable. Such a variable is an ordinary global variable as far as your C program is concerned. In addition, the script is able to access it.
my_io_polling()is a function that is part of the Application Specifics sub-block. As the name suggests, it is called regularly to poll some external device so as to keep the#webvariable up-to-date. The implementation of themy_io_polling()function is shown updating the#webvariable, but we don't specify the actual I/O reading function since that is too, well, application specific.Now you may be wondering what this scripting language, ZHTML, looks like. The following code shows the contents of the
my_app.zhtml file:<HTML><HEAD><TITLE>Web Variables</TITLE></HEAD>
<BODY><H1>Web Variables</H1>
<P>The current value of io_state is
<?z echo($io_state) ?>
</P>
</BODY></HTML>This looks like plain HTML, and it is, with the only difference being the existence of special commands flanked by "
<?z" and "?>." In this case, the command simply echos the current value of the web variable that was registered. The value (binary in the global variable) is converted to ASCII text by a defaultprintf()conversion, in this case "%d" because the variable is an integer. When the browser gets the results returned by the HTTP server, it will see:<HTML><HEAD><TITLE>Web Variables</TITLE></HEAD>
<BODY><H1>Web Variables</H1>
<P>The current value of io_state is
50
</P>
</BODY></HTML>Where the "50" represents the current variable value--of course, it may be any decimal value that an integer variable could take: -32768 through 32767.
This is still a trivial example, but it is infinitely more real-world than the toy example. We have introduced the concept of dynamic content, which is required for embedded type applications. One thing that has been glossed over is how (and even whether) the variable can be updated from the browser, rather than just within the application. Yes, all
#webvariables may be updated via the browser. This requires use of HTML forms, which is a subject covered in Chapter 4 and Chapter 5. We will not go over this again here; however, the possibility of remote updating introduces us to the topic of the next section, access control.2.6 Adding Access Controls
If your application allows updating of the controller state via remote access, and the network connection allows access from locations that are not always under control, then it is important to add some access controls or "security."
The most common way of doing this is to define a set of users, plus a method of authenticating those users, and attaching a set of "permissions" to each resource. The Dynamic C libraries allow you to do this fairly easily, via two tables. The relevant tables are:
The User Table
The user table contains a list of user IDs (short strings) and authentication information (currently a password string). Each user table entry also contains a group mask. The group mask indicates the user groups to which this user belongs. Up to 16 groups can be defined, and any given user can belong to one or more of these 16 groups. There are two additional masks in each user table entry. The first is a write access mask that indicates which server(s) allow the user to write (modify) its resources. The second mask indicates the server(s) that can recognize the user.
The Rule Table
The rule table is a list of information associated with each resource name, generally called "permissions." Each resource has the following information:
· The realm (string) that may be used by certain servers (including HTTP).
· The group mask of the user groups that are allowed read-only access.
· The group mask of the user groups that are allowed modify/write access.
· The server(s) that are allowed any access to this resource.
· The authentication method that is recommended.
· The MIME type of the resource.
Resources in the static and dynamic resource tables may be set up to have their own specific permissions, independent of the rule table itself. Resources in a filesystem may be very numerous hence a simple one-to-one table would waste a lot of storage. To solve this problem, the rule table uses a name
prefixmatching algorithm. Using this technique, entire directories of resources need only have one rule table entry provided that all resources therein use the same permissions.The following diagram shows the application components when access control is added:
The main difference between this and the previous diagram is that the Rule Table and User Table blocks have been activated.
The sample program is now expanded to add access control. As before, the changes are in
boldface.#define TCPCONFIG 1
#define USE_RABBITWEB 1#define USE_HTTP_BASIC_AUTHENTICATION 1#use "dcrtcp.lib"
#use "http.lib"#web_groups monitor_group, admin_group#ximport "my_app.zhtml" my_app_zhtmlSSPEC_MIMETABLE_START
SSPEC_MIME_FUNC(".html", "text/html", zhtml_handler),
SSPEC_MIMETABLE_ENDSSPEC_RESOURCETABLE_START
SSPEC_RESOURCE_XMEMFILE("/index.html", my_app_zhtml)
SSPEC_RESOURCETABLE_ENDint io_state;
#web io_stateauth=basic groups=monitor_group(ro),admin_groupvoid my_io_polling(void);void main(){sspec_addrule("/index.html", "Pet", admin_group|monitor_group,
0, SERVER_HTTP, SERVER_AUTH_BASIC, NULL);sauth_setusermask(sauth_adduser("admin", "dog", SERVER_ANY),
admin_group, NULL);sauth_setusermask(sauth_adduser("monitor", "cat", SERVER_ANY),
monitor_group, NULL);sock_init();
http_init();for (;;) {
my_io_polling();
http_handler();
}
}
void my_io_polling()
{
io_state =read_that_io_device();
}The first change is the definition of
USE_HTTP_BASIC_AUTHENTICATION. This sets up the HTTP server to be able to process this form of authentication. If not defined, then the server is unable to do this; there is little point in setting up any other access controls if the user cannot be verified!Next, the user groups are defined. In this case, we are defining an "admin" and a "monitor" group. Presumably, the admin group has ability to alter the state of the controller, but the monitor group can only read its current state. The names
admin_groupandmonitor_groupare actually defined to be unsigned integer constants with just one bit set out of 16.The
#webregistration of theio_statevariable is augmented with some access controls.#webvariables are not strictly resources--they are included as parts of other resources--however, they can be assigned some access controls of their own. In this example, access to the variable is being set to require "basic authentication," and the allowable user groups are both of the defined groups, with the proviso that the monitor group is to be allowed read-only access.The last major change is in the
main()function, where some runtime initialization needs to be performed. Since the user ID table cannot be statically initialized (i.e., at compile-time), this is a necessary step. The rule table can be statically initialized, but in this example we choose to do it at runtime.3 First, the rule table entry:sspec_addrule("/index.html", "Pet", admin_group|monitor_group, 0, SERVER_HTTP, SERVER_AUTH_BASIC, NULL);
The first parameter specifies the name of the resource to which this rule applies; or rather, the first characters in the resource name. For clarity, the sample shows the full name. In practice, since there is only one resource, it would be acceptable to use just "
/" instead of "/index.html."The second parameter, "Pet," is an arbitrary string called the "realm." This is presented to the browser's user when prompted for the password, as shown in Figure 2.5.
The third and fourth parameters indicate the group(s) that have read and write access to the resource. Both groups are allowed read access, and none write (0). Note that the resource in this case is the
index.htmlpage, not the variables which may or may not be displayed on it. Since this web page (actually a ZHTML script) is in program flash, it is obviously not modifiable.The
SERVER_HTTPparameter indicates that this resource is only visible from the HTTP server. This would be more relevant is there was another server, such as FTP, running concurrently.
SERVER_AUTH_BASICindicates that the server should use "basic authentication" when the browser calls for this resource. Note that Zserver does not enforce the method of authentication; it only stores the recommended method in the rule table. Any enforcement of authentication requires the co-operation of the server, since each different type of server may have widely different means of implementing the same type of authentication. Rest assured that the HTTP server (and other servers provided with Dynamic C) always enforce the suggested authentication method.The final NULL parameter allows some arbitrary data to be stored in the rule table entry. This data is available to the server. It is not currently used by any of the servers in Dynamic C, but it may be useful if you implement your own server.
Now, let's turn to the user ID initialization:
sauth_setusermask(sauth_adduser("admin", "dog", SERVER_ANY), admin_group, NULL);
This is a nested function call.
sauth_adduser()is called first, to add a user called "admin" with password "dog." This user is visible to all servers (SERVER_ANY).The result of this function call is a userID handle, which is the first parameter to
sauth_setusermask(). This function explicitly assigns a group mask to the user. You can omit this call; however, the default method of assigning group masks is designed to be backward compatible with old versions of the library, and may not be what you want when using new features. You should always use thesauth_setusermask()function for each user ID.In this example, we have added access control to the code. We do not need to change the ZHTML script, although in reality you would probably want to. Using the script unchanged, when the user tries to retrieve
index.html, the browser will prompt for a userid and password. If one of the valid users is entered, then the page will be displayed. Otherwise, the browser will print an error message saying that access was denied.Unfortunately, as written above, the sample will not allow us to test the distinction between the two users regarding the ability to modify the
#webvariable. We have shown how to add access control, but not how to actually specify a web form that allows the user to update the variable. It turns out that adding a form is not difficult. A modified script file is shown below. There is quite a lot to HTML forms, so most of the details are documented elsewhere. There are many good HTML reference books available.<HTML><HEAD><TITLE>Web Variables</TITLE></HEAD>
<BODY><H1>Web Variables</H1>
<P>The current value of io_state is
<?z echo($io_state) ?>
</P><?z if (error($io_state)) { ?>
<P>Sorry, you were not authorized to perform an update.</P>
<?z } ?><FORM ACTION="/index.html" METHOD="POST"><P>Enter a new value if you dare:</P><INPUT TYPE="text" NAME="io_state" SIZE=5</BODY></HTML>
VALUE="<?z echo($io_state) ?>">
<INPUT TYPE="submit" VALUE="Submit">
<INPUT TYPE="reset" VALUE="Reset">
</FORM>If you run the above sample with this script, then the user will be able to attempt an update to the
#webvariable,io_state. If the user was "monitor," that is, not able to make an update, then the "Sorry" message will be printed. Recall that the access toio_statewas set up when the variable was registered with#web.You may be asking how the application notices when the
#webvariable is updated by the browser, not just in themy_io_polling()function. This is a good question, since the HTTP server updates the variable just like a normal C variable. The solution to this requires that you specify an "update" callback function in the#webvariable registration. This is described in detail in Chapter 5. For the purposes of this section, just remember that it is easy to do.2.7 A Full-Featured Application
The previous examples have relied on
#ximportto store files in the program flash. This is limiting in terms of storage capacity and does not allow for dynamic file updates. Adding the ability to store files in a filesystem that is located somewhere besides the program flash is of high value because it adds storage capacity and allows for dynamic updates.As mentioned previously, Zserver implements a virtual filesystem that can be used by an application for a clean, consistent interface to the various available methods of resource organization. An application can also bypass the resource manager and access a filesystem directly. (Note that there is no arrow in the diagram showing this line of communication.)
Looking at the bottom of the diagram in Figure 2.6 you can see that there are some additional hardware requirements when using FAT or FS2. The FAT needs a serial flash; and FS2 needs a second flash or battery-backed RAM, as well as a Rabbit 2000 or 3000 processor.
The sample program is now expanded to use a FAT filesystem and has the ability to upload files to it. As before, the changes are in
boldface.#define FAT_USE_FORWARDSLASH
#define FAT_BLOCK
#define USE_HTTP_UPLOAD#define TCPCONFIG 1
#define USE_RABBITWEB 1
#define USE_HTTP_BASIC_AUTHENTICATION 1#use "sflash_fat.lib"
#use "fat.lib"#use "dcrtcp.lib"
#use "http.lib"
#web_groups monitor_group, admin_group
#ximport "my_app.zhtml" my_app_zhtmlSSPEC_MIMETABLE_START
SSPEC_MIME_FUNC(".html", "text/html", zhtml_handler),
SSPEC_MIME(".cgi", "")SSPEC_MIMETABLE_ENDSSPEC_RESOURCETABLE_START
SSPEC_RESOURCE_XMEMFILE("/index.html", my_app_zhtml),
SSPEC_RESOURCE_CGI("upload.cgi", http_defaultCGI)SSPEC_RESOURCETABLE_ENDint io_state;
#web io_state auth=basic groups=monitor_group(ro),admin_groupvoid my_io_polling(void);void main(){int rc;sspec_addrule("/index.html", "Pet", admin_group|monitor_group,
0, SERVER_HTTP, SERVER_AUTH_BASIC, NULL);sauth_setusermask(sauth_adduser("admin", "dog", SERVER_ANY),
admin_group, NULL);sauth_setusermask(sauth_adduser("monitor", "cat", SERVER_ANY),
monitor_group, NULL);rc = sspec_automount(SSPEC_MOUNT_ANY, NULL, NULL, NULL);if (rc)
printf("Failed to initialize, rc=%d\n
Proceeding anyway...\n", rc);sock_init();
http_init();for (;;) {
my_io_polling();
http_handler();
}
}The first change is the addition of
FAT_USE_FORWARDSLASHandFAT_BLOCK. These are needed by Zserver to work with the FAT filesystem. The definition ofUSE_HTTP_UPLOADis needed for Zserver to use the file upload feature. Next, the libraries for the FAT (fat.lib) and for the serial flash driver (sflash_fat.lib) are brought in with#usestatements.The MIME type mapping for CGIs is added to the MIME table with
SSPEC_RESOURCE_CGI. An empty string is the registered type for CGIs. This makes sense since CGIs are not displayed by the browser.Next, we want to give the server access to the CGI function by creating an entry for it in the static resource table with
SSPEC_RESOURCE_CGI. The first parameter is a string that must match the string used in the FORM ACTION tag in the HTML code. The second parameter identifies the CGI function that will be called when the form is submitted.http_defaultCGI()is a CGI that is provided with the HTTP server. It uploads files to a FAT filesystem, shows a status page to the browser after the upload and allows the user to click back to the server's home page. For a detailed description of the file upload feature, see Section 4.6.Finally, the FAT filesystem must be readied for use. The call to
sspec_automount()takes care of everything, assuming that a FAT partition already exists on the serial flash. How to create the initial filesystem is discussed in the Dynamic C User's Manual.The application now supports uploading files to the FAT, but we have yet to give the user any way to actually do it. That involves changing the HTML page.
<HTML><HEAD><TITLE>Web Variables</TITLE></HEAD>
<BODY><H1>Web Variables</H1>
<P>The current value of io_state is
<?z echo($io_state) ?>
</P>
<?z if (error($io_state)) { ?>
<P>Sorry, you were not authorized to perform an update.</P>
<?z } ?><FORM ACTION="/index.html" METHOD="POST">
<P>Enter a new value if you dare:</P><INPUT TYPE="text" NAME="io_state" SIZE=5
VALUE="<?z echo($io_state) ?>">
<INPUT TYPE="submit" VALUE="Submit">
<INPUT TYPE="reset" VALUE="Reset">
</FORM><BR><FORM ACTION="upload.cgi" METHOD="POST" enctype="multipart/form-data"><TABLE BORDER=0 CELLSPACING=2 CELLPADDING=1></BODY></HTML>
<TR>
<TD ALIGN=RIGHT>File to upload<BR>(to /A/new.htm)</TD>
<TD><INPUT TYPE="FILE" NAME="/A/new.htm" SIZE=50></TD>
</TR>
</TABLE>
<INPUT TYPE="SUBMIT" VALUE="Upload">
</FORM>The text in boldface is the description of a new form, which, when displayed by the browser, allows a file to be uploaded to a FAT filesystem.
The FORM tag includes the METHOD attribute, which is the same as that of the first form. The ACTION attribute has changed to specify the CGI function that was added to the server's static resource table; this is the default CGI provided by the server. When the Upload button is clicked,
http_defaultCGI()will be called by the server. A new attribute is included that specifies the MIME type used to submit the form to the server: enctype="multipart/form-data". This is the MIME type required when the returned document includes files.Note that the two forms are being submitted and processed separately. Could they be processed as one form? Yes, but from a modular design perspective, it makes sense to keep the form submissions separate when the purpose of each form is entirely separate.
You may have noticed that no security was added to protect the filesystem--anyone can upload a file that passed the initial user and password protection that limits access to the web page. This is probably not the ideal situation. Typically there needs to be some limit placed on who is able to write to the filesystem.When considering security, there are three possible things to protect:
The web page that contains the form. Give read access only to those users who could conceivably upload the files specified therein.
The CGI itself. Protect the same as the web page.
The uploaded resource. You should set up a rule allowing write access only to the intended user(s).
When defining user IDs that can use the upload, don't forget to give those users overall write access using e.g.,
sauth_setwriteaccess(uid, SERVER_HTTP);Another way to design this application is to have a separate HTML file that contains the form for the file upload; then instead of having the form for the file upload on the current HTML page, you put a link to the new page and then apply a permission to allow the new page to be displayed, such as:
sspec_addrule("/newpage.html", "Pet", admin_group, admin_group, SERVER_HTTP, SERVER_AUTH_BASIC, NULL);
That way the only people who see the Upload button are those authorized to use it. Design decisions such as these are guided by the needs of the application. The point here is that these design decisions are not limited by the underlying tools you are using to accomplish your goal.
2.8 Living Without RabbitWeb and FAT
Without the use of RabbitWeb we are back to SSI tags in the HTML page and writing a CGI to process them. With the new-style CGIs introduced in Dynamic C 8.50, this is easier than it used to be. If there is no serial flash, the FAT filesystem isn't available; but if there is a second flash or some battery-backed RAM, FS2 is. The following diagram shows the components that are used in this case. Note that even though both the second flash and the battery-backed RAM are highlighted, an application can use either or both.
The sample program is now modified to use the FS2 filesystem. It still has the ability to upload files to the filesystem. As before, the changes are in
boldface.#define USE_HTTP_UPLOAD#define TCPCONFIG 1
#define USE_HTTP_BASIC_AUTHENTICATION 1#use "fs2.lib"#define admin_group 0x0001
#define monitor_group 0x0002#use "dcrtcp.lib"
#use "http.lib"#ximport "my_app.shtml" my_app_shtmlSSPEC_MIMETABLE_START
SSPEC_MIME_FUNC(".ssi", "text/html",shtml_handler),
SSPEC_MIME(".cgi", "")
SSPEC_MIMETABLE_ENDint io_state;SSPEC_RESOURCETABLE_START
SSPEC_RESOURCE_ROOTVAR("io_state", &io_state, INT16, "%d"),SSPEC_RESOURCE_XMEMFILE("/index.html",my_app_shtml),
SSPEC_RESOURCE_CGI("upload.cgi", http_defaultCGI),
SSPEC_RESOURCE_CGI("update.cgi", VarUpdateCGI)SSPEC_RESOURCETABLE_ENDvoid my_io_polling(void);void main(){
int rc;io_state = 42;sspec_addrule("/index.html", "Pet", admin_group|monitor_group,
0, SERVER_HTTP, SERVER_AUTH_BASIC, NULL);sauth_setusermask(sauth_adduser("admin", "dog", SERVER_ANY),
admin_group, NULL);sauth_setusermask(sauth_adduser("monitor", "cat", SERVER_ANY),
monitor_group, NULL);rc = sspec_automount(SSPEC_MOUNT_ANY, NULL, NULL, NULL);if (rc)
printf("Failed to initialize, rc=%d\n
Proceeding anyway...\n", rc);sock_init();
http_init();for (;;) {
my_io_polling();
http_handler();
}
}The first change is the removal of the macros we added for FAT and also the removal of
#usestatements for the FAT library and the associated serial flash driver library. As with the sample in the last section, this code assumes that a valid filesystem partition exists on the target board; in this case, it's an FS2 partition. In the simplest case, which is one FS2 partition on the secondary flash, bringing infs2.liband then mounting the filesystem with a call tosspec_automount()is all that is required. (For more information on FS2, refer to the Dynamic C User's Manual.)The next change is the
#defineof the user groups. Each user group has to be explicitly given a value when RabbitWeb is not available to do it. Note that they are word values, each with a unique bit position set.Next, the first entry in the MIME table was changed. Recall that the entry "
/" and requests without an extension are dealt with by the handler in the first entry of the MIME table. In this example, if a browser points to the Rabbit board's IP address, the page is processed byshtml_handler(), a handler that will understand the SSI tags that we are about to add to the HTML file. The #ximport statement did not, technically, need to change. The extension used for the file was changed from.zhtmlto.shtml. These file extensions are only a convention. The important thing is that the HTML file is touched by the correct handler function. As a matter of fact, in this example, our HTML page is not recognized by the server as ending with either .zhtml or .shtml, but by.html. The name known to the server is determined by the name parameter of the file's resource table entry, "/index.html."The next change is a new entry in the static resource table. This reflects the shift in how the variable
io_statebecomes known to the HTTP server. Previously, it was done using the#webstatement of RabbitWeb.A second new entry in the resource table is for a CGI function that will handle the processing when
io_stateis updated. When using RabbitWeb, this same form submission did not require a CGI. The enhanced HTTP server took care of all the details for us. Without RabbitWeb, we have to do the work ourselves. Fortunately, the new-style CGIs make this job easier. A detailed description of writing a new-style CGI is given in Section 4.6 "HTTP File Upload." As we saw in Section 2.7, there is a CGI inhttp.libthat processes file uploads to a filesystem. If you study and understand Section 4.6 and the code inhttp_defaultCGI(), you will be able to write a new-style CGI that will process the form that is submitted whenio_stateis changed.Since we are not using RabbitWeb and have changed from using FAT to FS2, the HTML page must be changed. As before, all changes are in boldface.
<HTML><HEAD><TITLE>Web Variables</TITLE></HEAD>
<BODY><H1>Web Variables</H1><P>The current value of io_state is:<!--#echo var="io_state" --></P><FORM ACTION="update.cgi"METHOD="POST"enctype="multipart/form-data"><P>Enter a new value if you dare:</P><INPUT TYPE="text" NAME="io_state" SIZE=5
VALUE="<!--#echo var="io_state" -->">
<INPUT TYPE="submit" VALUE="Submit">
<INPUT TYPE="reset" VALUE="Reset">
</FORM><BR><FORM ACTION="upload.cgi" METHOD="POST" enctype="multipart/form-data"><TABLE BORDER=0 CELLSPACING=2 CELLPADDING=1>
<TR>
<TD ALIGN=RIGHT>File to upload<BR>(to /A/new.htm)</TD>
<TD><INPUT TYPE="FILE" NAME="/fs2/ext1/new.htm"
SIZE=50></TD>
</TR>
</TABLE>
<INPUT TYPE="SUBMIT" VALUE="Upload">
</FORM>
</BODY></HTML>The first change is the substitution of the new server-parsed tags with SSI tags. The next change is the absence of any error checking. Without RabbitWeb, it is difficult to achieve this same functionality. The CGI responsible for the processing the variable update would need to do it. Which brings us to the next change in this HTML page, the need for a second CGI function.
The ACTION attribute in the FORM tag identifies the new CGI by name,
update.cgi. The FORM tag also has a parameter for the encoding type. When no encoding type is specified, it defaults to URL-encoded. All new-style CGIs must set the encoding type in the FORM tag to "multipart/form-data" as shown above.The other change on this page is the NAME attribute in the first INPUT tag of the second form. When uploading to an FS2 partition, the mount-point "/fs2" must be prepended to the filename. The /ext1 part is also prepended to the filename and refers to the second flash. The default CGI function can now store an uploaded file in a valid FS2 partition.
1 This is a necessary optimization. There may be hundreds of individual users; however, the majority of these would be considered to be in a single "class," with that class giving equal access to all its members. Considering the class, i.e., group, as the entity that is requesting a resource reduces the amount of information that needs to be stored.
2 Most applications will want to use a different resource suffix to distinguish between "ordinary" HTML files and script files. The samples provided with dynamic C use .zhtml for script files, and .html for plain HTML. In this sample, we only have script files, so it is convenient to retain the .html suffix. The other reason for this relates to the way the HTTP server handles requests for a directory. If given a URL of "/", the HTTP server will append "index.html" to determine the actual resource. We take advantage of this default behavior so that this sample would work as expected.
3 In this example we also choose to use a rule table. This is not strictly necessary since no filesystem is in use. The alternative is to use a different form of initializing the static resource table, namely by using theSSPEC_RESOURCE_P_XMEMFILEmacro, which allows permission information to be stored in the static table instead of in the rule table. See Section 3.2.5.3.
| TCP/IP Manual Vol. 2 |
<< Previous | Index | Next>> | rabbit.com |