libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit 32af5be6bc46b5285a613b584f827627db594e84
parent d5b39b0218b3c3a8548afefdd2fe183c13b8a7da
Author: Daniel Pittman <depittman@gmail.com>
Date:   Wed,  4 Apr 2007 00:02:54 +0000

First draft implementation of libmicrohttpd API. Many features incomplete at this time, some functionality has yet to be tested. 

Diffstat:
Msrc/daemon/daemon.c | 1541++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/daemon/daemontest.c | 215++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 1605 insertions(+), 151 deletions(-)

diff --git a/src/daemon/daemon.c b/src/daemon/daemon.c @@ -1,6 +1,6 @@ /* This file is part of libmicrohttpd - (C) 2007 YOUR NAME HERE + (C) 2007 Daniel Pittman libmicrohttpd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published @@ -20,41 +20,524 @@ /** * @file daemon.c - * @brief FIXME - * @author FIXME + * @brief This is my implementation of the libmicrohttpd interface. Many features incomplete at this time. + * @author Daniel Pittman + * @version 0.1.0 */ #include "config.h" #include "microhttpd.h" +#include <netinet/in.h> +#include <stdio.h> +#include <stdlib.h> +#include <netdb.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <pthread.h> + + +#define MHD_MAX_CONNECTIONS FD_SETSIZE -4 +#define MHD_MAX_BUF_SIZE 2048 +#define MHD_MAX_HEADERS 1024 +#define MHD_MAX_HANDLERS 1024 +#define MHD_MAX_RESPONSE 1024 + + +int MHD_handle_read(int, struct MHD_Daemon *); +int MHD_handle_write(int, struct MHD_Daemon *); +void * MHD_spawn_connections(void * data); +void * MHD_select(void * data); +int MHD_parse_message(struct MHD_Session * session); +void MHD_parse_URL(struct MHD_Session * session); + +struct MHD_Daemon { + unsigned int options; + + unsigned short port; + int socket_fd; + int max_fd; + + MHD_AcceptPolicyCallback apc; + void * apc_cls; + + fd_set read_fd_set; + fd_set write_fd_set; + fd_set except_fd_set; + + int shutdown; + pthread_t pid; + + struct MHD_Session * connections[MHD_MAX_CONNECTIONS]; + + int firstFreeHandler; + struct MHD_Access_Handler * handlers[MHD_MAX_HANDLERS]; + MHD_AccessHandlerCallback dh; + void * dh_cls; +}; + +struct MHD_Session { + struct sockaddr_in addr; + + int readLoc; + int writeLoc; + + int id; + int socket_fd; + pthread_t pid; + + struct MHD_Daemon * daemon; + + int bufPos; + int messagePos; + char inbuf[MHD_MAX_BUF_SIZE]; + + int firstFreeHeader; + char * requestType; + char * documentName; + struct MHD_HTTP_Header * headers[MHD_MAX_HEADERS]; + + unsigned short responsePending; + int currentResponse; + struct MHD_Response * currentResponses[MHD_MAX_RESPONSE]; +}; + + +struct MHD_Response { + pthread_mutex_t mutex; + + unsigned int responseCode; + + int freeWhenFinished; + + unsigned int headersSent; + + size_t size; + void * data; + int bytesSentSoFar; + + int must_free; + MHD_ContentReaderCallback crc; + + void * crc_cls; + MHD_ContentReaderFreeCallback crfc; + + int firstFreeHeader; + struct MHD_HTTP_Header * headers[MHD_MAX_HEADERS]; + + struct MHD_Session * currentSession; +}; + +struct MHD_Access_Handler { + char * uri_prefix; + MHD_AccessHandlerCallback dh; + void * dh_cls; +}; + +struct MHD_HTTP_Header { + char * header; + char * headerContent; + enum MHD_ValueKind kind; +}; /** - * 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 + * Add a header line to the response. + * + * @return MHD_NO on error (i.e. invalid header or content format). */ -struct MHD_Daemon * -MHD_start_daemon(unsigned int options, - unsigned short port, - MHD_AcceptPolicyCallback apc, - void * apc_cls, - MHD_AccessHandlerCallback dh, - void * dh_cls) { - return NULL; +int +MHD_add_response_header(struct MHD_Response * response, + const char * header, + const char * content) { + //Note that as of this time this function will also return + //an error if the maximum number of headers allowed is exceeded. + + char * saveptr; + char * newHeader; + char * newContent; + int i; + + if(response == NULL || header == NULL || content == NULL || strlen(header) == 0 || strlen(content) == 0) { + return MHD_NO; + } + + if(response->firstFreeHeader >= MHD_MAX_HEADERS) { + return MHD_NO; + } + + newHeader = (char *)malloc(strlen(header)+1); + newContent = (char *)malloc(strlen(content)+1); + + if(newHeader == NULL || newContent == NULL) { + fprintf(stderr, "Error allocating memory!\n"); + return MHD_NO; + } + + sprintf(newHeader, "%s", header); + sprintf(newContent, "%s", content); + + + if(strtok_r(newHeader, " \t\r\n", &saveptr) != NULL) { + fprintf(stderr, "Malformed header!\n"); + free(newContent); + free(newHeader); + return MHD_NO; + } + + if(strtok_r(newContent, "\n", &saveptr) != NULL) { + fprintf(stderr, "Malformed content!\n"); + free(newContent); + free(newHeader); + return MHD_NO; + } + + struct MHD_HTTP_Header * newHTTPHeader = (struct MHD_HTTP_Header *)malloc(sizeof(struct MHD_HTTP_Header)); + + if(newHTTPHeader == NULL) { + fprintf(stderr, "Error allocating memory!\n"); + free(newContent); + free(newHeader); + return MHD_NO; + } + + response->headers[response->firstFreeHeader]->header = newHeader; + response->headers[response->firstFreeHeader]->headerContent = newContent; + + //For now, everything is a HTTP Header... this needs to be improved! + response->headers[response->firstFreeHeader]->kind = MHD_HEADER_KIND; + + response->firstFreeHeader=MHD_MAX_HEADERS; + for(i = 0; i < MHD_MAX_HEADERS; i++) { + if(response->headers[i] == NULL) { + response->firstFreeHeader = i; + break; + } + } + + return MHD_YES; } +/** + * This function accepts an incoming connection + * and creates the MHD_Session object for it. + * It also enforces policy by way of calling the accept policy callback + */ +int +MHD_create_connection(struct MHD_Daemon * daemon) { + int i, first_free, size; + + if(daemon == NULL) + return -1; + + first_free = -1; + for(i = 0; i < MHD_MAX_CONNECTIONS; i++) { + if(daemon->connections[i] == NULL) { + first_free = i; + break; + } + } + + if(first_free == -1) + return -1; + + daemon->connections[first_free] = (struct MHD_Session *)malloc(sizeof(struct MHD_Session)); + if(daemon->connections[first_free] == NULL) { + if((daemon->options & MHD_USE_DEBUG) != 0) + fprintf(stderr, "Error allocating memory!\n"); + return -1; + } + + size = sizeof(struct sockaddr); + daemon->connections[first_free]->socket_fd = + + accept(daemon->socket_fd, (struct sockaddr *)&daemon->connections[first_free]->addr, + (socklen_t *)&size); + + if(daemon->connections[first_free]->socket_fd == -1) { + free(daemon->connections[first_free]); + daemon->connections[first_free] = NULL; + if((daemon->options & MHD_USE_DEBUG) != 0) { + fprintf(stderr, "Error accepting incoming connections!\n"); + } + return -1; + } + + if(daemon->apc != NULL && daemon->apc(daemon->apc_cls, (const struct sockaddr *)&daemon->connections[first_free]->addr, (socklen_t)sizeof(struct sockaddr_in))==MHD_NO) { + close(daemon->connections[first_free]->socket_fd); + free(daemon->connections[first_free]); + daemon->connections[first_free] = NULL; + if((daemon->options & MHD_USE_DEBUG) != 0) { + fprintf(stderr, "Connection denied based on accept policy callback!\n"); + } + return -1; + } + + daemon->connections[first_free]->id = first_free; + daemon->connections[first_free]->daemon = daemon; + daemon->connections[first_free]->pid = NULL; + daemon->connections[first_free]->bufPos = 0; + daemon->connections[first_free]->messagePos= 0; + daemon->connections[first_free]->responsePending = 0; + memset(daemon->connections[first_free]->inbuf, '\0', MHD_MAX_BUF_SIZE); + daemon->connections[first_free]->currentResponse = 0; + daemon->connections[first_free]->firstFreeHeader = 0; + + for(i = 0; i < MHD_MAX_HEADERS; i++) { + daemon->connections[first_free]->headers[i] = NULL; + } + + for(i = 0; i < MHD_MAX_RESPONSE; i++) { + daemon->connections[first_free]->currentResponses[i] = NULL; + } + + if(daemon->max_fd < daemon->connections[first_free]->socket_fd) { + daemon->max_fd = daemon->connections[first_free]->socket_fd; + } + + return first_free; +} /** - * Shutdown an http daemon. + * Create a response object. The response object can be extended with + * header information and then be used any number of times. + * + * @param size size of the data portion of the response, -1 for unknown + * @param crc callback to use to obtain response data + * @param crc_cls extra argument to crc + * @param crfc callback to call to free crc_cls resources + * @return NULL on error (i.e. invalid arguments, out of memory) + */ +struct MHD_Response * +MHD_create_response_from_callback(size_t size, + MHD_ContentReaderCallback crc, + void * crc_cls, + MHD_ContentReaderFreeCallback crfc) { + + struct MHD_Response * retVal; + int i; + + + if(crc == NULL) { + fprintf(stderr, "A ContentReaderCallback must be provided to MHD_create_response_from_callback!\n"); + return NULL; + } + + retVal = (struct MHD_Response *) malloc(sizeof(struct MHD_Response)); + if(retVal == NULL) { + fprintf(stderr, "Error allocating memory!\n"); + return NULL; + } + + retVal->size = size; + + retVal->crc = crc; + retVal->crc_cls = crc_cls; + + retVal->crfc = crfc; + + retVal->firstFreeHeader = 0; + + retVal->responseCode = 0; + + retVal->headersSent = 0; + + retVal->bytesSentSoFar = 0; + + retVal->freeWhenFinished = 0; + retVal->currentSession = NULL; + + if(pthread_mutex_init(&retVal->mutex, NULL) != 0) { + fprintf(stderr, "Error initializing mutex!\n"); + free(retVal); + return NULL; + } + + for(i = 0; i < MHD_MAX_HEADERS; i++) { + retVal->headers[i] = NULL; + } + + retVal->data = NULL; + retVal->must_free = 0; + + return retVal; +} + +/** + * Create a response object. The response object can be extended with + * header information and then be used any number of times. + * + * @param size size of the data portion of the response + * @param data the data itself + * @param must_free libmicrohttpd should free data when done + * @param must_copy libmicrohttpd must make a copy of data + * right away, the data maybe released anytime after + * this call returns + * @return NULL on error (i.e. invalid arguments, out of memory) + */ +struct MHD_Response * +MHD_create_response_from_data(size_t size, + void * data, + int must_free, + int must_copy) { + + struct MHD_Response * retVal; + int i; + + + if(data == NULL) { + fprintf(stderr, "data must be provided to MHD_Create_response_from_data!\n"); + return NULL; + } + + retVal = (struct MHD_Response *) malloc(sizeof(struct MHD_Response)); + if(retVal == NULL) { + fprintf(stderr, "Error allocating memory!\n"); + return NULL; + } + + retVal->size = size; + + retVal->crc = NULL; + retVal->crc_cls = NULL; + retVal->crfc = NULL; + + retVal->responseCode = 0; + + retVal->firstFreeHeader = 0; + retVal->freeWhenFinished = 0; + retVal->currentSession = NULL; + + retVal->headersSent = 0; + + retVal->bytesSentSoFar = 0; + + for(i = 0; i < MHD_MAX_HEADERS; i++) { + retVal->headers[i] = NULL; + } + + if(pthread_mutex_init(&retVal->mutex, NULL) != 0) { + fprintf(stderr, "Error initializing mutex!\n"); + free(retVal); + return NULL; + } + + if(must_copy) { + retVal->data = malloc(size); + if(retVal->data == NULL) { + fprintf(stderr, "Error allocating memory!\n"); + free(retVal); + return NULL; + } + memcpy(retVal->data, data, size); + retVal->must_free = 1; + } else { + retVal->data = data; + retVal->must_free = must_free; + } + + return retVal; +} + +/** + * Delete a header line from the response. + * + * @return MHD_NO on error (no such header known) + */ +int +MHD_del_response_header(struct MHD_Response * response, + const char * header, + const char * content) { + int i; + + if(header == NULL || content == NULL) { + return MHD_NO; + } + + for(i = 0; i < MHD_MAX_HEADERS; i++) { + if(response->headers[i] != NULL && + strncmp(header, response->headers[i]->header, strlen(header)) == 0 && + strncmp(content, response->headers[i]->headerContent, strlen(header)) == 0) { + free(response->headers[i]->header); + free(response->headers[i]->headerContent); + free(response->headers[i]); + response->headers[i] = NULL; + return MHD_YES; + } + } + return MHD_NO; +} + +/** + * Destroy a response object and associated resources. Note that + * libmicrohttpd may keep some of the resources around if the response + * is still in the queue for some clients, so the memory may not + * necessarily be freed immediatley. */ void -MHD_stop_daemon(struct MHD_Daemon * daemon) { +MHD_destroy_response(struct MHD_Response * response) { + int i; + + if(response == NULL) { + return; + } + + pthread_mutex_lock(&response->mutex); + + if(response->currentSession != NULL) { + response->freeWhenFinished = 1; + pthread_mutex_unlock(&response->mutex); + return; + } + + if(response->must_free && response->data != NULL) { + free(response->data); + } + + if(response->crfc != NULL && response->crc_cls != NULL) { + response->crfc(response->crc_cls); + } + + for(i = 0; i < MHD_MAX_HEADERS; i++) { + if(response->headers[i] == NULL) + continue; + + free(response->headers[i]->header); + free(response->headers[i]->headerContent); + free(response->headers[i]); + } + + pthread_mutex_unlock(&response->mutex); + pthread_mutex_destroy(&response->mutex); + + free(response); +} + +/** + * Thi function is similar to destroy_response except + * that it was created to destroy the session object. + */ +void +MHD_destroy_session(struct MHD_Session * session) { + int i; + + for(i = 0; i < MHD_MAX_HEADERS; i++) { + if(session->headers[i] != NULL) { + free(session->headers[i]); + } + } + + for(i = 0; i < MHD_MAX_RESPONSE; i++) { + if(session->currentResponses[i] != NULL) { + pthread_mutex_lock(&session->currentResponses[i]->mutex); + session->currentResponses[i]->currentSession = NULL; + pthread_mutex_unlock(&session->currentResponses[i]->mutex); + } + } + + close(session->socket_fd); + free(session); } /** @@ -70,54 +553,62 @@ MHD_get_fdset(struct MHD_Daemon * daemon, fd_set * write_fd_set, fd_set * except_fd_set, int * max_fd) { - return 0; -} -/** - * 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) { - return 0; -} + int i; + if(daemon == NULL || read_fd_set == NULL || write_fd_set == NULL || except_fd_set == NULL || max_fd == NULL) { + return MHD_NO; + } -/** - * 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) { - return 0; + if((daemon->options & MHD_USE_THREAD_PER_CONNECTION) != 0) { + return MHD_NO; + } + + FD_ZERO(read_fd_set); + FD_ZERO(write_fd_set); + FD_ZERO(except_fd_set); + + FD_SET(daemon->socket_fd, &daemon->read_fd_set); + for(i = 0; i < MHD_MAX_CONNECTIONS; i++) { + if(daemon->connections[i] != NULL) { + FD_SET(daemon->connections[i]->socket_fd, read_fd_set); + FD_SET(daemon->connections[i]->socket_fd, write_fd_set); + } + } + + *max_fd = daemon->max_fd; + + return MHD_YES; } /** - * Unregister an access handler for the URIs beginning with - * uri_prefix. + * Get all of the headers added to a response. * - * @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) { - return 0; + * @param iterator callback to call on each header; + * maybe NULL (then just count headers) + * @param iterator_cls extra argument to iterator + * @return number of entries iterated over + */ +int +MHD_get_response_headers(struct MHD_Response * response, + MHD_KeyValueIterator * iterator, + void * iterator_cls) { + int i, numHeaders; + + if(response == NULL) { + return -1; + } + + numHeaders = 0; + for(i = 0; i < MHD_MAX_HEADERS; i++) { + if(response->headers[i] != NULL) { + if(iterator != NULL) { + (*iterator)(iterator_cls, response->headers[i]->kind, response->headers[i]->header, response->headers[i]->headerContent); + } + numHeaders++; + } + } + return numHeaders; } /** @@ -132,7 +623,331 @@ int MHD_get_session_values(struct MHD_Session * session, enum MHD_ValueKind kind, MHD_KeyValueIterator * iterator, - void * iterator_cls); + void * iterator_cls) { + int i, numHeaders; + + if(session == NULL) { + return -1; + } + numHeaders = 0; + for(i = 0; i < MHD_MAX_HEADERS; i++) { + if(session->headers[i] != NULL && session->headers[i]->kind == kind) { + if(iterator != NULL) { + (*iterator)(iterator_cls, session->headers[i]->kind, session->headers[i]->header, session->headers[i]->headerContent); + } + numHeaders++; + } + } + return numHeaders; +} + +/** + * This function is intented to be called in the case of + * multithreaded connections. A thread will be spawned calling this + * function with a particular connection, and the thread will poll the connection + * (this should be improved) until there is something to do + */ +void * +MHD_handle_connection(void * data) { + struct MHD_Session * con; + int num_ready; + struct timeval timeout; + fd_set read; + fd_set write; + + con = data; + + if(con == NULL) + return NULL; + + do { + + FD_ZERO(&read); + FD_ZERO(&write); + + FD_SET(con->socket_fd, &read); + FD_SET(con->socket_fd, &write); + + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + num_ready = select(con->socket_fd + 1, + &read, &write, NULL, &timeout); + + if(num_ready > 0) { + if(FD_ISSET(con->socket_fd, &read)) { + if(MHD_handle_read(con->id, con->daemon) == MHD_NO) { + pthread_detach(pthread_self()); + return NULL; + } + } + if (FD_ISSET(con->socket_fd, &write)) { + if(MHD_handle_write(con->id, con->daemon) == MHD_NO) { + pthread_detach(pthread_self()); + return NULL; + } + } + } + } while (!con->daemon->shutdown); + + return NULL; +} + +/** + * This function is created to handle the except file descriptor + * set, but it is doubtfull that it will ever be used. + */ +void +MHD_handle_except(int connection_id, struct MHD_Daemon * daemon) { + //It is unlikely that this function will ever need to be implemented. +} + +/** + * This function handles a particular connection when it has been + * determined that there is data to be read off a socket. All implementations + * (multithreaded, external select, internal select) call this function + * to handle reads. + */ +int +MHD_handle_read(int connection_id, struct MHD_Daemon * daemon) { + int bytes_read,i; + + if((daemon->options & MHD_USE_DEBUG) != 0) { + fprintf(stderr, "Enter MHD_handle_read\n"); + } + + if(daemon == NULL || daemon->connections[connection_id]==NULL) { + return MHD_NO; + } + + if(daemon->connections[connection_id]->responsePending) { + return MHD_YES; + } + + daemon->connections[connection_id]->firstFreeHeader = 0; + daemon->connections[connection_id]->requestType = NULL; + + for(i = 0; i < MHD_MAX_HEADERS; i++) { + daemon->connections[connection_id]->headers[i] = NULL; + } + + + + memmove(daemon->connections[connection_id]->inbuf, daemon->connections[connection_id]->inbuf+daemon->connections[connection_id]->messagePos, daemon->connections[connection_id]->bufPos - daemon->connections[connection_id]->messagePos); + + memset(daemon->connections[connection_id]->inbuf + daemon->connections[connection_id]->bufPos - daemon->connections[connection_id]->messagePos, + 0, MHD_MAX_BUF_SIZE - daemon->connections[connection_id]->bufPos + (daemon->connections[connection_id]->bufPos - daemon->connections[connection_id]->messagePos)); + + bytes_read = recv(daemon->connections[connection_id]->socket_fd, + daemon->connections[connection_id]->inbuf + daemon->connections[connection_id]->bufPos - daemon->connections[connection_id]->messagePos, + MHD_MAX_BUF_SIZE - (daemon->connections[connection_id]->bufPos - daemon->connections[connection_id]->messagePos), 0); + + daemon->connections[connection_id]->bufPos = bytes_read + daemon->connections[connection_id]->bufPos - daemon->connections[connection_id]->messagePos; + + if(bytes_read == 0) { + MHD_destroy_session(daemon->connections[connection_id]); + daemon->connections[connection_id] = NULL; + return MHD_NO; + } else { + fprintf(stderr, "\"%s\"\n", daemon->connections[connection_id]->inbuf); + i = MHD_parse_message(daemon->connections[connection_id]); + if(i == -1) { + daemon->connections[connection_id]->messagePos = daemon->connections[connection_id]->bufPos; + return MHD_YES; + } else { + daemon->connections[connection_id]->messagePos = i; + fprintf(stderr, "Number of bytes in header: %i\n", daemon->connections[connection_id]->messagePos); + } + + daemon->connections[connection_id]->responsePending = 1; + + MHD_parse_URL(daemon->connections[connection_id]); + + for(i = 0; i < MHD_MAX_HANDLERS; i++) { + if(daemon->handlers[i] == NULL) + continue; + + //header 0 will hold the url of the request + if(strstr(daemon->connections[connection_id]->headers[0]->headerContent, daemon->handlers[i]->uri_prefix) != NULL){ + return daemon->handlers[i]->dh(daemon->handlers[i]->dh_cls, daemon->connections[connection_id], + daemon->connections[connection_id]->documentName, daemon->connections[connection_id]->requestType, NULL, NULL); + } + } + return daemon->dh(daemon->dh_cls, daemon->connections[connection_id], + daemon->connections[connection_id]->documentName, daemon->connections[connection_id]->requestType, NULL, NULL); + } + + return MHD_YES; +} + +/** + * This function was created to handle writes to sockets when it has been + * determined that the socket can be written to. If there is no data + * to be written, however, the function call does nothing. All implementations + * (multithreaded, external select, internal select) call this function + */ +int +MHD_handle_write(int connection_id, struct MHD_Daemon * daemon) { + struct MHD_Session * session; + + struct MHD_Response * response; + + int i; + + char * buffer[2048]; + + char * responseMessage; + int numBytesInMessage; + + if((daemon->options & MHD_USE_DEBUG) != 0) { + fprintf(stderr, "Enter MHD_handle_write\n"); + } + + + session = daemon->connections[connection_id]; + + response = session->currentResponses[session->currentResponse]; + + numBytesInMessage = 25; + + responseMessage = malloc(25); + if(responseMessage == NULL) { + if(daemon->options & MHD_USE_DEBUG) + fprintf(stderr, "Error allocating memory!\n"); + return MHD_NO; + } + + if(response == NULL) + return MHD_NO; + + pthread_mutex_lock(&response->mutex); + + if(!response->headersSent) { + sprintf(responseMessage, "HTTP/1.1 %i Go to hell!\r\n", response->responseCode); + fprintf(stderr, "%s\n", responseMessage); + if(send(session->socket_fd, responseMessage, strlen(responseMessage), 0) != strlen(responseMessage)) { + fprintf(stderr, "Error! could not send an entire header in one call to send! unable to handle this case as of this time.\n"); + pthread_mutex_unlock(&response->mutex); + return MHD_NO; + } + + for(i = 0; i < MHD_MAX_HEADERS; i++) { + if(response->headers[i] == NULL) + continue; + + if(strlen(response->headers[i]->header) + strlen(response->headers[i]->headerContent) + 5 > numBytesInMessage) { + free(responseMessage); + responseMessage = malloc(strlen(response->headers[i]->header) + strlen(response->headers[i]->headerContent) + 5); + if(responseMessage == NULL) { + if(daemon->options & MHD_USE_DEBUG) + fprintf(stderr, "Error allocating memory!\n"); + pthread_mutex_unlock(&response->mutex); + return MHD_NO; + } + numBytesInMessage = strlen(response->headers[i]->header) + strlen(response->headers[i]->headerContent) + 5; + } + sprintf(responseMessage, "%s: %s\r\n", response->headers[i]->header, response->headers[i]->headerContent); + fprintf(stderr, "%s\n", responseMessage); + if(send(session->socket_fd, responseMessage, strlen(responseMessage), 0) != strlen(responseMessage)) { + fprintf(stderr, "Error! could not send an entire header in one call to send! unable to handle this case as of this time.\n"); + pthread_mutex_unlock(&response->mutex); + return MHD_NO; + } + } + + response->headersSent = 1; + } + + if(response->data != NULL) { + if(response->bytesSentSoFar == 0) { + if(numBytesInMessage < 32) { + free(responseMessage); + responseMessage = malloc(32); + if(responseMessage == NULL) { + if(daemon->options & MHD_USE_DEBUG) + fprintf(stderr, "Error allocating memory!\n"); + pthread_mutex_unlock(&response->mutex); + return MHD_NO; + } + } + sprintf(responseMessage, "Content-length: %i\r\n\r\n", response->size); + fprintf(stderr, "%s\n", responseMessage); + if(send(session->socket_fd, responseMessage, strlen(responseMessage),0)!= strlen(responseMessage)) { + fprintf(stderr, "Error! could not send an entire header in one call to send! unable to handle this case as of this time.\n"); + pthread_mutex_unlock(&response->mutex); + return MHD_NO; + } + } + + i = send(session->socket_fd, response->data+response->bytesSentSoFar, response->size-response->bytesSentSoFar,0); + response->bytesSentSoFar += i; + + fprintf(stderr, "Sent %i bytes of data\nTotal to send is %i bytes\n", i, response->size); + + if(response->bytesSentSoFar == response->size) { + session->currentResponses[session->currentResponse] = NULL; + session->currentResponse = (session->currentResponse + 1) % MHD_MAX_RESPONSE; + response->currentSession = NULL; + + if(response->freeWhenFinished) { + pthread_mutex_unlock(&response->mutex); + MHD_destroy_response(response); + } + /*THIS NEEDS TO BE HANDLED ANOTHER WAY!!! TIMEOUT, ect..., as of now this is the only way to get test case to work + * since client never disconnects on their own! + */ + if(session->currentResponses[session->currentResponse] == NULL) { + MHD_destroy_session(session); + daemon->connections[connection_id] = NULL; + return MHD_NO; + } + } + } else { + if(response->crc == NULL) { + pthread_mutex_unlock(&response->mutex); + return MHD_NO; + } + + if(response->bytesSentSoFar == 0) { + if(send(session->socket_fd, "\r\n", response->size,0) != 2) { + fprintf(stderr, "Error! could not send an entire header in one call to send! unable to handle this case as of this time.\n"); + pthread_mutex_unlock(&response->mutex); + return MHD_NO; + } + } + memset(buffer, 0, 2048); + + i = response->crc(response->crc_cls, response->bytesSentSoFar, (char *)buffer, 2048); + + if(i == -1) { + pthread_mutex_unlock(&response->mutex); + + session->currentResponses[session->currentResponse] = NULL; + session->currentResponse = (session->currentResponse + 1) % MHD_MAX_RESPONSE; + response->currentSession = NULL; + + if(response->freeWhenFinished) { + pthread_mutex_unlock(&response->mutex); + MHD_destroy_response(response); + } + /*THIS NEEDS TO BE HANDLED ANOTHER WAY!!! TIMEOUT, ect..., as of now this is the only way to get test case to work + * since client never disconnects on their own! + */ + if(session->currentResponses[session->currentResponse] == NULL) { + MHD_destroy_session(session); + daemon->connections[connection_id] = NULL; + return MHD_NO; + } + + } else { + i = send(session->socket_fd, buffer, i,0); + response->bytesSentSoFar += i; + } + } + pthread_mutex_unlock(&response->mutex); + return MHD_YES; +} + /** * Get a particular header value. If multiple @@ -145,7 +960,106 @@ const char * MHD_lookup_session_value(struct MHD_Session * session, enum MHD_ValueKind kind, const char * key) { - return NULL; + int i; + + for(i = 0; i < MHD_MAX_HEADERS; i++) { + if(session->headers[i] != NULL && + session->headers[i]->kind == kind && + strncmp(session->headers[i]->header, key, strlen(session->headers[i]->header)) == 0) { + return (const char *)session->headers[i]->headerContent; + } + } + + return NULL; +} + + +/** + * This function is designed to parse the input buffer of a given session. + * It is assumed that the data being parsed originates at buffer location + * 0 (a valid assumption since the buffer is shifted after each message) + */ +int +MHD_parse_message(struct MHD_Session * session) { + const char * crlfcrlf = "\r\n\r\n"; + const char * crlf = "\r\n"; + + char * saveptr; + char * saveptr1; + + struct MHD_HTTP_Header * newHeader; + char * curTok; + char * curTok1; + + int numBytes; + + curTok = strstr(session->inbuf, crlfcrlf); + + if(curTok == NULL) { + return -1; + } + + memset(curTok+2, 0, 2); + + numBytes = strlen(session->inbuf) + 2; + + curTok = strtok_r(session->inbuf, crlf, &saveptr); + + session->requestType = strtok_r(curTok, " ", &saveptr1); + + newHeader = (struct MHD_HTTP_Header *)malloc(sizeof(struct MHD_HTTP_Header)); + if(newHeader == NULL) { + if(session->daemon->options & MHD_USE_DEBUG) + fprintf(stderr, "Error allocating memory!\n"); + return -1; + } + newHeader->kind = MHD_GET_ARGUMENT_KIND; + newHeader->header = session->requestType; + newHeader->headerContent = strtok_r(NULL, " ", &saveptr1); + + session->headers[session->firstFreeHeader++] = newHeader; + + curTok = strtok_r(NULL, crlf, &saveptr); + while(curTok != NULL && session->firstFreeHeader < MHD_MAX_HEADERS) { + curTok1 = strtok_r(curTok, ":", &saveptr1); + newHeader = (struct MHD_HTTP_Header *)malloc(sizeof(struct MHD_HTTP_Header)); + if(newHeader == NULL) { + if(session->daemon->options & MHD_USE_DEBUG) + fprintf(stderr, "Error allocating memory!\n"); + return -1; + } + newHeader->header = curTok1; + newHeader->headerContent = curTok + strlen(curTok1) + 2; + //For now, everything is a get! + newHeader->kind = MHD_GET_ARGUMENT_KIND; + session->headers[session->firstFreeHeader++] = newHeader; + curTok = strtok_r(NULL, crlf, &saveptr); + } + + return numBytes; +} + +/** + * This function needs to do a lot more (i.e. break up get arguments) + * but for now just seperates the prefix of the url from the document + * portion. + */ +void +MHD_parse_URL(struct MHD_Session * session) { + char * working; + int pos,i; + + working = session->headers[0]->headerContent; + + pos = 0; + for(i = 0; i < strlen(working); i++) { + if(working[i] == '/') + pos = i+1; + } + if(pos >= strlen(working)) + pos = 0; + + session->documentName = session->headers[0]->headerContent+pos; } /** @@ -162,105 +1076,464 @@ int MHD_queue_response(struct MHD_Session * session, unsigned int status_code, struct MHD_Response * response) { - return 0; + + //As of now this function can only support a fixed amount of queued responses, and will + //return MHD_NO if that queue is full + + int index; + + if(session == NULL || response == NULL) { + return MHD_NO; + } + + pthread_mutex_lock(&response->mutex); + + if(response->currentSession != NULL) { + return MHD_NO; + } + + if(session->currentResponses[session->currentResponse] == NULL) { + index = session->currentResponse; + } else if(session->currentResponses[session->currentResponse + 1 % MHD_MAX_RESPONSE] == NULL) { + index = session->currentResponse + 1 % MHD_MAX_RESPONSE; + } else { + pthread_mutex_unlock(&response->mutex); + return MHD_NO; + } + + response->responseCode = status_code; + session->currentResponses[index] = response; + response->currentSession = session; + session->responsePending = 0; + + pthread_mutex_unlock(&response->mutex); + + return MHD_YES; } - /** - * Create a response object. The response object can be extended with - * header information and then be used any number of times. + * @return -1 if no data uploaded; otherwise number of bytes + * read into buf; 0 for end of transmission + * Specification not complete at this time. + */ +int +MHD_read_file_upload(struct MHD_Session * session, + void * buf, + size_t len) { + //This function will not be implemented until the specification is completed. + return -1; +} + +/** + * 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. * - * @param size size of the data portion of the response, -1 for unknown - * @param crc callback to use to obtain response data - * @param crc_cls extra argument to crc - * @param crfc callback to call to free crc_cls resources - * @return NULL on error (i.e. invalid arguments, out of memory) + * @return MHD_YES on success, MHD_NO if this + * daemon was not started with the right + * options for this call. */ -struct MHD_Response * -MHD_create_response_from_callback(size_t size, - MHD_ContentReaderCallback crc, - void * crc_cls, - MHD_ContentReaderFreeCallback crfc) { - return NULL; +int +MHD_run(struct MHD_Daemon * daemon) { + + if((daemon->options & MHD_USE_THREAD_PER_CONNECTION) != 0) { + daemon->shutdown = 0; + if(pthread_create(&daemon->pid, NULL, (void *) &MHD_spawn_connections, (void *)daemon) == 0) { + return MHD_YES; + } else { + return MHD_NO; + } + } else if((daemon->options & MHD_USE_SELECT_INTERNALLY) != 0) { + daemon->shutdown = 0; + if(pthread_create(&daemon->pid, NULL, (void *) &MHD_select, (void *)daemon) == 0) { + return MHD_YES; + } else { + return MHD_NO; + } + } else { + daemon->shutdown = 1; + return (MHD_select((void *)daemon) == NULL); + } } + /** - * Create a response object. The response object can be extended with - * header information and then be used any number of times. + * Register an access handler for all URIs beginning with uri_prefix. * - * @param size size of the data portion of the response - * @param data the data itself - * @param must_free libmicrohttpd should free data when done - * @param must_copy libmicrohttpd must make a copy of data - * right away, the data maybe released anytime after - * this call returns - * @return NULL on error (i.e. invalid arguments, out of memory) + * @param uri_prefix + * @return MRI_NO if a handler for this exact prefix + * already exists */ -struct MHD_Response * -MHD_create_response_from_data(size_t size, - void * data, - int must_free, - int must_copy) { - return NULL; +int +MHD_register_handler(struct MHD_Daemon * daemon, + const char * uri_prefix, + MHD_AccessHandlerCallback dh, + void * dh_cls) { + int i; + + //This function will also return MHD_NO if the maximum number of supported handlers is exceeded + + if(daemon == NULL || uri_prefix == NULL || dh == NULL) { + return MHD_NO; + } + + if(daemon->firstFreeHandler >= MHD_MAX_HANDLERS) { + return MHD_NO; + } + + daemon->handlers[daemon->firstFreeHandler] = malloc(sizeof(struct MHD_Access_Handler)); + + if(daemon->handlers[daemon->firstFreeHandler] == NULL) { + if((daemon->options & MHD_USE_DEBUG) != 0) + fprintf(stderr, "Error allocating memory!\n"); + return MHD_NO; + } + + daemon->handlers[daemon->firstFreeHandler]->uri_prefix = malloc(strlen(uri_prefix)+1); + if(daemon->handlers[daemon->firstFreeHandler]->uri_prefix == NULL) { + if((daemon->options & MHD_USE_DEBUG) != 0) { + free(daemon->handlers[daemon->firstFreeHandler]); + fprintf(stderr, "Error allocating memory!\n"); + } + return MHD_NO; + } + sprintf(daemon->handlers[daemon->firstFreeHandler]->uri_prefix, "%s", uri_prefix); + + daemon->handlers[daemon->firstFreeHandler]->dh = dh; + daemon->handlers[daemon->firstFreeHandler]->dh_cls = dh_cls; + + daemon->firstFreeHandler = MHD_MAX_HANDLERS; + for(i = 0; i < MHD_MAX_HANDLERS; i++) { + if(daemon->handlers[i] == NULL) { + daemon->firstFreeHandler = i; + break; + } + } + + return MHD_YES; + } /** - * Destroy a response object and associated resources. Note that - * libmicrohttpd may keep some of the resources around if the response - * is still in the queue for some clients, so the memory may not - * necessarily be freed immediatley. + * This function is the entry point for either internal or external select. + * The only differences between the two forms of running is whether the call is + * made from a new thread or the main thread, and whether the initial value + * of shutdown is 0 or 1 (1 for loop, 0 for one time pass) */ -void -MHD_destroy_response(struct MHD_Response * response) { +void * +MHD_select(void * data) { + struct MHD_Daemon * daemon; + int i, num_ready; + struct timeval timeout; + + daemon = data; + if(daemon == NULL) { + return NULL; + } + do { + FD_ZERO(&daemon->read_fd_set); + FD_ZERO(&daemon->write_fd_set); + FD_ZERO(&daemon->except_fd_set); + + FD_SET(daemon->socket_fd, &daemon->read_fd_set); + for(i = 0; i < MHD_MAX_CONNECTIONS; i++) { + if(daemon->connections[i] != NULL) { + FD_SET(daemon->connections[i]->socket_fd, &daemon->read_fd_set); + FD_SET(daemon->connections[i]->socket_fd, &daemon->write_fd_set); + } + } + + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + num_ready = select(daemon->max_fd + 1, + &(daemon->read_fd_set), &(daemon->write_fd_set), &(daemon->except_fd_set), &timeout); + + if(num_ready > 0) { + for(i = 0; i < MHD_MAX_CONNECTIONS; i++) { + if(daemon->connections[i] != NULL) { + if(FD_ISSET(daemon->connections[i]->socket_fd, &(daemon->read_fd_set))) { + if(MHD_handle_read(i, daemon) == MHD_NO) + continue; + } + if (FD_ISSET(daemon->connections[i]->socket_fd, &(daemon->write_fd_set))) { + if(MHD_handle_write(i, daemon) == MHD_NO) + continue; + } + if (FD_ISSET(daemon->connections[i]->socket_fd, &(daemon->except_fd_set))) { + MHD_handle_except(i, daemon); + } + } + } + if(FD_ISSET(daemon->socket_fd, &(daemon->read_fd_set))) { + if(MHD_create_connection(daemon) == -1) { + continue; + } + } + } + } while (!daemon->shutdown); + + return NULL; } /** - * Add a header line to the response. - * - * @return MHD_NO on error (i.e. invalid header or content format). + * This function was created for the case of multithreaded connections. + * A thread will spawned to sit on this function, and in turn spawns more + * threads, one per connection. */ -int -MHD_add_response_header(struct MHD_Response * response, - const char * header, - const char * content) { - return 0; +void * +MHD_spawn_connections(void * data) { + struct MHD_Daemon * daemon; + int con, num_ready; + struct timeval timeout; + fd_set read; + + daemon = data; + if(daemon == NULL) { + return NULL; + } + + do { + FD_ZERO(&read); + FD_SET(daemon->socket_fd, &read); + + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + num_ready = select(daemon->socket_fd + 1,&read, NULL, NULL, &timeout); + + if(num_ready > 0) { + con = MHD_create_connection(daemon); + if(con == -1) + continue; + + if(pthread_create(&daemon->connections[con]->pid, NULL, (void *) &MHD_handle_connection, (void *)daemon->connections[con]) != 0) { + if((daemon->options & MHD_USE_DEBUG) != 0) + fprintf(stderr, "Error creating connection handler!.\n"); + } + } + } while (!daemon->shutdown); + + return NULL; } /** - * Delete a header line from the response. - * - * @return MHD_NO on error (no such header known) + * 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 */ -int -MHD_del_response_header(struct MHD_Response * response, - const char * header, - const char * content) { - return 0; +struct MHD_Daemon * +MHD_start_daemon(unsigned int options, + unsigned short port, + MHD_AcceptPolicyCallback apc, + void * apc_cls, + MHD_AccessHandlerCallback dh, + void * dh_cls) { + + + struct MHD_Daemon * retVal = NULL; + int socket_fd, opt, res, i; + struct sockaddr_in servaddr; + struct hostent *hostptr; + char hostid[32]; + + if((options & MHD_USE_SSL) != 0) { + if((options & MHD_USE_DEBUG) != 0) + fprintf(stderr, "SSL at this time is unsupported.\n"); + return NULL; + } + if((options & MHD_USE_IPv6) != 0) { + if((options & MHD_USE_DEBUG) != 0) + fprintf(stderr, "IP version 6 is not supported at this time.\n"); + return NULL; + } + + if((options & MHD_USE_IPv4) != 0) { + if((options & MHD_USE_DEBUG) != 0) + fprintf(stderr, "Enter MHD_start_daemon. Starting Daemon on port %i\n", port); + + if(port < 1) { + if((options & MHD_USE_DEBUG) != 0) + fprintf(stderr, "Invalid port: %i!\n", port); + return NULL; + } + + if(dh == NULL) { + if((options & MHD_USE_DEBUG) != 0) + fprintf(stderr, "A default access handler must be provided\n"); + return NULL; + } + + retVal = (struct MHD_Daemon *)malloc(sizeof(struct MHD_Daemon)); + if(retVal == NULL) { + if((options & MHD_USE_DEBUG) != 0) + fprintf(stderr, "Error allocating memory!\n"); + return NULL; + } + + retVal->options = options; + retVal->port = port; + retVal->apc = apc; + retVal->apc_cls = apc_cls; + retVal->dh = dh; + retVal->dh_cls = dh_cls; + retVal->shutdown = 0; + retVal->pid = NULL; + + retVal->firstFreeHandler = 0; + for(i = 0; i < MHD_MAX_HANDLERS; i++) { + retVal->handlers[i] = NULL; + } + + FD_ZERO(&retVal->read_fd_set); + FD_ZERO(&retVal->write_fd_set); + FD_ZERO(&retVal->except_fd_set); + + for(i = 0; i < MHD_MAX_CONNECTIONS; i++) { + retVal->connections[i] = NULL; + } + + socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if(socket_fd < 0) { + if((options & MHD_USE_DEBUG) != 0) + perror("Error creating socket!"); + return NULL; + } + + memset((void *)&servaddr, 0, (size_t)sizeof(servaddr)); + + if (gethostname(hostid,32) < 0){ + if((options & MHD_USE_DEBUG) != 0) + perror("server_tcp:gethostname"); + return NULL; + } + + if ((hostptr = gethostbyname(hostid)) == NULL){ + if((options & MHD_USE_DEBUG) != 0) + fprintf(stderr, "invalid host name, %s\n",hostid); + return NULL; + } + + servaddr.sin_family = AF_INET; + memcpy((void *)&(servaddr.sin_addr), (void *)(hostptr->h_addr), hostptr->h_length); + servaddr.sin_port = htons(port); + + + if (bind(socket_fd, (struct sockaddr *)&servaddr, (socklen_t)sizeof(servaddr)) < 0) { + if((options & MHD_USE_DEBUG) != 0) + perror("server:bind"); + return NULL; + } + + if(listen(socket_fd, 20) < 0) { + if((options & MHD_USE_DEBUG) != 0) + perror("server:bind"); + return NULL; + } + + retVal->socket_fd = socket_fd; + retVal->max_fd = socket_fd; + FD_SET(socket_fd, &retVal->read_fd_set); + + opt = fcntl(socket_fd, F_GETFL, 0); + res = fcntl(socket_fd, F_SETFL, opt | O_NONBLOCK); + if(res < 0) { + if((options & MHD_USE_DEBUG) != 0) + perror("Error disabling block on socket!"); + return NULL; + } + + return retVal; + } + + if((options & MHD_USE_DEBUG) != 0) + fprintf(stderr, "No options given to start_daemon!\n"); + + return NULL; + } /** - * Get all of the headers added to a response. - * - * @param iterator callback to call on each header; - * maybe NULL (then just count headers) - * @param iterator_cls extra argument to iterator - * @return number of entries iterated over - */ -int -MHD_get_response_headers(struct MHD_Response * response, - MHD_KeyValueIterator * iterator, - void * iterator_cls) { - return -1; + * Shutdown an http daemon. + */ +void +MHD_stop_daemon(struct MHD_Daemon * daemon) { + int i, j; + + if(daemon == NULL) { + return; + } + + if((daemon->options & MHD_USE_DEBUG) != 0) + fprintf(stderr, "Enter MHD_stop_daemon. Stopping daemon on port %i\n", daemon->port); + + daemon->shutdown = 1; + + if(daemon->pid != NULL) { + pthread_join(daemon->pid, NULL); + } + + for(i = 0; i < MHD_MAX_CONNECTIONS; i++) { + if(daemon->connections[i] != NULL) { + if(daemon->connections[i]->pid != NULL) { + pthread_join(daemon->connections[i]->pid, NULL); + } + + for(j = 0; j < MHD_MAX_RESPONSE; j++) { + if(daemon->connections[i]->currentResponses[j] != NULL) { + MHD_destroy_response(daemon->connections[i]->currentResponses[j]); + } + } + MHD_destroy_session(daemon->connections[i]); + } + } + + for(i = 0; i < MHD_MAX_HANDLERS; i++) { + if(daemon->handlers[i] != NULL) { + free(daemon->handlers[i]->uri_prefix); + free(daemon->handlers[i]); + } + } + + close(daemon->socket_fd); + + free(daemon); } + /** - * @return -1 if no data uploaded; otherwise number of bytes - * read into buf; 0 for end of transmission + * 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_read_file_upload(struct MHD_Session * session, - void * buf, - size_t len) { - return -1; +MHD_unregister_handler(struct MHD_Daemon * daemon, + const char * uri_prefix, + MHD_AccessHandlerCallback dh, + void * dh_cls) { + int i; + + for(i = 0; i < MHD_MAX_HANDLERS; i++) { + if(daemon->handlers[i] != NULL) { + if(strncmp(daemon->handlers[i]->uri_prefix, uri_prefix, strlen(daemon->handlers[i]->uri_prefix)) == 0) { + if(daemon->handlers[i]->dh == dh && daemon->handlers[i]->dh_cls == dh_cls) { + free(daemon->handlers[i]->uri_prefix); + free(daemon->handlers[i]); + return MHD_YES; + } + } + } + } + + return MHD_NO; } diff --git a/src/daemon/daemontest.c b/src/daemon/daemontest.c @@ -32,7 +32,15 @@ #include <string.h> static int testStartError() { - return NULL != MHD_start_daemon(0, 0, NULL, NULL, NULL, NULL); + struct MHD_Daemon * d; + + d = MHD_start_daemon(MHD_USE_DEBUG, 0, NULL, NULL, NULL, NULL); + + if(d == NULL) { + return 0; + } else { + return 1; + } } static int apc_nothing(void * cls, @@ -99,41 +107,184 @@ static int ahc_echo(void * cls, static int testStartStop() { struct MHD_Daemon * d; - d = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY | MHD_USE_IPv4, + d = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY | MHD_USE_IPv4 | MHD_USE_DEBUG, 1080, &apc_nothing, NULL, &ahc_nothing, NULL); + if (d == NULL) { + return 1; + } + MHD_stop_daemon(d); + return 0; +} + +static int testRun() { + struct MHD_Daemon * d; + fd_set read; + int maxfd; + int i; + + d = MHD_start_daemon(MHD_USE_IPv4 | MHD_USE_DEBUG, + 1080, + &apc_all, + NULL, + &ahc_nothing, + NULL); + + if(d == NULL) { + return 1; + } + fprintf(stderr, "Testing external select!\n"); + i = 0; + while(i < 15) { + MHD_get_fdset(d, &read, &read, &read, &maxfd); + if(MHD_run(d) == MHD_NO) { + MHD_stop_daemon(d); + return 1; + } + sleep(1); + i++; + } + return 0; +} + +static int testThread() { + struct MHD_Daemon * d; + d = MHD_start_daemon(MHD_USE_IPv4 | MHD_USE_DEBUG | MHD_USE_SELECT_INTERNALLY, + 1081, + &apc_all, + NULL, + &ahc_nothing, + NULL); + + if(d == NULL) { + return 1; + } + + fprintf(stderr, "Testing internal select!\n"); + if (MHD_run(d) == MHD_NO) { + return 1; + } else { + sleep(15); + MHD_stop_daemon(d); + } + return 0; +} + +static int testMultithread() { + struct MHD_Daemon * d; + d = MHD_start_daemon(MHD_USE_IPv4 | MHD_USE_DEBUG | MHD_USE_THREAD_PER_CONNECTION, + 1082, + &apc_all, + NULL, + &ahc_nothing, + NULL); + + if(d == NULL) { + return 1; + } + + fprintf(stderr, "Testing thread per connection!\n"); + if (MHD_run(d) == MHD_NO) { + return 1; + } else { + sleep(15); + MHD_stop_daemon(d); + } + return 0; +} + +static int testInternalGet() { + struct MHD_Daemon * d; + CURL * c; + char buf[2048]; + struct CBC cbc; + + cbc.buf = buf; + cbc.size = 2048; + cbc.pos = 0; + d = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY | MHD_USE_IPv4 | MHD_USE_DEBUG, + 1083, + &apc_all, + NULL, + &ahc_echo, + "GET"); if (d == NULL) return 1; + + if(MHD_run(d) == MHD_NO) { + MHD_stop_daemon(d); + return 2; + } + + c = curl_easy_init(); + curl_easy_setopt(c, + CURLOPT_URL, + "http://localhost:1083/hello_world"); + curl_easy_setopt(c, + CURLOPT_WRITEFUNCTION, + &copyBuffer); + curl_easy_setopt(c, + CURLOPT_WRITEDATA, + &cbc); + curl_easy_setopt(c, + CURLOPT_FAILONERROR, + 1); + curl_easy_setopt(c, + CURLOPT_CONNECTTIMEOUT, + 15L); + // NOTE: use of CONNECTTIMEOUT without also + // setting NOSIGNAL results in really weird + // crashes on my system! + curl_easy_setopt(c, + CURLOPT_NOSIGNAL, + 1); + if (CURLE_OK != curl_easy_perform(c)) + return 3; + + curl_easy_cleanup(c); + + if (cbc.pos != strlen("hello_world")) + return 4; + + if (0 != strncmp("hello_world", + cbc.buf, + strlen("hello_world"))) + return 5; + MHD_stop_daemon(d); + return 0; } -static int testGet() { +static int testMultithreadedGet() { struct MHD_Daemon * d; CURL * c; - int ret; char buf[2048]; struct CBC cbc; cbc.buf = buf; cbc.size = 2048; cbc.pos = 0; - ret = 0; - d = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY | MHD_USE_IPv4, - 1080, + d = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION | MHD_USE_IPv4 | MHD_USE_DEBUG, + 1084, &apc_all, NULL, &ahc_echo, "GET"); if (d == NULL) return 1; + + if(MHD_run(d) == MHD_NO) + return 2; + + c = curl_easy_init(); curl_easy_setopt(c, CURLOPT_URL, - "http://localhost:1080/hello_world"); + "http://localhost:1084/hello_world"); curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, &copyBuffer); @@ -146,31 +297,61 @@ static int testGet() { curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT, 15L); - /* NOTE: use of CONNECTTIMEOUT without also - setting NOSIGNAL results in really weird - crashes on my system! */ + // NOTE: use of CONNECTTIMEOUT without also + // setting NOSIGNAL results in really weird + // crashes on my system! curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1); if (CURLE_OK != curl_easy_perform(c)) - ret = 1; + return 3; curl_easy_cleanup(c); if (cbc.pos != strlen("hello_world")) - ret = 2; + return 4; + if (0 != strncmp("hello_world", cbc.buf, strlen("hello_world"))) - ret = 3; + return 5; + MHD_stop_daemon(d); - return ret; + + return 0; } int main(int argc, char * const * argv) { unsigned int errorCount = 0; - + fprintf(stderr, "***testStartError()***\n"); + fprintf(stderr, "***This test verifies the start function responds to bad arguments correctly***\n"); errorCount += testStartError(); + fprintf(stderr, "errorCount is %i\n", errorCount); + fprintf(stderr, "***testStartStop()***\n"); + fprintf(stderr, "***This test verifies that the daemon can be started and stopped normally***\n"); errorCount += testStartStop(); - errorCount += testGet(); + fprintf(stderr, "errorCount is %i\n", errorCount); + fprintf(stderr, "***testInternalGet()***\n"); + fprintf(stderr, "***This test verifies the functionality of internal select using a canned request***\n"); + errorCount += testInternalGet(); + fprintf(stderr, "errorCount is %i\n", errorCount); + fprintf(stderr, "***testMultithreadedGet()***\n"); + fprintf(stderr, "***This test verifies the functionality of multithreaded connections using a canned request***\n"); + errorCount += testMultithreadedGet(); + fprintf(stderr, "errorCount is %i\n", errorCount); + fprintf(stderr, "***testRun()***\n"); + fprintf(stderr, "***This test verifies the functionality of external select***\n"); + fprintf(stderr, "***The sever will sit on the announced port for 15 seconds and wait for external messages***\n"); + errorCount += testRun(); + fprintf(stderr, "errorCount is %i\n", errorCount); + fprintf(stderr, "***testThread()***\n"); + fprintf(stderr, "***This test verifies the functionality of internal select***\n"); + fprintf(stderr, "***The sever will sit on the announced port for 15 seconds and wait for external messages***\n"); + errorCount += testThread(); + fprintf(stderr, "errorCount is %i\n", errorCount); + fprintf(stderr, "***testMultithread()***\n"); + fprintf(stderr, "***This test verifies the functionality of multithreaded connections***\n"); + fprintf(stderr, "***The sever will sit on the announced port for 15 seconds and wait for external messages***\n"); + errorCount += testMultithread(); + fprintf(stderr, "errorCount is %i\n", errorCount); return errorCount != 0; /* 0 == pass */ }