/*============================================================================ project: apache file: mod_libpq.c author: Andrew Smith date: 2005-11-13 language: C NOTES Apache module for creating persistent connections to PostgreSQL. Copyright 2005, Andrew Smith ------------------------------------------------------------------------------ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ------------------------------------------------------------------------------ -- INSTALLATION -------------------------------------------------------------- # requires minimum of apache 1.3 and postgresql 7.4 development libraries apxs -c -I /usr/include/postgresql/ -lpq -o mod_libpq.so mod_libpq.c install -s -m 644 mod_libpq.so /usr/lib/apache/1.3/ ------------------------------------------------------------------------------ -- CONFIGURATION (add to httpd.conf) ----------------------------------------- LoadModule libpq_module /usr/lib/apache/1.3/mod_libpq.so SetHandler libpq-request # mandatory parameter libpqConnection "host=localhost dbname=mydb" # optional parameters libpqSystem "mydb@grover" libpqHeadersQuery "SELECT cgi.Headers($1,$2,$3,$4,$5,$6)" libpqContentQuery "SELECT cgi.Content($1)" ------------------------------------------------------------------------------ -- IMPLEMENTATION (add to mydb) ---------------------------------------------- CREATE SCHEMA cgi; SET SEARCH_PATH = cgi; GRANT USAGE ON SCHEMA cgi TO "www-data"; -- preprocess the request and return a unique token for the cookie CREATE FUNCTION Headers(TEXT,TEXT,TEXT,TEXT,TEXT,TEXT) RETURNS TEXT AS $$ DECLARE aSystem ALIAS FOR $1; aAddress ALIAS FOR $2; aCookie ALIAS FOR $3; aQuery ALIAS FOR $4; aContent ALIAS FOR $5; aBrowser ALIAS FOR $6; vToken TEXT; BEGIN vToken := TEXT(CURRENT_TIMESTAMP); RETURN vToken; END; $$ LANGUAGE plpgsql VOLATILE; -- generate the response for the request identified by the token CREATE FUNCTION Content(TEXT) RETURNS TEXT AS $$ DECLARE aToken ALIAS FOR $1; vDocument TEXT; BEGIN vDocument := 'Hello world!\nYour cookie is ' || aToken; RETURN vDocument; END; $$ LANGUAGE plpgsql VOLATILE; ------------------------------------------------------------------------------ ============================================================================*/ // include files ------------------------------------------------------------- #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_main.h" #include "http_protocol.h" #include "util_script.h" #include #include #include #include #include #include #include #include #include #define kVersion "mod_libpq-0.0a2" // Headers(System,Address,Query,Cookie,Submit,Browser) => Token // Content(Token) => Content #define kHeadersQuery "SELECT cgi.Headers($1,$2,$3,$4,$5,$6)" #define kContentQuery "SELECT cgi.Content($1)" module MODULE_VAR_EXPORT libpq_module; typedef struct tConfig { char *ConnectionP; char *SystemP; char *HeadersQueryP; char *ContentQueryP; } tConfig; typedef struct tVirtual { struct tVirtual *VirtualP; server_rec *ServerP; PGconn *DatabaseP; } tVirtual; static tVirtual *gVirtualP; //---------------------------------------------------------------------------- // local functions ----------------------------------------------------------- #define kMaxRetries 3 // attempt to execute a query static PGresult *CallSql ( PGconn *DatabaseP, const char *QueryP, const char **ArgumentsP, int Count ) { PGresult *ResultP; int Retries = 0; ResultP = PQexecParams ( DatabaseP, QueryP, Count, NULL, ArgumentsP, NULL, NULL, 0 ); while ( (Retries++ < kMaxRetries) && (PQstatus(DatabaseP) == CONNECTION_BAD) ) { PQclear(ResultP); PQreset(DatabaseP); ResultP = PQexecParams ( DatabaseP, QueryP, Count, NULL, ArgumentsP, NULL, NULL, 0 ); } if ( (PQstatus(DatabaseP) == CONNECTION_BAD) || (PQresultStatus(ResultP) != PGRES_TUPLES_OK) ) { PQclear(ResultP); ResultP = NULL; } return ResultP; } // get the configuration from a server record static tConfig* ServerConfigP(server_rec *ServerP) { return (tConfig*) ap_get_module_config(ServerP->module_config, &libpq_module); } //---------------------------------------------------------------------------- // command handlers ---------------------------------------------------------- // receive the interface database connection string static const char* Connection(cmd_parms *CommandP, void *ModuleP, char *ValueP) { server_rec *ServerP = CommandP->server; tConfig *ConfigP = ServerConfigP(ServerP); ConfigP->ConnectionP = ap_pstrdup(CommandP->pool,ValueP); return NULL; } // receive the database system code static const char* System(cmd_parms *CommandP, void *ModuleP, char *ValueP) { server_rec *ServerP = CommandP->server; tConfig *ConfigP = ServerConfigP(ServerP); ConfigP->SystemP = ap_pstrdup(CommandP->pool,ValueP); return NULL; } // receive the headers query string static const char* HeadersQuery(cmd_parms *CommandP, void *ModuleP, char *ValueP) { server_rec *ServerP = CommandP->server; tConfig *ConfigP = ServerConfigP(ServerP); ConfigP->HeadersQueryP = ap_pstrdup(CommandP->pool,ValueP); return NULL; } // receive the content query string static const char* ContentQuery(cmd_parms *CommandP, void *ModuleP, char *ValueP) { server_rec *ServerP = CommandP->server; tConfig *ConfigP = ServerConfigP(ServerP); ConfigP->ContentQueryP = ap_pstrdup(CommandP->pool,ValueP); return NULL; } // command handler descriptors static const command_rec CommandHandlers[] = { { "LibpqConnection", Connection, NULL, RSRC_CONF, TAKE1, "interface database connection string" }, { "LibpqSystem", System, NULL, RSRC_CONF, TAKE1, "database system code" }, { "LibpqHeadersQuery", HeadersQuery, NULL, RSRC_CONF, TAKE1, "query to process and return headers" }, { "LibpqContentQuery", ContentQuery, NULL, RSRC_CONF, TAKE1, "query to process and return content" }, {NULL} }; //---------------------------------------------------------------------------- // content handlers ---------------------------------------------------------- enum tArgument { kSystemArgument, kAddressArgument, kCookieArgument, kQueryArgument, kContentArgument, kAgentArgument, kMaxArgument }; static int Request(request_rec *RequestP) { server_rec *ServerP = RequestP->server; tConfig *ConfigP = ServerConfigP(ServerP); const char *Arguments[kMaxArgument]; PGresult *ResultP = NULL; PGconn *DatabaseP = NULL; char *ContentP = NULL; char *CookieP = NULL; tVirtual *VirtualP; int ResultCode; // get the database connection for the server for ( VirtualP = gVirtualP; VirtualP != NULL && VirtualP->ServerP != ServerP; VirtualP = VirtualP->VirtualP ); if (VirtualP != NULL) DatabaseP = VirtualP->DatabaseP; if (DatabaseP == NULL) return HTTP_SERVICE_UNAVAILABLE; // collect the form data ResultCode = ap_setup_client_block(RequestP,REQUEST_CHUNKED_ERROR); if (ResultCode != OK) return ResultCode; ap_hard_timeout("mod_libpq get request",RequestP); if (ap_should_client_block(RequestP)) { int Length = RequestP->remaining; ContentP = ap_pcalloc(RequestP->pool,Length + 32); if (ContentP != NULL) ap_get_client_block(RequestP,ContentP,Length); else { ap_kill_timeout(RequestP); return HTTP_INSUFFICIENT_STORAGE; } } ap_kill_timeout(RequestP); // collect the request headers ap_soft_timeout("mod_libpq send response",RequestP); RequestP->no_cache = 1; RequestP->content_type = "text/html; charset=utf-8"; Arguments[kSystemArgument] = ConfigP->SystemP; Arguments[kAddressArgument] = RequestP->connection->remote_ip; Arguments[kCookieArgument] = ap_table_get(RequestP->headers_in,"Cookie"); Arguments[kQueryArgument] = RequestP->args; Arguments[kContentArgument] = ContentP; Arguments[kAgentArgument] = ap_table_get(RequestP->headers_in,"User-Agent"); // get and send the response headers ResultP = CallSql ( DatabaseP, ConfigP->HeadersQueryP, Arguments, kMaxArgument ); if (ResultP != NULL) { if (PQgetisnull(ResultP,0,0) == 0) { CookieP = ap_pstrdup(RequestP->pool,PQgetvalue(ResultP,0,0)); if (CookieP != NULL) ap_table_set(RequestP->headers_out,"Set-Cookie",CookieP); } PQclear(ResultP); } ap_send_http_header(RequestP); // get and send the response content if (RequestP->header_only == 0) { if (CookieP == NULL) ap_rputs("failed to process the request",RequestP); else { Arguments[0] = CookieP; ResultP = CallSql ( DatabaseP, ConfigP->ContentQueryP, Arguments, 1 ); if (ResultP == NULL) ap_rputs("failed to process the response",RequestP); else { ap_rputs(PQgetvalue(ResultP,0,0),RequestP); PQclear(ResultP); } } } // clean up and leave ap_kill_timeout(RequestP); return OK; } // content handler descriptors static const handler_rec ContentHandlers[] = { {"libpq-request", Request}, {NULL} }; //---------------------------------------------------------------------------- // callback handlers --------------------------------------------------------- // initialise a server instance static void* CreateServer(pool *PoolP, server_rec *ServerP) { tConfig *ConfigP = (tConfig*) ap_pcalloc(PoolP,sizeof(tConfig)); ConfigP->HeadersQueryP = ap_pstrdup(PoolP,kHeadersQuery); ConfigP->ContentQueryP = ap_pstrdup(PoolP,kContentQuery); return (void*) ConfigP; } // merge server instances static void* MergeServers(pool *PoolP, void *Config1P, void *Config2P) { tConfig *OldP = (tConfig*) Config1P; tConfig *NewP = (tConfig*) Config2P; tConfig *ConfigP = (tConfig*) ap_pcalloc(PoolP,sizeof(tConfig)); if (NewP->ConnectionP != NULL) ConfigP->ConnectionP = ap_pstrdup(PoolP,NewP->ConnectionP); else if (OldP->ConnectionP != NULL) ConfigP->ConnectionP = ap_pstrdup(PoolP,OldP->ConnectionP); if (NewP->SystemP != NULL) ConfigP->SystemP = ap_pstrdup(PoolP,NewP->SystemP); else if (OldP->SystemP != NULL) ConfigP->SystemP = ap_pstrdup(PoolP,OldP->SystemP); if (NewP->HeadersQueryP != NULL) ConfigP->HeadersQueryP = ap_pstrdup(PoolP,NewP->HeadersQueryP); else if (OldP->HeadersQueryP != NULL) ConfigP->HeadersQueryP = ap_pstrdup(PoolP,OldP->HeadersQueryP); else ConfigP->HeadersQueryP = ap_pstrdup(PoolP,kHeadersQuery); if (NewP->ContentQueryP != NULL) ConfigP->ContentQueryP = ap_pstrdup(PoolP,NewP->ContentQueryP); else if (OldP->ContentQueryP != NULL) ConfigP->ContentQueryP = ap_pstrdup(PoolP,OldP->ContentQueryP); else ConfigP->ContentQueryP = ap_pstrdup(PoolP,kContentQuery); return (void*) ConfigP; } // initialise the module static void Initialise(server_rec *ServerP, pool *PoolP) { ap_add_version_component(kVersion); } // initialise a child process // create a database connection for the main server and every virtual server static void InitialiseChild(server_rec *ServerP, pool *PoolP) { tConfig *ConfigP; tVirtual *VirtualP; for (; ServerP != NULL; ServerP = ServerP->next) { ConfigP = ServerConfigP(ServerP); if (ConfigP->ConnectionP != NULL) { VirtualP = (tVirtual*) ap_pcalloc(PoolP,sizeof(tVirtual)); if (VirtualP != NULL) { VirtualP->ServerP = ServerP; VirtualP->DatabaseP = PQconnectdb(ConfigP->ConnectionP); VirtualP->VirtualP = gVirtualP; gVirtualP = VirtualP; } } } } // finalise a child process // close the database connections for the main server and every virtual server static void FinaliseChild(server_rec *ServerP, pool *PoolP) { tVirtual *VirtualP; for (VirtualP = gVirtualP; VirtualP != NULL; VirtualP = VirtualP->VirtualP) if (VirtualP->DatabaseP != NULL) PQfinish(VirtualP->DatabaseP); } //---------------------------------------------------------------------------- // module descriptor --------------------------------------------------------- module MODULE_VAR_EXPORT libpq_module = { STANDARD_MODULE_STUFF, Initialise, NULL, // create directory configuration, NULL, // merge directory configurations, CreateServer, MergeServers, CommandHandlers, ContentHandlers, NULL, // URL translation handler, NULL, // check identity, NULL, // check authorisation, NULL, // check access, NULL, // check types, NULL, // fix stuff, NULL, // log the request, NULL, // parse the header, InitialiseChild, FinaliseChild, NULL // post process request }; //---------------------------------------------------------------------------- /*==========================================================================*/