Files
libmicrohttpd/src/daemon/daemon.c
T
2007-08-08 20:48:41 +00:00

652 lines
16 KiB
C

/*
This file is part of libmicrohttpd
(C) 2007 Daniel Pittman and Christian Grothoff
libmicrohttpd 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, or (at your
option) any later version.
libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
/**
* @file daemon.c
* @brief A minimal-HTTP server library
* @author Daniel Pittman
* @author Christian Grothoff
*/
#include "internal.h"
#include "response.h"
#include "connection.h"
#include "memorypool.h"
/**
* Default connection limit.
*/
#define MHD_MAX_CONNECTIONS_DEFAULT FD_SETSIZE -4
/**
* Default memory allowed per connection.
*/
#define MHD_POOL_SIZE_DEFAULT (1024 * 1024)
/**
* Register an access handler for all URIs beginning with uri_prefix.
*
* @param uri_prefix
* @return MRI_NO if a handler for this exact prefix
* already exists
*/
int
MHD_register_handler(struct MHD_Daemon * daemon,
const char * uri_prefix,
MHD_AccessHandlerCallback dh,
void * dh_cls) {
struct MHD_Access_Handler * ah;
if ( (daemon == NULL) ||
(uri_prefix == NULL) ||
(dh == NULL) )
return MHD_NO;
ah = daemon->handlers;
while (ah != NULL) {
if (0 == strcmp(uri_prefix,
ah->uri_prefix))
return MHD_NO;
ah = ah->next;
}
ah = malloc(sizeof(struct MHD_Access_Handler));
ah->next = daemon->handlers;
ah->uri_prefix = strdup(uri_prefix);
ah->dh = dh;
ah->dh_cls = dh_cls;
daemon->handlers = ah;
return MHD_YES;
}
/**
* Unregister an access handler for the URIs beginning with
* uri_prefix.
*
* @param uri_prefix
* @return MHD_NO if a handler for this exact prefix
* is not known for this daemon
*/
int
MHD_unregister_handler(struct MHD_Daemon * daemon,
const char * uri_prefix,
MHD_AccessHandlerCallback dh,
void * dh_cls) {
struct MHD_Access_Handler * prev;
struct MHD_Access_Handler * pos;
if ( (daemon == NULL) ||
(uri_prefix == NULL) ||
(dh == NULL) )
return MHD_NO;
pos = daemon->handlers;
prev = NULL;
while (pos != NULL) {
if ( (dh == pos->dh) &&
(dh_cls == pos->dh_cls) &&
(0 == strcmp(uri_prefix,
pos->uri_prefix)) ) {
if (prev == NULL)
daemon->handlers = pos->next;
else
prev->next = pos->next;
free(pos);
return MHD_YES;
}
prev = pos;
pos = pos->next;
}
return MHD_NO;
}
/**
* Obtain the select sets for this daemon.
*
* @return MHD_YES on success, MHD_NO if this
* daemon was not started with the right
* options for this call.
*/
int
MHD_get_fdset(struct MHD_Daemon * daemon,
fd_set * read_fd_set,
fd_set * write_fd_set,
fd_set * except_fd_set,
int * max_fd) {
struct MHD_Connection * pos;
if ( (daemon == NULL) ||
(read_fd_set == NULL) ||
(write_fd_set == NULL) ||
(except_fd_set == NULL) ||
(max_fd == NULL) ||
( (daemon->options & MHD_USE_THREAD_PER_CONNECTION) != 0) )
return MHD_NO;
FD_SET(daemon->socket_fd,
read_fd_set);
if ( (*max_fd) < daemon->socket_fd)
*max_fd = daemon->socket_fd;
pos = daemon->connections;
while (pos != NULL) {
if (MHD_YES != MHD_connection_get_fdset(pos,
read_fd_set,
write_fd_set,
except_fd_set,
max_fd))
return MHD_NO;
pos = pos->next;
}
return MHD_YES;
}
/**
* Main function of the thread that handles an individual
* connection.
*/
static void *
MHD_handle_connection(void * data) {
struct MHD_Connection * con = data;
int num_ready;
fd_set rs;
fd_set ws;
fd_set es;
int max;
if (con == NULL)
abort();
while ( (! con->daemon->shutdown) &&
(con->socket_fd != -1) ) {
FD_ZERO(&rs);
FD_ZERO(&ws);
FD_ZERO(&es);
max = 0;
MHD_connection_get_fdset(con,
&rs,
&ws,
&es,
&max);
num_ready = SELECT(max + 1,
&rs,
&ws,
&es,
NULL);
if (num_ready <= 0) {
if (errno == EINTR)
continue;
break;
}
if ( ( (FD_ISSET(con->socket_fd, &rs)) &&
(MHD_YES != MHD_connection_handle_read(con)) ) ||
( (con->socket_fd != -1) &&
(FD_ISSET(con->socket_fd, &ws)) &&
(MHD_YES != MHD_connection_handle_write(con)) ) )
break;
if ( (con->headersReceived == 1) &&
(con->response == NULL) )
MHD_call_connection_handler(con);
}
if (con->socket_fd != -1) {
CLOSE(con->socket_fd);
con->socket_fd = -1;
}
return NULL;
}
/**
* Accept an incoming connection and create the MHD_Connection object for
* it. This function also enforces policy by way of checking with the
* accept policy callback.
*/
static int
MHD_accept_connection(struct MHD_Daemon * daemon) {
struct MHD_Connection * connection;
struct sockaddr_in6 addr6;
struct sockaddr * addr = (struct sockaddr*) &addr6;
socklen_t addrlen;
int s;
if (sizeof(struct sockaddr) > sizeof(struct sockaddr_in6))
abort(); /* fatal, serious error */
addrlen = sizeof(struct sockaddr_in6);
memset(addr,
0,
sizeof(struct sockaddr_in6));
s = ACCEPT(daemon->socket_fd,
addr,
&addrlen);
if ( (s < 0) ||
(addrlen <= 0) ) {
MHD_DLOG(daemon,
"Error accepting connection: %s\n",
STRERROR(errno));
if (s != -1)
CLOSE(s); /* just in case */
return MHD_NO;
}
if (daemon->max_connections == 0) {
/* above connection limit - reject */
CLOSE(s);
return MHD_NO;
}
if (MHD_NO == daemon->apc(daemon->apc_cls,
addr,
addrlen)) {
CLOSE(s);
return MHD_YES;
}
connection = malloc(sizeof(struct MHD_Connection));
memset(connection,
0,
sizeof(struct MHD_Connection));
connection->pool = NULL;
connection->addr = malloc(addrlen);
if (connection->addr == NULL) {
CLOSE(s);
free(connection);
return MHD_NO;
}
memcpy(connection->addr,
addr,
addrlen);
connection->addr_len = addrlen;
connection->socket_fd = s;
connection->daemon = daemon;
if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION) ) &&
(0 != pthread_create(&connection->pid,
NULL,
&MHD_handle_connection,
connection)) ) {
MHD_DLOG(daemon,
"Failed to create a thread: %s\n",
STRERROR(errno));
free(connection->addr);
CLOSE(s);
free(connection->addr);
free(connection);
return MHD_NO;
}
connection->next = daemon->connections;
daemon->connections = connection;
daemon->max_connections--;
return MHD_YES;
}
/**
* Free resources associated with all closed connections.
* (destroy responses, free buffers, etc.). A connection
* is known to be closed if the socket_fd is -1.
*
* Also performs connection actions that need to be run
* even if the connection is not selectable (such as
* calling the application again with upload data when
* the upload data buffer is full).
*/
static void
MHD_cleanup_connections(struct MHD_Daemon * daemon) {
struct MHD_Connection * pos;
struct MHD_Connection * prev;
void * unused;
pos = daemon->connections;
prev = NULL;
while (pos != NULL) {
if (pos->socket_fd == -1) {
if (prev == NULL)
daemon->connections = pos->next;
else
prev->next = pos->next;
if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) {
pthread_kill(pos->pid, SIGALRM);
pthread_join(pos->pid, &unused);
}
if (pos->response != NULL)
MHD_destroy_response(pos->response);
MHD_pool_destroy(pos->pool);
free(pos->addr);
free(pos);
daemon->max_connections++;
if (prev == NULL)
pos = daemon->connections;
else
pos = prev->next;
continue;
}
if ( (pos->headersReceived == 1) &&
(pos->response == NULL) )
MHD_call_connection_handler(pos);
prev = pos;
pos = pos->next;
}
}
/**
* Main select call.
*
* @param may_block YES if blocking, NO if non-blocking
* @return MHD_NO on serious errors, MHD_YES on success
*/
static int
MHD_select(struct MHD_Daemon * daemon,
int may_block) {
struct MHD_Connection * pos;
int num_ready;
fd_set rs;
fd_set ws;
fd_set es;
int max;
struct timeval timeout;
int ds;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
if(daemon == NULL)
abort();
FD_ZERO(&rs);
FD_ZERO(&ws);
FD_ZERO(&es);
max = 0;
if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) {
/* single-threaded, go over everything */
if (MHD_NO == MHD_get_fdset(daemon,
&rs,
&ws,
&es,
&max))
return MHD_NO;
} else {
/* accept only, have one thread per connection */
max = daemon->socket_fd;
FD_SET(daemon->socket_fd, &rs);
}
num_ready = SELECT(max + 1,
&rs,
&ws,
&es,
may_block == MHD_NO ? &timeout : NULL);
if (num_ready < 0) {
if (errno == EINTR)
return MHD_YES;
MHD_DLOG(daemon,
"Select failed: %s\n",
STRERROR(errno));
return MHD_NO;
}
ds = daemon->socket_fd;
if (ds == -1)
return MHD_YES;
if (FD_ISSET(ds,
&rs))
MHD_accept_connection(daemon);
if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) {
/* do not have a thread per connection, process all connections now */
pos = daemon->connections;
while (pos != NULL) {
ds = pos->socket_fd;
if (ds == -1) {
pos = pos->next;
continue;
}
if (FD_ISSET(ds, &rs))
MHD_connection_handle_read(pos);
if (FD_ISSET(ds, &ws))
MHD_connection_handle_write(pos);
pos = pos->next;
}
}
return MHD_YES;
}
/**
* Run webserver operations (without blocking unless
* in client callbacks). This method should be called
* by clients in combination with MHD_get_fdset
* if the client-controlled select method is used.
*
* @return MHD_YES on success, MHD_NO if this
* daemon was not started with the right
* options for this call.
*/
int
MHD_run(struct MHD_Daemon * daemon) {
if ( (daemon->shutdown != 0) ||
(0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) ||
(0 != (daemon->options & MHD_USE_SELECT_INTERNALLY)) )
return MHD_NO;
MHD_select(daemon, MHD_NO);
MHD_cleanup_connections(daemon);
return MHD_YES;
}
/**
* Thread that runs the select loop until the daemon
* is explicitly shut down.
*/
static void *
MHD_select_thread(void * cls) {
struct MHD_Daemon * daemon = cls;
while (daemon->shutdown == 0) {
MHD_select(daemon, MHD_YES);
MHD_cleanup_connections(daemon);
}
return NULL;
}
/**
* Start a webserver on the given port.
*
* @param port port to bind to
* @param apc callback to call to check which clients
* will be allowed to connect
* @param apc_cls extra argument to apc
* @param dh default handler for all URIs
* @param dh_cls extra argument to dh
* @return NULL on error, handle to daemon on success
*/
struct MHD_Daemon *
MHD_start_daemon(unsigned int options,
unsigned short port,
MHD_AcceptPolicyCallback apc,
void * apc_cls,
MHD_AccessHandlerCallback dh,
void * dh_cls,
...) {
const int on = 1;
struct MHD_Daemon * retVal;
int socket_fd;
struct sockaddr_in servaddr4;
struct sockaddr_in6 servaddr6;
const struct sockaddr * servaddr;
socklen_t addrlen;
va_list ap;
enum MHD_OPTION opt;
if ((options & MHD_USE_SSL) != 0)
return NULL;
if ( (port == 0) ||
(dh == NULL) )
return NULL;
if ((options & MHD_USE_IPv6) != 0)
socket_fd = SOCKET(PF_INET6, SOCK_STREAM, 0);
else
socket_fd = SOCKET(PF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) {
if ((options & MHD_USE_DEBUG) != 0)
fprintf(stderr,
"Call to socket failed: %s\n",
STRERROR(errno));
return NULL;
}
if ( (SETSOCKOPT(socket_fd,
SOL_SOCKET,
SO_REUSEADDR,
&on,
sizeof(on)) < 0) &&
(options & MHD_USE_DEBUG) != 0)
fprintf(stderr,
"setsockopt failed: %s\n",
STRERROR(errno));
if ((options & MHD_USE_IPv6) != 0) {
memset(&servaddr6,
0,
sizeof(struct sockaddr_in6));
servaddr6.sin6_family = AF_INET6;
servaddr6.sin6_port = htons(port);
servaddr = (struct sockaddr*) &servaddr6;
addrlen = sizeof(struct sockaddr_in6);
} else {
memset(&servaddr4,
0,
sizeof(struct sockaddr_in));
servaddr4.sin_family = AF_INET;
servaddr4.sin_port = htons(port);
servaddr = (struct sockaddr*) &servaddr4;
addrlen = sizeof(struct sockaddr_in);
}
if (BIND(socket_fd,
servaddr,
addrlen) < 0) {
if ( (options & MHD_USE_DEBUG) != 0)
fprintf(stderr,
"Failed to bind to port %u: %s\n",
port,
STRERROR(errno));
CLOSE(socket_fd);
return NULL;
}
if (LISTEN(socket_fd, 20) < 0) {
if ((options & MHD_USE_DEBUG) != 0)
fprintf(stderr,
"Failed to listen for connections: %s\n",
STRERROR(errno));
CLOSE(socket_fd);
return NULL;
}
retVal = malloc(sizeof(struct MHD_Daemon));
memset(retVal,
0,
sizeof(struct MHD_Daemon));
retVal->options = options;
retVal->port = port;
retVal->apc = apc;
retVal->apc_cls = apc_cls;
retVal->socket_fd = socket_fd;
retVal->default_handler.dh = dh;
retVal->default_handler.dh_cls = dh_cls;
retVal->default_handler.uri_prefix = "";
retVal->default_handler.next = NULL;
retVal->max_connections = MHD_MAX_CONNECTIONS_DEFAULT;
retVal->pool_size = MHD_POOL_SIZE_DEFAULT;
va_start(ap, dh_cls);
while (MHD_OPTION_END != (opt = va_arg(ap, enum MHD_OPTION))) {
switch (opt) {
case MHD_OPTION_CONNECTION_MEMORY_LIMIT:
retVal->pool_size = va_arg(ap, unsigned int);
break;
case MHD_OPTION_CONNECTION_LIMIT:
retVal->max_connections = va_arg(ap, unsigned int);
break;
default:
fprintf(stderr,
"Invalid MHD_OPTION argument! (Did you terminate the list with MHD_OPTION_END?)\n");
abort();
}
}
va_end(ap);
if ( ( (0 != (options & MHD_USE_THREAD_PER_CONNECTION)) ||
(0 != (options & MHD_USE_SELECT_INTERNALLY)) ) &&
(0 != pthread_create(&retVal->pid,
NULL,
&MHD_select_thread,
retVal)) ) {
MHD_DLOG(retVal,
"Failed to create listen thread: %s\n",
STRERROR(errno));
free(retVal);
CLOSE(socket_fd);
return NULL;
}
return retVal;
}
/**
* Shutdown an http daemon.
*/
void
MHD_stop_daemon(struct MHD_Daemon * daemon) {
void * unused;
if (daemon == NULL)
return;
daemon->shutdown = 1;
CLOSE(daemon->socket_fd);
daemon->socket_fd = -1;
if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) ||
(0 != (daemon->options & MHD_USE_SELECT_INTERNALLY)) ) {
pthread_kill(daemon->pid, SIGALRM);
pthread_join(daemon->pid, &unused);
}
while (daemon->connections != NULL) {
if (-1 != daemon->connections->socket_fd) {
CLOSE(daemon->connections->socket_fd);
daemon->connections->socket_fd = -1;
}
MHD_cleanup_connections(daemon);
}
free(daemon);
}
#ifndef WINDOWS
static struct sigaction sig;
static struct sigaction old;
static void sigalrmHandler(int sig) {
}
/**
* Initialize the signal handler for SIGALRM.
*/
void __attribute__ ((constructor)) pthread_handlers_ltdl_init() {
/* make sure SIGALRM does not kill us */
memset(&sig, 0, sizeof(struct sigaction));
memset(&old, 0, sizeof(struct sigaction));
sig.sa_flags = SA_NODEFER;
sig.sa_handler = &sigalrmHandler;
sigaction(SIGALRM, &sig, &old);
}
void __attribute__ ((destructor)) pthread_handlers_ltdl_fini() {
sigaction(SIGALRM, &old, &sig);
}
#endif
/* end of daemon.c */