gitignore, simpler directory structure and Makefile
parent
1827b1c558
commit
4220d44625
|
@ -0,0 +1,4 @@
|
||||||
|
.build
|
||||||
|
.deps
|
||||||
|
run_tests
|
||||||
|
*.swp
|
6
Makefile
6
Makefile
|
@ -1,11 +1,11 @@
|
||||||
MODULES= test libraries/HttpServer
|
MODULES= src test
|
||||||
|
|
||||||
SOURCES ?= $(wildcard $(addsuffix /*.cpp, $(MODULES)))
|
SOURCES ?= $(wildcard $(addsuffix /*.cpp, $(MODULES)))
|
||||||
OBJECTS := $(addsuffix .o, $(addprefix .build/, $(basename $(SOURCES))))
|
OBJECTS := $(addsuffix .o, $(addprefix .build/, $(basename $(SOURCES))))
|
||||||
DEPFILES := $(subst .o,.dep, $(subst .build/,.deps/, $(OBJECTS)))
|
DEPFILES := $(subst .o,.dep, $(subst .build/,.deps/, $(OBJECTS)))
|
||||||
TESTCPPFLAGS = -D_TEST_ -Ilibraries/HttpServer -Itest -Iarduino
|
TESTCPPFLAGS = -D_TEST_ $(addprefix -I, $(MODULES)) -Iarduino
|
||||||
CPPDEPFLAGS = -MMD -MP -MF .deps/$(basename $<).dep
|
CPPDEPFLAGS = -MMD -MP -MF .deps/$(basename $<).dep
|
||||||
TEST_TARGET=test_HttpServer
|
TEST_TARGET=run_tests
|
||||||
|
|
||||||
.PHONY: all test clean
|
.PHONY: all test clean
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
#TODO:
|
||||||
|
* header name should be case-insensitive
|
||||||
|
* HttpRequest.capture() handle improperly formed request
|
||||||
|
* HttpRequest.capture() handle running out of stream
|
|
@ -14,14 +14,27 @@ HttpHeaders::HttpHeaders() : _n(0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpHeaders::set(const char* name, const char* value) {
|
void HttpHeaders::set(const char* name, const char* value) {
|
||||||
|
|
||||||
|
int name_len = strlen(name);
|
||||||
|
int value_len = strlen(value);
|
||||||
|
|
||||||
|
// search for existing node with name and append value
|
||||||
for (int i = 0; i < count(); i++) {
|
for (int i = 0; i < count(); i++) {
|
||||||
if (strcmp(name, headers[i].name) != 0)
|
if (strcmp(name, headers[i].name) != 0)
|
||||||
continue;
|
continue;
|
||||||
free(headers[i].value);
|
int old_value_len = strlen(headers[i].value);
|
||||||
headers[i].value = (char *) malloc(sizeof(char) * strlen(value));
|
headers[i].value = (char *) realloc(headers[i].value, old_value_len + value_len + 2);
|
||||||
strcpy(headers[i].value, value);
|
strcpy(headers[i].value+old_value_len, ", ");
|
||||||
|
strcpy(headers[i].value+old_value_len+2, value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Grow headers if needed
|
||||||
|
if (_n && (_n % HTTPHEADERS_SIZE) == 0) {
|
||||||
|
headers = (HttpHeaderNode*) realloc(headers, sizeof(HttpHeaderNode) * HTTPHEADERS_SIZE * (_n / HTTPHEADERS_SIZE + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// create and store new node with name and value
|
||||||
headers[_n].name = (char *) malloc(sizeof(char) * strlen(name));
|
headers[_n].name = (char *) malloc(sizeof(char) * strlen(name));
|
||||||
headers[_n].value = (char *) malloc(sizeof(char) * strlen(value));
|
headers[_n].value = (char *) malloc(sizeof(char) * strlen(value));
|
||||||
strcpy(headers[_n].name, name);
|
strcpy(headers[_n].name, name);
|
|
@ -0,0 +1,92 @@
|
||||||
|
#include "HttpRequest.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#define HTTPREQUEST_TRIM(stream) while(stream.peek()==' ') { stream.read(); }
|
||||||
|
#define HTTPREQUEST_BUFFER_UNTIL(buff, strm, stop, n)\
|
||||||
|
for (n=0; strm.peek() != stop;n++) \
|
||||||
|
{ buff[n] = (char) strm.read(); }
|
||||||
|
|
||||||
|
void HttpRequest::capture()
|
||||||
|
{
|
||||||
|
long int i, k;
|
||||||
|
char buffer[HTTPREQUEST_MAX_MESSAGE_SIZE] = {0};
|
||||||
|
|
||||||
|
HTTPREQUEST_TRIM(client);
|
||||||
|
|
||||||
|
// Read method until ' '
|
||||||
|
//for (i=0; client.peek() != ' '; i++) buffer[i] = (char) client.read();
|
||||||
|
HTTPREQUEST_BUFFER_UNTIL(buffer, client, ' ', i);
|
||||||
|
strncpy(method, buffer, HTTPREQUEST_METHOD_SIZE);
|
||||||
|
for (k=0; k <= i; k++) buffer[k] = '\0'; //reset buffer
|
||||||
|
|
||||||
|
HTTPREQUEST_TRIM(client);
|
||||||
|
|
||||||
|
// Read url until ' '
|
||||||
|
for (i=0; client.peek() != ' '; i++) buffer[i] = (char) client.read();
|
||||||
|
url = (char*) malloc( i + 1 );
|
||||||
|
strcpy(url, buffer);
|
||||||
|
for (k=0; k <= i; k++) buffer[k] = '\0'; //reset buffer
|
||||||
|
|
||||||
|
HTTPREQUEST_TRIM(client);
|
||||||
|
|
||||||
|
// Read httpver until '\r'
|
||||||
|
for (i=0; client.peek() != '\r'; i++) buffer[i] = (char) client.read();
|
||||||
|
strncpy(httpver, buffer, HTTPREQUEST_HTTPVER_SIZE);
|
||||||
|
for (k=0; k <= i; k++) buffer[k] = '\0'; //reset buffer
|
||||||
|
|
||||||
|
// discard line break
|
||||||
|
if (client.peek() == '\r') client.read();
|
||||||
|
if (client.peek() == '\n') client.read();
|
||||||
|
|
||||||
|
//read each line, parsing headers, discarding \r\n until a blank line is reached
|
||||||
|
while (client.peek() != '\r' && client.peek() != '\n') {
|
||||||
|
for (i=0; client.peek() != ':'; i++) // copy client contents into buffer
|
||||||
|
buffer[i] = (char) client.read();
|
||||||
|
client.read(); // discard the ':'
|
||||||
|
HTTPREQUEST_TRIM(client); // discard whitespace
|
||||||
|
buffer[i++] = '\0';
|
||||||
|
k = i; // start of value
|
||||||
|
for (; client.peek() != '\r'; i++) buffer[i] = (char) client.read();
|
||||||
|
headers.set(buffer, (buffer + k) );
|
||||||
|
for (k=0; k <= i; k++) buffer[k] = '\0'; //reset buffer
|
||||||
|
|
||||||
|
// discard next line break
|
||||||
|
if (client.peek() == '\r') client.read();
|
||||||
|
if (client.peek() == '\n') client.read();
|
||||||
|
}
|
||||||
|
client.read();
|
||||||
|
if (client.peek() == '\n') client.read(); // consume \r\n
|
||||||
|
|
||||||
|
// Determine or guess the length of the message
|
||||||
|
long int content_length;
|
||||||
|
if (headers.has("Content-Length") && !headers.has("Transfer-Encoding")) {
|
||||||
|
char* length_hdr = headers.get("Content-Length");
|
||||||
|
char* length_hdr_end = strlen(length_hdr) + length_hdr;
|
||||||
|
content_length = strtol(length_hdr, &length_hdr_end, 10);
|
||||||
|
} else {
|
||||||
|
content_length = HTTPREQUEST_MAX_MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy rest of message
|
||||||
|
message = (char*) malloc( content_length + 1);
|
||||||
|
if (!message) {
|
||||||
|
//todo handle out of memory
|
||||||
|
}
|
||||||
|
for (i = 0; i < content_length && client.available(); i++) {
|
||||||
|
message[i] = (char) client.read();
|
||||||
|
}
|
||||||
|
message[i] = '\0';
|
||||||
|
|
||||||
|
// handle content_length too short
|
||||||
|
if (client.available()) {
|
||||||
|
//todo: content length too short
|
||||||
|
}
|
||||||
|
message_length = i;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpRequest::~HttpRequest()
|
||||||
|
{
|
||||||
|
if (url) free(url);
|
||||||
|
if (message) free(message);
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
#pragma once
|
||||||
|
#include "HttpHeaders.h"
|
||||||
|
|
||||||
|
#ifndef HTTPREQUEST_MAX_MESSAGE_SIZE
|
||||||
|
#define HTTPREQUEST_MAX_MESSAGE_SIZE 512
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _TEST_
|
||||||
|
#include "dummy/DummyStream.h"
|
||||||
|
#else
|
||||||
|
#include "Arduino.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define HTTPREQUEST_METHOD_SIZE 8
|
||||||
|
#define HTTPREQUEST_HTTPVER_SIZE 9
|
||||||
|
|
||||||
|
class HttpRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HttpRequest(Stream& s)
|
||||||
|
:client(s), method(), url(), httpver(), headers(), message() {};
|
||||||
|
~HttpRequest();
|
||||||
|
Stream& client;
|
||||||
|
void capture();
|
||||||
|
char method [HTTPREQUEST_METHOD_SIZE];
|
||||||
|
char * url;
|
||||||
|
char httpver [HTTPREQUEST_HTTPVER_SIZE];
|
||||||
|
HttpHeaders headers;
|
||||||
|
long int message_length;
|
||||||
|
char * message;
|
||||||
|
};
|
|
@ -1,17 +1,25 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "stdlib.h"
|
||||||
|
#include "stdint.h"
|
||||||
|
#include "string.h"
|
||||||
|
|
||||||
#include "Arduino.h"
|
class Stream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual int available() = 0;
|
||||||
|
virtual int read() = 0;
|
||||||
|
virtual int peek() = 0;
|
||||||
|
virtual size_t write(uint8_t) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
class DummyStream: public Stream
|
class DummyStream: public Stream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DummyStream(char* buff): buffer(buffer), pos(0) {};
|
DummyStream(const char* buff): buffer(buff), pos(0) {};
|
||||||
char * buffer;
|
const char * buffer;
|
||||||
int pos;
|
int pos;
|
||||||
virtual int available() { return (buffer[pos] != '\0'); }
|
virtual int available() { return (strlen(buffer) - pos); }
|
||||||
virtual int read() { return buffer[pos++]; }
|
virtual int read() { return buffer[pos++]; }
|
||||||
virtual int peek() { return buffer[pos]; }
|
virtual int peek() { return buffer[pos]; }
|
||||||
virtual size_t write(uint8_t c) { return 0; } // gone forever
|
virtual size_t write(uint8_t c) { return 0; } // gone forever
|
||||||
};
|
};
|
||||||
|
|
||||||
extern DummyStream Stream
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
#include "catch.hpp"
|
||||||
|
#include "dummy/DummyStream.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
TEST_CASE("DummyStream is availble","[DummyStream]")
|
||||||
|
{
|
||||||
|
const char* input = "Foo\nBar\nBaz\n";
|
||||||
|
DummyStream stream(input);
|
||||||
|
REQUIRE(strlen(input) > 0);
|
||||||
|
REQUIRE(stream.available());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("DummyStream is not available","[DummyStream]")
|
||||||
|
{
|
||||||
|
const char input[1] = {0};
|
||||||
|
DummyStream stream(input);
|
||||||
|
REQUIRE(strlen(input) == 0);
|
||||||
|
CHECK(stream.available()==0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("DummyStream read","[DummyStream]")
|
||||||
|
{
|
||||||
|
const char* input = "Foo";
|
||||||
|
DummyStream stream(input);
|
||||||
|
REQUIRE(stream.available() == 3);
|
||||||
|
REQUIRE(stream.read() == 'F');
|
||||||
|
REQUIRE(stream.available() == 2);
|
||||||
|
REQUIRE(stream.read() == 'o');
|
||||||
|
REQUIRE(stream.available() == 1);
|
||||||
|
REQUIRE(stream.read() == 'o');
|
||||||
|
REQUIRE(stream.available() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("DummyStream peek","[DummyStream]")
|
||||||
|
{
|
||||||
|
const char* input = "Bar";
|
||||||
|
DummyStream stream(input);
|
||||||
|
REQUIRE(stream.peek() == 'B');
|
||||||
|
REQUIRE(stream.available() == 3);
|
||||||
|
REQUIRE(stream.read() == 'B');
|
||||||
|
REQUIRE(stream.peek() == 'a');
|
||||||
|
REQUIRE(stream.available() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("DummyStream is a Stream","[DummyStream]")
|
||||||
|
{
|
||||||
|
const char* input = "Baz";
|
||||||
|
Stream* stream = new DummyStream(input);
|
||||||
|
delete(stream);
|
||||||
|
}
|
|
@ -2,6 +2,8 @@
|
||||||
#include "HttpHeaders.h"
|
#include "HttpHeaders.h"
|
||||||
#include "string.h"
|
#include "string.h"
|
||||||
|
|
||||||
|
using Catch::Matchers::Equals;
|
||||||
|
|
||||||
TEST_CASE("Empty HttpHeaders","[HttpHeaders]") {
|
TEST_CASE("Empty HttpHeaders","[HttpHeaders]") {
|
||||||
HttpHeaders headers;
|
HttpHeaders headers;
|
||||||
|
|
||||||
|
@ -20,32 +22,33 @@ TEST_CASE("Get and set headers","[HttpHeaders]"){
|
||||||
CHECK(headers.count() == 1);
|
CHECK(headers.count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Overwrite header with smaller header","[HttpHeaders]"){
|
TEST_CASE("Append headers","[HttpHeaders]"){
|
||||||
HttpHeaders headers;
|
HttpHeaders headers;
|
||||||
headers.set("foo","sushi");
|
headers.set("foo","sushi");
|
||||||
headers.set("foo","soy");
|
headers.set("foo","soy");
|
||||||
|
|
||||||
CHECK(strcmp(headers.get("foo"),"soy") == 0);
|
CHECK_THAT(headers.get("foo"), Equals("sushi, soy"));
|
||||||
CHECK(headers.count() == 1);
|
CHECK(headers.count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Overwrite header with larger header","[HttpHeaders]"){
|
|
||||||
HttpHeaders headers;
|
|
||||||
headers.set("foo","pear");
|
|
||||||
headers.set("foo","watermelon");
|
|
||||||
|
|
||||||
CHECK(strcmp(headers.get("foo"),"watermelon") == 0);
|
|
||||||
CHECK(headers.count()==1);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Force headers to grow","[HttpHeaders]") {
|
TEST_CASE("Force headers to grow","[HttpHeaders]") {
|
||||||
HttpHeaders headers;
|
HttpHeaders headers;
|
||||||
char * name = "header_";
|
char name[8] = "";
|
||||||
/*
|
strcpy(name, "header");
|
||||||
for (int i = 0; i < 30; i++) {
|
|
||||||
|
for (int i = 0; i < 26; i++) {
|
||||||
name[6] = 0x41 + i;
|
name[6] = 0x41 + i;
|
||||||
headers.set(name,"Foo");
|
headers.set(name,"Foo");
|
||||||
REQUIRE(strcmp(headers.get(name),"Foo") == 0);
|
REQUIRE(strcmp(headers.get(name),"Foo") == 0);
|
||||||
}
|
CHECK(headers.count() == (i+1) );
|
||||||
*/
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO: enable this test case while fixing header case sensitivity
|
||||||
|
TEST_CASE("Header name is case insensitive","[HttpHeaders]") {
|
||||||
|
HttpHeaders headers;
|
||||||
|
|
||||||
|
headers.set("Foo","bar");
|
||||||
|
|
||||||
|
CHECK(headers.has("foo"));
|
||||||
|
}*/
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
#include "catch.hpp"
|
||||||
|
#include "HttpRequest.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using Catch::Matchers::Equals;
|
||||||
|
|
||||||
|
TEST_CASE("Create and capture an HTTP Request","[HttpRequest]") {
|
||||||
|
const char * req_str = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||||
|
DummyStream client(req_str);
|
||||||
|
HttpRequest req(client);
|
||||||
|
req.capture();
|
||||||
|
|
||||||
|
CHECK_THAT(req.method, Equals("GET"));
|
||||||
|
CHECK_THAT(req.url, Equals("/"));
|
||||||
|
CHECK_THAT(req.httpver, Equals("HTTP/1.1"));
|
||||||
|
|
||||||
|
REQUIRE(req.headers.has("Host"));
|
||||||
|
CHECK_THAT(req.headers.get("Host"), Equals("localhost"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Capture a complicated HTTP Request","[HttpRequest]") {
|
||||||
|
const char * req_str = "POST /foo HTTP/1.1\r\nHost: localhost\r\nContent-Length: 11\r\n\r\nfoo=bar&baz";
|
||||||
|
DummyStream client(req_str);
|
||||||
|
HttpRequest req(client);
|
||||||
|
req.capture();
|
||||||
|
|
||||||
|
CHECK_THAT(req.url, Equals("/foo"));
|
||||||
|
CHECK_THAT(req.headers.get("Content-Length"), Equals("11"));
|
||||||
|
CHECK_THAT(req.message, Equals("foo=bar&baz"));
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue