Investigated CGI scripts. Don't think they are feasable.
Currently investigating custom web server.
Used some existing files from a previous project
(network.h network.c log.h log.c).
I'm making some error in parsing the request, but out of time for now.
--- /dev/null
+This software will implement an interface between requests via HTTP and low level sensors/control on the server.
+
+Briefly investigated CGI scripts. Realised that interprocess communication will be difficult and complex.
+Low level sensors will be polled continuously, but the client will not be continuously receiving information.
+
+Currently looking at implementing webserver and low level sensors/control in the same program on the server.
--- /dev/null
+#include "log.h"
+#include "options.h"
+#include <unistd.h>
+
+static int last_len = 0;
+
+void log_print(int level, char * funct, char * fmt, ...)
+{
+ if (level > options.verbosity)
+ return;
+
+
+
+ char severity[BUFSIZ];
+ switch (level)
+ {
+ case LOGERR:
+ sprintf(severity, "ERROR");
+ break;
+ case LOGWARN:
+ sprintf(severity, "WARNING");
+ break;
+ case LOGNOTE:
+ sprintf(severity, "NOTICE");
+ break;
+ case LOGINFO:
+ sprintf(severity, "INFO");
+ break;
+ default:
+ sprintf(severity, "DEBUG");
+ 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, " ");
+ }
+ va_list va;
+ va_start(va, fmt);
+ vfprintf(stderr, fmt, va);
+ va_end(va);
+ fprintf(stderr, "\n");
+}
+
+void error(char * funct, char * fmt, ...)
+{
+ if (funct != NULL)
+ last_len = fprintf(stderr, "%s [%d] : Fatal error in %s - ", options.program, getpid(), funct);
+ else
+ {
+ for (int i = 0; i < last_len; ++i)
+ fprintf(stderr, " ");
+ fprintf(stderr, "Fatal - ");
+ }
+ va_list va;
+ va_start(va, fmt);
+ vfprintf(stderr, fmt, va);
+ va_end(va);
+ fprintf(stderr, "\n");
+
+ exit(EXIT_FAILURE);
+}
+
+
--- /dev/null
+#ifndef _LOG_H
+#define _LOG_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#include <stdarg.h>
+
+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, ...);
+
+#endif //_LOG_H
+
+//EOF
--- /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
--- /dev/null
+/**
+ * @file options.h
+ * @purpose Declaration of structure to handle options passed to program
+ */
+
+#include <stdint.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;
+
+
+
+extern Options options;
--- /dev/null
+/**
+ * @file webserver.c
+ * @purpose Test implementing a minimalistic webserver
+ *
+ */
+
+#define _POSIX_C_SOURCE 200809L // needed for some low level POSIX stuff to work
+
+// --- Standard headers --- //
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h> // string helper functions
+#include <wctype.h> // wide character classication (white space)
+#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;
+ options.port = 8080; // Using 8080 instead of 80 for now because to use 80 you have to run the program as root
+ 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.");
+ Network_close(options.sfd); // close socket
+ Network_close(options.bound_sfd); // unbind
+ 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(const char * request, int sfd)
+{
+ log_print(LOGDEBUG, "Get", "Got GET request: %s", request);
+ char response[BUFSIZ];
+
+ // TODO: Magical low level interfacing stuff!
+
+ int len = sprintf(response, "Content-type: text/plain\n\nYou requested %s using GET\n", request);
+ 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(const char * request, int sfd)
+{
+ log_print(LOGDEBUG, "Post", "Got POST request: %s", request);
+ char response[BUFSIZ];
+
+ // TODO: Magical low level interfacing stuff!
+
+ int len = sprintf(response, "Content-type: text/plain\n\nYou requested %s using POST\n", request);
+ 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 (iswspace(buffer[i])) // whitespace
+ {
+ while (iswspace(buffer[++i]) && buffer[i] != '\0'); // Skip whitespace
+ char * req = buffer+i; // set request string
+ buffer[i] = '\0'; // terminate request type
+ 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[] = "Content-type: text/plain\n\nError: Unrecognised request";
+ write(options.sfd, response, sizeof(response));
+ }
+ break;
+ }
+ }
+
+ // Close connection
+ Network_close(options.sfd);
+ log_print(LOGDEBUG, "main", "Closed connection to client");
+ }
+
+
+
+
+ return 0;
+}
+
+