Investigate software for interfacing with hardware via HTTP requests
authorSam Moore <[email protected]>
Tue, 6 Aug 2013 10:50:04 +0000 (18:50 +0800)
committerSam Moore <[email protected]>
Tue, 6 Aug 2013 10:50:04 +0000 (18:50 +0800)
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.

testing/web2io/README [new file with mode: 0644]
testing/web2io/log.c [new file with mode: 0644]
testing/web2io/log.h [new file with mode: 0644]
testing/web2io/network.c [new file with mode: 0644]
testing/web2io/network.h [new file with mode: 0644]
testing/web2io/options.h [new file with mode: 0644]
testing/web2io/webserver.c [new file with mode: 0644]

diff --git a/testing/web2io/README b/testing/web2io/README
new file mode 100644 (file)
index 0000000..43353f4
--- /dev/null
@@ -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 (file)
index 0000000..3a8c55c
--- /dev/null
@@ -0,0 +1,67 @@
+#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);
+}
+
+
diff --git a/testing/web2io/log.h b/testing/web2io/log.h
new file mode 100644 (file)
index 0000000..75e2780
--- /dev/null
@@ -0,0 +1,17 @@
+#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
diff --git a/testing/web2io/network.c b/testing/web2io/network.c
new file mode 100644 (file)
index 0000000..1a1b862
--- /dev/null
@@ -0,0 +1,174 @@
+#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);
+}
diff --git a/testing/web2io/network.h b/testing/web2io/network.h
new file mode 100644 (file)
index 0000000..07c9459
--- /dev/null
@@ -0,0 +1,29 @@
+#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
diff --git a/testing/web2io/options.h b/testing/web2io/options.h
new file mode 100644 (file)
index 0000000..2a6d323
--- /dev/null
@@ -0,0 +1,20 @@
+/**
+ * @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;
diff --git a/testing/web2io/webserver.c b/testing/web2io/webserver.c
new file mode 100644 (file)
index 0000000..e8b72f1
--- /dev/null
@@ -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 <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;
+}
+
+

UCC git Repository :: git.ucc.asn.au