Note:
Before creating plug-in functions, you should be familiar with the server configuration files and the built-in functions. See Appendix B, "Server configuration files" for information on the configuration files calledOf 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:magnus.conf
andobj.conf
.
|
A thread is a single independent stream of execution within a program. A critical section is a section of program code that must be executed by only one thread at a time. Portions of code that perform such tasks as switching between threads or updating a record in a database are critical sections. A condition variable is a thread-synchronization variable that lets the user specify arbitrary conditions on which to block.
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 build the regular functions for the directives used in both magnus.conf
(the server configuration file) and obj.conf
(the object configuration file). These regular functions are described in the Administrator's Guide.
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 plug-in functions:
.so
file).
obj.conf
you tell the server to load your shared object file.
obj.conf
).
magnus.conf
file, which contains information the server uses to configure itself. For example, the file tells the server what port to bind with, what the server name is, what user account to use after start-up, and so on. You can find a sample of the magnus.conf
file on page 67.
Once it has started running, the server waits for requests to come in from client applications (such as Netscape Navigator). When a request comes in, the server uses another file, obj.conf
, to determine if and how it should service the request. You can find a sample obj.con
file on page 69
For example, the
obj.conf
file tells the server whether a user has access to the document they're requesting, and if they have access, it determines (among other things) if the server should attach information to the document when it sends it to the client. This process is much more detailed, as described here andin the following sections.
The Netscape server responds to a client request by following a prescribed process in which each step is done once for all objects matching the request, then another step is done for all objects, and so on. For example, authorization translation (the first step) is performed on all objects in obj.conf
, and then name translation (the second step) is done for all objects.
How the server uses directives and functions in an object
The process steps are as follows:
text/html
and image/gif
, or
they can be internal document identification types.
Internal types always begin with
magnus-internal/
, and are used to
select a server function to use to decode the document. (Only used for local
access; the server system calls these routines automatically when
necessary.)
The first six of these steps map directly to six of the configuration directives allowed for each object. There is a seventh configuration directive (send-error) that controls how the server responds to the client when it encounters an error. If at any time one of these steps fails, another step must be taken to handle the error and inform the client about what happened. In this case, the Netscape server lets you customize the response that is sent with more site-specific information about the error.
obj.conf
. You use the format:
directive fn="ourfunction" value="v1" ... value ="vn"The directive is the directive the function belongs to (for example, the AuthTrans directive). You can then send any number of optional values to your function as arguments you specify in
obj.conf
. For example, you could create a function for the NameTrans directive called myfunc1
that takes a URL path as a value and maps it to a hard-coded path specified in your function. The entry in obj.conf
might look like this:
NameTrans fn="myfunc1" URLpath="/special/docs"The response returned by the function can tell the server to do the following (
REQ
stands for request):REQ_PROCEED
indicates that the function has performed its task without a problem. When a function of a NameTrans, Service, or Error directive returns this, the server skips any remaining directives of the same type and moves to the next type of directive in an object. However, when the function is of an AuthTrans, PathCheck, ObjectType, or AddLog driective , a REQ_PROCEED
value has the same effect as REQ_NOACTION
--the server moves to the next line in the object.
REQ_ABORTED
indicates that an error occurred and that the request (not necessarily the session) should be terminated.
REQ_NOACTION
indicates that the function found conditions such that it did not perform its intended action. The meaning of this depends on the step being performed, but usually the server moves to the next line in the object file.
REQ_EXIT
should be used only when a read or write error has occurred while talking to the client, and the entire session cannot continue.
Note:
Before you can use any of your plug-in functions, you must tell the server to
load the functions. You do this in obj.conf
. The full process is described
later in this chapter.
magnus.conf
is the server's main technical configuration file. It controls aspects of the server operation not related to documents, such as host name and port.
obj.conf
is the server's object configuration file. It controls access to the server and manages the files and directories the server can send to clients.
mime.types
is the file the server uses to convert file-name extensions such as .GIF into a MIME type like image/gif.
admpw
is the administrative password file. Its format is user:password. The password is DES-encrypted, like /etc/passwd.
Caution!If you are using the Administration forms, you shouldn't use continuation lines in the magnus.conf file. Instead, put each Init configuration entirely on a single line. If you are absolutely sure you will never use the Administration form, you can use the backslash character.
# Sample magnus.conf file for Netscape server 2.0 # The server's home--its root directory ServerRoot /usr/ns-home/https-server1 # The server's name ServerName www.netscape.com # Which port? Port 443 # This tells the server to get its objects from obj.conf, and use # the "default" object as the default. LoadObjects obj.conf RootObject default # The logfile for errors, and the file where it should keep # the pid of the master server process. ErrorLog /usr/ns-home/https-server1/logs/errors PidLog /usr/ns-home/https-server1/logs/pid # Which user should the server run as? This is the UNIX user # account name. User http # Processes - number of processes to spawn MaxProcs 1 MinThreads 4 MaxThreads 128 # Use DNS? (turn this off for performance reasons) DNS on # Security directives: is security on, where is my keyfile, # which ciphers should I support (Ciphers directive is on the # US version only) Security on Keyfile ServerKey.db Certfile ServerCertFor the details about the directives, see "Directives in magnus.conf" on page 161.
The obj.conf file
The object configuration file, called obj.conf, uses objects to control how the server handles documents.
Objects (also referred to as resources) are settings that tell the server how to treat all documents, CGI programs, directories, imagemap files, and so on. You can define objects in two ways:
Also, with cgi-bin directories, all files are treated as programs and are run rather than sent. If you create a CGI template and assign the template to CGI programs (using name translation), then you can have any number of CGI directories and they all use the template configuration.
<Object ppath=wildcardpattern>
Directives
<Client dns=wildcardpattern>
Directives
</Client>
</Object>
<Object name=cgi>
Directives
</Object>You use wildcard patterns to control what is grouped in the object, or you use a name to create a template. You then specify one or more directives to control what the server does when it encounters anything that uses the template or that matches the wildcard pattern specified with ppath. You can also set options for specific client hosts. This is a powerful feature because, unlike other servers where a host either can or cannot access a document, you make the server act differently for a client depending on the document they access. Although you don't need any <Client> sections in an object section, you can specify more than one--so the server acts differently based both on who requests something and what they request.
Directive fn=function [parameter1=value1][parameterN=valueN]Directive identifies an aspect of server operation. This string is case insensitive and must appear at the beginning of a line. Function is a function and parameters given to the directive. Its format depends on the directive.
<Object ppath="/user/public/*"> PathCheck fn=deny-existence Service fn=server-retrieve </Object>The Service directive tells the server to get the documents by default. Sample obj.conf file
# # Sample obj.conf file for Netscape server 2.0. # # This file was automatically generated by the server. # Edit at your own risk. # The default object. This is what the server uses if none of the other # objects fit. # # This one has a CGI directory specified in /usr/local/bin/cgi, # a directory mapping to a bigger disk in /gig-drive/sales, # and a document root of /usr/http-docs # # Initializations, such as log files and loading NSAPI libraries Init format.access="%Ses->client.ip% - %Req->vars.auth-user% [%SYSDATE%] \"%Req->reqpb.clf-request%\" %Req->srvhdrs.clf-status% %Req->srvhdrs.content-length%" fn="flex-init" access="/usr/ns-home/httpd-80/logs/access" Init fn=load-types mime-types=mime.types Init fn="dns-cache-init" cache-size="512" expire="1200" <Object name="default"> NameTrans from="/ns-icons" fn="pfx2dir" dir="/usr/ns-home/ns-icons" NameTrans from="/cgi-bin" dfn="pfx2dir" ir="/usr/local/bin/cgi" name="cgi" NameTrans from="/sales" fn="pfx2dir" dir="/gig-drive/sales" NameTrans root="/usr/http-docs" fn="document-root" PathCheck fn="unix-uri-clean" PathCheck fn="find-pathinfo" PathCheck index-names="index.html,home.html" fn="find-index" ObjectType fn="type-by-extension" ObjectType fn="force-type" type="text/plain" Service fn="imagemap" method="(GET|HEAD)" type="magnus-internal/imagemap" Service fn="index-common" method="(GET|HEAD)" type="magnus-internal/directory" Service fn="send-cgi" type="magnus-internal/cgi" Service fn="send-file" method="(GET|HEAD)" type="*~magnus-internal/*" AddLog fn="flex-log" name="access" </Object> # All CGI directories have these configuration options set <Object name="cgi"> ObjectType fn="force-type" type="magnus-internal/cgi" Service fn="send-cgi" </Object>
<Object name="default"> NameTrans fn="pfx2dir" from="/mc-icons" dir="/usr/home/mc-icons" NameTrans fn="document-root" root="/usr/http-docs" PathCheck fn="unix-uri-clean" PathCheck fn="find-pathinfo" ObjectType fn="type-by-extension" ObjectType fn="force-type" type="text/plain" Service fn="imagemap" method="(GET|HEAD)" type="magnus-internal/imagemap" Service fn="index-common" method="(GET|HEAD)" type="magnus-internal/directory" Service fn="send-file" method="(GET|HEAD)" type="*~magnus-internal/*" </Object>
<Object name="cgi"> ObjectType fn="force-type" type="magnus-internal/cgi" Service fn="send-cgi" </Object>
nsapi/examples/
directory contains C files with examples for each class of function you can create.
nsapi/include/
directory contains all the header files you need to include when programming your plug-in functions.
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.
nsapi/include/base
contains header files that deal with low-level, platform independent functions such as memory, file, and network access.
nsapi/include/frame
contains header files of functions that deal with server and HTTP-specific functions such as handling access to configuration files and dealing with HTTP.
base
directory
Header files in the
frame
directory
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!
This 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.
Public 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 5, "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 returns 1 if the parameter was non-NULL, and returns 0 if the parameter was NULL. This 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. It returns the newly allocated parameter block.
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. If it's successful, it returns the parameter block. If not, it returns NULL. You then use the pblock_findval function to get the actual value in the name-value pair.
The pblock_findval function finds the name-value entry with a given name in a given parameter block, and returns its value; otherwise, it returns NULL.
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 name and value in the parameter are also newly allocated. The pblock_nninsert function requires that the value be an integer, but the pblock_nvinsert function accepts a string. Both functions return the parameter that was allocated (in case you need it).
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". This function returns the number of parameters added to the table, or it returns -1 if it encounters an error.
The pblock_pblock2str function places all of the parameters in the given parameter block into the given string (NULL if it needs creation). It reallocates more space for the 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 155) and Request (see "The Request data structure" on page 157). 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 81.
The Request->reqpb
parameter block contains the request parameters that are sent by the client:
The Request->headers
parameter block contains the client's HTTP headers. HTTP sends any number of headers in the form (RFC 822):
Name: valueIf more than one header has the same name, then they are concatenated with commas as follows:
Name: value1, value2The parameter block is keyed on the fully lowercase version of the name string without the colon. Application functions should not access this parameter block directly, but instead use the function request_header, which finds the named header depending on the requesting protocol.
#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 following call to obtain stat( ) information about the file:
#include "frame/req.h" /* * request_stat_path tries to stat path. If path is NULL, it will look in * the rq->vars pblock for "path". If the stat is successful, it returns the * stat structure. If not, returns NULL and copies an error message into err. * If a previous call to this function was successful, and path is the same, * the function will simply return the previously found value. * * Application functions should not free this structure. */ struct stat *request_stat_path(char *path, char *err, Request *rq);Using this function avoids multiple, unnecessary calls to the stat function.
Reporting errors to the server
When problems occur, the server application functions should set server status codes that give the client an idea of what went wrong. The function should also log an error in the server error log file.
There are two interfaces for reporting errors: setting a response code and reporting an error.
Setting a 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.5). 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, the default is PROTOCOL_SERVER_ERROR.)
Status codes used with protocol_status
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"
/*
* log_error formats the arguments with the printf() style fmt.
* Returns whether the log was successful.
* It also records the current date.
* sn and rq are optional parameters. If specified, information
* about the client is reported.
*/
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 under Unix and might need to be changed for
other operating systems. Therefore, it is best to set fd to zero.
Conditions for each function class
The following sections list the special conditions that exist in the variables of the Request structure, and the return codes for each class of server function.
AuthTrans
Authorization is split into two steps. First, the authorization data sent by the client (if any) is decoded and verified against internal databases. If the data is valid, it should be stored into the Request->vars
parameter block. In the second step, this data is compared against the required authorization for the requested path (performed in a PathCheck class function).
There is no reason that both of these steps could not be performed in one AuthTrans or one PathCheck directive; however, the steps are split for flexibility and for possible future support the SHTTP protocol.
There are no variables active in the Request->vars
parameter block upon entry to an AuthTrans class function. Upon successful translation and function exit, the following variables are customarily set:
auth-type
identifies the authorization type. Currently, only basic
is defined for HTTP Basic user authorization.
auth-user
gives the name of the remote user as verified by internal databases.
REQ_ABORTED
if the request is to be aborted, REQ_NOACTION
if the translation was unsuccessful, or REQ_PROCEED
if successful. Request->vars
parameter block for NameTrans class functions: ppath
is a Partial Path. Initially, it is the virtual path given to the server by the client. If other NameTrans functions have tampered with the value of ppath
, this might be a partially translated path. Your function can change the value of ppath
regardless of the return code.
name
, if inserted into the parameter block by your function, dictates that the given named object should be added to the set of active objects for this request. The directives in that object will be applied.
REQ_PROCEED
: Indicates that a name translation was performed, and that name translation for this object should not continue. This means the server moves to the next directive type in the object in obj.conf
(usually PathCheck).
REQ_ABORTED
indicates that the request has encountered an error and that an error message should be sent to the client. No functions are called after your function returns this, except any functions that are designated error handlers.
REQ_NOACTION
indicates that no terminal name translation was applied by your function. This doesn't mean that ppath was not changed. The server continues applying the rest of the object's name translation functions.
REQ_EXIT
indicates that an I/O error occurred while talking with the client. The request should be aborted and no error message should be sent.
Request->vars
parameter block: the value of path
is the path resulting from the execution of all NameTrans directives. Any variables that have been created by previous NameTrans or AuthTrans directives are also active.
On return from a PathCheck class function, a code other than REQ_ABORTED
is considered a success.
path
in Request->vars
and the variables defined by the previous directives. Upon return from an ObjectType class function, a code other than REQ_ABORTED
indicates success.
The server's base functionality library provides mechanisms for typing files natively to the machine's operating system. Under most systems, this consists of file name extension recognition code.
path
in Request->vars
and the variables defined by the previous directives.
Upon return from a Service class function, the REQ return codes have the following meaning: REQ_PROCEED
indicates that the response has been sent to the client successfully.
REQ_NOACTION
indicates that the function was not applied. The server should look at the rest of the Service directives.
REQ_ABORTED
indicates that an error has occurred and a message should be sent to the client.
REQ_EXIT
indicates that the connection to the client should end.
int protocol_start_response(Session *sn, Request *rq);If this function returns
REQ_NOACTION
, then the body response should be skipped and the application function should return successfully. Otherwise, this function returns REQ_PROCEED
.
If cross platform considerations are not required, then operating-system specific I/O calls can be made by Service class functions. Session
and Request
parameters are NULL. The parameter pblock
is filled with information from the server's technical configuration file (obj.conf
).
Static data should be limited to read-only data if possible. Other mediums will need to employ some method of locking to ensure that only one process or thread is accessing the data at a time. The routines in the base/sem.h
header file are used to coordinate processes, and those in the base/crit.h
file are used to coordinate threads.
Upon failure, plug-in Init-class functions should return REQ_ABORTED
and insert into their parameter block a variable named error
that contains a string describing the error. Any other return code is considered success.
If the server is restarted, these modules might need to be terminated before they are started again by the server code. For this purpose, the server provides the following function:
void magnus_atrestart(void (*fn)(void *), void *data)The given callback
fn
is called by the server when the server is restarted. The data pointer is given to the function as its argument.
Note:
Netscape does not call the termination callbacks upon server termination, only upon restart.
base/systhr.h
and base/crit.h for threads, critical sections, and condition variables.
The UNIX servers are still capable of running in multiple-process mode. Each process then has a pool of threads that it uses to answer requests. If your server plug-in modules are not thread-safe, you can run the server with one thread per process to simulate the 1.1 server architecture; however this can result in a performance loss. You can run one process with multiple threads to simplify programming. This lets you share context without using shared memory. However, high traffic sites that use CGI should use several processes (2-8) with fewer maximum threads per process. For example, if you normally run one process with 128 threads, run 4 processes with 32 maximum threads each if your site serves a very high volume. This avoids large virtual address spaces for a single process.
New thread manipulation calls exist in base/systhr.h
. The following functions can manipulate threads:base/sem.h
remain the same and provide server-wide mutual exclusion. Under NT, these are simply critical sections that apply to all threads in the server process. Under UNIX, these primitives operate on all threads in all processes of the server, so they are very heavyweight. They should only be used to protect access to process-shared resources such as shared memory and files.
New calls exist in base/crit.h
that provide process-local critical sections and condition variables for NT and UNIX. These calls only affect threads in the current process. Critical sections are simply a lock that can be owned by only one thread at a time. They can be recursively entered. Condition variables are used in conjunction with a critical section to provide "wait" and "notify" primitives.
On IRIX, Solaris 2.x, and OSF/1, the operating systems provide threading support, and multithreaded versions of libc
. On those systems, you can call most libc
routines safely. One notable exception is that you must use the localtime_r and gmtime_r functions instead of the localtime and gmtime functions respectively. Many other functions also must be called in their reentrant form. Check the man pages for details.
On systems that don't have threading support, we provide the threading via user-level context switches. The server doesn't currently try to perform thread pre-emption, which means that you can still use libc calls that aren't re-entrant. Thread switching is performed when a lock is used, when the NSAPI I/O functions such as net_read are called, and when a systhread_yield function is explicitly called. This also means that if your NSAPI extension makes a system call that will block, you will block the process that your thread is running in (thus blocking all threads in that process). DNS calls such as gethostbyaddr() are one example of a call which will block the process, however the NSAPI function session_dns will not block the process.
Under IRIX, you must compile with -D_SGI_MP_SOURCE
.
Under Solaris, you must compile with -D_REENTRANT
.
Under OSF/1, you must compile with -threads
./foo/bar/baz.txt
, and then another client requests /foo/bar/baz.txt
, the server's response will be the same as long as /foo/bar/baz.txt
doesn't change between the requests. When the server can avoid calling the NSAPI functions for a request, it can return the responses faster.
There is a new variable called directive_is_cachable in the Request structure in 2.0. By default, this variable is set to 0 when calling your NSAPI functions. If you do not set this variable to 1 before your function returns, the server will not try to cache the request, and each subsequent request will call your function again. If you set it to 1, the server may not call your function when the next client makes the same request.
You should cache requests that do not depend on some aspect of the client to determine how they are returned. If your function performs access control, logging, switches data based on user-agent, or anything similar, you should not mark your directive as cachable. If your function is doing something which does not depend on the client IP address or the headers the client sends, then you can mark your directive as cachable.
<Client urlhost=www.(any|such).com> NameTrans fn=my-nametrans from=/anysuchdir </Client>
placed in the default object will call my-nametrans only when the browser thinks it is accessing www.any.com or www.auch.com. Similarly,
<Client urlhost=*~www.(any|such).com> NameTrans fn=my-nametrans from=/anysuchdir </Client>
will apply my-nametrans to everyone except those who access www.any.com and www.such.com including browsers that don't send the Host: header.
magnus.conf
.
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.Linking options
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 programmed them for, as described in the following section.
Directivefn=
function[
name1=
value1] ... [
nameN=
valueN]
fn=
function identifies the function to be called using the function's unique character-string name.
myaddlog
that adds an entry to a log file called mylogfile
. The plug-in function takes another parameter that defines how much information to log.
AddLog fn=myaddlog name="mylogfile" type="maxinfo"