Commits

Aristarkh Zagorodnikov committed ec69e35 Merge

Merge with cxx-driver

Comments (0)

Files changed (8)

 This is an Apache 2.2+ module that supports serving files from MongoDB GridFS.
+
 See http://www.mongodb.org/ and http://www.mongodb.org/display/DOCS/GridFS for more information.
 
-Setup mongo-c-driver:
-1.	Grab sources (tested with v0.5) from https://github.com/mongodb/mongo-c-driver
-2.	Put "mongo-c-driver/src/*" files into "gridfs" directory
+See LICENSE file for licensing details.
 
-Build Apache with module:
-1.	Copy "gridfs" directory contents into "modules" directory of Apache 2.2+ source tree and integrate it with the following command:
-	./buildconf
-2.	If using Apache 2.2 (not 2.4), enable GNU99 C extensions:
-	export CFLAGS="-std=gnu99"
-3.	Run "configure script":
-	./configure --enable-gridfs [--prefix=/path/to/apache/install/dir]
-4.	Then, build and install as normal:
-	make -s
-	make -s install
+Building (see next section for Ubuntu fast-forward instructions):
+Make sure you have g++ and boost libraries installed
+Install Apache 2.2+ and APXS
+Grab the module from https://bitbucket.org/onyxmaster/mod_gridfs/, remember the absolute path to "gridfs" directory (referenced as /path/to/gridfs later on)
+Grab MongoDB C++ driver from http://downloads.mongodb.org/cxx-driver/mongodb-linux-x86_64-latest.tgz, unpack it and go to the target directory
+Fix the driver build environment:
+	chmod -R a+r .
+	echo 'env.Append(CCFLAGS="-fPIC")' >> SConstruct
+Build and install the results to "gridfs/driver" directory:
+	mkdir /path/to/gridfs/driver
+	scons install --prefix=/path/to/gridfs/driver
+
+Ubuntu build fast-forward instructions:
+sudo apt-get -y install mercurial wget g++ apache2 apache2-threaded-dev libboost-thread-dev libboost-filesystem-dev libboost-system-dev
+cd ~
+hg clone https://bitbucket.org/onyxmaster/mod_gridfs/
+cd mod_gridfs
+hg update
+wget http://downloads.mongodb.org/cxx-driver/mongodb-linux-x86_64-latest.tgz
+tar xzf mongodb-linux-x86_64-latest.tgz
+rm mongodb-linux-x86_64-latest.tgz
+cd mongo-cxx-driver-nightly
+chmod -R a+r .
+echo 'env.Append(CCFLAGS="-fPIC")' >> SConstruct
+mkdir ~/mod_gridfs/gridfs/driver
+scons install --prefix=$HOME/mod_gridfs/gridfs/driver
+cd ..
+rm -rf mongo-cxx-driver-nightly
+cd gridfs
+make && sudo make install
+
+Ubuntu installation fast-forward instructions (copy ~/mod_gridfs directory to a target machine first):
+sudo apt-get -y install apache2 libstdc++6 libboost-thread libboost-filesystem libboost-system
+cd ~/mod_gridfs
+sudo make install
 
 Configuration:
-GridFS On|Off -- enable/disable module
+GridFSConnection -- Connection string ("host[:port]" for single hosts, "replicaSetName/host[:port],[host[:port], ...]" for replica sets)
 GridFSDatabase <database> -- Set database name
-GridFSHost <host>:<port> -- Add host (multiple hosts allowd for replica sets if GridFSReplicaSet is configured)
-GridFSReplicaSet <replicaSet> -- Optional, set replica set name if connecting to a replica set
-GridFSPoolSize <poolSize> -- Optional, set per-process connection pool size (default is 16, maximum is 1024)
 GridFSCacheMaxAge <maxAge> -- Optional, set cache max age in seconds (default is 1 week, maximum is 10 years), set to 0 to disable expiration caching (see Notes below)
 GridFSConnectTimeout <timeout> -- Optional, set MongoDB connection timeout in seconds (default is 5 seconds)
+GridFSSlaveOk <On|Off> -- Optional, set MongoDB slaveOk mode (horizontal sharding), currently requires patched driver until https://jira.mongodb.org/browse/SERVER-5568 is resolved
 
 Configuration example:
-GridFS On
+GridFSConnection rsTest/db1,db2
 GridFSDatabase my_database
-GridFSReplicaSet rsTest
-GridFSHost db1:27017
-GridFSHost db2:27017
 
 Implementation notes:
 This module was developed to serve static content that is cached on frontend reverse proxies, hence by default it sets the following headers:
 Also, it supports HEAD requests, conditional queries (If-Modified-Since and/or If-None-Match with proper 304 Not Modified response).
 It doesn't support range queries (not going to be implemented soon).
 
-Roadmap:
-Migration to C++ driver (C driver lacks certain needed features)
-SlaveOK queries
+Known problems:
+If you're using gcc version less than 4.5, apache parent process would crash when reloading modules (on restart after a module list update).
+This is a known problem with gcc and libstdc++. Unfortunately, the only workaround is to upgrade your compiler and standard library.
Empty file added.
+#
+#  Makefile -- Build procedure for mod_gridfs Apache module
+#
+#  See LICENSE file for licensing details.
+#
+
+#   Set build environment
+APXS=apxs2
+CXX=g++
+
+#	Set MongoDB C++ driver location
+mongo_includedir=driver/include
+mongo_libdir=driver/lib
+
+#	Determine paths using apxs
+builddir=.
+top_srcdir=$(shell $(APXS) -q installbuilddir)/..
+top_builddir=$(shell $(APXS) -q installbuilddir)/..
+_CXX:=$(CXX)
+include $(top_builddir)/build/special.mk
+CXX=$(_CXX)
+
+#   Additional defines, includes and libraries
+#DEFS=-D
+INCLUDES=-I$(mongo_includedir) -I$(mongo_includedir)/mongo
+SH_LIBS=-L$(mongo_libdir) -lstdc++ -lmongoclient -lboost_thread -lboost_filesystem -lboost_system
+
+#   the default target
+all: local-shared-build
+
+#   install the shared object file into Apache 
+install: install-modules-yes
+
+#   cleanup
+clean:
+	-rm -f mod_gridfs.o mod_gridfs.lo mod_gridfs.slo mod_gridfs.la 

gridfs/Makefile.in

-
-include $(top_srcdir)/build/special.mk
-

gridfs/config.m4

-APACHE_MODPATH_INIT(gridfs)
-
-gridfs_objs="mod_gridfs.lo bson.lo encoding.lo env_posix.lo gridfs.lo md5.lo mongo.lo numbers.lo"
-
-APACHE_MODULE(gridfs, GridFS-Apache2 bridge, "$gridfs_objs", , no)
-
-APACHE_MODPATH_FINISH

gridfs/mod_gridfs.c

-/*
-	mod_gridfs.c -- Apache 2.2+ module that supports serving of files from MongoDB GridFS.
-
-	See http://www.mongodb.org/ and http://www.mongodb.org/display/DOCS/GridFS for more information.
-
-	Copyright (c) 2012, Aristarkh Zagorodnikov
-	All rights reserved.
-
-	Redistribution and use in source and binary forms, with or without
-	modification, are permitted provided that the following conditions are met: 
-
-	1. Redistributions of source code must retain the above copyright notice, this
-	   list of conditions and the following disclaimer. 
-	2. Redistributions in binary form must reproduce the above copyright notice,
-	   this list of conditions and the following disclaimer in the documentation
-	   and/or other materials provided with the distribution. 
-
-	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-	ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-	WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-	DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
-	ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-	ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-#include <unistd.h>
-
-#include "apr_strings.h"
-
-#include "httpd.h"
-#include "http_log.h"
-#include "http_config.h"
-#include "http_protocol.h"
-#include "http_request.h"
-
-#include "bson.h"
-#include "mongo.h"
-#include "gridfs.h"
-
-//	Declare module
-module AP_MODULE_DECLARE_DATA gridfs_module;
-
-//	Default GridFS pool size
-static const int DEFAULT_GFS_POOL_SIZE = 16;
-
-//	Default cache max age in seconds
-static const int DEFAULT_CACHE_MAX_AGE = 604800;
-
-//	Default connect timeout in seconds
-static const int DEFAULT_CONNECT_TIMEOUT = 5;
-
-//	Maximum GridFS pool size
-static const int MAX_GFS_POOL_SIZE = 1024;
-
-//	Maximum cache age in seconds
-static const int MAX_CACHE_MAX_AGE = 86400 * 365 * 10;
-
-//	Module configuration
-typedef struct
-{
-	int enabled;
-	int enabled_set;
-	const char* db_name;
-	const char* replica_set_name;
-	mongo_host_port* hosts;
-	int gfs_pool_size;
-	int gfs_pool_size_set;
-	int cache_max_age;
-	int cache_max_age_set;
-	int connect_timeout;
-	int connect_timeout_set;
-	gridfs** gfs_pool;
-	int gfs_pool_count;
-	int incomplete_config;
-} gridfs_config;
-
-struct
-{
-#if APR_HAS_THREADS
-	apr_thread_mutex_t* gfs_pool_mutex;
-#endif
-} child_state;
-
-//	Logs last connection error
-static void conn_error(mongo* conn, request_rec* request)
-{
-	switch (conn->err)
-	{
-		case MONGO_OK:
-			ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Unexpected success.");
-			break;
-
-		case MONGO_CONN_NO_SOCKET: 		
-			ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Could not create socket."); 
-			break;
-
-		case MONGO_CONN_FAIL:      		
-			ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Connection failed."); 
-			break;
-
-		case MONGO_CONN_ADDR_FAIL:		
-			ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Name resolution failed."); 
-			break;
-
-		case MONGO_CONN_NOT_MASTER:		
-			ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Not master."); 
-			break;
-
-		case MONGO_CONN_BAD_SET_NAME:	
-			ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Mismatched set name."); 
-			break;
-
-		case MONGO_CONN_NO_PRIMARY:		
-			ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: No primary found."); 
-			break;
-
-		case MONGO_IO_ERROR:			
-			ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: I/O error."); 
-			break;
-
-		case MONGO_READ_SIZE_ERROR:		
-			ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Invalid read size."); 
-			break;
-
-		case MONGO_COMMAND_FAILED:		
-			ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Command failed."); 
-			break;
-
-		case MONGO_BSON_INVALID:		
-			ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Invalid BSON data."); 
-			break;
-
-		case MONGO_BSON_NOT_FINISHED:	
-			ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Incomplete BSON data."); 
-			break;
-
-		default:						
-			ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, request, "mod_gridfs: MongoDB error %d.", conn->err); 
-			break;
-	}
-}
-
-//	Locks GridFS pool
-static int lock_gfs_pool(request_rec* request)
-{
-#if APR_HAS_THREADS
-	if (apr_thread_mutex_lock(child_state.gfs_pool_mutex) != APR_SUCCESS)
-	{
-		ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, request, "mod_gridfs: Failed to lock GridFS mutex."); 
-		return 0;
-	}
-#endif
-	return 1;
-}
-
-//	Unlocks GridFS pool
-static void unlock_gfs_pool(request_rec* request)
-{
-#if APR_HAS_THREADS
-	if (apr_thread_mutex_unlock(child_state.gfs_pool_mutex) != APR_SUCCESS)
-		ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, request, "mod_gridfs: Failed to unlock GridFS mutex."); 
-#endif
-}
-
-//	Obtains GridFS object (potentially from pool)
-static gridfs* get_gfs(gridfs_config* config, request_rec* request)
-{
-	mongo* conn;
-	gridfs* gfs;
-	if (!lock_gfs_pool(request))
-		return 0;
-	if (!config->gfs_pool_count || !config->gfs_pool)
-	{
-		unlock_gfs_pool(request);
-		conn = mongo_create();
-		mongo_set_op_timeout(conn, config->connect_timeout);
-		int status;
-		if (config->replica_set_name)
-		{
-			mongo_replset_init(conn, config->replica_set_name);
-			mongo_host_port* host = config->hosts;
-			while (host)
-			{
-				mongo_replset_add_seed(conn, host->host, host->port);
-				host = host->next;
-			}
-			status = mongo_replset_connect(conn);
-		}
-		else
-		{
-			mongo_host_port* host = config->hosts;
-			if (!host)
-				return 0;
-			status = mongo_connect(conn, host->host, host->port);
-		}
-		if (status != MONGO_OK)
-		{
-			conn_error(conn, request);
-			mongo_destroy(conn);
-			mongo_dispose(conn);
-			return 0;
-		}
-		gfs = gridfs_create();
-		status = gridfs_init(conn, config->db_name, "fs", gfs);
-		if (status != MONGO_OK)
-		{
-			gridfs_destroy(gfs);
-			conn_error(conn, request);
-			mongo_destroy(conn);
-			mongo_dispose(conn);
-			return 0;
-		}
-	}
-	else
-	{
-		gfs = config->gfs_pool[--config->gfs_pool_count];
-		unlock_gfs_pool(request);
-	}
-	return gfs;
-}
-
-//	Destroys GridFS object
-static void destroy_gfs(gridfs* gfs)
-{
-	mongo* conn = gfs->client;
-	gridfs_destroy(gfs);
-	gridfs_dispose(gfs);
-	mongo_destroy(conn);
-	mongo_dispose(conn);
-}
-
-//	Returns GridFS object to pool 
-static void return_gfs(gridfs_config* config, request_rec* request, gridfs* gfs)
-{
-	if (!config->gfs_pool || !lock_gfs_pool(request))
-	{
-		destroy_gfs(gfs);
-		return;
-	}
-	if (config->gfs_pool_count < config->gfs_pool_size)
-		config->gfs_pool[config->gfs_pool_count++] = gfs;
-	unlock_gfs_pool(request);
-}
-
-//	Creates module configuration
-static void* gridfs_create_config(apr_pool_t* pool, char* dummy)
-{
-    gridfs_config* config = (gridfs_config*)apr_pcalloc(pool, sizeof(gridfs_config));
-    if (!config)
-    	return 0;
-    config->gfs_pool_size = DEFAULT_GFS_POOL_SIZE;
-   	config->cache_max_age = DEFAULT_CACHE_MAX_AGE;
-    config->connect_timeout = DEFAULT_CONNECT_TIMEOUT;
-    config->gfs_pool = apr_palloc(pool, sizeof(gridfs*) * config->gfs_pool_size);
-    return config;
-}
-
-//	Merges module configuration
-static void* gridfs_merge_config(apr_pool_t* pool, void *basev, void *addv)
-{
-    gridfs_config* config = (gridfs_config*)apr_pcalloc(pool, sizeof(gridfs_config));
-    if (!config)
-    	return 0;
-    gridfs_config *base = (gridfs_config*) basev;
-    gridfs_config *add = (gridfs_config*) addv;
-    config->enabled = add->enabled_set ? add->enabled : base->enabled;
-    config->enabled_set = add->enabled_set | base->enabled_set;
-    config->db_name = add->db_name ? add->db_name : base->db_name;
-    config->replica_set_name = add->replica_set_name ? add->replica_set_name : base->replica_set_name;
-    config->hosts = add->hosts ? add->hosts : base->hosts;
-    config->gfs_pool_size = add->gfs_pool_size_set ? add->gfs_pool_size : base->gfs_pool_size;
-    config->gfs_pool_size_set = add->gfs_pool_size_set | base->gfs_pool_size_set;
-    config->cache_max_age = add->cache_max_age_set ? add->cache_max_age : base->cache_max_age;
-    config->cache_max_age_set = add->cache_max_age_set | base->cache_max_age_set;
-    config->connect_timeout = add->connect_timeout_set ? add->connect_timeout : base->connect_timeout;
-    config->connect_timeout_set = add->connect_timeout_set | base->connect_timeout_set;
-    config->gfs_pool = apr_palloc(pool, sizeof(gridfs*) * config->gfs_pool_size);
-    return config;
-}
-
-//	Handles "GridFS <On|Off>" command
-static const char* gridfs_enabled_command(cmd_parms* command, void* module_config, int flag)
-{
-    gridfs_config* config = module_config;
-    config->enabled = flag;
-    config->enabled_set = 1;
-    return 0;
-}
-
-//	Handles "GridFSHost <host:port>" command
-static const char* gridfs_host_command(cmd_parms* command, void* module_config, const char* argument)
-{
-    gridfs_config* config = module_config;
-    mongo_host_port* host = (mongo_host_port*)apr_pcalloc(command->pool, sizeof(mongo_host_port));
-    if (!host)
-    {
-		ap_log_error(APLOG_MARK, APLOG_CRIT, 0, command->server, "mod_gridfs: Failed to allocate host memory."); 
-    	return "failed to allocate memory";
-   	}
-    mongo_parse_host(argument, host);
-    if (!strlen(host->host))
-    	return "empty host";
-    mongo_host_port** last = &config->hosts;
-    while (*last)
-    	last = &(*last)->next;
-   	*last = host;
-    return 0;
-}
-
-//	Handles "GridFSPoolSize <pool size>" command
-static const char* gridfs_pool_size_command(cmd_parms* command, void* module_config, const char* argument)
-{
-    gridfs_config* config = module_config;
-    int size = atoi(argument);
-    if (size <= 0 || size > MAX_GFS_POOL_SIZE)
-    	return "pool size out of range";
-    config->gfs_pool_size = size;
-    config->gfs_pool_size_set = 1;
-    return 0;
-}
-
-//	Handles "GridFSCacheMaxAge <cache max age>" command
-static const char* gridfs_cache_max_age_command(cmd_parms* command, void* module_config, const char* argument)
-{
-    gridfs_config* config = module_config;
-    int cache_max_age = atoi(argument);
-    if (cache_max_age < 0 || cache_max_age > MAX_CACHE_MAX_AGE)
-    	return "max age out of range";
-    config->cache_max_age = cache_max_age;
-    config->cache_max_age_set = 1;
-    return 0;
-}
-
-//	Handles "GridFSConnectTimeout <connect timeout>" command
-static const char* gridfs_connect_timeout_command(cmd_parms* command, void* module_config, const char* argument)
-{
-    gridfs_config* config = module_config;
-    int connect_timeout = atoi(argument);
-    if (connect_timeout <= 0 || connect_timeout > 300)
-    	return "timeout out of range (1-300s)";
-    config->connect_timeout = connect_timeout;
-    config->connect_timeout_set = 1;
-    return 0;
-}
-
-//	Initialize child
-static void gridfs_child_init(apr_pool_t* pool, server_rec* server)
-{
-#if APR_HAS_THREADS
-	if (apr_thread_mutex_create(&child_state.gfs_pool_mutex, APR_THREAD_MUTEX_DEFAULT, pool) != APR_SUCCESS)
-		ap_log_error(APLOG_MARK, APLOG_CRIT, 0, server, "mod_gridfs: Failed to create mutex."); 
-#endif
-}
-
-//	Handles request
-static int gridfs_handler(request_rec* request)
-{
-    gridfs_config* config = ap_get_module_config(request->per_dir_config, &gridfs_module);
-	if (!config->enabled)
-		return DECLINED;
-	if (config->incomplete_config)
-		return HTTP_INTERNAL_SERVER_ERROR;
-    if (request->method_number != M_GET)
-		return HTTP_NOT_IMPLEMENTED;
-	int length = strlen(request->uri);
-	if (length <= 1)
-		return HTTP_NOT_FOUND;
-	if (!config->db_name)
-	{
-		ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: GridFSDbName is not set."); 
-		config->incomplete_config = 1;
-		return HTTP_INTERNAL_SERVER_ERROR;
-	}
-	if (!config->hosts)
-	{
-		ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: GridFSHost is not set."); 
-		config->incomplete_config = 1;
-		return HTTP_INTERNAL_SERVER_ERROR;
-	}
-	char* filename = apr_pstrdup(request->pool, request->uri + 1);
-	if (!filename)
-	{
-		ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, request, "mod_gridfs: Failed to allocate filename memory."); 
-		return HTTP_INTERNAL_SERVER_ERROR;
-	}
-	gridfs* gfs = get_gfs(config, request);
-	if (!gfs)
-		return HTTP_INTERNAL_SERVER_ERROR;
-	gridfile file;
-	int status = gridfs_find_filename(gfs, filename, &file);
-	if (status != MONGO_OK)
-	{
-		if (gfs->client->err == MONGO_OK)
-		{
-			return_gfs(config, request, gfs);
-			return HTTP_NOT_FOUND;
-		}
-		conn_error(gfs->client, request);
-		destroy_gfs(gfs);
-		ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Failed to get file '%s'.", filename);
-		return HTTP_INTERNAL_SERVER_ERROR;
-	}
-	apr_time_t lastmodified_time = apr_time_from_sec(gridfile_get_uploaddate(&file) / 1000);
-	const char* content_type = gridfile_get_contenttype(&file);
-	const char* md5 = gridfile_get_md5(&file);
-	gridfs_offset file_length = gridfile_get_contentlength(&file);
-    request->mtime = lastmodified_time;
-    ap_set_last_modified(request);
-	if (md5)
-	{
-		if (*md5 != '\0')
-			apr_table_set(request->headers_out, "Etag", md5);
-		else
-			md5 = 0;
-	}
-	if (ap_meets_conditions(request) == HTTP_NOT_MODIFIED)
-	{
-		if (md5)
-			apr_table_unset(request->headers_out, "Etag");
-		return HTTP_NOT_MODIFIED;
-	}
-	if (!request->header_only)
-	{
-		int num_chunks = gridfile_get_numchunks(&file);
-		mongo_cursor* chunks = gridfile_get_chunks(&file, 0, num_chunks);
-		gridfile_destroy(&file);
-		if (!chunks)
-		{
-			conn_error(gfs->client, request);
-			destroy_gfs(gfs);
-			ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Failed to get file chunks (%d) for file '%s'.", num_chunks, filename);
-			apr_table_clear(request->headers_out);
-			return HTTP_INTERNAL_SERVER_ERROR;
-		}
-        for (int chunk_index = 0;chunk_index < num_chunks;chunk_index++) 
-        {
-            if (mongo_cursor_next(chunks) != MONGO_OK)
-            {
-				mongo_cursor_destroy(chunks);
-				conn_error(gfs->client, request);
-				destroy_gfs(gfs);
-				ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Failed to iterate over file chunks file '%s' (chunk %d of %d).", filename, chunk_index, num_chunks);
-				apr_table_clear(request->headers_out);
-            	return HTTP_INTERNAL_SERVER_ERROR;
-            }
-            bson chunk = chunks->current;
-            bson_iterator it;
-            bson_find(&it, &chunk, "data");
-            int chunk_length = bson_iterator_bin_len(&it);
-            const char* chunk_data = bson_iterator_bin_data(&it);
-			ap_rwrite(chunk_data, chunk_length, request);
-        }
-		mongo_cursor_destroy(chunks);
-	}
-	else
-	{
-		gridfile_destroy(&file);
-		ap_set_content_length(request, file_length);
-	}
-	return_gfs(config, request, gfs);
-	if (config->cache_max_age)
-	{
-		char cache_control[32];
-		snprintf(cache_control, sizeof(cache_control) - 1, "public, max-age=%d", config->cache_max_age);
-		apr_table_set(request->headers_out, "Cache-Control", cache_control);
-		apr_time_t expires_time = request->request_time + apr_time_from_sec(config->cache_max_age);
-	    char datestr[APR_RFC822_DATE_LEN];
-	    apr_rfc822_date(datestr, expires_time);
-		apr_table_set(request->headers_out, "Expires", datestr);
-	}
-	if (content_type && *content_type)
-	{
-		ap_set_content_type(request, content_type);
-	}
-	else
-	{
-		request->finfo.filetype = APR_REG;
-		request->filename = filename;
-		ap_run_type_checker(request);
-	}
-    return OK;
-}
-
-//	Registers httpd hooks
-static void gridfs_register_hooks(apr_pool_t *p)
-{
-    ap_hook_child_init(gridfs_child_init, 0, 0, APR_HOOK_MIDDLE);
-    ap_hook_handler(gridfs_handler, 0, 0, APR_HOOK_MIDDLE);
-}
-
-//	Describes module configuration commands
-static const command_rec gridfs_commands[] =
-{
-    AP_INIT_FLAG("GridFS", gridfs_enabled_command, 0, OR_FILEINFO, "GridFS on/off."),
-    AP_INIT_TAKE1("GridFSDatabase", ap_set_string_slot, (void *)APR_OFFSETOF(gridfs_config, db_name), OR_FILEINFO, "GridFS database name."),
-    AP_INIT_TAKE1("GridFSReplicaSet", ap_set_string_slot, (void *)APR_OFFSETOF(gridfs_config, replica_set_name), OR_FILEINFO, "GridFS replica set name (if connecting to replica set)."),
-    AP_INIT_ITERATE("GridFSHost", gridfs_host_command, 0, OR_FILEINFO, "GridFS host and port."),
-    AP_INIT_TAKE1("GridFSPoolSize", gridfs_pool_size_command, 0, OR_FILEINFO, "GridFS pool size (per-process)."),
-    AP_INIT_TAKE1("GridFSCacheMaxAge", gridfs_cache_max_age_command, 0, OR_FILEINFO, "GridFS cache max age (seconds, 0 to disable expiration)."),
-    AP_INIT_TAKE1("GridFSConnectTimeout", gridfs_connect_timeout_command, 0, OR_FILEINFO, "GridFS connection timeout (seconds)."),
-    {0}
-};
-
-//	Defines module
-module AP_MODULE_DECLARE_DATA gridfs_module =
-{
-    STANDARD20_MODULE_STUFF,
-    gridfs_create_config,
-    gridfs_merge_config,
-    0,
-    0,
-    gridfs_commands,
-    gridfs_register_hooks
-};
-

gridfs/mod_gridfs.cpp

+/*
+	mod_gridfs.c -- Apache 2.2+ module that supports serving of files from MongoDB GridFS.
+
+	See http://www.mongodb.org/ and http://www.mongodb.org/display/DOCS/GridFS for more information.
+
+	See LICENSE file for licensing details.
+*/
+
+#include <unistd.h>
+
+#include "client/dbclient.h"
+#include "client/gridfs.h"
+
+#include "apr_strings.h"
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_config.h"
+#include "http_protocol.h"
+#include "http_request.h"
+
+//	Support for slaveOk (requires patched driver until https://jira.mongodb.org/browse/SERVER-5568 is resolved)
+#define DRIVER_HAS_SLAVE_OK 0
+
+//	Declare module
+extern "C"
+{
+	extern module AP_MODULE_DECLARE_DATA gridfs_module;
+}
+
+//	Default cache max age in seconds
+const int DEFAULT_CACHE_MAX_AGE = 604800;
+
+//	Default connect timeout in seconds
+const int DEFAULT_CONNECT_TIMEOUT = 5;
+
+//	Maximum cache age in seconds
+const int MAX_CACHE_MAX_AGE = 86400 * 365 * 10;
+
+//	Maximum connect timeout in seconds
+const int MAX_CONNECT_TIMEOUT = 300;
+
+//	Module configuration
+struct gridfs_config
+{
+	mongo::ConnectionString *connection_string;
+	const std::string *database;
+	int cache_max_age;
+	bool cache_max_age_set;
+	int connect_timeout;
+	bool connect_timeout_set;
+	bool slave_ok;
+	bool slave_ok_set;
+};
+
+//	Creates module configuration
+static void *gridfs_create_config(apr_pool_t *const pool, char *const dummy)
+{
+	gridfs_config *const config = static_cast<gridfs_config *>(apr_pcalloc(pool, sizeof(gridfs_config)));
+	if (!config)
+		return 0;
+	config->cache_max_age = DEFAULT_CACHE_MAX_AGE;
+	config->connect_timeout = DEFAULT_CONNECT_TIMEOUT;
+	return config;
+}
+
+//	Merges module configuration
+static void *gridfs_merge_config(apr_pool_t *const pool, void *const basev, void *const addv)
+{
+	gridfs_config *const config = static_cast<gridfs_config *>(apr_palloc(pool, sizeof(gridfs_config)));
+	if (!config)
+		return 0;
+	const gridfs_config *const base = static_cast<const gridfs_config *>(basev);
+	const gridfs_config *const add = static_cast<const gridfs_config *>(addv);
+	config->connection_string = add->connection_string ? add->connection_string : base->connection_string;
+	config->database = add->database ? add->database : base->database;
+	config->cache_max_age = add->cache_max_age_set ? add->cache_max_age : base->cache_max_age;
+	config->cache_max_age_set = add->cache_max_age_set || base->cache_max_age_set;
+	config->connect_timeout = add->connect_timeout_set ? add->connect_timeout : base->connect_timeout;
+	config->connect_timeout_set = add->connect_timeout_set || base->connect_timeout_set;
+	config->slave_ok = add->slave_ok_set ? add->slave_ok : base->slave_ok;
+	config->slave_ok_set = add->slave_ok_set || base->slave_ok_set;
+	return config;
+}
+
+//	Handles "GridFSConnection <connection string>" command
+static const char *gridfs_connection_command(cmd_parms *const command, void *const module_config, const char *const argument)
+{
+	gridfs_config *const config = static_cast<gridfs_config *>(module_config);
+	void *const connection_string_data = apr_palloc(command->pool, sizeof(mongo::ConnectionString));
+	if (!connection_string_data)
+		return "GridFSConnection failed to allocate data.";
+	mongo::ConnectionString * connection_string;
+	try
+	{
+		connection_string = new (connection_string_data) mongo::ConnectionString(argument, mongo::ConnectionString::SET);
+	}
+	catch (...)
+	{
+		return "GridFSConnection exception.";
+	}
+	if (!connection_string->isValid())
+		return "Invalid GridFSConnection.";
+	config->connection_string = connection_string;
+	return 0;
+}
+
+//	Handles "GridFSDatabase <database name>" command
+static const char *gridfs_database_command(cmd_parms *const command, void *const module_config, const char *const argument)
+{
+	gridfs_config *const config = static_cast<gridfs_config *>(module_config);
+	void *const database_data = apr_palloc(command->pool, sizeof(std::string));
+	if (!database_data)
+		return "GridFSDatabase failed to allocate data.";
+	std::string * database;
+	try
+	{
+		database = new (database_data) std::string(argument);
+	}
+	catch (...)
+	{
+		return "GridFSDatabase exception.";
+	}
+	config->database = database;
+	return 0;
+}
+
+//	Handles "GridFSCacheMaxAge <cache max age>" command
+static const char *gridfs_cache_max_age_command(cmd_parms *const command, void *const module_config, const char *const argument)
+{
+	gridfs_config *const config = static_cast<gridfs_config *>(module_config);
+	const int cache_max_age = std::atoi(argument);
+	if (cache_max_age < 0 || cache_max_age > MAX_CACHE_MAX_AGE)
+		return "GridFSCacheMaxAge out of range.";
+	config->cache_max_age = cache_max_age;
+	config->cache_max_age_set = true;
+	return 0;
+}
+
+//	Handles "GridFSConnectTimeout <connect timeout>" command
+static const char *gridfs_connect_timeout_command(cmd_parms *const command, void *const module_config, const char *const argument)
+{
+	gridfs_config *const config = static_cast<gridfs_config *>(module_config);
+	int connect_timeout = atoi(argument);
+	if (connect_timeout < 0 || connect_timeout > MAX_CONNECT_TIMEOUT)
+		return "GridFSConnectTimeout out of range.";
+	config->connect_timeout = connect_timeout;
+	config->connect_timeout_set = true;
+	return 0;
+}
+
+//	Handles "GridFSSlaveOk <On|Off>" command
+static const char *gridfs_slave_ok_command(cmd_parms *const command, void *const module_config, int flag)
+{
+#if !DRIVER_HAS_SLAVE_OK
+	if (flag)
+		return "GridFSSlaveOk is not supported by the driver.";
+#endif
+	gridfs_config *const config = static_cast<gridfs_config *>(module_config);
+	config->slave_ok = flag != 0;
+	config->slave_ok_set = true;
+	return 0;
+}
+
+//	Handles request
+static int gridfs_handler(request_rec *const request)
+{
+	const gridfs_config *const config = static_cast<gridfs_config *>(ap_get_module_config(request->per_dir_config, &gridfs_module));
+	if (!config->connection_string || !config->database)
+		return DECLINED;
+	if (request->method_number != M_GET)
+		return HTTP_NOT_IMPLEMENTED;
+	if (*request->uri != '/')
+		return HTTP_NOT_FOUND;
+	const char *const filename = apr_pstrdup(request->pool, request->uri + 1);
+	if (!filename)
+	{
+		ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, request, "mod_gridfs: Failed to allocate filename memory."); 
+		return HTTP_INTERNAL_SERVER_ERROR;
+	}
+	try
+	{
+		mongo::ScopedDbConnection connection(*config->connection_string, config->connect_timeout);
+		try
+		{
+			const mongo::GridFS gridfs(connection.conn(), *config->database);
+#if DRIVER_HAS_SLAVE_OK
+			const mongo::GridFile& gridfile = gridfs.findFile(filename, config->slave_ok);
+#else
+			const mongo::GridFile& gridfile = gridfs.findFile(filename);
+#endif
+			if (!gridfile.exists())
+			{
+				connection.done();
+				return HTTP_NOT_FOUND;
+			}
+			const mongo::gridfs_offset file_length = gridfile.getContentLength();
+			const mongo::Date_t upload_date = gridfile.getUploadDate();
+			const std::string& md5 = gridfile.getMD5();
+			const std::string& content_type = gridfile.getContentType();
+			request->mtime = apr_time_from_sec(upload_date.toTimeT());
+			ap_set_last_modified(request);
+			if (!md5.empty())
+				apr_table_set(request->headers_out, "Etag", md5.c_str());
+			if (ap_meets_conditions(request) == HTTP_NOT_MODIFIED)
+			{
+				if (!md5.empty())
+					apr_table_unset(request->headers_out, "Etag");
+				connection.done();
+				return HTTP_NOT_MODIFIED;
+			}
+			if (!request->header_only)
+			{
+				const int num_chunks = gridfile.getNumChunks();
+				for (int chunk_index = 0;chunk_index < num_chunks;chunk_index++) 
+				{
+					mongo::GridFSChunk chunk = gridfile.getChunk(chunk_index);
+					int chunk_length;
+					const char* chunk_data = chunk.data(chunk_length);
+					if (chunk_length != 0)
+						ap_rwrite(chunk_data, chunk_length, request);
+				}
+			}
+			else
+				ap_set_content_length(request, file_length);
+			if (content_type.empty())
+			{
+				request->finfo.filetype = APR_REG;
+				request->filename = const_cast<char *>(filename);
+				ap_run_type_checker(request);
+			}
+			else
+				ap_set_content_type(request, content_type.c_str());
+		}
+		catch (...)
+		{
+			connection.done();
+			throw;
+		}
+		connection.done();
+	}
+	catch (const std::exception& exception)
+	{
+		ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: %s.", exception.what()); 
+		return HTTP_INTERNAL_SERVER_ERROR;
+	}
+	catch (...)
+	{
+		ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, request, "mod_gridfs: Unknown handler exception occured."); 
+		return HTTP_INTERNAL_SERVER_ERROR;
+	}
+	if (config->cache_max_age)
+	{
+		char cache_control[32];
+		snprintf(cache_control, sizeof(cache_control) - 1, "public, max-age=%d", config->cache_max_age);
+		apr_table_set(request->headers_out, "Cache-Control", cache_control);
+		apr_time_t expires_time = request->request_time + apr_time_from_sec(config->cache_max_age);
+		char datestr[APR_RFC822_DATE_LEN];
+		apr_rfc822_date(datestr, expires_time);
+		apr_table_set(request->headers_out, "Expires", datestr);
+	}
+	return OK;
+}
+
+//	Registers hooks
+static void gridfs_register_hooks(apr_pool_t *const pool)
+{
+	ap_hook_handler(gridfs_handler, 0, 0, APR_HOOK_MIDDLE);
+}
+
+//	Describes module configuration commands
+static const command_rec gridfs_commands[] =
+{
+	AP_INIT_TAKE1("GridFSConnection", reinterpret_cast<cmd_func>(gridfs_connection_command), 0, OR_FILEINFO, "GridFS connection string."),
+	AP_INIT_TAKE1("GridFSDatabase", reinterpret_cast<cmd_func>(gridfs_database_command), 0, OR_FILEINFO, "GridFS database name."),
+	AP_INIT_TAKE1("GridFSCacheMaxAge", reinterpret_cast<cmd_func>(gridfs_cache_max_age_command), 0, OR_FILEINFO, "GridFS cache max age (seconds, 0 to disable expiration)."),
+	AP_INIT_TAKE1("GridFSConnectTimeout", reinterpret_cast<cmd_func>(gridfs_connect_timeout_command), 0, OR_FILEINFO, "GridFS connect timeout (seconds, 0 for infinite)."),
+	AP_INIT_FLAG("GridFSSlaveOk", reinterpret_cast<cmd_func>(gridfs_slave_ok_command), 0, OR_FILEINFO, "GridFS slaveOk flag."),
+	{0}
+};
+
+//	Defines module
+extern "C"
+{
+	module AP_MODULE_DECLARE_DATA gridfs_module =
+	{
+		STANDARD20_MODULE_STUFF,
+		gridfs_create_config,
+		gridfs_merge_config,
+		0,
+		0,
+		gridfs_commands,
+		gridfs_register_hooks
+	};
+}

gridfs/modules.mk

+mod_gridfs.la: mod_gridfs.slo
+	$(SH_LINK) -rpath $(libexecdir) -module -avoid-version  mod_gridfs.lo
+DISTCLEAN_TARGETS = modules.mk
+shared =  mod_gridfs.la