Commit 8335d652 authored by octal's avatar octal

Started testing the http parser

parent 1dac2f9a
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <iostream> #include <iostream>
#include <stdexcept> #include <stdexcept>
#include <ctime> #include <ctime>
#include <cstdio>
#include <iomanip> #include <iomanip>
#include "common.h" #include "common.h"
#include "http.h" #include "http.h"
...@@ -40,6 +41,31 @@ namespace Private { ...@@ -40,6 +41,31 @@ namespace Private {
throw HttpError(code, msg); throw HttpError(code, msg);
} }
void
Step::raiseF(Code code, const char* fmt, ...) {
struct VaList {
VaList(va_list& args)
: args(args)
{ }
~VaList() { va_end(args); }
va_list& args;
};
va_list args;
va_start(args, fmt);
VaList guard(args);
char *v;
vasprintf(&v, fmt, args);
std::string str(v);
free(v);
throw HttpError(code, str);
}
State State
RequestLineStep::apply(StreamCursor& cursor) { RequestLineStep::apply(StreamCursor& cursor) {
StreamCursor::Revert revert(cursor); StreamCursor::Revert revert(cursor);
...@@ -70,14 +96,16 @@ namespace Private { ...@@ -70,14 +96,16 @@ namespace Private {
} }
if (!found) { if (!found) {
raise("Unknown HTTP request method"); //raise("Unknown HTTP request method");
return State::Again;
} }
int n; int n;
if (cursor.eof()) return State::Again; if (cursor.eof()) return State::Again;
else if ((n = cursor.current()) != ' ') else if ((n = cursor.current()) != ' ')
raise("Malformed HTTP request after Method, expected SP"); raiseF(Code::Bad_Request,
"Malformed HTTP request after Method, expected 'SP', got '%c' (0x%d)", n, n);
if (!cursor.advance(1)) return State::Again; if (!cursor.advance(1)) return State::Again;
...@@ -125,6 +153,7 @@ namespace Private { ...@@ -125,6 +153,7 @@ namespace Private {
const char* ver = versionToken.rawText(); const char* ver = versionToken.rawText();
const size_t size = versionToken.size(); const size_t size = versionToken.size();
if (strncmp(ver, "HTTP/1.0", size) == 0) { if (strncmp(ver, "HTTP/1.0", size) == 0) {
request->version_ = Version::Http10; request->version_ = Version::Http10;
} }
......
...@@ -406,6 +406,7 @@ namespace Private { ...@@ -406,6 +406,7 @@ namespace Private {
virtual State apply(StreamCursor& cursor) = 0; virtual State apply(StreamCursor& cursor) = 0;
void raise(const char* msg, Code code = Code::Bad_Request); void raise(const char* msg, Code code = Code::Bad_Request);
void raiseF(Code code, const char *msg, ...);
Request *request; Request *request;
}; };
......
...@@ -97,7 +97,7 @@ public: ...@@ -97,7 +97,7 @@ public:
} }
memcpy(bytes + size, data, len); memcpy(bytes + size, data, len);
Base::setg(bytes, bytes + size, bytes + size + len); Base::setg(bytes, this->gptr(), bytes + size + len);
size += len; size += len;
return true; return true;
} }
......
...@@ -13,3 +13,7 @@ add_test( async_test run_async_test ) ...@@ -13,3 +13,7 @@ add_test( async_test run_async_test )
add_executable( run_typeid_test typeid_test.cc ) add_executable( run_typeid_test typeid_test.cc )
target_link_libraries(run_typeid_test gtest gtest_main) target_link_libraries(run_typeid_test gtest gtest_main)
add_test( typeid_test run_typeid_test ) add_test( typeid_test run_typeid_test )
add_executable( run_parser_test parser_test.cc )
target_link_libraries(run_parser_test gtest gtest_main net)
add_test( parser_test run_parser_test )
#include "gtest/gtest.h"
#include "http.h"
#include <algorithm>
#include <string>
using namespace Net;
using namespace Net::Http;
class TestScenario {
public:
TestScenario& recvData(const char* data) {
buf += data;
return *this;
}
TestScenario& exception() {
entries.push_back(makeEntry<Exception>());
return *this;
}
TestScenario& yield() {
entries.push_back(makeEntry<Yield>());
return *this;
}
TestScenario& method(Http::Method meth) {
entries.push_back(makeEntry<Method>(meth));
return *this;
}
TestScenario& resource(const std::string& res) {
entries.push_back(makeEntry<Resource>(res));
return *this;
}
TestScenario& version(Http::Version ver) {
entries.push_back(makeEntry<Version>(ver));
return *this;
}
TestScenario& crlf() {
buf.append( {0xD, 0xA } );
return *this;
}
void run() {
for (const auto& entry: entries) {
entry.element->feed(parser, entry.data);
entry.element->check(parser);
}
}
private:
struct Element {
virtual void feed(Private::Parser& parser, const std::string& data) const {
parser.feed(data.c_str(), data.size());
parser.parse();
}
virtual void check(Private::Parser& parser) const = 0;
};
struct Entry {
std::string data;
std::unique_ptr<Element> element;
};
struct Exception : public Element {
virtual void feed(Private::Parser& parser, const std::string& data) const {
parser.feed(data.c_str(), data.size());
ASSERT_THROW(parser.parse(), HttpError);
}
void check(Private::Parser& parser) const { }
};
struct Yield : public Element {
void feed(Private::Parser& parser, const std::string& data) const {
parser.feed(data.c_str(), data.size());
ASSERT_EQ(parser.parse(), Private::State::Again);
}
void check(Private::Parser& parser) const { }
};
struct Method : public Element {
Method(Http::Method method)
: method_(method)
{ }
void check(Private::Parser& parser) const {
ASSERT_EQ(parser.request.method(), method_);
}
private:
Http::Method method_;
};
struct Resource : public Element {
Resource(const std::string& resource)
: resource_(resource)
{ }
void check(Private::Parser& parser) const {
ASSERT_EQ(parser.request.resource(), resource_);
}
private:
std::string resource_;
};
struct Version : public Element {
Version(Http::Version version) :
version_(version)
{ }
void check(Private::Parser& parser) const {
ASSERT_EQ(parser.request.version(), version_);
}
private:
Http::Version version_;
};
template<typename E, typename... Args>
Entry makeEntry(Args&& ...args) {
Entry entry;
entry.data = buf;
entry.element.reset(new E(std::forward<Args>(args)...));
buf.clear();
return entry;
}
std::vector<Entry> entries;
Private::Parser parser;
std::string buf;
};
TEST(parser_test, test_http_parsing) {
TestScenario scenario1;
scenario1
.recvData("GET").method(Method::Get)
.recvData(" /foo ").resource("/foo")
.recvData("HTTP/1.1").crlf().version(Version::Http11);
scenario1.run();
TestScenario scenario2;
scenario2
.recvData("GE").yield()
.recvData("T").method(Method::Get)
.recvData(" ").yield()
.recvData("/foo").yield()
.recvData("Index").yield()
.recvData(" ").resource("/fooIndex")
.recvData("HT").yield()
.recvData("TP/").yield()
.recvData("1.1").crlf().version(Version::Http11);
scenario2.run();
TestScenario scenario3;
scenario3
.recvData("POST").method(Method::Post)
.recvData("-").exception();
scenario3.run();
TestScenario scenario4;
scenario4
.recvData("PUT").method(Method::Put)
.recvData(" / ").resource("/")
.recvData("H").yield()
.recvData("T").yield()
.recvData("T").yield()
.recvData("P").yield()
.recvData("/1.0").crlf().version(Version::Http10);
scenario4.run();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment