DEVEDGE CHAMPIONS FOR THE NSAPI NEWSGROUP Philippe Le Berre, works for Hewlett-Packard in Grenoble (France) and specializes in e-commerce and Internet security.
Scott Leerssen, works for Hewlett-Packard in Atlanta (USA) and specializes in Internet and system security.
There are two instances where the NSAPI can be particularly useful:
char *base = pblock_findval("ntrans-base",rq->vars); char *uri = pblock_findval("uri",rq->reqpb); char *trans = (char *)CALLOC(sizeof(char) * (strlen(base) + strlen(uri) + 1)); strcpy(trans,base); strcat(trans,uri); |
Platform | Command lines |
---|---|
Solaris 2.x (SPARC) | gcc -DXP_Unix -DMCC_HTTPD -DNET_SSL -DSOLARIS -D_REENTRANT -Wall -c
module.c
ld -G module.o -o module.so |
IRIX | cc -DXP_Unix -DMCC_HTTPD -DNET_SSL -DIRIX -D_SGI_MP_SOURCE -fullwarn
-c module.c
ld -32 -shared module.o -o module.so |
Windows NT (VC++ 4.0) | cl -LD -MD -DMCC_HTTPD -DXP_WIN32 -DNET_SSL module.c -Insapi\include /link nsapi\examples\libhttpd.lib |
HP -UX | cc -DXP_Unix -DMCC_HTTPD -DNET_SSL -DHPUX -D_HPUX_SOURCE -Aa +DA1.0
+z -c module.c
ld -b module.o -o module.so |
SunOS 4.x | gcc -DXP_Unix -DMCC_HTTPD -DNET_SSL -DSUNOS4 -fpic -c module.c
ld -assert pure-text |
OSF/1 or Digital Unix | cc -threads -DXP_Unix -DMCC_HTTPD -DNET_SSL -DOSF1 -c module.c
ld -shared -all -expect_unresolved "*" module.o -o module.so |
extern "C" NSAPI_PUBLIC int myNSAPIFunc(Session *sn, Request *rq, pblock *pb) { [...] } |
You will also need to wrap NSAPI header files under the extern "C" brackets like:
extern "C" { #include <base/...> #include <frame/...> } |
gcc -g, and dbx ns-httpd -d/your conf directory/Then set breakpoints where you need them, and run. You MAY have to copy some .so libs to your current dir, dbx crashes if it can't open .so's which are loaded into current executableThis didn't work for Zasha Weinberg, who suggests:
Someone in another thread [...] suggested dbx ns-httpd -d/config dir/This didn't work for me. What seems to happen is that NES arranges for the process with pid=1 to run it, then exits. Then when it gets run by #1, it forks a child. For debugging an NSAPI, you have to attach to the child. Look for the ns-httpd process with the highest PID.
3.1: How do I set a cookie with NSAPI?
pblock_nvinsert("set-cookie", "myCookie=Mozilla_Chip", rq->srvhdrs); |
char * value; request_header("cookie", &value, sn, rq); |
char * value; value = pblock_findval("cookie", rq->headers); |
pblock_nvinsert("set-cookie", "AuthUser=Yes; path=/", rq->srvhdrs); pblock_nvinsert("Location", diversion, rq->srvhdrs); protocol_status(sn, rq, PROTOCOL_REDIRECT, NULL); protocol_start_response(sn, rq); // Here this is needed return REQ_ABORTED; |
4.1: What's the deal with NSAPI and threads?
Server | Version | Unix | NT |
---|---|---|---|
Commerce/Communications | 1.* | User | Native |
Enterprise | 2.0 | User | Native |
Enterprise | 2.01 | Solaris and Irix are Native HP-UX see Tech Note 980304-6 All others User |
Native |
When programming with threads on natively-threaded servers, it's safe to use the native thread libraries (and typically link with -lpthread or -lthread). On user-level-threaded servers, you should only use the NSAPI-provided threads.
Mixing natively-threaded libraries with user-level-threaded servers or non-threadsafe code with natively-threaded servers requires extreme care to ensure that all static or global data access are protected by some locking system.
4.2: What does the message "select thread miss" mean, and what can I do about it?
4.3: How do I use NSAPI semaphores, critical sections and condition variables (condvars)?
The sem_* routines in NSAPI are not really semaphores in the traditional sense, they're really inter-process locks. Sem_init function takes as parameter a name. When sem_init is called, the server tries to create a file in /tmp for the lock, so if there's no /tmp on your machine or if the parameters you're passing for name and id are causing it to try to create a file in /tmp that already exists (with no-write permissions) then this might cause a failure. When you want to use shared memory to communicate between processes, you would create a shared memory arena and a lock (such as a sem) in an Init function, and have that inherited by each child. Each child can then grab the sem, modify its portion of the data, and release the sem.
The crit_* functions are used for controlling access to thread-specific resources. I've used the crit_* functions many times, typically to prevent multiple server threads from attempting to use a library function that isn't thread-safe, or to control write access to static variables.Basically you'll need to write 3 NSAPI functions: an Init directive, a daemon_atrestart() hook function, and of course the NSAPI directive that needs to lock a thread-specific resource. I've only addressed the specific calls, not complete NSAPI functions - the normal examples cover creating functions. You'll need to include from the NSAPI include tree.
Condvars are merely another form of thread synchronization. They provide a way for you to have a thread wait for another thread to notify it that a certain condition has been met.Suppose you have a variable, count, and you want to know when it reaches 5. To initialize things at server startup, you could do:
The other threads would then do:
Note that contrary to a vague comment in the crit.h header file, if multiple threads have called condvar_wait on the same condition, only one will be awakened by condvar_notify.
4.4: How do I set the thread stack size?
There are a couple of different things you can do, none of them will sound very appealing I'm sure. One way to do it is to simply cast the structure pointer to a char *. This will work as long as the pblock is not rq->headers, and as long as the structure can be freed with a call to FREE(). The reason this should work is that the server does not actually look at the values which the server did not produce for the entries in the vars pblock.
Another way to do it is to sprintf the pointer into a string, and then sscanf it back. This is ugly and will not automatically free the structure when the request is finished, but is safe.
This problem is due to the fact that pblocks do not allow arbitrary types, a limitation which is being looked at and may be addressed in 3.0.
pblock_nvinsert("Name", "Value1", rq->vars); pblock_nvinsert("Name", "Value2", rq->vars); pblock_nvinsert("Name", "Value3", rq->vars); |
value=pblock_pblock2str(rq->vars, NULL); /* You will get the string as * Name="Value3" Name="Value2" Name="Value1" * see the string formed is also in LIFO order */ value = pblock_findval("Name", rq->vars); // You will get "Value3" param_free(pblock_remove("Name", rq->vars)); value = pblock_findval("Name", rq->vars); // You will get "Value2" param_free(pblock_remove("Name", rq->vars)); value = pblock_findval("Name", rq->vars); // You will get "Value1" |
#include "nsapi.h" NSAPI_PUBLIC int print_pblocks(pblock *pb, Session *sn, Request *rq) { char *directive; char *block; directive = pblock_findval("directive", pb); if ( directive == NULL ) { directive = "print-pblocks"; } log_error(LOG_INFORM, directive, sn, rq, "--- start ---", block); block = pblock_pblock2str(pb, NULL); log_error(LOG_INFORM, directive, sn, rq, "pb: %s", block); FREE(block); block = pblock_pblock2str(rq->srvhdrs, NULL); log_error(LOG_INFORM, directive, sn, rq, "rq->srvhdrs: %s", block); FREE(block); block = pblock_pblock2str(rq->reqpb, NULL); log_error(LOG_INFORM, directive, sn, rq, "rq->reqpb: %s", block); FREE(block); block = pblock_pblock2str(rq->vars,NULL); log_error(LOG_INFORM, directive, sn, rq, "rq->vars: %s", block); FREE(block); block = pblock_pblock2str(rq->headers,NULL); log_error(LOG_INFORM, directive, sn, rq, "rq->headers: %s", block); FREE(block); block = pblock_pblock2str(sn->client,NULL); log_error(LOG_INFORM, directive, sn, rq, "rq->client: %s", block); FREE(block); log_error(LOG_INFORM, directive, sn, rq, "--- end ---", block); return REQ_NOACTION; } |
<Object name=default> AuthTrans print-pblocks directive=AuthTrans . . . NameTrans print-pblocks directive=NameTrans . . . PathCheck print-pblocks directive=PathCheck . . . ObjectType print-pblocks directive=ObjectType . . . Service print-pblocks directive=Service . . . AddLog print-pblocks directive=AddLog . . . </Object> |
for (i=0; i<rq->headers->hsize; i++) { for (p=rq->headers->ht[i]; p; p = p->next) { /* do what you want here p->param->name is the value name p->param->value is the value */ } } |
Simple document redirect
To simply send the client to another document, wait until the PathCheck directive find-pathinfo has run and change the path value in the parameter block:
char *newpath = "/my/new/document"; ... param_free(pblock_remove("path",rq->vars)); pblock_nvinsert("path",newpath,rq->vars); return REQ_NOACTION; |
Redirect with new Location
To send the client to a new document, but warn them in the Location field of their browser, simulate a PROTOCOL_REDIRECT:
char *newpath = "/my/new/document"; ... pblock_nvinsert("Location", newpath, rq->srvhdrs); protocol_status(sn, rq, PROTOCOL_REDIRECT, NULL); protocol_start_response(sn, rq); return REQ_ABORTED; |
CGI Redirect with GET/POST method
You could turn POST into a GET if you are confident that the query string is short enough to be supported by GET
#include "nsapi.h" NSAPI_PUBLIC int cgi_redirect(pblock *pb,Session *sn,Request *rq) { /* This redirects a POST to a script named "spam.cgi" to the "real" CGI script "echo.cgi" */ char *old = pblock_findval("uri",rq->reqpb); char *find; find = strstr(old, "spam.cgi"); if ( find != NULL ) { param_free(pblock_remove("path", rq->vars)); pblock_nvinsert("path", "/serverpath/netscape/cgi-bin/echo.cgi", rq->vars); } return REQ_NOACTION; } |
<Object name=cgi> ObjectType fn=force-type type=magnus-internal/cgi Service fn="cgi_redirect" method="POST" Service fn=send-cgi </Object> |
The "normal" processing of user authentication uses a combination of the AuthTrans and PathCheck functions, where AuthTrans simply gathers all the information necessary to identify a potential user and PathCheck says yea or nay based on the user and what he's trying to get to. If you simply want to allow/deny access, you could do all your processing in an AuthCheck function or userfn hung off of basic-auth.
Here is a little description of how it works since it confuses a lot of people when the first user access blows right past their basic-auth userfn:
request_header("authorization", &authstr, sn, rq); |
authstr = pblock_findval("authorization", rq->headers); |
NSAPI_PUBLIC int myUserPass(Session *sn, Request *rq, pblock *pb) { char *username, *password; username = pblock_findval("user", pb); password = pblock_findval("pw", pb); [...] } |
char * username = pblock_findval("auth-user", rq->vars); |
Note that MD5 and randoms functions are available (since 3.0), see nsapi.h.
Two warnings:
int save_pos = sn->inbuf->pos; post2qstr(); sn->inbuf->pos = save_pos; |
Steve Buffum wrote an NSAPI proxy to do this, although he stresses that he would, "stop short of globally recommending its approach." The idea behind the code is:
Another proxy, a CGI proxy written by Scott Leerssen provides a way to pre and post process CGI output if you so desire. Again, not really an elegant approach, and certainly not speedy.
pblock_nvinsert("my_var",rq->headers); |
By comparing the time provided by the stat() call with the time of the request, you can determine whether the content has been modified. If not, you can signal this to the browser by the appropriate setting of the return status:
protocol_status(sn, rq, PROTOCOL_NOT_MODIFIED, NULL); return REQ_PROCEED; |
The way to fix the problem with MSIE4, is to set the 'HTTP Persistent Connection Timeout:' value to 0 in our NS3 server (it's under Server Preferences, Performance Tuning, Advanced Settings).Within an NSAPI directive, you can manually close the client socket with net_close() or protocol_finish_request(sn, rq), although this may have unintended side-effects. The patch from Netscape is sure to be a cleaner solution.
In my humble opinion, the non-blocking read and systhread_sleep() is the best choice, and you can use the NSAPI_PUBLIC int net_makenonblocking(SYS_NETFD sd) function to set your socket to non-blocking for a somewhat system-independent implementation.I use this method in my own send-cgi() implementation for POSTs, and it works nicely with a 10ms sleep-time.
The problem seems to be caused when calling ct_init. The Sybase libraries never return. It must call assert and kill the thread. As Dave Orwant writes below, this is a known bug with the Sybase software:I just got off the phone with Sybase tech support about this problem. The support person, Heidi, told me that they have three open cases concerning this problem, and are submitting it to engineering with a high priority. She told me when engineering delivers a fix they will issue an immediate EBF to ctlib 11.1.
#define server_portnum conf_getglobals()->Vport #define server_hostname conf_getglobals()->Vserver_hostname |
param_free( pblock_remove("Content-type", rq->srvhdrs)); pblock_nvinsert( "Content-type", application/mine ); pblock_nvinsert( "Content-disposition", "filename=file.mine", rq->srvhdrs ); |
Content contributed voluntarily by our awesome DevEdge Champions based on peer-to-peer discussion in the DevEdge newsgroups. While Netscape hosts them, it does not offer any direct developer support via the newsgroups.
This FAQ is intended as a developer resource. Any reference to third parties or third-party products should not be construed as an endorsement by Netscape Communications Corporation.