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)))
|
||||
OBJECTS := $(addsuffix .o, $(addprefix .build/, $(basename $(SOURCES))))
|
||||
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
|
||||
TEST_TARGET=test_HttpServer
|
||||
TEST_TARGET=run_tests
|
||||
|
||||
.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) {
|
||||
|
||||
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++) {
|
||||
if (strcmp(name, headers[i].name) != 0)
|
||||
continue;
|
||||
free(headers[i].value);
|
||||
headers[i].value = (char *) malloc(sizeof(char) * strlen(value));
|
||||
strcpy(headers[i].value, value);
|
||||
int old_value_len = strlen(headers[i].value);
|
||||
headers[i].value = (char *) realloc(headers[i].value, old_value_len + value_len + 2);
|
||||
strcpy(headers[i].value+old_value_len, ", ");
|
||||
strcpy(headers[i].value+old_value_len+2, value);
|
||||
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].value = (char *) malloc(sizeof(char) * strlen(value));
|
||||
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
|
||||
#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
|
||||
{
|
||||
public:
|
||||
DummyStream(char* buff): buffer(buffer), pos(0) {};
|
||||
char * buffer;
|
||||
DummyStream(const char* buff): buffer(buff), pos(0) {};
|
||||
const char * buffer;
|
||||
int pos;
|
||||
virtual int available() { return (buffer[pos] != '\0'); }
|
||||
virtual int available() { return (strlen(buffer) - pos); }
|
||||
virtual int read() { return buffer[pos++]; }
|
||||
virtual int peek() { return buffer[pos]; }
|
||||
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 "string.h"
|
||||
|
||||
using Catch::Matchers::Equals;
|
||||
|
||||
TEST_CASE("Empty HttpHeaders","[HttpHeaders]") {
|
||||
HttpHeaders headers;
|
||||
|
||||
|
@ -20,32 +22,33 @@ TEST_CASE("Get and set headers","[HttpHeaders]"){
|
|||
CHECK(headers.count() == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Overwrite header with smaller header","[HttpHeaders]"){
|
||||
TEST_CASE("Append headers","[HttpHeaders]"){
|
||||
HttpHeaders headers;
|
||||
headers.set("foo","sushi");
|
||||
headers.set("foo","soy");
|
||||
|
||||
CHECK(strcmp(headers.get("foo"),"soy") == 0);
|
||||
CHECK_THAT(headers.get("foo"), Equals("sushi, soy"));
|
||||
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]") {
|
||||
HttpHeaders headers;
|
||||
char * name = "header_";
|
||||
/*
|
||||
for (int i = 0; i < 30; i++) {
|
||||
char name[8] = "";
|
||||
strcpy(name, "header");
|
||||
|
||||
for (int i = 0; i < 26; i++) {
|
||||
name[6] = 0x41 + i;
|
||||
headers.set(name,"Foo");
|
||||
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