Sun Java System Web Proxy Server 4.0.11 NSAPI Developer's Guide

Chapter 3 Examples of Custom SAFs and Filters

This chapter provides examples of custom Server Application Functions (SAFs) and filters for each directive in the request-response process. You can use these examples as the basis for implementing your own custom SAFs and filters. For more information about creating your own custom SAFs, see Chapter 2, Creating Custom Filters

Before writing custom SAFs, you should be familiar with the request-response process and the role of the obj.conf configuration file. This file is discussed in Sun Java System Web Proxy Server 4.0.11 Configuration File Reference.

Before writing your own SAF, whether see if an existing SAF serves your purpose. The predefined SAFs are discussed in Sun Java System Web Proxy Server 4.0.11 Configuration File Reference.

For a list of the NSAPI functions for creating new SAFs, see Chapter 4, NSAPI Function Reference

This chapter contains the following sections:

Examples in the Build

The plugins/nsapi/examples subdirectory within the server installation directory contains examples of source code for SAFs.

You can use the example.mak makefile in the same directory to compile the examples and create a library containing the functions in all of the example files.

To test an example, load the examples shared library into the Sun Java System Web Proxy Server by adding the following directive in the Init section of obj.conf:


Init fn=load-modules shlib=examples.so/dll
funcs=
         function1,function2,function3

      

The funcs parameter specifies the functions to load from the shared library.

If the example uses an initialization function, specify the initialization function in the funcs argument to load-modules. Also, add an Init directive to call the initialization function.

For example, the PathCheck example implements the restrict-by-acf function, which is initialized by the acf-init function. The following directive loads both these functions:

Init fn=load-modules yourlibrary funcs=acf-init,restrict-by-acf

The following directive calls the acf-init function during server initialization:

Init fn=acf-init file=extra-arg

To invoke the new SAF at the appropriate step in the response handling process, add an appropriate directive in the object to which it applies, for example:

PathCheck fn=restrict-by-acf

After adding new Init directives to obj.conf, restart the Sun Java System Web Proxy Server to load the changes Init directives are only applied during server initialization.

AuthTrans Example

This simple example of an AuthTrans function demonstrates how to use custom methods to verify that the user name and password that a remote client provided is accurate. This program uses a hard-coded table of user names and passwords and checks a given user’s password against the one in the static data array. The userdb parameter is not used in this function.

AuthTrans directives work in conjunction with PathCheck directives. An AuthTrans function checks whether the user name and password associated with the request are acceptable. This directory does not allow or deny access to the request. Access is handled by a PathCheck function.

AuthTrans functions get the user name and password from the headers associated with the request. When a client initially makes a request, the user name and password are unknown. The AuthTrans function and PathCheck function therefore together to reject the request, because they can’t validate the user name and password. When the client receives the rejection, the usual response is to present a dialog box asking the user for the user name and password. The client then submits the request again, this time including the user name and password in the headers.

In this example, the hardcoded-auth function, which is invoked during the AuthTrans step, checks whether the user name and password correspond to an entry in the hard-coded table of users and passwords.

Installing the AuthTrans Example

To install the function on the Sun Java System Web Proxy Server, add the following Init directive to obj.conf to load the compiled function:

Init fn=load-modules shlib=yourlibrary funcs=hardcoded-auth

Inside the default object in obj.conf, add the following AuthTrans directive:


AuthTrans fn=basic-auth auth-type="basic" userfn=hardcoded-auth
userdb=unused

         

This function does not enforce authorization requirements. The function only takes given information and informs the server whether the information is correct or not. The PathCheck function require-auth performs the enforcement, so add the following PathCheck directive as well:

PathCheck fn=require-auth realm="test realm" auth-type="basic"

AuthTrans Example Source Code

The source code for this example is in the auth.c file in the nsapi/examples/ or plugins/nsapi/examples subdirectory of the server root directory.

#include "nsapi.h"
typedef struct {
    char *name;
    char *pw;
} user_s;

static user_s user_set[] = {
    {"joe", "shmoe"},
    {"suzy", "creamcheese"},
    {NULL, NULL}
};

