Advanced Programming Techniques

The techniques described in this section help you to expand the capabilities of your Application Dispatcher applications. It is a good idea to review the basic programming techniques that are described in The Program Component before continuing with this section.

Data Passing and Program Chaining

Overview of Data Passing and Program Chaining

Only the simplest Application 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 ensure 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
  • on the client, in hidden form fields
  • on the client, in cookies or Web page scripts
  • on the server.
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:
  1. The first HTML form calls the first program.
  2. The first program performs some type of setup to initialize the user.
  3. At the end of the first program, the second HTML page is created by writing to _WEBOUT.
  4. When the HTML form on the second page is written out, you dynamically generate a series of HTML fields by using the TYPE="hidden" attribute.
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 can have multiple second programs. The logic in the first program can decide which second program the current user should run.
The following sections contain examples of programs that can be chained.

First HTML Form

<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.

First Program (PGM1.SAS)

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 hardcoded. 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.

Second Program (PGM2.SAS)

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:
  • simple to do
  • easy to debug
  • state is maintained indefinitely
  • works seamlessly across multiple Application Servers.
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 is 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.

Modified Version of First Program (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 might 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 might 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
  • it is easier if there is a large volume of data needs to be passed
  • it is nearly invisible to the user
  • it is a significant security improvement
  • it reduces duplication of program code.
You might 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.

Embedded Graphics

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:
<IMG SRC="mykids.gif">
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 Web browser loads the image:
<IMG SRC="/cgi-bin/broker?_service=default&_program=sample.webgraph.sas">
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 Application Dispatcher and a separate program execution. It is important to keep that in mind when you design your application. You might want to show only one or just a few graphics on each page. That reduces the demand on your Application Server.
Sometimes, you might 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 Application 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 Web browser to display a new picture.
Note: Unfortunately, there is a serious defect in some Web browsers that prevents the new graphic image from being displayed. This is not a defect in SAS/IntrNet software. Web browsers that exhibit this defect load the URL, which causes the Application 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 might be seen. Click REFRESH or RELOAD in the Web browser to display the new image. 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 might not need this caption. The best solution is to trick the Web 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 Web browser never has 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 Web browser into not using its cache.
Note: On some systems, it might 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.

Web Browser Referral by Using the Location Header

Another advanced programming technique is to have the output from one Application Dispatcher program invoke another Application 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 Web browser recognizes. This header re-directs the Web browser to another Web page. The only parameter to this header line is the URL that the Web browser should load. In your first program, you can dynamically construct this URL and refer the Web browser to another Application 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 Web browser referrals by using the <META> tag in the generated HTML page.

Creating Various Date/Time Formats

Occasionally, you might find the need to create various specialized date/time formats as part of your Application Dispatcher output. These formats might 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 enables 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.