Creating server plug-in functions

his chapter describes how to create and compile your plug-in functions using the server plug-in application programming interface (API), and how to use the functions you create.

Note:
Before creating plug-in functions, you should be familiar with the server configuration files and the built-in functions. See Chapter 4, "Server operation" and Appendix B, "Server configuration files".
Of the systems the Netscape FastTrack and Enterprise servers support, the following systems can load functions into the server at run time and can therefore use plug-in functions:

What is the server plug-in API?

The server plug-in API is a set of functions and header files that help you create functions to use with the directives in server configuration files. The Netscape FastTrack and Enterprise servers use this API to create the functions for the directives used in both magnus.conf (the server configuration file) and obj.conf (the object configuration file). These built-in functions are described in Appendix B.

The servers use this API, so by becoming familiar with the API, you can learn how the servers work. This means you can override the server functionality, add to it, or customize your own functions. For example, you can create functions that use a custom database for access control or you can create functions that create custom log files with special entries.

The following steps are a brief overview of the process for creating your own plug-in functions:

  1. You write code for your plug-in functions. Each function you create is written specifically for the directive it will be used with in the configuration files.
  2. You compile your code to create a shared object file (.so file).
  3. In the Init directives of obj.conf, you tell the server to load your shared object file.
  4. You use your plug-in functions in your server configuration file (obj.conf). Before you write your functions, you should understand how the server handles requests. Chapter 4, "Server operation" describes this process.

Writing plug-in functions

This section describes how to begin writing your plug-in functions. It also describes the header files you need to include in your code. See the section "Compiling and linking your code" on page 100 for additional information.

The server root directory has a subdirectory called /nsapi that contains sample code, the header files, and a makefile. You should familiarize yourself with the code and samples. This documentation is written as a starting point for exploring that code.

The hierarchy of server plug-in API header files

The server and its header files are written in ANSI C. On some systems, such as Windows NT, you must have an import list that specifies all global variables and functions you need to access from the server binary.

The server plug-in API header files

This section describes the header files you can include when writing your plug-in functions. This section is intended as a starting point for learning the functions included in the header files.

Header files are stored in two directories:

Getting data from the server: the parameter block

The server stores variables in name-value pairs. The parameter block, or pblock, is a hash table keyed on the name string. The pblock maps these name strings onto their value character strings.

Basically, your plug-in functions use parameter blocks to get, change, add, and remove name-value pairs of data. In order to use the functions to do these actions, you need to know a bit about how the hash table is formed and how the data structures are managed.

The pb_param structure is used to manage the name-value pairs for each client request. The pb_entry structure creates linked lists of pb_param structures. See "The Session data structure" on page 193.

Passing parameters to server application functions

All server application functions (regardless of class) are described by the following prototype:

int function(pblock *pb, Session *sn, Request *rq);

pb is the parameter block containing the parameters given by the site administrator for this function invocation.

Caution!
The pb parameter should be considered read-only, and any data modification should be performed on copies of the data. Doing otherwise is unsafe in threaded server architectures, and will yield unpredictable results in multiprocess server architectures.

Parameter-manipulating functions

When adding, removing, editing, and creating name-value pairs, you use the following functions. This list might seem overwhelming, but you'll use only a handful of these functions in your plug-in functions. For a more detailed look, see Chapter 6, "Server plug-in API function definitions".

The param_create function creates a parameter with the given name and value. If the name and value aren't null, they are copied and placed into the new pb_param structure.

The param_free function frees a given parameter if it's non-NULL. It is also useful for error checking before using the pblock_remove function.

The pblock_create function creates a new parameter block with a hash table of a chosen size.

The pblock_free function frees a given parameter block and any entries inside it.

The pblock_find function finds the name-value entry with the given name in a given parameter block.

The pblock_findval function finds the value portion of a name-value entry with a given name in a given parameter block, and returns its value.

The pblock_remove function behaves like the pblock_find function, but when it finds the given parameter block, it removes it.

The pblock_nninsert and pblock_nvinsert functions both create a new parameter with a given name and value, and insert it into a given parameter block. The pblock_nninsert function requires that the value be an integer, but the pblock_nvinsert function accepts a string.

The pblock_pinsert function inserts a parameter into a parameter block.

The pblock_str2pblock function scans the given string for parameter pairs in the format name=value or name="value".

The pblock_pblock2str function places all of the parameters in the given parameter block into the given string. Each parameter is of the form name="value" and is separated by a space from any adjacent parameter.

Data structures and data access functions

The data structures are Session (see "The Session data structure" on page 193) and Request (see "The Request data structure" on page 195). The data access function is request_header.

The Request->vars parameter block contains the server's working variables. The set of active variables is different depending on which step of the request the server is processing, as discussed on page 80.

The Request->reqpb parameter block contains the request parameters that are sent by the client:

The request_header function
The request_header function finds the parameter block that contains the client's HTTP headers.

#include "frame/req.h"
int request_header(char *name, char **value, Session *sn, Request *rq);
The name parameter should be the lowercase header name string to look for, and value is a pointer to your char * that should contain the header. If no header with the given name was sent, value is set to NULL.

The Request->srvhdrs parameter block is the set of HTTP headers for the server to send back. This parameter block can be modified by any function.

The last three entries in the Request structure should be considered transparent to application code because they are used by the server's base code.

After the server has a path for the file it intends to return, application functions should use the request_stat_path function to obtain stat information about the file. This avoids multiple, unnecessary calls to the stat function.

Application function status codes

When your plug-in function is done working with the name-value pairs, it must return a code that tells the server how to proceed with the request. These integer status codes are discussed on page 99.

Reporting errors to the server

When problems occur, server application functions should set an HTTP response status code to give the client an idea of what went wrong. The function should also log an error in the error log file.

There are two ways of reporting errors: setting a response status code and reporting an error.

Setting an HTTP response status code
The protocol_status function sets the status to the code and reason string. If the reason is NULL, the server attempts to match a string with the given status code (see Table 5.3). If it can't find a string, it uses "Unknown error".

#include "frame/protocol.h" 
void protocol_status(Session *sn, Request *rq, int n, char *r);
Generally, protocol_status will be called with a NULL reason string, and one of the following status codes defined in the protocol.h file. (If no status is set (or the code is set as NULL), the default is PROTOCOL_SERVER_ERROR.)

Status Code

Definition

PROTOCOL_OK

Normal status, the request will be fulfilled normally. This should only be set by Service-class functions.

PROTOCOL_REDIRECT

The client should be directed to a new URL, which your function should insert into the rq->vars parameter block as url.

PROTOCOL_NOT_MODIFIED

If the client gave a conditional request, such as an HTTP request with the if-modified-since header, then this indicates that the client should use its local copy of the data.

PROTOCOL_BAD_REQUEST

The request was unintelligible. Used primarily in the framework library.

PROTOCOL_UNAUTHORIZED

The client did not give sufficient authorization for the action it was trying to perform. A WWW-authenticate header should be present in the rq->srvhdrs parameter block that indicates to the client the level of authorization it needs to perform its action.

PROTOCOL_FORBIDDEN

The client is explicitly forbidden to access the object and should be informed of this fact.

PROTOCOL_NOT_FOUND

The server was unable to locate the item requested.

PROTOCOL_SERVER_ERROR

Some sort of server-side error has occurred. Possible causes include misconfiguration, resource unavailability, and so on. Any error unrelated to the client generally falls under this rather broad category.

PROTOCOL_NOT_IMPLEMENTED

The client has asked the server to perform an action that it knows it cannot do. Generally, you would use this to indicate your refusal to implement an HTTP feature.

Error reporting
When errors occur, it's customary to report them in the server's error log file. To do this, your plug-in functions should call log_error. This logs an error and then returns to tell you if the log was recorded successfully (a return value of 0 means success, -1 means failure).

#include "frame/log.h" 
 
int log_error(int degree, char *func, Session *sn, Request *rq,  
			char *fmt, ...);
You can give log_error any printf( ) style string to describe the error. If an error occurs after a system call, use the following function to translate an error number to an error string:

#include "base/file.h" 
char *system_errmsg(SYS_FILE fd );
Note:
The fd parameter is vestigial and might need to be changed for operating systems other than Unix and Windows NT. Therefore, it is best to set fd to zero.

Compiling and linking your code

You can compile your code with any ANSI C compiler. See the makefile in the /nsapi/include directory. The make file assumes the use of gmake.

This section lists the linking options you need to use in order to create a Unix shared object. The server can be instructed to load by commands in the magnus.conf configuration file.

The following table describes the commands used to link object files into a shared object under the various Unix platforms. In these examples, the compiled object files t.o and u.o are linked to form a shared object called test.so.
System

Compile options

IRIX

ld -shared t.o u.o -o test.so

SunOS

ld -assert pure-text t.o u.o -o test.so

Solaris

ld -G t.o u.o -o test.so

OSF/1

ld -all -shared -expect_unresolved "*" t.o u.o -o test.so

HP-UX

ld -b t.o u.o -o test.so When compiling your code, you must also use the +z flag to the HP C compiler.

AIX

cc -bM:SRE -berok t.o u.o -o test.so -bE:ext.exp -lc The ext.exp file must be a text file with the name of a function that is externally accessible for each line.

Loading your shared object

After you've compiled your code, you need to tell the server to load the shared object and its functions so that you can begin using your plug-in functions in obj.conf.

When the server starts, it uses obj.conf to get its configuration information. To tell the server to load your shared object and functions in the shared object, you add the following line to obj.conf:

Init fn=load-modules shlib=[path]filename.so funcs="function1,function1,...,functionN"
This initialization function opens the given shared object file and loads the functions function1, function2, and so on. You then use the functions function1 and function2 in the server configuration files (either magnus.conf or obj.conf). Remember to use the functions only with the directives you wrote them for, as described in the following section.

Using your plug-in functions

When you have compiled and arranged for the loading of your functions, you need to provide for their execution. All functions are called as follows:

Directive fn=function [name1=value1] ... [nameN=valueN]