#include "frame/log.h"


#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC int hardcoded_auth(pblock *param, Session *sn, Request *rq)
{
    /* Parameters given to us by auth-basic */
    char *pwfile = pblock_findval("userdb", param);
    char *user = pblock_findval("user", param);
    char *pw = pblock_findval("pw", param);

    /* Temp variables */
    register int x;

    for(x = 0; user_set[x].name != NULL; ++x) {
        /* If this isn’t the user we want, keep going */
        if(strcmp(user, user_set[x].name) != 0) continue;

        /* Verify password */
        if(strcmp(pw, user_set[x].pw)) {
             log_error(LOG_SECURITY, "hardcoded-auth", sn, rq,
                    "user %s entered wrong password", user);
            /* This will cause the enforcement function to ask */
            /* user again */
            return REQ_NOACTION;
        }
        /* If we return REQ_PROCEED, the username will be accepted */
        return REQ_PROCEED;
    }
    /* No match, have it ask them again */
    log_error(LOG_SECURITY, "hardcoded-auth", sn, rq,
        "unknown user %s", user);
    return REQ_NOACTION;
}

NameTrans Example

The ntrans.c file in the plugins/nsapi/examples subdirectory of the server root directory contains source code for two example NameTrans functions:

This section discusses the first example. The source code in ntrans.c provides the second example.


Note –

A NameTrans function is used primarily to convert the logical URL in ppath in rq->vars to a physical path name. However, in the example explicit_pathinfo does not translate the URL into a physical path name. It changes the value of the requested URL. See the second example, https_redirect, in ntrans.c for an example of a NameTrans function that converts the value of ppath in rq->vars from a URL to a physical path name.


The explicit_pathinfo example allows URLs to explicitly include extra path information for use by a CGI program. The extra path information is delimited from the main URL by a specified separator, such as a comma.

For example:

http://server-name/cgi/marketing,/jan/releases/hardware

In this case, the URL of the requested resource, which would be a CGI program, is http://server-name/cgi/marketing, and the extra path information to give to the CGI program is /jan/releases/hardware.

When choosing a separator, use a character that will never be used as part of the real URL.

The explicit_pathinfo function reads the URL, the text following the comma, and puts it in the path-info field of the vars field in the request object (rq->vars). CGI programs can access this information through the PATH_INFO environment variable.

When explicit_pathinfo is used the separator character is added to the end of the SCRIPT_NAME CGI environment variable.

NameTrans directives usually return REQ_PROCEED when they change the path, so that the server does not process any more NameTrans directives. However, in this case name translation should continue after the path information has been extracted , because the URL has not yet been translated to a physical path name.

Installing the NameTrans Example

To install the function on the Sun Java System Web Proxy Server, add the following Init directive to obj.conf to load the compiled function:

Init fn=load-modules shlib=your-library funcs=explicit-pathinfo

Inside the default object in obj.conf, add the following NameTrans directive:

NameTrans fn=explicit-pathinfo separator=","

This NameTrans directive should appear before other NameTrans directives in the default object.

NameTrans Example Source Code

The following example is located in the ntrans.c file in the plugins/nsapi/examples subdirectory of the server root directory.

#include "nsapi.h"
#include <string.h>           /* strchr */
#include "frame/log.h"         /* log_error */
#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC int explicit_pathinfo(pblock *pb, Session *sn, Request *rq)
{
    /* Parameter: The character to split the path by */
    char *sep = pblock_findval("separator", pb);
    /* Server variables */
    char *ppath = pblock_findval("ppath", rq->vars);
    /* Temp var */
    char *t;
    /* Verify correct usage */
    if(!sep) {
       log_error(LOG_MISCONFIG, "explicit-pathinfo", sn, rq,
           "missing parameter (need root)");
       /*     When we abort, the default status code is 500 Server
          Error */
       return REQ_ABORTED;
    }
    /* Check for separator. If not there, don’t do anything */
    t = strchr(ppath, sep[0]);
   if(!t)
       return REQ_NOACTION;
    /* Truncate path at the separator */
    *t++ = ’\\0’;
    /* Assign path information */
    pblock_nvinsert("path-info", t, rq->vars);
    /*     Normally NameTrans functions return REQ_PROCEED when they
        change the path. However, we want name translation to
       continue after we’re done. */
   return REQ_NOACTION;
}
#include "base/util.h"        /* is_mozilla */
#include "frame/protocol.h"   /* protocol_status */
#include "base/shexp.h"        /* shexp_cmp */
#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC int https_redirect(pblock *pb, Session *sn, Request *rq)
{
    /* Server Variable */
    char *ppath = pblock_findval("ppath", rq->vars);
    /* Parameters */
    char *from = pblock_findval("from", pb);
    char *url = pblock_findval("url", pb);
    char *alt = pblock_findval("alt", pb);
    /* Work vars */
    char *ua;
    /* Check usage */
    if((!from) || (!url)) {
       log_error(LOG_MISCONFIG, "https-redirect", sn, rq,
          "missing parameter (need from, url)");
       return REQ_ABORTED;
    }
    /*     Use wildcard match to see if this path is one we should
       redirect */
   if(shexp_cmp(ppath, from) != 0)
       return REQ_NOACTION;   /* no match */
    /*     Sigh. The only way to check for SSL capability is to
        check UA */
    if(request_header("user-agent", &ua, sn, rq) == REQ_ABORTED)
       return REQ_ABORTED;
    /*     The is_mozilla function checks for Mozilla version 0.96
        or greater */
    if(util_is_mozilla(ua, "0", "96")) {
       /* Set the return code to 302 Redirect */
       protocol_status(sn, rq, PROTOCOL_REDIRECT, NULL);
       /*     The error handling functions use this to set the
           Location: */
       pblock_nvinsert("url", url, rq->vars);
       return REQ_ABORTED;
    }
    /* No match. Old client. */
    /* If there is an alternate document specified, use it. */
    if(alt) {
       pb_param *pp = pblock_find("ppath", rq->vars);
       /* Trash the old value */
       FREE(pp->value);
       /*     We must dup it because the library will later free
           this pblock */
       pp->value = STRDUP(alt);
       return REQ_PROCEED;
    }
    /* Else do nothing */
    return REQ_NOACTION;
}

PathCheck Example

The example in this section demonstrates how to implement a custom SAF for performing path checks. This example checks whether the requesting host is on a list of allowed hosts.

The Init function acf-init loads a file containing a list of allowable IP addresses with one IP address per line. The PathCheck function restrict_by_acf gets the IP address of the host that is making the request and checks whether it is on the list. If the host is on the list, it is allowed access; otherwise, access is denied.

For simplicity, the stdio library is used to scan the IP addresses from the file.

Installing the PathCheck Example

To load the shared object containing your functions, add the following line in the Init section of the obj.conf file:

Init fn=load-modules yourlibrary funcs=acf-init,restrict-by-acf

To call acf-init to read the list of allowable hosts, add the following line to the Init section in obj.conf. This line must appears after the line that loads the library containing acf-init).

Init fn=acf-init file=fileContainingHostsList

To execute your custom SAF during the request-response process for some object, add the following line to that object in the obj.conf file:

PathCheck fn=restrict-by-acf

PathCheck Example Source Code

The source code for this example is located in pcheck.c in the plugins/nsapi/examples subdirectory within the server root directory.

#include "nsapi.h"
/*     Set to NULL to prevent problems with people not calling
    acf-init */
