adp-overview - NaviServer ADP Development
Probably the easiest way to make NaviServer output dynamic content is to embed a Tcl script in an HTML page with NaviServer Dynamic Pages (ADPs). ADPs are HTML pages that are parsed and run on the server when the page is accessed. ADPs contain HTML tags and Tcl scripts that are embedded within the HTML tags. The embedded Tcl scripts can call other Tcl scripts that reside in separate files, allowing you to reuse Tcl code.
ADPs are ideal in situations where you want to generate all or part of a specific page dynamically. You can reuse code by storing Tcl scripts in Tcl libraries and invoking them from within multiple ADPs. You can also include files and parse other ADPs from within your ADPs.
Here are some examples of applications you can use ADPs for:
Returning HTML conditionally
Retrieving information from a database to use in a page
Inserting information from a form into a database
The alternative to embedding Tcl scripts in HTML pages using ADPs, is to store Tcl scripts in Tcl libraries and register them to handle specific URLs or URL hierarchies. There are some situations, such as those listed below, that are better suited to the Tcl libraries approach.
Inheritance: If you want one Tcl script to handle a URL and all of its sub-URLs, it is better to store the script in a Tcl library and register it using ns_register_proc to handle a URL hierarchy. For example, you may want to manage a server domain name change by redirecting every response to the corresponding domain name on another server.
Special Extensions: If you want one Tcl script to handle all files with a specific extension, like /*.csv, you would register the script with ns_register_proc to handle those files.
Scheduled Procedures: If you want a Tcl script to be run at specific intervals, you can use the ns_schedule_* functions to run a script from the Tcl library at scheduled intervals. These procedures do not normally involve returning HTML pages and so are not well suited to ADPs.
Filters: If you want a Tcl script to be called at pre-authorization, post-authorization, or trace time for a group of URLs, you would register a filter using the ns_register_filter function.
Reusing Tcl Scripts: If there are Tcl scripts that you want to use in multiple situations, you can store them in a Tcl library and invoke them from within any ADP or Tcl script.
Since you will be creating HTML pages that contain Tcl scripts, you will need to specify which pages the server will need to parse for Tcl commands and process.
Use the Map configuration parameter to determine which files are to be processed. For example, you can specify that all files with the .adp extension are to be processed by the server. Or, you can specify that all files that reside in a certain directory (for example, /usr/pages/adp) will be processed.
The following table describes all the parameters that can be set within the ADP section of the configuration file:
Section ns/server/$server/adp:
Cache - If set to on, ADP caching is enabled, Default: on
DebugInit - The procedure to run when debugging begins, Default: ns_adp_debuginit
EnableExpire - If set to on, the "Expires: now" header is set on all outgoing ADPs, Default: off
EnableCompress - If set to on, extraneous spaces within an HTML page are removed, Default: off
EnableDebug - If set to on, appending "?debug" to a URL will enable TclPro debugging, Default: off
Map - The Map parameter specifies which pages the server will parse. You can specify a file extension (such as /*.adp) or a directory (such as /usr/pages/adp). If no directory is specified, the pages directory is assumed. The wildcards * ? and can be included in the Map specification. You can specify multiple Map settings.
The following example specifies that all files in the pages directory with the extensions .adp or .asp will be parsed, and all files in the /usr/pages/myadps directory will be parsed.
Map "/*.adp" Map "/*.asp" Map "/usr/pages/myadps"
StartPage - The file to be run on every connection instead of the requested ADP. It can be used to perform routine initialization. It would then usually include the requested ADP by calling:
ns_adp_include [ns_adp_argv 0]
Create an HTML page. Use an HTML editor or a file editor to create an HTML page. Be sure to either name the file with the correct extension or save it in the correct directory on your server as specified by the Map parameter setting.
If you plan to use the Tcl script to return part or all of the page's content, just omit that part of the page, but you can create all of the surrounding HTML.
Add your Tcl scripts with a file editor. Insert your Tcl scripts in the HTML file where you want them to be processed. Be sure to enclose each Tcl script using one of the <SCRIPT> or <% ...%> syntaxes . Save the HTML file.
View the HTML page in a browser. Visit the page you have created in a browser to see if the Tcl scripts work correctly. If you have set the EnableDebug parameter, you can append "?debug" to the URL to enable TclPro debugging.
Continue editing and viewing until it works correctly. Continue editing the page in a file editor, saving it, and refreshing it in a browser until it works the way you want it to.
To debug your ADPs with TclPro, follow these steps:
Download and install TclPro from http://www.scriptics.com/tclpro/. Temporary licenses are available.
Copy the prodebug.tcl file from the TclPro distribution to the modules/tcl directory.
Set the EnableDebug parameter in the ns/server/$server/adp section of the configuration file to On.
Run TclPro and start a "remote project", which puts TclPro into a mode waiting for NaviServer to connect.
Open an ADP file with the ?debug=<pattern> query string in addition to any other query data you may send. NaviServer will then trap on ADP files matching the pattern. For example, you can just use debug=*.adp to trap all files, or you can use debug=someinclude.inc file to trap in an included file.
To set a breakpoint in a procedure you'll have to "instrument" the procedure either after the debugger traps the first time or with the "dproc=<pattern>" query argument.
There are three different syntaxes you can use to embed a Tcl script into an HTML page. Not all syntaxes are available with all ADP parsers. You must be using the appropriate ADP parser to process a specific syntax.
Insert Tcl commands between any of the following markup variants:
<script language="tcl" runat="server" stream="on"> ... </script>
The contents of the script are interpreted using the Tcl interpreter. The result is not inserted into the page, however. You can use the ns_adp_puts Tcl function to put content into the page.
The language="tcl" attribute is optional. Future enhancements to ADPs will include the capability to embed scripts in other scripting languages, and you will be able to configure the default language. If the language attribute is set to anything except tcl, the script will not be processed, and a warning will be written to the log file.
The runat="server" attribute is required. If this attribute is missing, the script will not be processed. The runat="server" attribute is necessary so that client-side scripts are not executed accidentally.
The stream="on" attribute is optional. If it is included, all output for the rest of the page is streamed out to the browser as soon as it is ready. Streaming is useful when your script may take a long time to complete (such as a complex database query). Content is output to the page gradually (incrementally) as the script is running (this is also called "streaming HTML"). One disadvantage of streaming HTML is that the server cannot return a Content-length header with the response, such that persistent connections are not possible.
<% ... %>
This syntax is evaluated exactly the same as the first syntax above, except that you cannot specify any of the attributes. The language="tcl" and runat="server" attributes are implied. There is no way to specify HTML streaming for this syntax, but when HTML streaming was activated in a previous script, it will be honored. This syntax can also be used inside HTML tags.
<%= ... %>
The Tcl commands within these tags are evaluated as the argument to an ns_adp_puts command, which inserts the results into the page. This syntax can also be used inside HTML tags.
You can define your own ADP tags with the ns_adp_register* Tcl functions.
This section contains the following ADP examples:
Example 1: Return partial HTML page conditionally
Example 2: Return full HTML page conditionally
Example 3: Return information from the database
Example 4: Get form information and insert into the database
Example 5: ADP sampler with includes, recursion, and streaming
This ADP example tests for various browsers and returns a different message in each case.
<% ns_adp_puts "Hello" set ua [ns_set get [ns_conn headers] "user-agent"] ns_adp_puts "$ua " if [string match "*MSIE*" $ua] { ns_adp_puts "This is MS Internet Explorer" } elseif [string match "*Mozilla*" $ua] { ns_adp_puts "This is Netscape or a Netscape compatible browser" } else { ns_adp_puts "Couldn't determine the browser" } %>
This example consists of a form, cookbook.html, that asks the user whether they want to view a page with or without frames, and an ADP, cookbook.adp, that determines the response and displays the appropriate page, either the page with frames or the page without frames.
This is the cookbook.html file containing the form:
<html> <head><title>The ABC's of Fruit Cookbook</title> </head> <body bggolor="#ffffff"> <h1>The ABC's of Fruit Cookbook</h1> <p> How would you like to view this cookbook? <form action="cookbook.adp" method="POST"> <input type="radio" name="question" value="yes" CHECKED>With Frames<BR> <input type="radio" name="question" value="no">Without Frames <p> <input type="submit" value="View Cookbook"> </form> <p> </body> </html>
This is the ADP, cookbook.adp, that determines the response and displays the appropriate page:
<html> <head><title>The ABC's of Fruit Cookbook</title></head> <body bggolor="#ffffff"> <% # Get form data and assign to variables set r [ns_conn form] set question [ns_set get $r question] # Display cookbook in appropriate format if {$question eq "yes"} { ns_adp_include cookset.html } else { ns_adp_include cook.html } %> </body> </html>
The cookset.html file contains a frame set for the cookbook. The cook.html file contains the cookbook without frames.
This example retrieves information from the database -- a list of tables -- and returns it as the options in a select box. When the user chooses a table from the list, another ADP is run as the POST for the form which retrieves information from the database on the chosen table.
The first ADP, db.adp, creates a form with a select box with the list of database tables:
<html> <head><title>DB Example</title></head> <body> <h1>DB Example</h1> <p>Select a db table from the default db pool: <form method="POST" action="db2.adp"> <select name="Table"> <% set db [ns_db gethandle] set sql "select * from tables" set row [ns_db select $db $sql] while {[ns_db getrow $db $row]} { set table [ns_set get $row name] ns_adp_puts "<option value=\"$table\">$table" } %> </select> <input type="submit" value="Show Data"> </form> </body> </html>
The second ADP, db2.adp, is used as the POST from the first ADP:
<html> <head><title>DB Example page 2</title></head> <body> <h1>DB Example page 2</h1> <% set table [ns_set get [ns_conn form] Table] set db [ns_db gethandle] %> Contents of <%= $table %>: <table border="1"> <% set row [ns_db select $db "select * from $table"] set size [ns_set size $row] while {[ns_db getrow $db $row]} { ns_adp_puts "<tr>" for {set i 0} {$i < $size} {incr i} { ns_adp_puts "<td>[ns_set value $row $i]</td>" } ns_adp_puts "</tr>" } %> </table> </body> </html>
This is another database example, but one where the user types information into a form, and the submit runs an ADP that enters the information into the database. Then it sends an email message to both the db administrator and the user that the record was updated. The survey.html file contains the form and calls the survey.adp file as the POST action.
Here is the survey.html file, which consists of a simple form and a submit button which calls an ADP:
<html> <head><title>Survey Form</title> </head> <body bggolor="#ffffff"> <h2>Online Newsletter Subscription</h2> <p> <i>Sign up to be notified when this web site changes, or to receive an ASCII version via email. Thanks!</i> <form action="survey.adp" method="POST"> <b>Name</b> <input type="text" name="name" size="40"> <p><b>Title</b><input type="text" name="title" size="40" maxlength="80"> <p><input type="checkbox" name="notify" value="1">Notify me by email when this newsletter changes online <p><input type="checkbox" name="sendemail" value="1">Send me an ASCII version of this newsletter by email <p><b>Email Address</b><input type="text" name="emailaddr" size="40" maxlength="60"> <p><input type="submit"> </form> </body> </html>
Here is the survey.adp file, which gets the form data from the survey, inserts it into the database, sends email to the subscription administrator and the user, and displays a confirmation message:
<html> <head><title>Subscription Processed Successfully</title> </head> <body bggolor="#ffffff"> <h2>Online Newsletter Subscription</h2> <p> Thank You for subscribing to our newsletter! <% # Get form data and assign to variables set r [ns_conn form] set name [ns_set get $r name] set title [ns_set get $r title] set notify [ns_set get $r notify] set sendemail [ns_set get $r sendemail] set emailaddr [ns_set get $r emailaddr] # Set subscription options explicitly to 0 if not checked if {$notify ne 1} {set notify 0} if {$sendemail ne 1} {set sendemail 0} # Update database with new subscription set db [ns_db gethandle] ns_db dml $db "insert into test values ([ns_dbquotevalue $name], [ns_dbquotevalue $title], $notify, $sendemail, [ns_dbquotevalue $emailaddr])" # Send email message to subscription administrator set body "A new newsletter subscription was added for " append body $name append body ". The database has been updated." ns_sendmail "subscript@thecompany.com" "dbadmin@thecompany.com" "New Subscription" $body # Send email message to user set body "Your online newsletter subscription has been successfully processed." ns_sendmail $emailaddr "dbadmin@thecompany.com" "Your Online Subscription" $body # Show type of subscription to user if {$notify eq 1} { ns_adp_puts "You will be notified via email when the online newsletter changes." } if {$sendemail eq 1} { ns_adp_puts "Future issues of the newsletter will be sent to you via email." } %> </body> </html>
The following HTML is an example of a page containing several Tcl scripts using the various ADP syntaxes. It invokes some Tcl functions, includes a file, executes another ADP, and uses streaming.
<html> <head><title>This is a test of ADP</title> </head> <body> <h1>This is a test of ADP</h1> <% ## Proxies should cache this page for a maximum of 1 hour: ns_setexpires 3600 set host [ns_set iget [ns_conn headers] host] ## How many times has this page been accessed ## since the server was started? set count [nsv_incr . count 1] %> Number of accesses since server start: <%= $count %><br> tcl_version: <%= $::tcl_version %><br> tcl_library: <%= $::tcl_library %><br> Host: <%= $host %><br> <!-- Include the contents of a file: --> <% ns_adp_include standard-header %> <script language="tcl" runat="server"> ## You can do recursive ADP processing as well: ns_adp_include time.adp </script> <p>Here's an example of streaming: <script language="tcl" stream="on" runat="server"> ns_adp_puts "<br>1...<br>" ns_sleep 2s ns_adp_puts "2...<br>" ns_sleep 2s ns_adp_puts "3!<br>" </script> <p> <b>End</b> </body> </html>
The standard-header file referenced in the above example looks like this:
This is a standard header.
The time.adp file referenced in the example looks like this:
The time is: <%=[ns_httptime [ns_time]]%>
Because of the streaming used in the last script, the output "1...", "2...", "3!" and "End" will be displayed incrementally in the page.
ns_adp, ns_adp_argv, ns_adp_eval, ns_adp_include, ns_adp_puts, ns_adp_register, ns_sleep