|SAS/IntrNet 9.1: Application Dispatcher|
The techniques described in this section will help you to expand the capabilities of your Dispatcher applications. It is a good idea to review the basic programming techniques that are described in The Four Types of Programs before continuing with this section.
Only the simplest Dispatcher applications contain a single page. With the addition of a second and subsequent pages, you face the problem of passing information from one page to another. It is also typical to have an application that contains more than a single program. This means that you must find a way to connect the programs that compose your application and make sure that all the data collected along the way is available in the appropriate places.
It is good programming practice to design applications so that they do not request the same information multiple times. Because HTTP is a stateless environment, each program request is separate from all other requests. If users enter a phone number on the first page of an application and submit the form, that phone number is available only to the first program. But after that program completes, the state of the data values passed is lost. If the third program in the application needs to know the specified phone number, the application must ask for the phone number again or retrieve the data from a stored location. There are several ways to solve this problem. You can store data values
Storing data on the client, in hidden fields, is the simplest technique. To do this, you must dynamically generate all of the HTML pages in your application except for the first HTML page. Because each HTML page functions as a mechanism for transporting data values from the previous program to the next program, it cannot be static HTML stored in a file.
Usually, the process involves the following steps:
Each hidden field in the second form can contain one name/value data pair passed from the first form. You should use unique names for all of the data values in the entire application. In this way you can pass all of the application data throughout the entire application.
At the same time that you dynamically generate the second form, you can write out the name of the second program in the hidden field _PROGRAM. Because the first program contains the logic to determine the second program, this is referred to as program chaining. Your application may have multiple second programs. The logic in the first program can decide which second program the current user should run.
Here is an example.
<FORM ACTION="/cgi-bin/broker"> Please enter your first name: <INPUT TYPE="text" NAME="fname"><BR> <INPUT TYPE="hidden" NAME="_service" VALUE="default"> <INPUT TYPE="hidden" NAME="_program" VALUE="mylib.pgm1.sas"> <INPUT TYPE="submit" VALUE="Run Program"> </FORM>
This form passes the first name of the user as the variable FNAME to the program named PGM1.SAS in the program library MYLIB.
data _null_; file _webout; put 'Content-type: text/html'; put; put '<HTML>'; /*create reference to the broker from special automatic macro variable _url*/ url=symget('_url'); put '<FORM ACTION="' url +(-1) '">'; /*supply service name*/ service=symget('_service'); put '<INPUT TYPE="hidden" NAME="_service" VALUE="' service +(-1) '">'; /*use current program library so that this application can be easily moved to another library*/ pgmlib=symget('_pgmlib'); program=compress(pgmlib)||'.pgm2.sas'; put '<INPUT TYPE="hidden" NAME="_program" VALUE="' program +(-1) '">'; /*pass first name value on to next program*/ fname=symget('fname'); put '<INPUT TYPE="hidden" NAME="fname" VALUE="' fname +(-1) '">'; put 'What is your favorite color?'; put '<SELECT SIZE=1 NAME="fcolor">'; put '<OPTION VALUE="red">red'; put '<OPTION VALUE="green">green'; put '<OPTION VALUE="blue">blue'; put '<OPTION VALUE="other">other'; put '</SELECT><BR>'; put '<INPUT TYPE="submit" VALUE="Run Program">'; put '</FORM>'; put '</HTML>'; run;
This program uses the special variables _URL, _SERVICE, and _PGMLIB to maintain program portability. The second program name PGM2.SAS is hard-coded. The important section of this program is where the variable FNAME is received by calling the SYMGET function and written out as a hidden form variable. This is the key step that enables the data value to "live" beyond the stateless execution of this first program. In addition to inserting the hidden data value, this program generates a selection list that asks the user to enter a favorite color.
data _null_; file _webout; put 'Content-type: text/html'; put; put '<HTML>'; /*extract first name and favorite color and print them out*/ fname=symget('fname'); fcolor=symget('fcolor'); put 'Your first name is <b>' fname '</b>'; put '<BR>'; put 'Your favorite color is <b>' fcolor '</b>'; put '<BR>'; put '</HTML>'; run;
The second program prints the value of the variables from both the first and second form, illustrating that the data has been correctly passed throughout the entire application. The technique of passing data by using hidden fields has these advantages:
The major disadvantage of this technique is that it is easy for a user to change the values in the form and submit incorrect or falsified information to the application. Another technique that is nearly equivalent to using hidden fields is to pass name/value pair data as part of the query string in a hyperlink. In the Second Program, (PGM2.SAS), suppose the initial forms were the same, but you want the second page to contain a list of hyperlinks instead of a selection list. In this case, you would generate a list of hyperlinks by using the anchor tag, and the HTML source would look like this:
<A HREF="path to Application Broker plus data">Red</a> <A HREF="path to Application Broker plus data">Green</a> <A HREF="path to Application Broker plus data">Blue</a> <A HREF="path to Application Broker plus data">Other</a>
Using this example, the path to Application Broker plus data would consist of the URL for the Application Broker, which is stored in the special variable _URL followed by all of the name/value pair data that should be passed from the first program to the second program. This data includes the required fields _SERVICE and _PROGRAM. At least one additional parameter is added to each hyperlink that will be used to indicate which link is chosen. In this case, that additional field is COLOR. To use hyperlinks instead of an HTML form that has a select list, the first program must change to PGM1.SAS.
data _null_; file _webout; put 'Content-type: text/html'; put; put '<HTML>'; /*store broker path in data step variable*/ url=symget('_url'); /*store current service name*/ service=symget('_service'); /*use current program library so that this application can be easily moved to another library*/ pgmlib=symget('_pgmlib'); program=compress(pgmlib)||'.pgm2.sas'; /*pass first name value on to next program*/ fname=symget('fname'); /*build partial URL, color will be added later*/ href=trim(left(url))||'?_service='||trim(left(service))|| '&_program='||trim(left(program))|| '&fname='||urlencode(trim(left(fname))); put '<HTML>'; put 'What is your favorite color?<br>'; put '<A HREF="' href +(-1) '&color=red">red</a><br>'; put '<A HREF="' href +(-1) '&color=green">green</a><br>'; put '<A HREF="' href +(-1) '&color=blue">blue</a><br>'; put '<A HREF="' href +(-1) '&color=other">other</a><br>'; put '</HTML>'; run;
This modified version uses a special function named URLENCODE in this program. The purpose of this function is to encode any special characters that may be contained in the query string. Because the values for _PROGRAM and _SERVICE do not contain any special characters, it is not necessary to encode them.
However, the value that the user supplies for a first name may contain some special characters. For the First Program, (PGM1.SAS) to pass this value safely to the second program, (PGM2.SAS) it should be URL-encoded. It does not harm a value to URL-encode it even when it does not contain special characters. The URLENCODE function was not used because if data is passed through an HTML form, then the Web browser would perform the encoding for you. Aside from the need to URL-encode data and the different HTML syntax, the use of a hyperlink and hidden form fields are essentially the same.
Two alternatives to passing the data throughout every form in the application are: storing the data in a Web browser cookie or storing data within the Application Server environment. Both of these techniques are more difficult than using hidden form fields, but they have different advantages.
HTTP cookies are packets of information that are stored in the client Web browser. They are shuttled back and forth with the CGI requests. In this general sense, they are quite similar to hidden form fields. Cookies have the advantage of being nearly invisible to the user. They contain a built-in expiration mechanism, and they are slightly more secure than hidden fields. They also work seamlessly across multiple Application Servers.
Storing data within the Application Server environment is a tempting way to solve the problem of passing data. You can create a data set and assign each user a unique key variable. All the data collected for that user can be stored in the data set within the Application Server environment. This is a much better mechanism for applications that have a large volume of data to be collected and passed from program-to-program. The key variable still needs to be passed along by the Web browser, and that can be done by using cookies or a hidden form field. If the key is unique and sufficiently difficult to guess, then this data passing mechanism also has an added level of security. The advantages to this technique are
You may experience contention problems if your service contains multiple servers and all servers try to update the same data set that contains the user data. Using a SAS/SHARE data server to read and write to the data set can overcome this problem.
The typical Web page contains embedded graphic images. This is easy to do in static pages. To your static HTML, add an IMG tag, for example:
In a dynamic environment such as the Application Dispatcher, the value for the SRC parameter must be a URL that invokes the Application Broker. For example, inserting the following HTML code in a static HTML page causes the sample graphics program to be run when the browser loads the image:
Programs called from an IMG tag must respond with a content type of image/gif or image/jpeg. You can add multiple parameters to this type of URL to make your graphics program output more flexible and customizable. Each dynamic image tag in your HTML page represents a separate request to the Dispatcher and a separate program execution. It is important to keep that in mind when you design your application. You may want to show only one or just a few graphics on each page. That will reduce the demand on your Application Server.
Sometimes, you may be generating a dynamic HTML page, and you want that dynamic page to contain one or more embedded graphics that are also dynamic. One way to accomplish this is to use the Output Delivery System (ODS). Using ODS is convenient, because it lets you run procedures that produce HTML and graphics within a single Dispatcher program.
If you choose not to use ODS, you must have separate programs for producing HTML and graphics. The first program that is called produces the HTML page by writing to _WEBOUT. At the appropriate place in the HTML source code, the first program writes an image tag that calls the second program -- the program that produces the graphics. By using the concepts outlined in Data Passing and Program Chaining, the first program passes any name/value pair data to the second program. Because the image tag is not an HTML form, you cannot use hidden fields to pass the name/value pairs. You must encode them in the query string of the URL that you generate for the SRC parameter.
Most of the time your embedded graphics represent some underlying data. If that underlying data changes, you expect the browser to display a new picture.
Note: Unfortunately, there is a serious defect in some browsers that prevents the new graphic image from being displayed. This is not a defect in SAS/IntrNet software. Browsers exhibiting this defect will load the URL, that causes the Dispatcher program to execute and deliver a new image; but the old, cached image is displayed. When the user arrives at the page by selecting a hyperlink, a favorite, or a bookmark, or submits a form, an old image may be seen. By clicking REFRESH or RELOAD in the browser the new image will display. Unfortunately, sending the graphic image by using the Expires or the Pragma: no-cache HTTP headers does not fix this problem.
One solution to this problem is to add a caption to your embedded graphics that tells the user to reload the page for the most up-to-date graphic. If your data does not change too quickly, you may not need this caption. The best solution is to trick the browser into not using a cached image. You can do this by generating a unique URL every time the program is executed. Because HTML files and images are cached according to their URLs, the browser will never have an old image that matches the unique URL that the program generates.
To create a unique URL, generate the SRC= URL string that you need and append to the end of the URL a name/value pair that has a value of the current SAS datetime, as shown in this example.
data _null_; file _webout; /*store broker path in data step variable*/ url=symget('_url'); /*store current service name*/ service=symget('_service'); /*use current program library so that this application can be easily moved to another library*/ pgmlib=symget('_pgmlib'); program=compress(pgmlib)||'.graphit.sas'; /*create partial URL*/ src=trim(left(url))||'?_service='||trim(left(service))|| '&_program='||trim(left(program)); /*the above URL is enough to embed the graphic but a unique string must be added to avoid incorrect caching*/ nocache=datetime(); src=src||'&nocache='||trim(left(nocache)); /*write out image tag*/ put '<IMG SRC="' src +(-1) '">'; run;
Each time this code is executed, a different datetime value is returned. This process results in a unique URL. The NOCACHE parameter is not used by the graphics program because its only purpose is to trick the browser into not using its cache.
Note: On some systems it may be better to call one of the SAS random functions instead of datetime. If your system is fast and you make repeated, close calls to the datetime function, it is possible to get the same value returned.
Another advanced programming technique is to have the output from one Dispatcher program invoke another Dispatcher program without displaying a page from the first program. Suppose that you have a program named PGM1.SAS, and it performs some error checking of the form data that is sent as input. If your program detects an error condition, you want to run some additional code. The additional code is contained in another program file named ERROR.SAS. Instead of copying the code from ERROR.SAS into PGM1.SAS and having to maintain two pieces of identical code, you can invoke the error program via the output of PGM1.SAS. This is another form of program chaining.
The HTTP header Location is a special header that the browser recognizes. This header re-directs the browser to another Web page. The only parameter to this header line is the URL that the browser should load. In your first program, you can dynamically construct this URL and refer the browser to another Dispatcher program. The URL that you supply by using the location header should be fully qualified and can contain any additional name/value pair data that you want to send to the second program shown as shown in this example code.
data _null_; if error>0 then do; /*construct referral URL*/ file _webout; /*because URL is fully qualified get name of the Web server from automatic exported variable*/ srvname=symget('_srvname'); /*store broker path in data step variable _url is a special automatic variable*/ url=symget('_url'); /*store current service name _service is a special automatic variable*/ service=symget('_service'); /*use current program library so that this application can be easily moved to another library _pgmlib is a special automatic variable*/ pgmlib=symget('_pgmlib'); program=compress(pgmlib)||'.error.sas'; /*create fully qualified URL*/ loc='http://'||trim(left(srvname))|| trim(left(url))||'?_service='|| trim(left(service))|| '&_program='||trim(left(program)); /*put out location header instead of content-type header make sure to include null line to terminate HTTP header no content needed after location header because browser will refer to another page*/ put 'Location: ' loc; put ; end; run;
It is also possible to perform browser referrals by using the <META> tag in the generated HTML page.
Occasionally, you may find the need to create various specialized date/time formats as part of your Dispatcher output. These formats may not be compatible with the standard set of SAS date/time formats that require you to create the format yourself. The most common format is the EXPIRES format. This format is used to expire HTTP cookies and pages that are cached in the Web browser. The DATATYPE option in the FORMAT procedure allows you to create specialized date/time formats easily, such the EXPIRES format. By specifying DATATYPE= in the PICTURE statement, you can use a special set of codes to represent both numerical and textual components of the date, the time, or the DATETIME value that you are formatting. Here is an example of how you can use this feature to create DATETIME format that expires:
proc format; picture expires other='%A, %d-%b-%y %0H:%0M:%0S GMT' (DATATYPE=DATETIME); run;
The special codes in the OTHER= parameter represent locale full weekday name, day of the month, locale abbreviated month name and so on. See the documentation on the FORMAT procedure in the SAS Procedures Guide for more information. The test program below illustrates how to use the DATETIME format.
data _null_; x=datetime(); /*adjust local datetime to GMT by adding five hours*/ x=x+5*3600; put x expires33.; run;
This program produces the following output:
Tuesday, 29-SEP-98 14:39:29 GMT
You can use this formatted output to create Expires headers or to set expiration dates and times for HTTP cookies.
|SAS/IntrNet 9.1: Application Dispatcher|