static char **hosts = NULL;
#include <stdio.h>
#include "base/daemon.h"
#include "base/util.h"      /* util_sprintf */
#include "frame/log.h"      /* log_error */
#include "frame/protocol.h" /* protocol_status */
/* The longest line we’ll allow in an access control file */
#define MAX_ACF_LINE 256
/* Used to free static array on restart */
#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC void acf_free(void *unused)
{
    register int x;
    for(x = 0; hosts[x]; ++x)
        FREE(hosts[x]);
    FREE(hosts);
    hosts = NULL;
}
#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC int acf_init(pblock *pb, Session *sn, Request *rq)
{
    /* Parameter */
    char *acf_file = pblock_findval("file", pb);
    /* Working variables */
    int num_hosts;
    FILE *f;
    char err[MAGNUS_ERROR_LEN];
    char buf[MAX_ACF_LINE];
    /*     Check usage. Note that Init functions have special
         error logging */
    if(!acf_file) {
        util_sprintf(err, "missing parameter to acf_init
             (need file)");
        pblock_nvinsert("error", err, pb);
        return REQ_ABORTED;
    }
    f = fopen(acf_file, "r");
    /* Did we open it? */
    if(!f) {
        util_sprintf(err, "can’t open access control file %s (%s)",
            acf_file, system_errmsg());
        pblock_nvinsert("error", err, pb);
        return REQ_ABORTED;
    }
    /* Initialize hosts array */
    num_hosts = 0;
    hosts = (char **) MALLOC(1 * sizeof(char *));
    hosts[0] = NULL;
    while(fgets(buf, MAX_ACF_LINE, f)) {
        /* Blast linefeed that stdio helpfully leaves on there */
        buf[strlen(buf) - 1] = ’\\0’;
        hosts = (char **) REALLOC(hosts, (num_hosts + 2) *
             sizeof(char *));
        hosts[num_hosts++] = STRDUP(buf);
        hosts[num_hosts] = NULL;
    }
    fclose(f);
    /* At restart, free hosts array */
    daemon_atrestart(acf_free, NULL);
    return REQ_PROCEED
}
#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC int restrict_by_acf(pblock *pb, Session *sn, Request *rq)
{
    /* No parameters */
    /* Working variables */
    char *remip = pblock_findval("ip", sn->client);
    register int x;
    if(!hosts) {
        log_error(LOG_MISCONFIG, "restrict-by-acf", sn, rq,
             "restrict-by-acf called without call to acf-init");
        /*     When we abort, the default status code is 500 Server
             Error */
        return REQ_ABORTED;
    }
    for(x = 0; hosts[x] != NULL; ++x) {
        /* If they’re on the list, they’re allowed */
        if(!strcmp(remip, hosts[x]))
        return REQ_NOACTION;
    }
    /* Set response code to forbidden and return an error. */
    protocol_status(sn, rq, PROTOCOL_FORBIDDEN, NULL);
    return REQ_ABORTED;
}

ObjectType Example

The example in this section demonstrates how to implement html2shtml, a custom SAF that instructs the server to treat a .html file as an .shtml file if an .shtml version of the requested file exists.

The ObjectType function checks whether the content type is already set. If the type is set, returns ObjectType REQ_NOACTION.


if(pblock_findval("content-type", rq->srvhdrs))
    return REQ_NOACTION;

      

The primary task an ObjectType directive needs to perform is to set the content type if it is not already set. This example sets the content type to magnus-internal/parsed-html in the following lines:


/* Set the content-type to magnus-internal/parsed-html */
pblock_nvinsert("content-type", "magnus-internal/parsed-html",
    rq->srvhdrs);

The html2shtml function checks at the requested file name. If the filename ends with .html, the function checks for a file with the same base name but with the extension .shtml instead. If such a file is found, the function uses that path and informs the server that the file is parsed HTML instead of regular HTML. This process requires an extra stat call for every HTML file accessed.

Installing the ObjectType Example

To load the shared object containing your function, add the following line in the Init section of the obj.conf file:

Init fn=load-modules shlib=yourlibrary funcs=html2shtml

To execute the custom SAF during the request-response process for some object, add the following line to that object in the obj.conf file:

ObjectType fn=html2shtml

ObjectType Example Source Code

The source code for this example is in otype.c in the nsapi/examples/ or plugins/nsapi/examples subdirectory within the server root directory.

#include "nsapi.h"
#include <string.h>    /* strncpy */
#include "base/util.h"

#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC int html2shtml(pblock *pb, Session *sn, Request *rq)
{
    /* No parameters */

    /* Work variables */
    pb_param *path = pblock_find("path", rq->vars);
    struct stat finfo;
    char *npath;
    int baselen;

    /* If the type has already been set, don’t do anything */
    if(pblock_findval("content-type", rq->srvhdrs))
        return REQ_NOACTION;

    /* If path does not end in .html, let normal object types do
     * their job */
    baselen = strlen(path->value) - 5;
    if(strcasecmp(&path->value[baselen], ".html") != 0)
        return REQ_NOACTION;

    /* 1 = Room to convert html to shtml */
    npath = (char *) MALLOC((baselen + 5) + 1 + 1);
    strncpy(npath, path->value, baselen);
    strcpy(&npath[baselen], ".shtml");

    /* If it’s not there, don’t do anything */
    if(stat(npath, &finfo) == -1) {
        FREE(npath);
        return REQ_NOACTION;
    }
    /* Got it, do the switch */
    FREE(path->value);
    path->value = npath;

    /* The server caches the stat() of the current path. Update it. */
    (void) request_stat_path(NULL, rq);

    pblock_nvinsert("content-type", "magnus-internal/parsed-html",
                     rq->srvhdrs);
    return REQ_PROCEED;
}

Output Example

This section describes an example NSAPI filter named example-replace, which examines outgoing data and substitutes one string for another. The example shows how you can create a filter that intercepts and modifies outgoing data.

Installing the Output Example

To load the filter, add the following line in the Init section of the obj.conf file:


Init fn="load-modules" shlib="<path>/replace.
            ext" NativeThread="no"

         

To execute the filter during the request-response process for some object, add the following line to that object in the obj.conf file:


Output fn="insert-filter" type="text/*" filter="example-replace" 
	from="iPlanet" to="Sun ONE"

Output Example Source Code

The source code for this example is in the replace.c file in the plugins/nsapi/examples subdirectory of the server root directory.

#ifdef XP_WIN32
#define NSAPI_PUBLIC __declspec(dllexport)
#else /* !XP_WIN32 */
#define NSAPI_PUBLIC
#endif /* !XP_WIN32 */

/*
  * nsapi.h declares the NSAPI interface.
 */
#include "nsapi.h"


/* -------------------ExampleReplaceData------------------------- */

/*
 * ExampleReplaceData will be used to store information between
 * filter method invocations. Each instance of the example-replace
 * filter will have its own ExampleReplaceData object.
*/

typedef struct ExampleReplaceData ExampleReplaceData;

struct ExampleReplaceData {
    char *from;   /* the string to replace */
    int fromlen;  /* length of "from" */
    char *to;     /* the string to replace "from" with */
    int tolen;    /* length of "to" */
    int matched;  /* number of "from" chars matched */
};


/* -------------- example_replace_insert ------------------------ */

/*
 * example_replace_insert implements the example-replace filter’s
 * insert method. The insert filter method is called before the
 * server adds the filter to the filter stack.
*/

#ifdef __cplusplus
extern "C"
#endif
int example_replace_insert(FilterLayer *layer, pblock *pb)
{
    const char *from;
    const char *to;
    ExampleReplaceData *data;

    /*
     * Look for the string to replace, "from", and the string to
     * replace it with, "to". Both values are required.
    */
    from = pblock_findval("from", pb);
    to = pblock_findval("to", pb);
    if (from == NULL || to == NULL || strlen(from) < 1) {
        log_error(LOG_MISCONFIG, "example-replace-insert",
                  layer->context->sn, layer->context->rq,
                  "missing parameter (need from and to)");
        return REQ_ABORTED; /* error preparing for insertion */
    }

    /*
     * Allocate an ExampleReplaceData object that will store
     * configuration and state information.
     */
    data = (ExampleReplaceData *)MALLOC(sizeof(ExampleReplaceData));
    if (data == NULL)
        return REQ_ABORTED; /* error preparing for insertion */

    /* Initialize the ExampleReplaceData */
    data->from = STRDUP(from);
    data->fromlen = strlen(from);
    data->to = STRDUP(to);
    data->tolen = strlen(to);
    data->matched = 0;

    /* Check for out of memory errors */
    if (data->from == NULL || data->to == NULL) {
        FREE(data->from);
        FREE(data->to);
        FREE(data);
        return REQ_ABORTED; /* error preparing for insertion */
    }

    /*
     * Store a pointer to the ExampleReplaceData object in the
     * FilterLayer. This information can then be accessed from other
     * filter methods.
     */
    layer->context->data = data;

    /* Remove the Content-length: header if we might change the
     * body length */
    if (data->tolen != data->fromlen) {
        pb_param *pp;
        pp = pblock_remove("content-length", layer->context->rq->srvhdrs);
        if (pp)
             param_free(pp);
    }

    return REQ_PROCEED; /* insert filter */
}


/* -------------- example_replace_remove ------------------------ */

/*
 * example_replace_remove implements the example-replace filter’s
 * remove method. The remove filter method is called before the
 * server removes the filter from the filter stack.
 */

#ifdef __cplusplus
extern "C"
#endif
void example_replace_remove(FilterLayer *layer)
{
    ExampleReplaceData *data;

    /* Access the ExampleReplaceData we allocated in 
		example_replace_insert */
    data = (ExampleReplaceData *)layer->context->data;

    /* Send any partial "from" match */
    if (data->matched > 0)
        net_write(layer->lower, data->from, data->matched);

    /* Destroy the ExampleReplaceData object */
    FREE(data->from);
    FREE(data->to);
    FREE(data);
}


/* -------------- example_replace_write ------------------------- */

/*
* example_replace_write implements the example-replace filter’s
* write method. The write filter method is called when there is data
* to be sent to the client.
*/

#ifdef __cplusplus
extern "C"
#endif
int example_replace_write(FilterLayer *layer, const void *buf, int amount)
{
    ExampleReplaceData *data;
    const char *buffer;
    int consumed;
    int i;
    int unsent;
    int rv;

    /* Access the ExampleReplaceData we allocated in 
		example_replace_insert */
    data = (ExampleReplaceData *)layer->context->data;

    /* Check for "from" matches in the caller’s buffer */
    buffer = (const char *)buf;
    consumed = 0;
    for (i = 0; i < amount; i++) {
        /* Check whether this character matches */
        if (buffer[i] == data->from[data->matched]) {
             /* Matched a(nother) character */
            data->matched++;

            /* If we’ve now matched all of "from"... */
            if (data->matched == data->fromlen) {
                /* Send any data that preceded the match */
                unsent = i + 1 - consumed - data->matched;
                 if (unsent > 0) {
                    rv = net_write(layer->lower, &buffer[consumed], unsent);
                    if (rv != unsent)
                         return IO_ERROR;
                }

                /* Send "to" in place of "from" */
                rv = net_write(layer->lower, data->to, data->tolen);
                if (rv != data->tolen)
                     return IO_ERROR;

                /* We’ve handled up to and including buffer[i] */
                 consumed = i + 1;

                /* Start looking for the next "from" match from scratch */
                data->matched = 0;
            }

        } else if (data->matched > 0) {
            /* This match didn’t pan out, we need to backtrack */
            int j;
            int backtrack = data->matched;
            data->matched = 0;

            /* Check for other potential "from" matches
             * preceding buffer[i] */
            for (j = 1; j < backtrack; j++) {
                /* Check whether this character matches */
                if (data->from[j] == data->from[data->matched]) {
                    /* Matched a(nother) character */
                    data->matched++;

                } else if (data->matched > 0) {
                   /* This match didn’t pan out, we need to
                    * backtrack */
                    j -= data->matched;
                    data->matched = 0;
                }
            }

            /* If the failed (partial) match begins before the buffer... */
             unsent = backtrack - data->matched;
             if (unsent > i) {
                /* Send the failed (partial) match */
                rv = net_write(layer->lower, data->from, unsent);
                if (rv != unsent)
                    return IO_ERROR;

                /* We’ve handled up to, but not including,
                 * buffer[i] */
                consumed = i;
            }

            /* We’re not done with buffer[i] yet */
            i--;
        }
    }

    /* Send any data we know won’t be part of a future
     * "from" match */
    unsent = amount - consumed - data->matched;
    if (unsent > 0) {
        rv = net_write(layer->lower, &buffer[consumed], unsent);
        if (rv != unsent)
             return IO_ERROR;
    }

    return amount;
}


/* ---------------- nsapi_module_init --------------------------- */

/*
 * This is the module initialization entry point for this NSAPI
 * plugin. The server calls this entry point in response to the
 * Init fn="load-modules" line in magnus.conf.
 */

NSAPI_PUBLIC nsapi_module_init(pblock *pb, Session *sn, Request *rq)
{
    FilterMethods methods = FILTER_METHODS_INITIALIZER;
    const Filter *filter;

    /*
     * Create the example-replace filter. The example-replace filter
     * has order FILTER_CONTENT_TRANSLATION, meaning it transforms
     * content (entity body data) from one form to another. The
     * example-replace filter implements the write filter method,
     * meaning it is interested in outgoing data.
     */
    methods.insert = &example_replace_insert;
    methods.remove = &example_replace_remove;
    methods.write = &example_replace_write;
    filter = filter_create("example-replace",
                           FILTER_CONTENT_TRANSLATION,
                            &methods);
    if (filter == NULL) {
        pblock_nvinsert("error", system_errmsg(), pb);
        return REQ_ABORTED; /* error initializing plugin */
    }

    return REQ_PROCEED; /* success */
}

Service Example

This section discusses a very simple Service function called simple_service. This function sends a message in response to a client request. The message is initialized by the init_simple_service function during server initialization.

For a more complex example, see the file service.c in the examples directory, which is discussed in More Complex Service Example

Installing the Service Example

To load the shared object containing your functions, add the following line in the Init section of the obj.conf file:


Init fn=load-modules shlib=
            yourlibrary funcs=simple-service-init,simple-service

         

To call the simple-service-init function to initialize the message representing the generated output, add the following line to the Init section in obj.conf. This line must appear after the line that loads the library containing simple-service-init.)


Init fn=simple-service-init
 generated-output="<H1>
            Generated output msg</H1>"

         

To execute the custom SAF during the request-response process for an object, add the following line to that object in the obj.conf file:

Service type="text/html" fn=simple-service

The type="text/html" argument indicates that this function is invoked during the Service stage only if the content-type has been set to text/html.

Service Example Source Code

#include <nsapi.h>
static char *simple_msg = "default customized content";
/* This is the initialization function.
 * It gets the value of the generated-output parameter
 * specified in the Init directive in magnus.conf
*/
NSAPI_PUBLIC int init-simple-service(pblock *pb, Session *sn,
 Request *rq)
{
    /* Get the message from the parameter in the directive in
      * magnus.conf
     */
    simple_msg = pblock_findval("generated-output", pb);    
    return REQ_PROCEED;
}
/* This is the customized Service SAF.
 * It sends the "generated-output" message to the client.
*/
NSAPI_PUBLIC int simple-service(pblock *pb, Session *sn, Request *rq)
 {    
     int return_value;
    char msg_length[8];    
    /* Use the protocol_status function to set the status of the
     * response before calling protocol_start_response.
    */
    protocol_status(sn, rq, PROTOCOL_OK, NULL);
    /* Although we would expect the ObjectType stage to
     * set the content-type, set it here just to be
    * completely sure that it gets set to text/html.
    */
    param_free(pblock_remove("content-type", rq->srvhdrs));
    pblock_nvinsert("content-type", "text/html", rq->srvhdrs);
    /* If you want to use keepalive, need to set content-length header.    
    * The util_itoa function converts a specified integer to a
     * string, and returns the length of the string. Use this
     * function to create a textual representation of a number.
    */
    util_itoa(strlen(simple_msg), msg_length);
    pblock_nvinsert("content-length", msg_length, rq->srvhdrs);
    /* Send the headers to the client*/
    return_value = protocol_start_response(sn, rq);
     if (return_value == REQ_NOACTION) {
         /* HTTP HEAD instead of GET */
         return REQ_PROCEED;
     }
    /* Write the output using net_write*/
    return_value = net_write(sn->csd, simple_msg,
         strlen(simple_msg));
     if (return_value == IO_ERROR) {
         return REQ_EXIT;
     }
    return REQ_PROCEED;
}

