This chapter describes how to write your own NSAPI plug-ins that define custom Server Application Functions (SAFs). The ability to create custom plug-ins enables you to modify or extend the Oracle iPlanet Web Server’s built-in functionality. For example, you can modify the server to handle user authorization in a special way or generate dynamic HTML pages based on information in a database.
This chapter has the following sections:
Before writing custom SAFs, you must familiarize yourself with the request-handling process, as described in detail in the Oracle iPlanet Web Server 7.0.9 Administrator’s Configuration File Reference. Also, before writing a custom SAF, check to see whether a built-in SAF already accomplishes the tasks you have in mind.
See Appendix B, Alphabetical List of NSAPI Functions and Macros for a list of the predefined Init SAFs. For information about predefined SAFs used in the obj.conf file, see the Oracle iPlanet Web Server 7.0.9 Administrator’s Configuration File Reference.
For a complete list of the NSAPI routines for implementing custom SAFs, see Chapter 6, NSAPI Function and Macro Reference.
To keep your custom plug-ins upgradable, do the following:
Make sure plug-in users know how to edit the configuration files, such as magnus.conf and obj.conf manually. The plug-in installation software should not be used to edit these configuration files.
Keep the source code so you can recompile the plug-in.
All SAFs whether custom or built-in have the same C interface regardless of the request-handling step for which they are written. SAFs are small functions intended for a specific purpose within a specific request-response step. SAFs receive parameters from the directive that invokes them in the obj.conf file, from the server, and from previous SAFs.
int function(pblock *pb, Session *sn, Request *rq);
The next section discusses the parameters in detail.
The SAF returns a result code that indicates whether and how it succeeded. The server uses the result code from each function to determine how to proceed with processing the request. For more information on the result codes, see Result Codes.
This section discusses the SAF parameters in detail.
The pb parameter is a pointer to a pblock data structure that contains values specified by the directive that invokes the SAF. A pblock data structure contains a series of name-value pairs.
For example, a directive that invokes the basic-nsca function might look like the following:
AuthTrans fn=basic-ncsa auth-type=basic dbm=users.db
In this case, the pb parameter passed to basic-ncsa contains name-value pairs that correspond to auth-type=basic and dbm=users.db.
NSAPI provides a set of functions for working with pblock data structures. For example, pblock_findval() returns the value for a given name in a pblock. For information on working with parameter blocks, see Parameter Block Manipulation Routines.
The sn parameter is a pointer to a Session data structure. This parameter contains variables related to an entire session, that is, the time between the opening and closing of the TCP/IP connection between the client and the server. The same sn pointer is passed to each SAF called within each request for an entire session. For a list of important fields, see Session Data Structure.
The rq parameter is a pointer to a Request data structure. This parameter contains variables related to the current request, such as the request headers, URI, and local file system path. The same Request pointer is passed to each SAF called in the request-response process for an HTTP request. For a list of important fields, see Request Data Structure.
Upon completion, a SAF returns a result code. The result code indicates what the server should do next.
The result codes are:
REQ_PROCEED— Indicates that the SAF achieved its objective. For some request-response steps (AuthTrans, NameTrans, Service, and Error), this code tells the server to proceed to the next request-response step, skipping any other SAFs in the current step. For the other request-response steps (Input, Output, Route, PathCheck, ObjectType, and AddLog), the server proceeds to the next SAF in the current step.
Indicates that an error occurred and an HTTP response should be sent to the client to indicate the cause of the error. A SAF returning REQ_ABORTED should also set the HTTP response status code. If the server finds an Error directive matching the status code or reason phrase, the server executes the SAF specified. If not, the server sends a default HTTP response with the status code and reason phrase, in addition to a short HTML page reflecting the status code and reason phrase for the user. The server then goes to the first AddLog directive.
Custom SAFs are functions in shared libraries that are loaded and called by the server.
The general steps to create a custom SAF are as follows:
Write the Source Code using the NSAPI functions. Each SAF is written for a specific directive.
Compile and Link the source code to create a shared library ( .so , .sl , or .dll) file.
Load and Initialize the SAF by editing the magnus.conf file to do the following actions:
Load the shared library file containing your custom SAFs
Initialize the SAF if necessary
Instruct the Server to Call the SAFs by editing obj.conf to call your custom SAFs at the appropriate time.
Test the SAF by accessing your server from a browser with a URL that triggers your function.
The following sections describe these steps in greater detail.
Write your custom SAFs using NSAPI functions. For a summary of some of the most commonly used NSAPI functions, see Overview of NSAPI C Functions and for available routines, see Chapter 6, NSAPI Function and Macro Reference.
For examples of custom SAFs, see Chapter 4, Examples of Custom SAFs and Filters.
The signature for all SAFs is as follows:
int function(pblock *pb, Session *sn, Request *rq);
For more details on the parameters, see SAF Parameters.
You must register your SAFs with the server. SAFs may be registered using the funcs parameter of the load-modules Init SAF or by a call to func_insert. A plug-in may define a nspai_module_init function that is used to call func_insert and perform any other initialization tasks. For more information, see nsapi_module_init() Function and func_insert() Function.
The server runs as a multi-threaded single process. On UNIX platforms, the server runs two processes, a parent and a child, for historical reasons. The parent process performs some initialization and forks the child process. The child process performs further initialization and handles all of the HTTP requests.
Keep the following in mind when writing your SAF:
Write thread-safe code
Blocking can affect performance
Write small functions with parameters and configure the parameters in obj.conf
Carefully check and handle all errors and log the errors so you can determine the source of problems and fix them
If necessary, write an initialization function that performs initialization tasks required by your new SAFs. The initialization function must be named nsapi_module_init and has the same signature as other SAFs:
int nsapi_module_init(pblock *pb, Session *sn, Request *rq);
SAFs should to be able to obtain certain types of information from their parameters. In most cases, parameter block (pblock) data structures provide the fundamental storage mechanism for these parameters. pblock maintains its data as a collection of name-value pairs. For a summary of the most commonly used functions for working with pblock structures, see Parameter Block Manipulation Routines.
When defining a SAF, you do not specifically state which directive it is written for. However, each SAF must be written for a specific directive, such as AuthTrans, Service, and so on. Each directive requires its SAFs to behave in particular ways, and your SAF must conform to the requirements of the directive for which it was written. For details on what each directive requires of its SAFs, see Required Behavior of SAFs for Each Directive.
Compile and link your code with the native compiler for the target platform. For UNIX, use the gmake command. For Windows, use the nmake command. For Windows, use Microsoft Visual C++ 6.0 or newer. You must have an import list that specifies all global variables and functions to access from the server binary. Use the correct compiler and linker flags for your platform. Refer to the example Makefile in the install-dir/samples/nsapi directory.
Adhere to the following guidelines for compiling and linking.
Add the install-dir/bin/https/lib (UNIX) or install-dir\bin\https\bin (Windows) library directory to your linker command.
The following table lists the library that you need to link to.Table 2–1 Libraries
ns-httpd40.dll in addition to the standard Windows libraries
All other UNIX platforms
To generate a shared library, use the commands and options listed in the following table.
ld -G or cc -G
cc +Z -b -Wl,+s -Wl,-B,symbolic
cc -p 0 -berok -blibpath:$(LD_RPATH)
Use the linker flags in the following table to specify which directories should be searched for shared objects during runtime to resolve symbols.
no flags, but the ns-httpd40.dll file must be in the system PATH variable
On UNIX, you can also set the library search path using the LD_LIBRARY_PATH environment variable, which must be set when you start the server.
The following table lists the flags and defines you need to use for compilation of your source code.
-DXP_UNIX -D_REENTRANT -KPIC -DSOLARIS
-DXP_WIN32 -DWIN32 /MD
-DXP_UNIX -D_REENTRANT -DHPUX
-DXP_UNIX -D_REENTRANT -DAIX $(DEBUG)
-DXP_UNIX -D_REENTRANT -fPIC
-o32 -exceptions -DXP_UNIX -KPIC
On the Solaris platform, the server can run in either 32–bit or 64–bit mode. Because a 32–bit shared library cannot be used in a 64–bit process and conversely, you may want to compile and link two separate shared libraries. By default, the Solaris compiler and linker produce 32–bit binaries. To compile and link your plug-in for 64–bit mode on Solaris SPARC, you must use Oracle Solaris Studio 5.0 or higher with the -xarch=v9 flag. To compile and link your plug-in for 64–bit mode on Solaris x86, you must use Oracle Java Studio 11 or higher with the -xarch=amd64 flag.
NSAPI plug-ins are typically written using the C programming language. Using the C++ programming language in an NSAPI plug-in raises special compatibility issues.
On Solaris, the server is built using the new C++ 5 ABI. If your shared library uses C++, it must be compiled with Oracle Solaris Studio 5.0 or higher. Oracle Java Studio 11 or higher is recommended. Do not use the -compat=4 option when compiling and linking a shared library that uses C++. When running in 32–bit mode on Solaris SPARC, the server provides some backward compatibility for the old C++ 4 ABI or Oracle Solaris Studio 4.2. This backward compatibility may be removed at some future date. For all new NSAPI plug-ins, use the new C++ 5 ABI or Oracle Solaris Studio 5.0 or later versions.
On Linux, Web Server is built using the gcc 3.2 C++ ABI. If your shared library uses C++, compile with gcc 3.2.x. Because of the volatility of the gcc C++ ABI, avoid using C++ in NSAPI plug-ins on Linux.
For each shared library (plug-in) containing custom SAFs to be loaded into the server, add an Init directive that invokes the load-modules SAF to magnus.conf. The load-modules SAF loads the shared library and calls the shared library's nsapi_module_init function. For more information, see nsapi_module_init() Function.
The syntax for a directive that calls load-modules is:
Init fn=load-modules [shlib=path] [funcs="SAF1,...,SAFn"] [name1="value1"]...[nameN="valueN"]
shlib is the local file system path to the shared library (plug-in).
funcs is an optional comma-separated list of function names to be loaded from the shared library. Function names are case sensitive. You may use a dash (-) in place of an underscore (_) in function names. Do not use a space in the function name list.
If the new SAFs require initialization, omit the funcs parameter and instead define an nsapi_module_init function in your shared library. Any custom parameters on the Init directive will be passed to nsapi_module_init in the pb parameter block.
nameN="valueN" are the optional names and values of parameters passed to the shared library's nsapi_module_init function in the pb parameter block.
Add directives to obj.conf to instruct the server to call each custom SAF at the appropriate time. The syntax for directives is:
Directive fn=function-name [name1="value1"]...[nameN="valueN"]
Directive is one of the server directives, such as AuthTrans, Service, and so on.
function-name is the name of the SAF to execute.
nameN="valueN" are the names and values of parameters that are passed to the SAF.
Depending on the purpose of the new SAF, you might need to add just one directive to obj.conf, or you might need to add more than one directive to provide complete instructions for invoking the new SAF.
For example, if you define a new AuthTrans or PathCheck SAF, you could just add an appropriate directive in the default object. However, if you define a new Service SAF to be invoked only when the requested resource is in a particular directory or has a new kind of file extension, you would need to take extra steps.
If your new Service SAF is to be invoked only when the requested resource has a new kind of file extension, you might need to add an entry to the MIME types file so that the type value gets set properly during the ObjectType stage. Then you could add a Service directive to the default object that specifies the desired type value.
If your new Service SAF is to be invoked only when the requested resource is in a particular directory, you might need to define a NameTrans directive that generates a name or ppath value that matches another object. Then in the new object you could invoke the new Service function.
For example, suppose your plug-in defines two new SAFs, do_small_anim and do_big_anim, which both take speed parameters. These functions run animations. All files to be treated as small animations reside in the directory D:/docs/animations/small, while all files to be treated as full-screen animations reside in the directory D:/docs/animations/fullscreen.
To ensure that the new animation functions are invoked whenever a client sends a request for either a small or full-screen animation, you would add NameTrans directives to the default object to translate the appropriate URLs to the corresponding path names and also assign a name to the request.
NameTrans fn=pfx2dir from="/animations/small" dir="D:/docs/animations/small" name="small_anim" NameTrans fn=pfx2dir from="/animations/fullscreen" dir="D:/docs/animations/fullscreen" name="fullscreen_anim"
<Object name="small_anim"> Service fn=do_small_anim speed=40 </Object> <Object name="fullscreen_anim"> Service fn=do_big_anim speed=20 </Object>
After modifying obj.conf, you need to restart the server. A restart is required for all plug-ins that implement SAFs and/or filters.
Test your SAF by accessing your server from a browser with a URL that triggers your function. For example, if your new SAF is triggered by requests to resources in http://server-name/animations/small, try requesting a valid resource that starts with that URI.
You should disable caching in your browser so that the server is sure to be accessed. In Mozilla Firefox, hold the shift key while clicking the Reload button to ensure that the cache is not used.
Examine the access log and error log to help with debugging.
NSAPI provides a set of C functions that are used to implement SAFs. These functions serve several purposes:
Provide platform independence across operating system and hardware platforms
Thread-safe, a requirement for SAFs
Prevent memory leaks
Provide functionality necessary for implementing SAFs
Always use these NSAPI routines when defining new SAFs.
This section provides an overview of the function categories available and some of the more commonly used routines. All of the public routines are detailed in Chapter 6, NSAPI Function and Macro Reference.
The main categories of NSAPI functions are:
Parameter Block Manipulation Routines
Protocol Utilities for Service SAFs
The parameter block manipulation functions provide routines for locating, adding, and removing entries in a pblock data structure:
pblock_findval returns the value for a given name in a pblock. For more information, see pblock_findval() Function.
pblock_nvinsert adds a new name-value pair entry to a pblock. For more information, see pblock_nvinsert() Function.
pblock_remove removes a pblock entry by name from a pblock. The entry is not disposed. Use param_free() Function to free the memory used by the entry. For more information, see pblock_remove() Function.
param_free frees the memory for the given pblock entry. For more information, see param_free() Function.
pblock_pblock2str creates a new string containing all of the name-value pairs from a pblock in the form “name=value name=value.” This function can be a useful for debugging. For more information, see pblock_pblock2str() Function.
Protocol utilities provide functionality necessary to implement Service SAFs:
protocol_status sets the HTTP response status code and reason phrase. For more information, see protocol_status() Function.
protocol_start_response sends the HTTP response and all HTTP headers to the browser. For more information, see protocol_start_response() Function.
Memory management routines provide fast, platform-independent versions of the standard memory management routines. These routines also prevent memory leaks by allocating from a temporary memory, called “pooled” memory for each request, and then disposing the entire pool after each request. Wrappers enable standard memory routines to use permanent memory. To disable the server's pooled memory allocator for debugging,use the built-in SAF pool-init. For more information, see the Oracle iPlanet Web Server 7.0.9 Administrator’s Configuration File Reference.
The file I/O functions provide platform-independent, thread-safe file I/O routines.
system_fopenRO opens a file for read-only access. For more information, see system_fopenRO() Function.
system_fopenRW opens a file for read-write access, creating the file if necessary. For more information, see system_fopenRW() Function.
system_fopenWA opens a file for write-append access, creating the file if necessary. For more information, see system_fopenWA() Function.
system_fclose closes a file. For more information, see system_fclose() Function.
system_fread reads from a file. For more information, see system_fread() Function.
system_fwrite writes to a file. For more information, see system_fwrite() Function.
system_fwrite_atomic locks the given file before writing to it. This locking avoids interference between simultaneous writes by multiple processes or threads. For more information, see system_fwrite_atomic() Function.
Network I/O functions provide platform-independent, thread-safe network I/O routines. These routines work with SSL when it is enabled.
netbuf_grab reads from a network buffer’s socket into the network buffer. For more information, see netbuf_grab() Function.
netbuf_getbytes gets a character from a network buffer. For more information, see netbuf_getbytes() Function.
net_flush flushes buffered data. For more information, see net_flush() Function.
net_read reads bytes from a specified socket into a specified buffer. For more information, see net_read() Function.
net_sendfile sends the contents of a specified file to a specified a socket. For more information, see net_sendfile() Function.
net_write writes to the network socket. For more information, see net_write() Function.
Thread functions include functions for creating your own threads that are compatible with the server’s threads. Routines also exist for critical sections and condition variables.
systhread_start creates a new thread. For more information, see systhread_start() Function.
systhread_sleep puts a thread to sleep for a given time. For more information, see systhread_sleep() Function.
crit_init creates a new critical section variable. For more information, see crit_init() Function.
crit_enter gains ownership of a critical section. For more information, see crit_enter() Function.
crit_exit surrenders ownership of a critical section. For more information, see crit_exit() Function.
crit_terminate disposes of a critical section variable. For more information, see crit_terminate() Function.
condvar_init creates a new condition variable. For more information, see condvar_init() Function.
condvar_notify awakens any threads blocked on a condition variable. For more information, see condvar_notify() Function.
condvar_wait blocks on a condition variable. For more information, see condvar_wait() Function.
condvar_terminate disposes of a condition variable. For more information, see condvar_terminate() Function.
prepare_nsapi_thread enables threads that are not created by the server to act like server-created threads. For more information, see prepare_nsapi_thread() Function.
Utility functions include platform-independent, thread-safe versions of many standard library functions such as string manipulation, as well as new utilities useful for NSAPI.
daemon_atrestart registers a user function to be called when the server is sent a restart signal (HUP) or at shutdown. For more information, see daemon_atrestart() Function.
daemon_atrestart gets the local host name as a fully qualified domain name. For more information, see util_hostname() Function.
util_later_than compares two dates. For more information, see util_later_than() Function.
util_sprintf is the same as the standard library routine sprintf(). For more information, see util_sprintf() Function.
util_strftime is the same as the standard library routine strftime(). For more information, see util_strftime() Function.
util_uri_escape converts the special characters in a string into URI-escaped format. For more information, see util_uri_escape() Function.
util_uri_unescape converts the URI-escaped characters in a string back into special characters. For more information, see util_uri_unescape() Function.
The virtual server functions provide routines for retrieving information about virtual servers.
request_get_vs finds the virtual server to which a request is directed. For more information, see request_get_vs() Function.
vs_alloc_slot allocates a new slot for storing a pointer to data specific to a certain virtual server. For more information, see vs_alloc_slot() Function.
vs_get_data finds the value of a pointer to data for a given virtual server and slot. For more information, see vs_get_data() Function.
vs_get_default_httpd_object obtains a pointer to the default or root object from the virtual server's virtual server class configuration. For more information, see vs_get_default_httpd_object() Function.
vs_get_doc_root finds the document root for a virtual server. For more information, see vs_get_doc_root() Function.
vs_get_httpd_objset obtains a pointer to the virtual server class configuration for a given virtual server. For more information, see
vs_get_id finds the ID of a virtual server. For more information, see vs_get_id() Function.
vs_get_mime_type determines the MIME type that would be returned in the content-type: header for the given URI. For more information, see vs_get_mime_type() Function.
vs_lookup_config_var finds the value of a configuration variable for a given virtual server. For more information, see vs_lookup_config_var() Function.
vs_register_cb enables a plug-in to register functions that will receive notifications of virtual server initialization and destruction events. For more information, see vs_register_cb() Function.
vs_set_data sets the value of a pointer to data for a given virtual server and slot. For more information, see vs_set_data() Function.
vs_translate_uri translates a URI as though it were part of a request for a specific virtual server. For more information, see vs_translate_uri() Function.
When writing a new SAF, you should define it to accomplish certain actions, depending on which stage of the request-handling process will invoke it. For example, SAFs to be invoked during the Init stage must conform to different requirements than SAFs to be invoked during the Service stage.
The rq parameter is the primary mechanism for passing along information throughout the request-response process. On input to a SAF, rq contains whatever values were inserted or modified by previously executed SAFs. On output, rq contains any modifications or additional information inserted by the SAF. Some SAFs depend on the existence of specific information provided at an earlier step in the process. For example, a PathCheck SAF retrieves values in rq->vars that were previously inserted by an AuthTrans SAF.
This section outlines the expected behavior of SAFs used at each stage in the request-handling process. The SAFs are described in the following sections:
For more detailed information about these SAFs, see the Oracle iPlanet Web Server 7.0.9 Administrator’s Configuration File Reference.
Purpose: Initialize at startup.
Called at server startup and restart.
rq and sn are NULL.
Initialize any shared resources such as files and global variables.
Can register callback function with daemon_atrestart() to clean up.
On error, insert error parameter into pb describing the error and return REQ_ABORTED.
If successful, return REQ_PROCEED.
Purpose: Verify any authorization information.
Return REQ_PROCEED if the user was successfully and completely authenticated, REQ_NOACTION otherwise.
Purpose: Convert logical URI to physical path.
Perform operations on logical path (ppath in rq->vars) to convert it into a full local file system path.
Return REQ_PROCEED if ppath in rq->vars contains the full local file system path, or REQ_NOACTION if not.
To redirect the client to another site, add url to rq->vars with full URL. For example, http://www.sun.com/. Call protocol_status() to set HTTP response status to PROTOCOL_REDIRECT, NULL. Return REQ_ABORTED.
Purpose: Check path validity and user’s access rights.
Check auth-type, auth-user, and/or auth-group in rq->vars.
Return REQ_PROCEED if user and group is authorized for this area, ppath in rq->vars.
If not authorized, insert WWW-Authenticate to rq->srvhdrs with a value such as: Basic; Realm=\"Our private area\". Call protocol_status() to set HTTP response status to PROTOCOL_UNAUTHORIZED. Return REQ_ABORTED.
Purpose: Determine content-type of data.
If content-type in rq->srvhdrs already exists, return REQ_NOACTION.
Determine the MIME type and create content-type in rq->srvhdrs
Return REQ_PROCEED if content-type is created, REQ_NOACTION otherwise.
Purpose: Insert filters that process incoming (client-to-server) data.
Input SAFs are executed when a plug-in or the server first attempts to read entity body data from the client.
Input SAFs are executed at most once per request.
Return REQ_PROCEED to indicate success, or REQ_NOACTION to indicate it performed no action.
Purpose: Insert filters that process outgoing server-to-client data.
Output SAFs are executed when a plug-in or the server first attempts to write entity body data from the client.
Output SAFs are executed at most once per request.
Return REQ_PROCEED to indicate success, or REQ_NOACTION to indicate it performed no action.
Purpose: Generate and send the response to the client.
A Service SAF is only called if each of the optional parameters type, method, and query specified in the directive in obj.conf match the request.
Remove existing content-type from rq->srvhdrs. Insert correct content-type in rq->srvhdrs.
Create any other headers in rq->srvhdrs.
Call protocol_status to set HTTP response status. For more information, see protocol_status() Function.
Call protocol_start_response to send HTTP response and headers. For more information, see protocol_start_response() Function.
Generate and send data to the client using net_write. For more information, see net_write() Function.
Return REQ_PROCEED if successful, REQ_EXIT on write error, REQ_ABORTED on other failures.
Purpose: Respond to an HTTP status error condition.
The Error SAF is only called if each of the optional parameters code and reason specified in the directive in obj.conf match the current error.
Error SAFs do the same as Service SAFs, but only in response to an HTTP status error condition.
Purpose: Log the transaction to a log file.
AddLog SAFs can use any data available in pb, sn, or rq to log this transaction.
.The CGI environment variables are not available to NSAPI. Therefore, if you need to convert a CGI variable into an SAF using NSAPI, you retrieve them from the NSAPI parameter blocks. The following table indicates how each CGI environment variable can be obtained in NSAPI.
Keep in mind that your code must be thread-safe under NSAPI. You should use NSAPI functions that are thread-safe. Also, you should use the NSAPI memory management and other routines for speed and platform independence.Table 2–2 Parameter Blocks for CGI Variables
pblock_findval( "*", rq->headers); (* is lowercase; dash replaces underscore)
session_dns(sn) ? session_dns(sn) : pblock_findval("ip", sn->client);
pblock_findval( "from", rq->headers); (not usually available)
conf_getglobals()->Vport; (as a string)
pblock_findval("auth-cert", rq->vars) ;
char *session_maxdns(sn); (may be null)
security_active ? "ON" : "OFF";
protocol_uri2url_dynamic("","", sn, rq);