NaviServer Programmable Web Server

adp-overview(n)

NaviServer Manual – 5.1.0


[ Main Table Of Contents | Table Of Contents | Keyword Index ]

Name

adp-overview - NaviServer ADP Development

Table Of Contents

Description

What Are ADPs?

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.

When to Use ADPs

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:

When to Use Tcl Libraries

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.

Writing ADP Pages

Building ADPs

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

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

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

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

ADP Syntax

ADP pages are ordinary text files, typically HTML, with embedded Tcl code. NaviServer evaluates the Tcl parts on the server and sends the resulting output to the client.

The most commonly used ADP syntax is based on <% ... %> and <%= ... %> blocks.

  • <% ... %>

    The Tcl script between the tags is evaluated. The script result is not inserted into the page automatically. Use commands such as ns_adp_puts, ns_adp_append, or ns_adp_include to append output explicitly.

     <p>The current time is:
     <%
         ns_adp_puts [clock format [clock seconds]]
     %>
     </p>
    
  • <%= ... %>

    The Tcl script between the tags is evaluated and the script result is appended to the ADP output. This form is useful for inserting simple values into the page.

     <p>Hello, <%= $user_name %>.</p>
    

    The <%= ... %> form is equivalent to evaluating the expression or script and appending its result to the page output.

  • The older script-tag form can also be used:

     <script language="tcl" runat="server">
         ns_adp_puts "Hello from Tcl"
     </script>
    

    The runat="server" attribute is required. It prevents accidental execution of ordinary client-side scripts as server-side Tcl code. The language="tcl" attribute is optional for Tcl code.

The <% ... %> and <%= ... %> forms can be used inside HTML elements and attributes, which makes them convenient for templating.

 <a href="/users/<%= $user_id %>">Profile</a>

By default, each Tcl block in an ADP page is parsed as an independent Tcl script. Therefore, a Tcl command or control structure should normally be complete within one block. The singlescript option changes this behavior by combining the Tcl fragments of an ADP page into a single script, which allows Tcl constructs to span multiple ADP blocks.

 <% foreach item $items { %>
     <li><%= $item %></li>
 <% } %>

This style requires singlescript mode. Without singlescript, the opening foreach block and the closing brace are parsed as separate Tcl scripts. In singlescript mode, an error in any part of the combined script stops execution of that ADP page.

The script-tag form also supports a stream="on" attribute, which enables streaming output for the remainder of the page. Streaming behavior is described in Output Buffering and Streaming.

Registered ADP Tags

ADP pages can use registered tags in addition to ordinary Tcl script blocks. Registered tags provide a way to associate markup-like ADP elements with Tcl procedures. They are useful when recurring page fragments or application widgets should be written in a form that is convenient for page authors, while the implementation remains in Tcl code.

Tags are registered with the ns_adp_register* commands. Once registered, the tag can be used in ADP pages and is expanded by calling the associated Tcl procedure during ADP processing.

Registered tags are mostly useful for application frameworks or libraries that provide a higher-level page-authoring interface. For simple pages, ordinary ADP script blocks and Tcl procedures are often sufficient.

Including and Parsing ADPs

ADP pages can include other ADP files or parse ADP fragments dynamically. This allows common page fragments, layouts, headers, footers, or reusable components to be factored into separate files.

The ns_adp_include command evaluates another ADP file and appends its output to the current ADP output stream. Arguments can be passed to the included ADP and accessed there with ns_adp_argv.

The ns_adp_parse command parses and evaluates an ADP string. This is useful when ADP content is generated dynamically or stored outside the file system.

Included and parsed ADPs are evaluated in their own ADP call frame, similar to a Tcl procedure call. This allows arguments and local variables to be handled predictably while still participating in the surrounding ADP output stream.

Commands such as ns_adp_abort can be used to interrupt ADP processing and unwind the ADP call stack.

ADP Runtime Behavior

ADP processing normally happens in the context of an HTTP request. A URL is mapped to an ADP file below the server page root, NaviServer allocates a Tcl interpreter, evaluates the ADP, and sends the generated output as the HTTP response.

The response MIME type is normally derived from the requested ADP file and the configured MIME type mappings. It can be changed for the current ADP with ns_adp_mimetype, or by setting the content type in the ns_set obtained via ns_conn outputheaders.

Error Handling

By default, errors in ADP script blocks are logged to the server log. The current script block is interrupted, but later text or script blocks may still be processed, depending on the configured ADP options.

This behavior can help produce a partial but still valid response after a minor error, but it can also hide errors from the client. Administrators and developers should therefore monitor the server log carefully when developing or debugging ADP pages.

The default behavior can be adjusted with configuration parameters such as stricterror, displayerror, and detailerror, or at runtime with ns_adp_ctl. An errorpage can be configured to handle uncaught ADP errors with a custom ADP page.

Output Buffering and Streaming

ADP output is normally buffered while the page is processed. Buffering allows NaviServer to determine the response size before sending the response and to apply normal response handling.

The size of the ADP output buffer is controlled by the bufsize configuration parameter. The buffer is flushed when it becomes full or when ADP code explicitly flushes output.

Streaming can be enabled when output should be sent incrementally while the ADP page is still running. This can be useful for long-running pages or progress-style output, but it changes response handling because the final content length is not known in advance. Streaming can be configured by default or controlled at runtime with ns_adp_ctl.

Character Encoding

ADP source and output pass through NaviServer's character encoding handling. ADP source files are converted to Tcl's internal string representation before evaluation, and text output is converted to the connection's output encoding when sent to the client.

The default output encoding is derived from the server configuration, in particular the per-server outputcharset setting. The output encoding for an individual connection can be inspected or changed with ns_conn encoding.

Applications should use UTF-8 where possible. When non-UTF-8 source files or responses are required, make sure that the configured charsets, MIME types, and response headers are consistent.

Debugging ADPs with TclPro

ADP debugging support can be enabled for development installations. When enabled, NaviServer can invoke the configured ADP debug initialization procedure when debugging is requested.

For details, see /ns_adp_debug.

Configuring ADP Processing

ADP processing is configured per virtual server in the ns/server/$server/adp section. The map parameter registers URL patterns that are handled as ADP pages.

 ns_section ns/server/$server/adp {
     ns_param map         /*.adp
     ns_param enabledebug false
 
     # Optional cache and buffer tuning.
     # ns_param cache     true
     # ns_param cachesize 10MB
     # ns_param bufsize   5MB
 
     # Optional common processing and error handling hooks.
     # ns_param startpage /path/to/startpage.adp
     # ns_param errorpage /path/to/errorpage.adp
 }

For static configuration, each map entry registers the ADP handler for GET, HEAD, and POST requests on the specified URL pattern. Applications that need more control, such as method-specific registration, expiration times, constraints, or per-registration ADP options, can register ADP handlers explicitly with ns_register_adp during server initialization.

The startpage and errorpage parameters provide hooks for common processing. A startpage is run before the requested ADP page and can perform common initialization before including the requested page. An errorpage is used when uncaught ADP code raises an error.

 ns_section ns/server/$server/adp {
     ns_param startpage /path/to/startpage.adp
     ns_param errorpage /path/to/errorpage.adp
 }

Several parameters control ADP memory use and execution behavior. For example, cachesize controls the per-server ADP page cache, bufsize controls the ADP output buffer, and tracesize controls how much trace information is written when ADP tracing is enabled.

Other parameters set default ADP options such as caching, streaming, debugging, safe evaluation, error handling, tracing, and whitespace trimming. These defaults can be customized per URL with the -options flag of ns_register_adp, or at run time for an individual page with ns_adp_ctl.

Configuration Parameters

The following parameters are accepted in the ns/server/$server/adp section.

Parameter name: autoabort

Failure to flush a buffer generates an ADP exception

  • Type: boolean

  • Default: true

Parameter name: bufsize

Size of ADP buffer

  • Type: size

  • Default: 1MB

Parameter name: cache

Enable ADP caching

  • Type: boolean

  • Default: false

Parameter name: cachesize

Size of ADP cache

  • Type: size

  • Default: 5MB

Parameter name: debuginit

Procedure to execute on ADP debug initialization

  • Type: proc

  • Default: ns_adp_debuginit

Parameter name: defaultextension

Optional file extension appended to unresolved ADP page filenames; useful when requests omit the ADP extension, for example resolving a request for page to page.adp

  • Type: string

  • Default:

Parameter name: detailerror

Include connection info in error backtrace

  • Type: boolean

  • Default: true

Parameter name: displayerror

Include error message in output

  • Type: boolean

  • Default: false

Parameter name: enabledebug

URL pattern registered for ADP processing; each map entry registers the ADP handler for GET, HEAD, and POST requests on the specified pattern

  • Type: boolean

  • Default: false

Parameter name: enableexpire

Set Expires: now on all ADPs

  • Type: boolean

  • Default: false

Parameter name: errorpage

Page for returning errors

  • Type: path

  • Default:

Parameter name: map

URL pattern registered for ADP processing; each entry maps the pattern for GET, HEAD, and POST requests

  • Type: list

Parameter name: safeeval

Disable inline scripts

  • Type: boolean

  • Default: false

Parameter name: singlescript

Collapse Tcl blocks to a single Tcl script; in singlescript mode, an error in any part of the combined script stops execution of that ADP page

  • Type: boolean

  • Default: false

Parameter name: startpage

File to run for every ADP request

  • Type: path

  • Default:

Parameter name: stream

Enable ADP streaming

  • Type: boolean

  • Default: false

Parameter name: stricterror

Interrupt execution on any error

  • Type: boolean

  • Default: false

Parameter name: trace

Trace execution of ADP scripts

  • Type: boolean

  • Default: false

Parameter name: tracesize

Maximum number of entries in ADP trace

  • Type: integer

  • Default: 40

Parameter name: trimspace

Trim whitespace from output buffer

  • Type: boolean

  • Default: false

Example ADPs

This section contains the following ADP examples:

Example 1: Return partial HTML page conditionally

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"
 }
 %>

Example 2: Return full HTML page conditionally

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.

Example 3: Return information from the database

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>

Example 4: Get form information and insert into the database

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>

Example 5: ADP sampler with includes, recursion, and streaming

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 get -nocase [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.

See Also

ns_adp, ns_adp_argv, ns_adp_include, ns_adp_puts, ns_adp_register, ns_sleep

Keywords

ADP, TclPro Debugger, configuration, templates