summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Makefile13
-rw-r--r--http.c351
-rw-r--r--http.h40
-rw-r--r--plop.c2
-rw-r--r--plop.h1
-rw-r--r--request.c317
-rw-r--r--request.h38
-rw-r--r--response.c43
-rw-r--r--response.h6
9 files changed, 417 insertions, 394 deletions
diff --git a/Makefile b/Makefile
index 8fba5b4..828fc4a 100644
--- a/Makefile
+++ b/Makefile
@@ -3,9 +3,16 @@ CFLAGS+=-I/usr/include/lua5.3
LDLIBS+=-llua5.3
PREFIX?=/usr/local
-plop: main.o plop.o http.o
-main.o plop.o: plop.h
-main.o plop.o http.o: http.h
+plop: main.o plop.o http.o response.o request.o
+
+http.o: http.h
+main.o: plop.h
+plop.o: http.h plop.h request.h response.h
+request.o: http.h request.h
+response.o: response.h
+
+plop.h: http.h request.h
+response.h: http.h
clean:
rm -f plop *.o
diff --git a/http.c b/http.c
index 89716cd..7616392 100644
--- a/http.c
+++ b/http.c
@@ -1,15 +1,5 @@
#include "http.h"
-#include <ctype.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-static const int REQUEST_DATA_SIZE = 4096;
-static const int MAX_HEADERS = 30;
-
const char * status_str[] = {
[STATUS_OK] = "200 OK",
[STATUS_BAD_REQUEST] = "400 Bad Request",
@@ -19,344 +9,3 @@ const char * status_str[] = {
[STATUS_NOT_IMPLEMENTED] = "501 Not Implemented",
[STATUS_VERSION_NOT_SUPPORTED] = "505 Version Not Supported",
};
-
-/// Allocates and initializes request structure.
-/// \return Pointer to initialized request or NULL in case of an error
-/// \see /free_request
-struct request * new_request(void)
-{
- struct request * request = malloc(sizeof(struct request));
-
- if (NULL == request)
- {
- return NULL;
- }
-
- memset(request, 0, sizeof(struct request));
- request->step = parse_step_method;
-
- request->data = malloc(REQUEST_DATA_SIZE);
-
- if (NULL == request->data)
- {
- free(request);
- return NULL;
- }
-
- return request;
-}
-
-/// Releases memory used by the request and the request itself.
-/// \param request Request to free
-/// \see /new_request
-void free_request(struct request * request)
-{
- if (NULL != request->data)
- {
- free(request->data);
- }
-
- if (NULL != request->headerv)
- {
- free(request->headerv);
- }
-
- free(request);
-}
-
-/// Sends a simple response only with a status to the client.
-/// \param fd File descriptor of the client socket
-/// \param status HTTP response status code
-/// \return Negative value if an error was encountered; numbers of bytes written otherwise
-int respond_only_status(const int fd, const enum status status)
-{
- static const char * pattern =
- "HTTP/1.1 %s\r\n"
- "Connection: close\r\n"
- "\r\n";
-
- return dprintf(fd, pattern, status_str[status]);
-}
-
-/// Sends a response with a status and a body to the client.
-/// \param fd File descriptor of the client socket
-/// \param status HTTP response status code
-/// \param body Content that will be sent
-/// \param size Size of the content in bytes
-/// \return Negative value if an error was encountered; numbers of bytes written otherwise
-int respond_with_body(const int fd, const enum status status, const char * body, const int size)
-{
- static const char * pattern =
- "HTTP/1.1 %s\r\n"
- "Connection: close\r\n"
- "Content-Type: application/json\r\n"
- "Content-Size: %d\r\n"
- "\r\n";
-
- if (0 > dprintf(fd, pattern, status_str[status], size))
- {
- return -1; // TODO: Handle errors properly
- }
-
- return write(fd, body, size);
-}
-
-/// Collects request between calls to `poll`.
-/// \param fd Client socket
-/// \param request Pointer to current request context of handled client
-/// \return Number of bytes parsed, -1 if an error occured or 0 if expects more data
-int parse_request(const int fd, struct request ** request)
-{
- if (NULL == *request)
- {
- *request = new_request();
- if (NULL == *request)
- {
- return -1;
- }
- }
-
- // TODO: Expand buffer until EAGAIN or arbitrary limit
- int length = read(fd, (*request)->data, REQUEST_DATA_SIZE - 1);
-
- if (0 == length || (-1 == length && EWOULDBLOCK != errno && EAGAIN != errno))
- {
- return -1; // TODO: Handle errors properly
- }
-
- (*request)->data[length] = 0;
- (*request)->length = length;
-
- return (*request)->step(*request);
-}
-
-
-/// Progresses the readout of the request until delimiter is found.
-/// \param request Request to process
-/// \param delimiter Character that marks the end of the readout
-/// \return Position of the delimiter or 0 if the deliminter could not be found
-static int read_until_char(struct request * request, const char delimiter)
-{
- char character;
- while (request->position < request->length)
- {
- character = request->data[request->position];
- if (delimiter == character)
- {
- return request->position;
- }
- request->position++;
- }
- return 0;
-}
-
-/// Progresses the readout of the request until non-whitespace character is found.
-/// \param request Request to process
-/// \return Position of the first non-whitespace character or 0 if it could not be found
-/// \see isalpha(3)
-static int read_until_word(struct request * request)
-{
- char character;
- while (request->position < request->length)
- {
- character = request->data[request->position];
- if (!isspace(character))
- {
- return request->position;
- }
- request->position++;
- }
- return 0;
-}
-
-/// Progresses the readout of the request until line separator is found.
-/// \param request Request to process
-/// \return Position of the first byte of the separator or 0 if line separator could not be found
-static int read_rest_of_line(struct request * request)
-{
- while (request->position < request->length - 1)
- {
- if ('\r' == request->data[request->position] && '\n' == request->data[request->position + 1])
- {
- return request->position;
- }
- else if ('\r' == request->data[request->position + 1])
- {
- request->position += 1;
- }
- else
- {
- request->position += 2;
- }
- }
- return 0;
-}
-
-/// Parses method field of the request.
-/// \param request Request to process
-/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed
-int parse_step_method(struct request * request)
-{
- request->method.length = read_until_char(request, ' ');
-
- if (0 == request->method.length)
- {
- return 0;
- }
-
- request->step = parse_step_path;
-
- return parse_step_path(request);
-}
-
-/// Parses path field of the request.
-/// \param request Request to process
-/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed
-// TODO: Consider spliting path into an actual path and arguments in this stage
-int parse_step_path(struct request * request)
-{
- if (0 >= request->path.start)
- {
- request->path.start = read_until_word(request);
-
- if (0 == request->path.start)
- {
- return 0;
- }
- }
-
- const int result = read_until_char(request, ' ');
-
- if (0 == result)
- {
- return 0;
- }
-
- request->path.length = request->position - request->path.start;
- request->step = parse_step_version;
-
- return parse_step_version(request);
-}
-
-/// Parses and verifies http version field of the request.
-/// \param request Request to process
-/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed
-// TODO: Return -1 if version is unsupported, meaning other than HTTP/1.1
-int parse_step_version(struct request * request)
-{
- if (0 >= request->version.start)
- {
- request->version.start = read_until_word(request);
-
- if (0 == request->version.start)
- {
- return 0;
- }
- }
-
- const int result = read_rest_of_line(request);
-
- if (0 == result)
- {
- return 0;
- }
-
- request->version.length = request->position - request->version.start;
- request->step = parse_step_header_name;
-
- return parse_step_header_name(request);
-}
-
-/// Parses and verifies name of a single header from the request.
-/// \param request Request to process
-/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed
-int parse_step_header_name(struct request * request)
-{
- if (NULL == request->headerv)
- {
- request->headerv = malloc(sizeof(struct header) * MAX_HEADERS);
- request->headerc = 0;
- memset(request->headerv, 0, sizeof(struct header) * MAX_HEADERS);
-
- if (NULL == request->headerv)
- {
- return -1;
- }
- }
-
- // TODO: Check for the header section end without backtracking.
- const int position = request->position;
- int result = read_rest_of_line(request);
-
- if (0 == result)
- {
- return 0;
- }
-
- if (2 == result - position)
- {
- // Header section ended, nothing left to parse.
- return request->position + 2;
- }
-
- request->position = position;
-
- if (0 >= request->headerv[request->headerc].name.start)
- {
- request->headerv[request->headerc].name.start = read_until_word(request);
-
- if (0 == request->headerv[request->headerc].name.start)
- {
- return 0;
- }
- }
-
- result = read_until_char(request, ':');
-
- if (0 == result)
- {
- return 0;
- }
-
- request->headerv[request->headerc].name.length = request->position - request->headerv[request->headerc].name.start;
- request->step = parse_step_header_value;
-
- // Skip ':'
- request->position++;
-
- return parse_step_header_value(request);
-}
-
-/// Parses and verifies value of a single header from the request.
-/// \param request Request to process
-/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed
-int parse_step_header_value(struct request * request)
-{
- if (0 >= request->headerv[request->headerc].value.start)
- {
- request->headerv[request->headerc].value.start = read_until_word(request);
-
- if (0 == request->headerv[request->headerc].value.start)
- {
- return 0;
- }
- }
-
- const int result = read_rest_of_line(request);
-
- if (0 == result)
- {
- return 0;
- }
-
- request->headerv[request->headerc].value.length = request->position - request->headerv[request->headerc].value.start;
- request->step = parse_step_header_name;
- request->headerc++;
-
- if (MAX_HEADERS == request->headerc)
- {
- return -1;
- }
-
- return parse_step_header_name(request);
-}
diff --git a/http.h b/http.h
index 7d98222..52a1822 100644
--- a/http.h
+++ b/http.h
@@ -1,31 +1,5 @@
#pragma once
-struct span
-{
- int start;
- int length;
-};
-
-struct header
-{
- struct span name;
- struct span value;
-};
-
-struct request
-{
- int (* step)(struct request *);
- char * data;
- int length;
- int position;
- struct span method;
- struct span path;
- struct span version;
- struct header * headerv;
- int headerc;
- struct span body;
-};
-
enum status
{
STATUS_OK = 200,
@@ -38,17 +12,3 @@ enum status
};
extern const char * status_str[];
-
-struct request * new_request(void);
-void free_request(struct request *);
-
-int respond_only_status(int, enum status);
-int respond_with_body(int, enum status, const char *, int);
-
-int parse_request(int, struct request **);
-
-int parse_step_method(struct request *);
-int parse_step_path(struct request *);
-int parse_step_version(struct request *);
-int parse_step_header_name(struct request *);
-int parse_step_header_value(struct request *);
diff --git a/plop.c b/plop.c
index dc6f819..8665ec9 100644
--- a/plop.c
+++ b/plop.c
@@ -16,6 +16,8 @@
#include <lualib.h>
#include "http.h"
+#include "request.h"
+#include "response.h"
/// Tries to create, bind and start listening on INET server socket.
/// \param node Hostname
diff --git a/plop.h b/plop.h
index 2ecd678..ef7ecfe 100644
--- a/plop.h
+++ b/plop.h
@@ -5,6 +5,7 @@
#include <lua.h>
#include "http.h"
+#include "request.h"
int make_server(const char *, const char *);
int handle_client(lua_State * L, struct pollfd *, struct request **, const int);
diff --git a/request.c b/request.c
new file mode 100644
index 0000000..f6f8a78
--- /dev/null
+++ b/request.c
@@ -0,0 +1,317 @@
+#include "request.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "http.h"
+
+static const int REQUEST_DATA_SIZE = 4096;
+static const int MAX_HEADERS = 30;
+
+/// Allocates and initializes request structure.
+/// \return Pointer to initialized request or NULL in case of an error
+/// \see /free_request
+struct request * new_request(void)
+{
+ struct request * request = malloc(sizeof(struct request));
+
+ if (NULL == request)
+ {
+ return NULL;
+ }
+
+ memset(request, 0, sizeof(struct request));
+ request->step = parse_step_method;
+
+ request->data = malloc(REQUEST_DATA_SIZE);
+
+ if (NULL == request->data)
+ {
+ free(request);
+ return NULL;
+ }
+
+ return request;
+}
+
+/// Releases memory used by the request and the request itself.
+/// \param request Request to free
+/// \see /new_request
+void free_request(struct request * request)
+{
+ if (NULL != request->data)
+ {
+ free(request->data);
+ }
+
+ if (NULL != request->headerv)
+ {
+ free(request->headerv);
+ }
+
+ free(request);
+}
+
+/// Collects request between calls to `poll`.
+/// \param fd Client socket
+/// \param request Pointer to current request context of handled client
+/// \return Number of bytes parsed, -1 if an error occured or 0 if expects more data
+int parse_request(const int fd, struct request ** request)
+{
+ if (NULL == *request)
+ {
+ *request = new_request();
+ if (NULL == *request)
+ {
+ return -1;
+ }
+ }
+
+ // TODO: Expand buffer until EAGAIN or arbitrary limit
+ int length = read(fd, (*request)->data, REQUEST_DATA_SIZE - 1);
+
+ if (0 == length || (-1 == length && EWOULDBLOCK != errno && EAGAIN != errno))
+ {
+ return -1; // TODO: Handle errors properly
+ }
+
+ (*request)->data[length] = 0;
+ (*request)->length = length;
+
+ return (*request)->step(*request);
+}
+
+
+/// Progresses the readout of the request until delimiter is found.
+/// \param request Request to process
+/// \param delimiter Character that marks the end of the readout
+/// \return Position of the delimiter or 0 if the deliminter could not be found
+static int read_until_char(struct request * request, const char delimiter)
+{
+ char character;
+ while (request->position < request->length)
+ {
+ character = request->data[request->position];
+ if (delimiter == character)
+ {
+ return request->position;
+ }
+ request->position++;
+ }
+ return 0;
+}
+
+/// Progresses the readout of the request until non-whitespace character is found.
+/// \param request Request to process
+/// \return Position of the first non-whitespace character or 0 if it could not be found
+/// \see isalpha(3)
+static int read_until_word(struct request * request)
+{
+ char character;
+ while (request->position < request->length)
+ {
+ character = request->data[request->position];
+ if (!isspace(character))
+ {
+ return request->position;
+ }
+ request->position++;
+ }
+ return 0;
+}
+
+/// Progresses the readout of the request until line separator is found.
+/// \param request Request to process
+/// \return Position of the first byte of the separator or 0 if line separator could not be found
+static int read_rest_of_line(struct request * request)
+{
+ while (request->position < request->length - 1)
+ {
+ if ('\r' == request->data[request->position] && '\n' == request->data[request->position + 1])
+ {
+ return request->position;
+ }
+ else if ('\r' == request->data[request->position + 1])
+ {
+ request->position += 1;
+ }
+ else
+ {
+ request->position += 2;
+ }
+ }
+ return 0;
+}
+
+/// Parses method field of the request.
+/// \param request Request to process
+/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed
+int parse_step_method(struct request * request)
+{
+ request->method.length = read_until_char(request, ' ');
+
+ if (0 == request->method.length)
+ {
+ return 0;
+ }
+
+ request->step = parse_step_path;
+
+ return parse_step_path(request);
+}
+
+/// Parses path field of the request.
+/// \param request Request to process
+/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed
+// TODO: Consider spliting path into an actual path and arguments in this stage
+int parse_step_path(struct request * request)
+{
+ if (0 >= request->path.start)
+ {
+ request->path.start = read_until_word(request);
+
+ if (0 == request->path.start)
+ {
+ return 0;
+ }
+ }
+
+ const int result = read_until_char(request, ' ');
+
+ if (0 == result)
+ {
+ return 0;
+ }
+
+ request->path.length = request->position - request->path.start;
+ request->step = parse_step_version;
+
+ return parse_step_version(request);
+}
+
+/// Parses and verifies http version field of the request.
+/// \param request Request to process
+/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed
+// TODO: Return -1 if version is unsupported, meaning other than HTTP/1.1
+int parse_step_version(struct request * request)
+{
+ if (0 >= request->version.start)
+ {
+ request->version.start = read_until_word(request);
+
+ if (0 == request->version.start)
+ {
+ return 0;
+ }
+ }
+
+ const int result = read_rest_of_line(request);
+
+ if (0 == result)
+ {
+ return 0;
+ }
+
+ request->version.length = request->position - request->version.start;
+ request->step = parse_step_header_name;
+
+ return parse_step_header_name(request);
+}
+
+/// Parses and verifies name of a single header from the request.
+/// \param request Request to process
+/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed
+int parse_step_header_name(struct request * request)
+{
+ if (NULL == request->headerv)
+ {
+ request->headerv = malloc(sizeof(struct header) * MAX_HEADERS);
+ request->headerc = 0;
+ memset(request->headerv, 0, sizeof(struct header) * MAX_HEADERS);
+
+ if (NULL == request->headerv)
+ {
+ return -1;
+ }
+ }
+
+ // TODO: Check for the header section end without backtracking.
+ const int position = request->position;
+ int result = read_rest_of_line(request);
+
+ if (0 == result)
+ {
+ return 0;
+ }
+
+ if (2 == result - position)
+ {
+ // Header section ended, nothing left to parse.
+ return request->position + 2;
+ }
+
+ request->position = position;
+
+ if (0 >= request->headerv[request->headerc].name.start)
+ {
+ request->headerv[request->headerc].name.start = read_until_word(request);
+
+ if (0 == request->headerv[request->headerc].name.start)
+ {
+ return 0;
+ }
+ }
+
+ result = read_until_char(request, ':');
+
+ if (0 == result)
+ {
+ return 0;
+ }
+
+ request->headerv[request->headerc].name.length = request->position - request->headerv[request->headerc].name.start;
+ request->step = parse_step_header_value;
+
+ // Skip ':'
+ request->position++;
+
+ return parse_step_header_value(request);
+}
+
+/// Parses and verifies value of a single header from the request.
+/// \param request Request to process
+/// \return -1 if an error has occured, 0 if too little data available or total number of bytes processed
+int parse_step_header_value(struct request * request)
+{
+ if (0 >= request->headerv[request->headerc].value.start)
+ {
+ request->headerv[request->headerc].value.start = read_until_word(request);
+
+ if (0 == request->headerv[request->headerc].value.start)
+ {
+ return 0;
+ }
+ }
+
+ const int result = read_rest_of_line(request);
+
+ if (0 == result)
+ {
+ return 0;
+ }
+
+ request->headerv[request->headerc].value.length = request->position - request->headerv[request->headerc].value.start;
+ request->step = parse_step_header_name;
+ request->headerc++;
+
+ if (MAX_HEADERS == request->headerc)
+ {
+ return -1;
+ }
+
+ return parse_step_header_name(request);
+}
diff --git a/request.h b/request.h
new file mode 100644
index 0000000..a302d61
--- /dev/null
+++ b/request.h
@@ -0,0 +1,38 @@
+#pragma once
+
+struct span
+{
+ int start;
+ int length;
+};
+
+struct header
+{
+ struct span name;
+ struct span value;
+};
+
+struct request
+{
+ int (* step)(struct request *);
+ char * data;
+ int length;
+ int position;
+ struct span method;
+ struct span path;
+ struct span version;
+ struct header * headerv;
+ int headerc;
+ struct span body;
+};
+
+struct request * new_request(void);
+void free_request(struct request *);
+
+int parse_request(int, struct request **);
+
+int parse_step_method(struct request *);
+int parse_step_path(struct request *);
+int parse_step_version(struct request *);
+int parse_step_header_name(struct request *);
+int parse_step_header_value(struct request *);
diff --git a/response.c b/response.c
new file mode 100644
index 0000000..df465d0
--- /dev/null
+++ b/response.c
@@ -0,0 +1,43 @@
+#include "response.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include "http.h"
+
+/// Sends a simple response only with a status to the client.
+/// \param fd File descriptor of the client socket
+/// \param status HTTP response status code
+/// \return Negative value if an error was encountered; numbers of bytes written otherwise
+int respond_only_status(const int fd, const enum status status)
+{
+ static const char * pattern =
+ "HTTP/1.1 %s\r\n"
+ "Connection: close\r\n"
+ "\r\n";
+
+ return dprintf(fd, pattern, status_str[status]);
+}
+
+/// Sends a response with a status and a body to the client.
+/// \param fd File descriptor of the client socket
+/// \param status HTTP response status code
+/// \param body Content that will be sent
+/// \param size Size of the content in bytes
+/// \return Negative value if an error was encountered; numbers of bytes written otherwise
+int respond_with_body(const int fd, const enum status status, const char * body, const int size)
+{
+ static const char * pattern =
+ "HTTP/1.1 %s\r\n"
+ "Connection: close\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Size: %d\r\n"
+ "\r\n";
+
+ if (0 > dprintf(fd, pattern, status_str[status], size))
+ {
+ return -1; // TODO: Handle errors properly
+ }
+
+ return write(fd, body, size);
+}
diff --git a/response.h b/response.h
new file mode 100644
index 0000000..ec90bc5
--- /dev/null
+++ b/response.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include "http.h"
+
+int respond_only_status(int, enum status);
+int respond_with_body(int, enum status, const char *, int);