From: Sam Moore Date: Tue, 6 Aug 2013 10:50:04 +0000 (+0800) Subject: Investigate software for interfacing with hardware via HTTP requests X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=64b0cd994eea9ee7e95bcd1a1d6dc06d84b83b84;p=matches%2FMCTX3420.git Investigate software for interfacing with hardware via HTTP requests 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. --- diff --git a/testing/web2io/README b/testing/web2io/README new file mode 100644 index 0000000..43353f4 --- /dev/null +++ b/testing/web2io/README @@ -0,0 +1,6 @@ +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. diff --git a/testing/web2io/log.c b/testing/web2io/log.c new file mode 100644 index 0000000..3a8c55c --- /dev/null +++ b/testing/web2io/log.c @@ -0,0 +1,67 @@ +#include "log.h" +#include "options.h" +#include + +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); +} + + diff --git a/testing/web2io/log.h b/testing/web2io/log.h new file mode 100644 index 0000000..75e2780 --- /dev/null +++ b/testing/web2io/log.h @@ -0,0 +1,17 @@ +#ifndef _LOG_H +#define _LOG_H + +#include +#include +#include + +#include + +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 diff --git a/testing/web2io/network.c b/testing/web2io/network.c new file mode 100644 index 0000000..1a1b862 --- /dev/null +++ b/testing/web2io/network.c @@ -0,0 +1,174 @@ +#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/testing/web2io/network.h b/testing/web2io/network.h new file mode 100644 index 0000000..07c9459 --- /dev/null +++ b/testing/web2io/network.h @@ -0,0 +1,29 @@ +#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/testing/web2io/options.h b/testing/web2io/options.h new file mode 100644 index 0000000..2a6d323 --- /dev/null +++ b/testing/web2io/options.h @@ -0,0 +1,20 @@ +/** + * @file options.h + * @purpose Declaration of structure to handle options passed to program + */ + +#include + +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; diff --git a/testing/web2io/webserver.c b/testing/web2io/webserver.c new file mode 100644 index 0000000..e8b72f1 --- /dev/null +++ b/testing/web2io/webserver.c @@ -0,0 +1,181 @@ +/** + * @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 +#include +#include // string helper functions +#include // wide character classication (white space) +#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; + 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; +} + +