From 5d2b87e5ad41b31eeb94dca4547d73bc8e30e6c3 Mon Sep 17 00:00:00 2001 From: Sam Moore Date: Wed, 14 Aug 2013 18:04:50 +0800 Subject: [PATCH] Remove HTTP server, get ready to write basic threaded program We will use a FastCGI to handle HTTP requests instead of writing a custom HTTP server. --- rpi/log.c | 78 ++++++++++++---- rpi/log.h | 12 ++- rpi/main.c | 62 ++++++++++++ rpi/network.c | 174 ---------------------------------- rpi/network.h | 29 ------ rpi/options.h | 14 ++- rpi/webserver.c | 243 ------------------------------------------------ 7 files changed, 136 insertions(+), 476 deletions(-) create mode 100644 rpi/main.c delete mode 100644 rpi/network.c delete mode 100644 rpi/network.h delete mode 100644 rpi/webserver.c diff --git a/rpi/log.c b/rpi/log.c index 3a8c55c..15e6618 100644 --- a/rpi/log.c +++ b/rpi/log.c @@ -1,16 +1,43 @@ +/** + * @file log.c + * @purpose Implement logging and error handling functions + */ + + +#include + +// --- Custom headers --- // #include "log.h" #include "options.h" -#include -static int last_len = 0; +// --- Static variables --- // +static char * unspecified_funct = (char*)"???"; + +// --- Function implementations --- // -void log_print(int level, char * funct, char * fmt, ...) +/** + * @funct Log + * @purpose Print a message to stderr + * @param level - Specify how severe the message is. + If level is higher (less urgent) than the program's verbosity (see options.h) no message will be printed + * @param funct - String indicating the function name from which this function was called. + If this is NULL, Log will show the unspecified_funct string instead + * @param fmt - A format string + * @param ... - Arguments to be printed according to the format string + */ +void Log(int level, char * funct, char * fmt, ...) { - if (level > options.verbosity) + if (fmt == NULL) // sanity check + Fatal("Log", "Format string is NULL"); + + // Don't print the message unless we need to + if (level > options.verbosity) return; - + if (funct == NULL) + funct = unspecified_funct; + // Make a human readable severity string char severity[BUFSIZ]; switch (level) { @@ -31,30 +58,43 @@ void log_print(int level, char * funct, char * fmt, ...) break; } - if (funct != NULL) - last_len = fprintf(stderr, "%s [%d] : %s : %s - ", options.program, getpid(), severity, funct); - else - { - for (int i = 0; i < last_len; ++i); - fprintf(stderr, " "); - } + // Print: Program name, PID, severity string, function name first + fprintf(stderr, "%s [%d] : %s : %s - ", options.program, getpid(), severity, funct); + + // Then pass additional arguments with the format string to vfprintf for printing va_list va; va_start(va, fmt); vfprintf(stderr, fmt, va); va_end(va); + + // End log messages with a newline fprintf(stderr, "\n"); } -void error(char * funct, char * fmt, ...) +/** + * @funct Fatal + * @purpose Handle a Fatal error in the program by printing a message and exiting the program + CALLING THIS FUNCTION WILL CAUSE THE PROGAM TO EXIT + * @param funct - Name of the calling function + * @param fmt - A format string + * @param ... - Arguments to be printed according to the format string + */ +void Fatal(char * funct, char * fmt, ...) { - if (funct != NULL) - last_len = fprintf(stderr, "%s [%d] : Fatal error in %s - ", options.program, getpid(), funct); - else + + if (fmt == NULL) { - for (int i = 0; i < last_len; ++i) - fprintf(stderr, " "); - fprintf(stderr, "Fatal - "); + // Fatal error in the Fatal function. + // (This really shouldn't happen unless someone does something insanely stupid) + Fatal("Fatal", "Format string is NULL"); + return; // Should never get here } + + if (funct == NULL) + funct = unspecified_funct; + + fprintf(stderr, "%s [%d] : %s : FATAL - ", options.program, getpid(), funct); + va_list va; va_start(va, fmt); vfprintf(stderr, fmt, va); diff --git a/rpi/log.h b/rpi/log.h index 75e2780..99d54d0 100644 --- a/rpi/log.h +++ b/rpi/log.h @@ -1,16 +1,22 @@ +/** + * @file log.h + * @purpose Declaration of functions for printing log messages and/or terminating program after a fatal error + */ + #ifndef _LOG_H #define _LOG_H #include #include #include - #include + +// An enum to make the severity of log messages human readable in code enum {LOGERR=0, LOGWARN=1, LOGNOTE=2, LOGINFO=3,LOGDEBUG=4}; -extern void log_print(int level, char * funct, char * fmt,...); -extern void error(char * funct, char * fmt, ...); +extern void Log(int level, char * funct, char * fmt,...); // General function for printing log messages to stderr +extern void Fatal(char * funct, char * fmt, ...); // Function that deals with a fatal error (prints a message, then exits the program). #endif //_LOG_H diff --git a/rpi/main.c b/rpi/main.c new file mode 100644 index 0000000..23ef071 --- /dev/null +++ b/rpi/main.c @@ -0,0 +1,62 @@ +/** + * @file main.c + * @purpose Entry point to the program, starts threads, handles cleanup on program exit + */ + +// --- Standard headers --- // +#include +#include +#include // for signal handling + +// --- Custom headers --- // +#include "options.h" + +// --- Variable definitions --- // +Options g_options; // options passed to program through command line arguments + +// --- Function definitions --- // + +/** + * @funct ParseArguments + * @purpose Parse command line arguments, set up an options variable + * @param argc - Num args + * @param argv - Array of args + * @param opts - Pointer to options. &g_options + */ +void ParseArguments(int argc, char ** argv, Options * opts) +{ + options.program = argv[0]; // program name + options.verbosity = LOGDEBUG; // default log level + if (argc > 1) + options.port = atoi(argv[1]); // Allow us change the port for testing (I keep getting "address in use" errors) + else + options.port = 8080; // Using 8080 instead of 80 for now because to use 80 you have to run the program as root + + Log(LOGDEBUG, "ParseArguments", "Called as %s with %d arguments.", options.program, argc); +} + +/** + * @funct SignalHandler + * @purpose Handle signals + * @param sig - The signal + */ +void SignalHandler(int sig) +{ + // At the moment just always exit. + // Call `exit` so that Cleanup will be called to... clean up. + Log(LOGWARN, "SignalHandler", "Got signal %d (%s). Exiting.", sig, strsignal(sig)); + exit(sig); +} + +/** + * @funct Cleanup + * @purpose Called when program exits + */ +void Cleanup() +{ + Log(LOGDEBUG, "Cleanup", "Begin cleanup."); + Log(LOGDEBUG, "Cleanup", "Finish cleanup."); + +} + + diff --git a/rpi/network.c b/rpi/network.c deleted file mode 100644 index 1a1b862..0000000 --- a/rpi/network.c +++ /dev/null @@ -1,174 +0,0 @@ -#include "network.h" -#include -#include -#include -#include "log.h" - -#define h_addr h_addr_list[0] - - - - - -int Network_get_port(int sfd) -{ - static struct sockaddr_in sin; - static socklen_t len = sizeof(struct sockaddr_in); - - if (getsockname(sfd, (struct sockaddr *)&sin, &len) != 0) - error("Network_port", "getsockname : %s", strerror(errno)); - return ntohs(sin.sin_port); -} - -int Network_server_bind(int port, int * bound) -{ - int sfd = socket(PF_INET, SOCK_STREAM, 0); - if (sfd < 0) - { - error("Network_server", "Creating socket on port %d : %s", port, strerror(errno)); - } - - struct sockaddr_in name; - - name.sin_family = AF_INET; - name.sin_addr.s_addr = htonl(INADDR_ANY); - name.sin_port = htons(port); - - if (bind( sfd, (struct sockaddr *) &name, sizeof(name) ) < 0) - { - error("Network_server", "Binding socket on port %d : %s", port, strerror(errno)); - } - - if (bound != NULL) - *bound = Network_get_port(sfd); - return sfd; -} - -int Network_server_listen(int sfd, char * addr) -{ - int port = Network_get_port(sfd); - if (listen(sfd, 1) < 0) - { - error("Network_server", "Listening on port %d : %s", port, strerror(errno)); - } - - int psd; - if (addr == NULL) - psd = accept(sfd, 0, 0); - else - { - struct sockaddr_in client; - struct hostent *hp; - - client.sin_family = AF_INET; - hp = gethostbyname(addr); - bcopy ( hp->h_addr, &(client.sin_addr.s_addr), hp->h_length); - client.sin_port = htons(port); - socklen_t len = sizeof(client); - - psd = accept(sfd, (struct sockaddr*)&client, &len); - } - //close(sfd); // don't close the bind socket here; we might want to reuse the port - assert(psd >= 0); - return psd; -} - -int Network_server(char * addr, int port) -{ - int bind = Network_server_bind(port, &port); - int sfd = Network_server_listen(bind, addr); - close(bind); // won't be able to reuse the port (it goes into TIME_WAIT) - return sfd; -} - -int Network_client(const char * addr, int port, int timeout) -{ - int sfd = socket(PF_INET, SOCK_STREAM, 0); - - //log_print(2, "Network_client", "Created socket"); - long arg = fcntl(sfd, F_GETFL, NULL); - arg |= O_NONBLOCK; - fcntl(sfd, F_SETFL, arg); - - if (sfd < 0) - { - error("Network_client", "Creating socket for address %s:%d : %s", addr, port, strerror(errno)); - } - struct sockaddr_in server; - struct hostent *hp; - - - server.sin_family = AF_INET; - hp = gethostbyname(addr); - if (hp == NULL) - { - error("Network_client", "Can't get host by name %s", addr); - } - bcopy ( hp->h_addr, &(server.sin_addr.s_addr), hp->h_length); - server.sin_port = htons(port); - - - int res = connect(sfd, (struct sockaddr *) &server, sizeof(server)); - - - if (res < 0 && errno == EINPROGRESS) - { - - fd_set writeSet; - FD_ZERO(&writeSet); - FD_SET(sfd, &writeSet); - - struct timeval tv; - tv.tv_sec = timeout; - tv.tv_usec = 0; - - struct timeval * tp; - tp = (timeout < 0) ? NULL : &tv; - - int err = select(sfd+1, NULL, &writeSet, NULL, tp); - - if (err == 0) - { - error("Network_client", "Timed out trying to connect to %s:%d after %d seconds", addr, port, timeout); - } - else if (err < 0) - { - error("Network_client", "Connecting to %s:%d - Error in select(2) call : %s", addr, port, strerror(errno)); - } - else if (FD_ISSET(sfd, &writeSet)) - { - int so_error; - socklen_t len = sizeof so_error; - getsockopt(sfd, SOL_SOCKET, SO_ERROR, &so_error, &len); - if (so_error != 0) - { - error("Network_client", "Connecting to %s:%d : %s", addr, port, strerror(so_error)); - } - } - else - { - error("Network_client", "select(2) returned %d but the socket is not writable!?", err); - } - } - else - { - error("Network_client", "Connecting to %s:%d : %s", addr, port, strerror(errno)); - } - - arg = fcntl(sfd, F_GETFL, NULL); - arg &= (~O_NONBLOCK); - fcntl(sfd, F_SETFL, arg); - - - - return sfd; -} - -void Network_close(int sfd) -{ - if (shutdown(sfd, 2) != 0) - { - error("Network_close", "Closing socket : %s", strerror(errno)); - } - close(sfd); -} diff --git a/rpi/network.h b/rpi/network.h deleted file mode 100644 index 07c9459..0000000 --- a/rpi/network.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef _NETWORK_H -#define _NETWORK_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -extern int Network_get_port(int socket); // get port used by socket -extern int Network_server(char * addr, int port); -extern int Network_client(const char * addr, int port, int timeout); - -extern int Network_server_bind(int port, int * bound); -extern int Network_server_listen(int sfd, char * addr); - -extern void Network_close(int sfd); - -#endif //_NETWORK_H - -//EOF diff --git a/rpi/options.h b/rpi/options.h index 2a6d323..df78870 100644 --- a/rpi/options.h +++ b/rpi/options.h @@ -3,18 +3,16 @@ * @purpose Declaration of structure to handle options passed to program */ -#include +#ifndef _OPTIONS_H +#define _OPTIONS_H typedef struct { const char * program; //name of program - uint8_t verbosity; // verbosity level - int port; // port to use for webserver - int bound_sfd; // socket webserver has bound to - int sfd; // socket connected to client - -} Options; + int verbosity; // verbosity level +} Options; +extern Options g_options; -extern Options options; +#endif //_OPTIONS_H diff --git a/rpi/webserver.c b/rpi/webserver.c deleted file mode 100644 index 6ff4f46..0000000 --- a/rpi/webserver.c +++ /dev/null @@ -1,243 +0,0 @@ -/** - * @file webserver.c - * @purpose Test implementing a minimalistic webserver - * - */ - -#define _POSIX_C_SOURCE 200809L // needed for some POSIX stuff to work - -// --- Standard headers --- // -#include -#include -#include // string helper functions -#include // character types -#include // for signal handling - -// --- Custom headers --- // -#include "log.h" // C functions to handle logging -#include "options.h" // Options structure -#include "network.h" // C functions to handle low level networking - -// --- Variable definitions --- // -Options options; // declared in "options.h" - -// --- Function definitions --- // - -/** - * @funct ParseArguments - * @purpose Parse Arguments and setup the global options variable - * @param argc - Num args - * @param argv - Array of args - */ -void ParseArguments(int argc, char ** argv) -{ - options.program = argv[0]; - options.verbosity = LOGDEBUG; - if (argc > 1) - options.port = atoi(argv[1]); // Allow us change the port for testing (I keep getting "address in use" errors) - else - options.port = 8080; // Using 8080 instead of 80 for now because to use 80 you have to run the program as root - options.sfd = -1; - options.bound_sfd = -1; - log_print(LOGDEBUG, "ParseArguments", "Called as %s with %d arguments.", options.program, argc); -} - -/** - * @funct SignalHandler - * @purpose Handle signals - * @param sig - The signal - */ -void SignalHandler(int sig) -{ - // At the moment just always exit. - // Call `exit` so that Cleanup will be called to... clean up. - log_print(LOGWARN, "SignalHandler", "Got signal %d (%s). Exiting.", sig, strsignal(sig)); - exit(sig); -} - -/** - * @funct Cleanup - * @purpose Called when program exits - */ -void Cleanup() -{ - log_print(LOGDEBUG, "Cleanup", "Begin cleanup."); - if (options.sfd >= 0) - { - Network_close(options.sfd); // close socket - options.sfd = -1; - } - if (options.bound_sfd >= 0) - { - Network_close(options.bound_sfd); // unbind - options.bound_sfd = -1; - } - log_print(LOGDEBUG, "Cleanup", "Unbound from port %d successfully", options.port); - log_print(LOGDEBUG, "Cleanup", "Done."); - -} - - -/** - * @funct Get - * @purpose Respond to a GET request - * @param request - The request string - * @param sfd - Socket to respond through - */ -void Get(char * request, int sfd) -{ - log_print(LOGDEBUG, "Get", "Got GET request: \"%s\"", request); - - int i = 0; - while (!isspace(request[++i]) && request[i] != '\0'); //NOTE: Don't need to check first character - request[i] = '\0'; - - char response[BUFSIZ]; - // TODO: Magical low level interfacing stuff! - - int len = sprintf(response, "HTTP/1.1 200 OK\nContent-type: text/html\n\n"); - write(sfd, response, len); - len = 0; - - if (strcmp("/sensor", request) == 0) // dummy test - { - len = sprintf(response, "SENSOR OFFLINE\n"); - } - else - { - FILE * f = fopen(request+1, "r"); - if (f == NULL) - { - log_print(LOGWARN, "Get", "File \"%s\" doesn't exist", request+1); - len = sprintf(response, "You requested \"%s\" using GET\n", request); - } - else - { - while (fgets(response, sizeof(response), f) != NULL) - { - write(sfd, response, strlen(response)); - } - } - } - if (len > 0) - write(sfd, response, len); -} - -/** - * @funct Post - * @purpose Respond to a POST request - * @param request - The request string - * @param sfd - Socket to respond through - */ -void Post(char * request, int sfd) -{ - log_print(LOGDEBUG, "Post", "Got POST request: \"%s\"", request); - int i = 0; - while (!isspace(request[++i]) && request[i] != '\0'); //NOTE: Don't need to check first character - request[i] = '\0'; - - char response[BUFSIZ]; - int len = sprintf(response, "HTTP/1.1 200 OK\nContent-type: text/html\n\n"); - write(sfd, response, len); - len = 0; - - // TODO: Magical low level interfacing stuff! - - - if (strcmp("/actuator", request) == 0) // dummy test - { - len = sprintf(response, "ACTUATOR OFFLINE\n"); - } - else - { - len = sprintf(response, "You requested \"%s\" using POST\n", request); - } - if (len > 0) - write(sfd, response, len); - -} - -/** - * @funct main - * @purpose Main program - * @param argc - Num arguments - * @param argv - Argument string array - * @returns error code (0 for no error) - */ - -int main(int argc, char ** argv) -{ - // Parse Arguments - ParseArguments(argc, argv); - // Set Cleanup to be called on program exit - atexit(Cleanup); - - // Setup signal handlers - int signals_to_handle[] = {SIGTERM, SIGINT, SIGHUP, SIGPIPE}; - for (int i = 0; i < sizeof(signals_to_handle)/sizeof(int); ++i) - { - int s = signals_to_handle[i]; - if (signal(s, SignalHandler) == SIG_ERR) - error("main", "Setting signal handler for %d (%s): %s", s, strsignal(s), strerror(errno)); - } - - // Bind to the port - options.bound_sfd = Network_server_bind(options.port, &(options.port)); - log_print(LOGDEBUG, "main", "Bound to port %d succesfully", options.port); - - while (true) - { - // Listen for a client - options.sfd = Network_server_listen(options.bound_sfd, NULL); - - log_print(LOGDEBUG, "main", "Connected to client"); - - char buffer[BUFSIZ]; //NOTE: Won't be able to respond to requests longer than BUFSIZ - // read a request - int len = read(options.sfd, buffer, sizeof(buffer)); - log_print(LOGDEBUG, "main", "Read %d characters. Buffer is \"%s\"", len, buffer); - - // Parse request - for (int i = 0; i < sizeof(buffer) && buffer[i] != '\0'; ++i) - { - // Look for "GET" or "POST" followed by a whitespace - if (isspace(buffer[i])) // whitespace - { - while (isspace(buffer[++i]) && buffer[i] != '\0'); // Skip whitespace - char * req = buffer+i; // set request string - - buffer[i-1] = '\0'; // terminate request type - while (buffer[++i] != '\n' && buffer[i] != '\0'); // find next newline - buffer[i-1] = '\0'; - - if (strcmp("GET", buffer) == 0) // Compare with "GET" - { - Get(req, options.sfd); - } - else if (strcmp("POST", buffer) == 0) // Compare with "POST" - { - Post(req, options.sfd); - } - else // Unknown request - { - log_print(LOGWARN, "main", "Unrecognised request type \"%s\" (request \"%s\")", buffer, req); - char response[] = "Error: Unrecognised request\n"; - write(options.sfd, response, sizeof(response)); - } - break; - } - } - - // Close connection - Network_close(options.sfd); - options.sfd = -1; - log_print(LOGDEBUG, "main", "Closed connection to client"); - } - - - - - return 0; -} - - -- 2.20.1