More Complex Service Example

The send-images function is a custom SAF that replaces the doit.cgi demonstration available on the iPlanet home pages. When a file is accessed as /dir1/dir2/something.picgroup, the send-images function checks whether the file is being accessed by a Mozilla/1.1 browser. If the file is not being accessed, the function sends a short error message. The file something.picgroup contains a list of lines, each of which specifies a file name followed by a content-type, for example, one.gif image/gif.

To load the shared object containing your function, add the following line at the beginning of the obj.conf file:

Init fn=load-modules shlib=yourlibrary funcs=send-images

Also, add the following line to the mime.types file:

type=magnus-internal/picgroup exts=picgroup

To execute the custom SAF during the request-response process for an object, add the following line to that object in the obj.conf file. send-images takes an optional parameter, delay, which is not used for this example.


Service method=(GET|HEAD) type=magnus-internal/picgroup fn=send-images

         

The source code is located in service.c in the plugins/nsapi/examples subdirectory within the server root directory.

AddLog Example

The example in this section demonstrates how to implement brief-log, a custom SAF for logging only three items of information about a request: the IP address, the method, and the URI, for example, 198.93.95.99 GET /jocelyn/dogs/homesneeded.html.

Installing the AddLog Example

To load the shared object containing your functions, add the following line in the Init section of the magnus.conf file:

Init fn=load-modules shlib=yourlibrary funcs=brief-init,brief-log

To call brief-init to open the log file, add the following line to the Init section in obj.conf. This line must appear after the line that loads the library containing brief-init.)

Init fn=brief-init file=/tmp/brief.log

To execute your custom SAF during the AddLog stage for an object, add the following line to that object in the obj.conf file:

AddLog fn=brief-log

AddLog Example Source Code

The source code is in addlog.c is in the plugins/nsapi/examples subdirectory within the server root directory.

#include "nsapi.h"
#include "base/daemon.h" /* daemon_atrestart */
#include "base/file.h"   /* system_fopenWA, system_fclose */
#include "base/util.h"   /* sprintf */


/* File descriptor to be shared between the processes */

static SYS_FILE logfd = SYS_ERROR_FD;

#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC void brief_terminate(void *parameter)
{
    system_fclose(logfd);
    logfd = SYS_ERROR_FD;
}

#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC int brief_init(pblock *pb, Session *sn, Request *rq)
{
    /* Parameter */
    char *fn = pblock_findval("file", pb);

    if(!fn) {
        pblock_nvinsert("error", "brief-init: please supply a file name", 
			pb); return REQ_ABORTED;
    }
    logfd = system_fopenWA(fn);
    if(logfd == SYS_ERROR_FD) {
        pblock_nvinsert("error", "brief-init: please supply a file name", 
			pb);return REQ_ABORTED;
    }
    /* Close log file when server is restarted */
    daemon_atrestart(brief_terminate, NULL);
    return REQ_PROCEED;
}

#ifdef __cplusplus
extern "C"
#endif
NSAPI_PUBLIC int brief_log(pblock *pb, Session *sn, Request *rq)
{
    /* No parameters */

    /* Server data */
    char *method = pblock_findval("method", rq->reqpb);
    char *uri = pblock_findval("uri", rq->reqpb);
    char *ip = pblock_findval("ip", sn->client);

    /* Temp vars */
    char *logmsg;
    int len;

    logmsg = (char *)
        MALLOC(strlen(ip) + 1 + strlen(method) + 1 + strlen(uri) + 1 + 1);
    len = util_sprintf(logmsg, "%s %s %s\\n", ip, method, uri);
    /* The atomic version uses locking to prevent interference */
    system_fwrite_atomic(logfd, logmsg, len);
    FREE(logmsg);

    return REQ_PROCEED;
}