3 * @purpose Test implementing a minimalistic webserver
7 #define _POSIX_C_SOURCE 200809L // needed for some POSIX stuff to work
9 // --- Standard headers --- //
12 #include <string.h> // string helper functions
13 #include <ctype.h> // character types
14 #include <signal.h> // for signal handling
16 // --- Custom headers --- //
17 #include "log.h" // C functions to handle logging
18 #include "options.h" // Options structure
19 #include "network.h" // C functions to handle low level networking
21 // --- Variable definitions --- //
22 Options options; // declared in "options.h"
24 // --- Function definitions --- //
27 * @funct ParseArguments
28 * @purpose Parse Arguments and setup the global options variable
29 * @param argc - Num args
30 * @param argv - Array of args
32 void ParseArguments(int argc, char ** argv)
34 options.program = argv[0];
35 options.verbosity = LOGDEBUG;
37 options.port = atoi(argv[1]); // Allow us change the port for testing (I keep getting "address in use" errors)
39 options.port = 8080; // Using 8080 instead of 80 for now because to use 80 you have to run the program as root
41 options.bound_sfd = -1;
42 log_print(LOGDEBUG, "ParseArguments", "Called as %s with %d arguments.", options.program, argc);
46 * @funct SignalHandler
47 * @purpose Handle signals
48 * @param sig - The signal
50 void SignalHandler(int sig)
52 // At the moment just always exit.
53 // Call `exit` so that Cleanup will be called to... clean up.
54 log_print(LOGWARN, "SignalHandler", "Got signal %d (%s). Exiting.", sig, strsignal(sig));
60 * @purpose Called when program exits
64 log_print(LOGDEBUG, "Cleanup", "Begin cleanup.");
67 Network_close(options.sfd); // close socket
70 if (options.bound_sfd >= 0)
72 Network_close(options.bound_sfd); // unbind
73 options.bound_sfd = -1;
75 log_print(LOGDEBUG, "Cleanup", "Unbound from port %d successfully", options.port);
76 log_print(LOGDEBUG, "Cleanup", "Done.");
83 * @purpose Respond to a GET request
84 * @param request - The request string
85 * @param sfd - Socket to respond through
87 void Get(char * request, int sfd)
89 log_print(LOGDEBUG, "Get", "Got GET request: \"%s\"", request);
92 while (!isspace(request[++i]) && request[i] != '\0'); //NOTE: Don't need to check first character
95 char response[BUFSIZ];
96 // TODO: Magical low level interfacing stuff!
98 int len = sprintf(response, "HTTP/1.1 200 OK\nContent-type: text/html\n\n");
99 write(sfd, response, len);
102 if (strcmp("/sensor", request) == 0) // dummy test
104 len = sprintf(response, "SENSOR OFFLINE\n");
108 FILE * f = fopen(request+1, "r");
111 log_print(LOGWARN, "Get", "File \"%s\" doesn't exist", request+1);
112 len = sprintf(response, "You requested \"%s\" using GET\n", request);
116 while (fgets(response, sizeof(response), f) != NULL)
118 write(sfd, response, strlen(response));
123 write(sfd, response, len);
128 * @purpose Respond to a POST request
129 * @param request - The request string
130 * @param sfd - Socket to respond through
132 void Post(char * request, int sfd)
134 log_print(LOGDEBUG, "Post", "Got POST request: \"%s\"", request);
136 while (!isspace(request[++i]) && request[i] != '\0'); //NOTE: Don't need to check first character
139 char response[BUFSIZ];
140 int len = sprintf(response, "HTTP/1.1 200 OK\nContent-type: text/html\n\n");
141 write(sfd, response, len);
144 // TODO: Magical low level interfacing stuff!
147 if (strcmp("/actuator", request) == 0) // dummy test
149 len = sprintf(response, "ACTUATOR OFFLINE\n");
153 len = sprintf(response, "You requested \"%s\" using POST\n", request);
156 write(sfd, response, len);
162 * @purpose Main program
163 * @param argc - Num arguments
164 * @param argv - Argument string array
165 * @returns error code (0 for no error)
168 int main(int argc, char ** argv)
171 ParseArguments(argc, argv);
172 // Set Cleanup to be called on program exit
175 // Setup signal handlers
176 int signals_to_handle[] = {SIGTERM, SIGINT, SIGHUP, SIGPIPE};
177 for (int i = 0; i < sizeof(signals_to_handle)/sizeof(int); ++i)
179 int s = signals_to_handle[i];
180 if (signal(s, SignalHandler) == SIG_ERR)
181 error("main", "Setting signal handler for %d (%s): %s", s, strsignal(s), strerror(errno));
185 options.bound_sfd = Network_server_bind(options.port, &(options.port));
186 log_print(LOGDEBUG, "main", "Bound to port %d succesfully", options.port);
190 // Listen for a client
191 options.sfd = Network_server_listen(options.bound_sfd, NULL);
193 log_print(LOGDEBUG, "main", "Connected to client");
195 char buffer[BUFSIZ]; //NOTE: Won't be able to respond to requests longer than BUFSIZ
197 int len = read(options.sfd, buffer, sizeof(buffer));
198 log_print(LOGDEBUG, "main", "Read %d characters. Buffer is \"%s\"", len, buffer);
201 for (int i = 0; i < sizeof(buffer) && buffer[i] != '\0'; ++i)
203 // Look for "GET" or "POST" followed by a whitespace
204 if (isspace(buffer[i])) // whitespace
206 while (isspace(buffer[++i]) && buffer[i] != '\0'); // Skip whitespace
207 char * req = buffer+i; // set request string
209 buffer[i-1] = '\0'; // terminate request type
210 while (buffer[++i] != '\n' && buffer[i] != '\0'); // find next newline
213 if (strcmp("GET", buffer) == 0) // Compare with "GET"
215 Get(req, options.sfd);
217 else if (strcmp("POST", buffer) == 0) // Compare with "POST"
219 Post(req, options.sfd);
221 else // Unknown request
223 log_print(LOGWARN, "main", "Unrecognised request type \"%s\" (request \"%s\")", buffer, req);
224 char response[] = "Error: Unrecognised request\n";
225 write(options.sfd, response, sizeof(response));
232 Network_close(options.sfd);
234 log_print(LOGDEBUG, "main", "Closed connection to client");