June 16, 2002 Douglas Clark is a program management consultant who first started using OS/2 version 1.3. He's married, with 2 girls, and is old enough to remember when 4 color mainframe terminals were a big thing. If you have a comment about the content of this article, please feel free to vent in the OS/2 eZine discussion forums. There is also a Printer Friendly version of this page. |
|
Report on Writing OS/2 Plug-ins
or
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
The Problem Two main requirements from the WarpDoctor project led to making the plug-in: absolute minimum maintenance effort to keep up the site, and public input of the site contents. The absolute minimum maintenance and upkeep requirement drove us to using a database to store all site content. Every page of content on the site, plus all the menu/links structure needed to get to the pages - the things you click on to get to the pages - are all stored in the database. The advantage to us is that we can build structures to automatically build links to pages of new content and we can insure that no dead-end links ever appear in the site by using regular database facilities such as triggers and referential integrity constraints. Public input of content means that we are depending on the OS/2 community to keep the site up to date. While the WarpDoctor team will be adding and revising the information stored in the site the bulk of the information will (hopefully) come from OS/2 users. When an OS/2 user discovers a way to install some hardware device successfully or a source for device drivers, the idea is that he would come to the site and enter that information. This means that we needed some way for the general public to make input to our SQL database. Sometimes very large input. For example one of the topics we have on configuring Netscape is over 650K of HTML text. We also envision a wide array of content: HTML text, PDF, Postscript, and storage of device drivers and other software within the database. We need not only to provide a way of typing text into the database but also some way of uploading files and storing the content from the file into the database. After all, who wants to type 650K of text into a data entry screen, and you can't make a data entry screen for entering PDF data. What we needed was a data entry screen with an attach file button, where the "attached" file is uploaded to the server to be input into the database. Uploading Files to a Web Server There are three basic ways of uploading a file from a client to a web server. The most straightforward way of uploading a file from a client machine to a server machine is having the user use the FTP feature of Netscape. The user types an FTP address in the address area of Netscape to connect to a FTP server, then uses the File - Upload File... menu option to select and send the file. Messy but it works. The problems with this method are: 1) it take more user intervention to get the file uploaded, 2) it is not integrated with the data entry process and 3) it is very difficult to associate the file that has been uploaded with the rest of the data entered on a data input screen. The most integrated method is to use the <Input Type=File> HTML tag/parameter combination on an HTML form used for data entry. This tag places a text entry area on the form where a file name can be typed and also places a BROWSE button next to the text entry box. The BROWSE button opens a standard file dialog box where the user can pick the file he wishes to upload. When the form is submitted the browser sends (or is supposed to send) the contents of the file in a special format called multi-part MIME. You have to write a CGI program on the server side that intercepts the contents sent by the browser and save those contents to a file on the server's hard drive. This by far is the most desirable method because it is integrated with the data entry form, it doesn't require a FTP server to process the file, and it doesn't require any software on the client side other than the browser. Unfortunately it also doesn't work, at least with the OS/2 version of Netscape. I converted the Unix source from www.kessels.com/Upload/index.html to OS/2 in order to test Netscape under OS/2 uploading a file to an OS/2 web server. What I found was that when uploading binary files Netscape more often than not would not send the entire contents of the file. When it did send the entire contents of the file the contents that were sent were not exactly identical to the file on the client. Clearly not an acceptable solution. It did seem to work fairly well sending plain text files, but our needs were to be able to handle text and binary files. The OS/2 ported version of the CGI program is available if anyone is interested. This leaves the third method: writing software that resides on the client machine to perform, or assist in, the file upload process. The method we chose was a Netscape plug-in. But before we can discuss how the plug-in works, or how to make a plug-in, we need to cover some very basic background on how web servers and web browsers work. In these next sections we will very briefly discuss how the various pieces of web communications work. The section is a very high level overview intended to provide just enough background to discuss plug-ins. Web browsers and web servers communicate with each other by sending messages back and forth. Every time you click on a link on a web page a message is sent from the browser to the server. When the server receives the message from the browser it decodes the message, figures out what the message is requesting, and returns a message to the browser. The format of the messages is defined by a protocol called HTTP - Hyper Text Transfer Protocol. The HTTP protocol defines both the format of the messages, and how the server and browser response to different types of messages and malformed messages. Your main concern is understanding the basic format of the messages. Each message in HTTP consists of one or more headers, and an optional body. All messages will have at least one header but a message may or may not have a body, depending on the message type. Each header in the message is on its own line, the line being defined by a terminating carriage return and line feed pair. The header section is terminated by a blank line. If a body exists, it appears in the message after the header section, that is after the blank line terminating the header section. We will look at two message examples below. This first message example was generated by typing the Uniform Resource Location (URL) The Connection: header requests that the socket be kept open for subsequent messages from the browser. The User-Agent header identifies the type of browser that is making the request. The Host line identifies the host portion of the URL that created the request, meaning the host that the message has been sent to. The Accept... headers define the type of content the browser can read (gif, xbitmap, jpeg, etc.), the types of encoding that can be applied to the body of the message, which languages the browser would like the text returned in, and the character sets or code pages the browser would prefer.
This first example message has no body because it is requesting information from the server. Most of the messages from the browser do not have a body. On the other hand most of the messages from the server do have a body because the server is usually returning information in response to a request and the information is in the body of the message. The second message example was sent from a browser when the submit button on the following form was pressed.
This message is a POST type message. It is requesting the server to return information from the program RunThis.exe located in the server's /cgi-bin directory. This message, in addition to requesting information from the server, is also sending some data to the server to be passed along to RunThis.exe. That information is contained in the body of the message and comes from the two text input areas of the HTML form being displayed on the screen above. The body of the message being The differences between this message and the previous message are:
The Content-type header specifies what type of data appears in the body of the message. The Content-length header specifies how many byes are in the body of the message. Messages that have a body almost always have Content-type and Content-length headers. HTML Hiearchical Text Markup Language is the language used in the majority of all web documents you see in your browser. HTML pages are contained in HTTP messages that have a Content-type of text/html and HTML is contained in the body of the message. HTML is a combination of a language that describes text, and a very basic language for building simple data entry screens, called forms. The HTML language is made up of tags. The tags identify types of text, such as paragraphs, level one headings, level 2 headings, numbered lists, etc. The browser reads the HTML tags and decides how to format each type of text based on the browsers current settings. HTML tags are enclosed in angled braces, like this <tag> where the tag name is in the angled brackets. In HTML some tags require a beginning and ending tag while other tags are self contained, i.e. there is no corresponding ending tag. For those tags that require an ending tag, the ending tag is also the same name as the beginning tag, except the name begins with a slash. For example
show the CENTER tag, which requires an ending tag. The following is an example of a very simple HTML file and what it looks like in Netscape.
Simple data entry type screens can be built in HTML in what are called forms. A form is indicated on an HTML page with a form tag - which is one of the tag types that also requires an ending tag. Between the beginning and ending form tags can appear a number of HTML tags that represent various "controls" such as: radio buttons, check boxes, push buttons, text entry boxes, drop down and pick lists, etc. The form tag can take a number of parameters such as the ACTION parameter and the METHOD parameter. The ACTION parameter specifies what program the server should execute when the form is "submitted". The METHOD parameter specifics which method (GET or POST) is used when executing the program in the ACTION parameter; we discuss this further in the CGI section below. The following is an example of a very simple form in HTML, and what it looks like in Netscape.
When the form is submitted the browser sends either a GET or a POST message to the web server, depending on the METHOD parameter on the FORM tag. The format of the message was discussed in the HTTP section above. Common Gateway Interface - CGI Web servers can be "extended" with external programs using the Common Gateway Interface (CGI). When an HTML form is "submitted" (usually by the user clicking the submit button) the web server executes the external program specified in the ACTION parameter and passes the data from the tags/controls in the "form" to the external program. The CGI specification defines how the web server passes information to the external program and how the external program returns information to the web server. The external program can be written in any language that can handle the methods used by the web server to pass information to the external program. The method used by the web server to pass information to the external program is determined by the METHOD parameter on the FORM tag. In GET method the web server passes the data from the form to the external program in an environment variable called QUERY_STRING. In the POST method the web server sends the data from the form to the external program using STDIN. In both cases the data from the form is represented by a name followed by an equal sign, followed by the value from the tag/control. For instance, the form in the above example would send the data as text1=some data The external program communicates with the web server and web browser by sending output to STDOUT. The web server intercepts the output from STDOUT from the external program and sends the output to the browser. So really the CGI program is communicating with the browser through the web server. Since the output generated by the CGI program goes more or less directly to the browser, the output must be in a format the browser understands. More specifically, the output must be in the format of an HTTP message the browser understands. I said the output goes "more or less directly" to the browser because the web server examines the output and adds headers to the message (the output) if necessary before it passes the message to the browser. (The most common header added to the message by the web server is the Content-length header.) For example if our program runThis.exe consisted of the following little C program
the program would return the following output
(The web server would add the header line Extending Netscape Netscape can be "extended" three different ways: with helper application, with Java applets, and with plug-ins. Netscape can also be controlled by external programs through DDE, but that really isn't extending Netscape since the external program doing the controlling is completely removed from Netscape. Helper applications are external programs that reside on the client machine and are called by Netscape when it encounters a MIME type (Content-type header) it does not handle internally, or when Netscape downloads a file with an extension associated with the application. Netscape saves the MIME type data to a temporary file and then calls the helper application program passing the temporary file name on the command line. MIME types and file extensions are associate with helper applications in the Edit - Preferences menu option. A Java applet is a Java program that is invoked by Netscape when it encounters an < applet> or <object> HTML tag. A parameter in the applet or object tag specifies the name of the Java program to be downloaded and run, along with the size of the frame on the HTML page that will contain the program's output. The Java applet resides only temporarily on the client machine - it is downloaded each time it is run. The applet cannot access the client hard drive without requesting permission from the user. A plug-in is an external DLL that are loaded by Netscape when it encounters a MIME type associated with the plug-in. The MIME type data is passed to the plug-in either as a temporary file or as a stream of data, depending on the plug-in's preference. The plug-in declares which MIME type(s) it handles when it is written and compiled. When Netscape starts up it checks each plug-in located in the plug-ins directory for the MIME types it handles and builds an association list that it uses to invoke the correct plug-in for a particular MIME type. The plug-in handles the data however it wants: some plug-ins handle graphical type data and display that data in a Netscape window, other plug-ins handle audio data and send the data to the computers speakers. plug-ins are also loaded by Netscape when it encounters an <embed> or <object> HTML tag with a name parameter that specifies a plug-in name in an HTML page. plug-ins are more integrated with Netscape than helper applications are. Plug-ins are notified of keyboard and mouse events by Netscape and can draw in Netscape windows, thus appearing to draw directly in the HTML page. Helper applications on the other hand are external applications that execute as their own process and draw into their own window. Both helper applications and plug-ins can access the user's hard drive without requesting permission from the user. plug-in Overview A plug-in is a module that is dynamically loaded by Netscape. The plug-in can
Plug-ins fall into three categories:
We will only be discussing faceless plug-ins in this article. However you will see that the categories become blurred next month when we start discussing using Rexx with plug-ins. Netscape communicates with the plug-in by calling functions in the plug-in, called plug-in Methods. The plug-in communicates and requests services from Netscape by calling functions implemented in Netscape, called Netscape Methods. plug-in methods begin with NPP_, for example NPP_New(). Netscape methods begin NPN_, for example NPN_Write(). Unlike an application, the life cycle of a plug-in is completely controlled by the web page that calls it. The life cycle details are:
Between NPP_New() and NPP_Destroy() the plug-in, just like a GUI program, waits for some event to happen that triggers an action in the plug-in. For windowed plug-ins those events are keyboard and mouse events. For faceless plug-ins the events are the creation of a new stream of data, or the completion of a Netscape method called by the plug-in. In a faceless plug-in it is easiest to include the actions you want do in the NPP_New() function that gets called when the instance initializes. You are limited somewhat in what Netscape methods you can call before the initialization completes but for simple tasks you can get away with placing those tasks at the end NPP_New() The following two tables summarize the functions the plug-in must provide that Netscape calls (Plug-in Methods), and the functions that Netscape provides that the plug-in can call (Netscape Methods.)
Programming the plug-in To make a plug-in you need:
The SDK contains a number of sample plug-ins that you can use as a starting point to build your own. The most basic example is called NPSHELL, which is a example that contains skeletal functions for each of the plug-in methods, the NPP_ functions that Netscape calls in the plug-in. To use NPSHELL you copy the sample files to a new directory and edit the NPSHELL.CPP file to add your code to the function shells as needed. The example code I am going to show is from the WarpDoctor plug-in, which was made from the NPSHELL sample program. WarpDoctor Plug-in The WarpDoctor plug-in is a faceless plug-in that performs two unrelated functions:
The source for the WarpDoctor plug-in contains the simplified screens that are shown here in order to make it easier to modify the plug-in for your own use. What it Looks Like To perform a file upload the user loads the plugin_fileupload.html file, as shown below, and enters the name of a file to be uploaded.
The file name can be typed into the entry field or the Browse button can be used to select the file. If the Browser button is used you probably will have to change the list of file types in the file dialog box in order to see all the files in a directory; Netscape assumes that you want to upload an HTML type file. When theUpload button is pushed an second window is opened, shown below.
This second window is the one that loads the plug-in. The plug-in sends the file specified in the first screen using FTP to an FTP server, then it invokes a CGI program on the web server, passing the name of the file to the CGI program. For our simplified example the CGI program does nothing but display the message "Cgi program would do something here" in the second window, as shown below.
On the WarpDoctor data entry screen a different CGI program is executed that takes the values input on the data entry screen and inserts those, along with the contents of the uploaded file, into a row of the database. How it Works Our first screen is a simple form with an <input type='file'> tag and a single button. The <input> tag produces on the form the text entry box and the Browse button. The source is shown below.
This file is a little unusual in that we do not submit the form that is displayed by the file. Instead the Upload button executes a little JavaScript function (lines 4 - 15) that opens another window (line 14) with the file plugin_fileupload2.html. Normally the <form> tag has an ACTION parameter that specifies the CGI program to be called when the form is submitted, but we don't want to submit this form. The reason is, we want the Browse button and file dialog box that Netscape provides with the <input type=file> tag but we don't want Netscape to send the file because we know that doesn't work - at least with binary files. Lines 6-7 get the current date and time, which is used in line 14 to create a unique "name" for the new window that is opened. We need to create a unique name for the new window in order to allow this form to be submitted multiple times simultaneously, hopefully each time to upload a different file, without each execution stepping on each other. The HTML file that is loaded in the second window executes a little JavaScript routine that gets the file name from the first window, and builds an <embed> tag using the file name as one of the embed parameters. The source for plugin_fileupload2.html is shown below.
Line 4 creates a JavaScript variable called flName. Line 5 sets the variable flName to the value of the form control/tag named uploadFileName on the first window, which is our <input type=file> tag, shown on line 26 of the previous figure (file plugin_fileupload.html.) Lines 13 - 34 output the embed tag (lines 22-32 are commented out), which loads the plug-in. We have to use JavaScript to create the embed tag and its parameters, instead of hard-coding it in the file, because we don't know the name of the file that is going to be uploaded until this file is executed. The way this file works is:
This is what the page looks like that is built by the JavaScript
When a plug-in is loaded by the <embed> or <object> tags Netscape passes the names and values of each of the parameters in the embed tag to the plug-in instance when it starts the instance. Since HTML ignores tags, and parameters on tags, that it doesn't recognize this allows us to pass "arguments" to our plug-in when it is loaded. We do this by adding parameters to the embed tag for the arguments we want to pass. In plugin_fileupload2.html lines 14-17 are required to get the plug-in loaded. Lines 18-32 are "extra" parameters that we have added to control the behavior of the plug-in. Line 14 specifies a MIME type for this plug-in. The MIME type is pretty useless in this case since the plug-in is also identified by name on line 17. Line 16 says that there is no frame in which our plug-in is going to draw, and line 15 says that the plug-in is hidden. These are the lines necessary to get the plug-in loaded. Line 19 (and following) is a parameter we created in order to pass an argument to the plug-in. The uploadAction argument tells the plug-in how to handle the original file that is uploaded. A value of 1 says to upload a copy of the file, 2 means to delete the original file from the client hard drive after it has been uploaded. 1 is the default. Line 20 is the name of the file that is to be uploaded. Line 21 is the URL of the CGI program to be executed after the file is finished uploading. After the file is uploaded the plug-in will either:
In our example we are executing the CGI program pluginCgi.exe after the file is uploaded. You can put any CGI program here you want, including a Rexx program if your web server supports executing Rexx via CGI. Line 19 is used to pass a parameter to the CGI program. This works because our plug-in ignores parameters it doesn't recognize, just as Netscape ignores parameters to the embed tag it doesn't recognize, which means you can use <embed> parameters to pass arguments to the CGI program that is executed after the file is uploaded. A summary of the parameters for the embed tag used by Netscape and the WarpDoctor plug-in is show in the table below; you can add any parameters you want for passing to the CGI program as long as the name doesn't conflict with what is shown in this table..
In the WarpDoctor Plug-in source package is included the CGI program pluginCgi.exe. This is used in our example to produce a message showing that the CGI program has been executed. If you use the CgiDebug parameter
then pluginCgi.exe will show all the parameter names and values passed to it when it starts up. PluginCgi.exe is also used by the WarpDoctor plug-in to display error and status messages when the plug-in is not able to create temporary files on the client workstation, so pluginCgi.exe should be installed in the cgi-bin directory of any web server you are going to use the plug-in from. PluginCgi.exe
is mostly the shell of a CGI program that you can take and modify to create your own CGI program. The main function is shown below.
Line 19 of plugin_fileupload2.html sets the parameter ACTION=79 when the plug-in is loaded. The ACTION parameter is passed from the plug-in to the CGI program pluginCgi.exe, which on line 32 above causes the CGI program to output the message "CGI would do something here". Notice that lines 49-54 in the figure above show how to output from a CGI program in HTTP message format. Some Example Plug-in Functions This section will show some examples of plug-in code from both the WarpDoctor plug-in and from the NPSHELL sample supplied in the Netscape Plug-in SDK. Each instance of a plug-in has a pointer that can be assigned to storage allocated for holding data that needs to be passed between the plug-in and Netscape methods. An example of a very simple structure that is passed to the functions, from NPSHELL, is shown below. You can add your own fields to the structure for storing data you need for your plug-in.
The figure below shows the plug-in method called by Netscape when the plug-in DLL is loaded. This is where you would add any code you need to run once at the beginning of the plug-in.
After executing NPP_Initialize, Netscape then calls NPP_New to create an instance of the plug-in. It is here where you initialize the structure fields to the correct values. To see how the structure is passed to the other functions look at the NPN_URLNotify example below.
This next example, from the WarpDoctor plug-in, shows how to post data to a URL, i.e. a CGI program. This function is used in the WarpDoctor plug-in to output messages when the plug-in cannot create temporary files on the client's hard drive. The function sends the message to the CGI program in the variable MESSAGE, setting the variable ACTION to indicate what type of message it is. The CGI program, pluginCgi.exe shown in a previous figure, creates HTML output that contains the message which the web server sends back to the browser. This rather complicated method of displaying messages was done to avoid using any platform specific GUI code in the plug-in to make it easier to port to other platforms.
Line 138 defines the variable myData to hold the message, and initializes the variable with the Content-type header and the first part of the Content-length header. Lines 142-143 add the actual length to the Content-length header - the +20 is to account for the action=99&message= part of the body, plus the terminating CRLF characters. Notice that what you are doing is building an HTTP message simulating what the browser sends a web server from a page that has the <form> tag on it. The last parameter to NPN_PostURLNotify on line 158 is the integer 97 (cast as void*). This value is used to identify to ourselves which notification we are receiving when we are notified that the URL has been posted, see below. Note - that while the Netscape Plug-in Guide document shows an example of posting data using NPN_PostURL() that function does not work. You must use the NPN_PostURLNotify(), even if you do want to be notified; just pass zero as the last parameter if you don't care about notification. This last example, from NPSHELL, shows the NPP_URLNotify function called by Netscape when a NPN_PostURLNotify or NPN_GetURLNotify has completed.
The "tracking" information you supplied to the get or post function (example: 97 from the previous figure) is passed as the parameter notifyData to the function so the function can tell which execution it is being notified about. Conclusion The Plug-in API allows Netscape to be extended to handle all sorts of special data and special situations. The plug-in API is also more powerful than helper applications because the plug-in can communicate back to the web server and the network using Netscape provided functions. Plug-ins can work with CGI programs on the web server to provide very flexible and powerful services to the client. The WarpDoctor plug-in, while intended to solve WarpDoctor's site problems, was also designed to be generic enough to be used by anyone having similar needs - the need to upload files from the client to the server and to invoke a program when the upload is complete. With a basic knowledge of how web servers and browsers communicate, and armed with the plug-in documentation and SDK, it is fairly simple to build a plug-in, especially on the OS/2. Next month we will get more OS/2 specific because we will be covering Rexx integration into the plug-in. We show how to download Rexx code from the server and execute it on the client, both plain old command-line Rexx and GUI Rexx packages built with DrDialog. In addition we show an interface between Rexx and the plug-in that allows the Rexx program to execute most of the functions in the plug-in, meaning that you can use Rexx with the WarpDoctor sample plug-in to do most of the stuff you would normally have to write your own plug-in in C to do. Stay tuned.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|