We will use a FastCGI to handle HTTP requests instead of writing a custom HTTP server.
+/**
+ * @file log.c
+ * @purpose Implement logging and error handling functions
+ */
+
+
+#include <unistd.h>
+
+// --- Custom headers --- //
#include "log.h"
#include "options.h"
-#include <unistd.h>
-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)
{
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);
+/**
+ * @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 <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
-
#include <stdarg.h>
+
+// 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
--- /dev/null
+/**
+ * @file main.c
+ * @purpose Entry point to the program, starts threads, handles cleanup on program exit
+ */
+
+// --- Standard headers --- //
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h> // 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.");
+
+}
+
+
+++ /dev/null
-#include "network.h"
-#include <assert.h>
-#include <errno.h>
-#include <sys/select.h>
-#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);
-}
+++ /dev/null
-#ifndef _NETWORK_H
-#define _NETWORK_H
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <netdb.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <strings.h>
-#include <stdarg.h>
-
-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
* @purpose Declaration of structure to handle options passed to program
*/
-#include <stdint.h>
+#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
+++ /dev/null
-/**
- * @file webserver.c
- * @purpose Test implementing a minimalistic webserver
- *
- */
-
-#define _POSIX_C_SOURCE 200809L // needed for some POSIX stuff to work
-
-// --- Standard headers --- //
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h> // string helper functions
-#include <ctype.h> // character types
-#include <signal.h> // 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;
-}
-
-