Commit 8ce1ed80 authored by Jeppe Pihl's avatar Jeppe Pihl

update to new master

parents bb1fa6ba 71846007
...@@ -4,6 +4,10 @@ include(CheckCXXCompilerFlag) ...@@ -4,6 +4,10 @@ include(CheckCXXCompilerFlag)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra -Wno-missing-field-initializers") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra -Wno-missing-field-initializers")
option(PISTACHE_BUILD_TESTS "build tests alongside the project" OFF)
option(PISTACHE_BUILD_EXAMPLES "build examples alongside the project" OFF)
option(PISTACHE_INSTALL "add pistache as install target (recommended)" ON)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
if(COMPILER_SUPPORTS_CXX11) if(COMPILER_SUPPORTS_CXX11)
...@@ -19,15 +23,19 @@ add_subdirectory (src) ...@@ -19,15 +23,19 @@ add_subdirectory (src)
include_directories (src) include_directories (src)
add_subdirectory (examples) if (PISTACHE_BUILD_EXAMPLES)
add_subdirectory (examples)
find_package(GTest)
if (GTEST_FOUND)
include_directories(${GTEST_INCLUDE_DIRS})
else()
ADD_SUBDIRECTORY (googletest-release-1.7.0)
include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
endif() endif()
enable_testing() if (PISTACHE_BUILD_TESTS)
add_subdirectory(tests) find_package(GTest)
if (GTEST_FOUND)
include_directories(${GTEST_INCLUDE_DIRS})
else()
ADD_SUBDIRECTORY (googletest-release-1.7.0)
include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
endif()
enable_testing()
add_subdirectory(tests)
endif()
...@@ -8,6 +8,15 @@ It is entirely written in pure-C++11 and provides a clear and pleasant API ...@@ -8,6 +8,15 @@ It is entirely written in pure-C++11 and provides a clear and pleasant API
Full documentation is located at [http://pistache.io](http://pistache.io). Full documentation is located at [http://pistache.io](http://pistache.io).
# Contributing
Pistache is an open-source project and will always stay open-source. However, working on an open-source project while having a full-time job is sometimes a difficult task to accomplish.
That's why your help is needed. If you would like to contribute to the project in any way (submitting ideas, fixing bugs, writing documentation, ...), please join the
[cpplang Slack channel](https://cpplang.now.sh/). Drop a private message to `@octal` and I will invite you to the channel dedicated to Pistache.
Hope to see you there !
# Example # Example
## Hello World (server) ## Hello World (server)
...@@ -15,7 +24,7 @@ Full documentation is located at [http://pistache.io](http://pistache.io). ...@@ -15,7 +24,7 @@ Full documentation is located at [http://pistache.io](http://pistache.io).
```cpp ```cpp
#include <pistache/endpoint.h> #include <pistache/endpoint.h>
using namespace Net; using namespace Pistache;
struct HelloHandler : public Http::Handler { struct HelloHandler : public Http::Handler {
HTTP_PROTOTYPE(HelloHandler) HTTP_PROTOTYPE(HelloHandler)
......
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
*/ */
#include "endpoint.h" #include "pistache/endpoint.h"
using namespace Net; using namespace Pistache;
class HelloHandler : public Http::Handler { class HelloHandler : public Http::Handler {
public: public:
...@@ -20,8 +20,8 @@ public: ...@@ -20,8 +20,8 @@ public:
}; };
int main() { int main() {
Net::Address addr(Net::Ipv4::any(), Net::Port(9080)); Pistache::Address addr(Pistache::Ipv4::any(), Pistache::Port(9080));
auto opts = Net::Http::Endpoint::options() auto opts = Pistache::Http::Endpoint::options()
.threads(1); .threads(1);
Http::Endpoint server(addr); Http::Endpoint server(addr);
......
...@@ -31,6 +31,7 @@ namespace Http { ...@@ -31,6 +31,7 @@ namespace Http {
#define STATUS_CODES \ #define STATUS_CODES \
CODE(100, Continue, "Continue") \ CODE(100, Continue, "Continue") \
CODE(101, Switching_Protocols, "Switching Protocols") \ CODE(101, Switching_Protocols, "Switching Protocols") \
CODE(102, Processing, "Processing") \
CODE(200, Ok, "OK") \ CODE(200, Ok, "OK") \
CODE(201, Created, "Created") \ CODE(201, Created, "Created") \
CODE(202, Accepted, "Accepted") \ CODE(202, Accepted, "Accepted") \
...@@ -38,6 +39,9 @@ namespace Http { ...@@ -38,6 +39,9 @@ namespace Http {
CODE(204, No_Content, "No Content") \ CODE(204, No_Content, "No Content") \
CODE(205, Reset_Content, "Reset Content") \ CODE(205, Reset_Content, "Reset Content") \
CODE(206, Partial_Content, "Partial Content") \ CODE(206, Partial_Content, "Partial Content") \
CODE(207, MultiStatus, "Multi-Status") \
CODE(208, Already_Reported, "Already Reported") \
CODE(226, IM_Used, "IM Used") \
CODE(300, Multiple_Choices, "Multiple Choices") \ CODE(300, Multiple_Choices, "Multiple Choices") \
CODE(301, Moved_Permanently, "Moved Permanently") \ CODE(301, Moved_Permanently, "Moved Permanently") \
CODE(302, Found, "Found") \ CODE(302, Found, "Found") \
...@@ -45,6 +49,7 @@ namespace Http { ...@@ -45,6 +49,7 @@ namespace Http {
CODE(304, Not_Modified, "Not Modified") \ CODE(304, Not_Modified, "Not Modified") \
CODE(305, Use_Proxy, "Use Proxy") \ CODE(305, Use_Proxy, "Use Proxy") \
CODE(307, Temporary_Redirect, "Temporary Redirect") \ CODE(307, Temporary_Redirect, "Temporary Redirect") \
CODE(308, Permanent_Redirect, "Permanent Redirect") \
CODE(400, Bad_Request, "Bad Request") \ CODE(400, Bad_Request, "Bad Request") \
CODE(401, Unauthorized, "Unauthorized") \ CODE(401, Unauthorized, "Unauthorized") \
CODE(402, Payment_Required, "Payment Required") \ CODE(402, Payment_Required, "Payment Required") \
...@@ -63,11 +68,30 @@ namespace Http { ...@@ -63,11 +68,30 @@ namespace Http {
CODE(415, Unsupported_Media_Type, "Unsupported Media Type") \ CODE(415, Unsupported_Media_Type, "Unsupported Media Type") \
CODE(416, Requested_Range_Not_Satisfiable, "Requested Range Not Satisfiable") \ CODE(416, Requested_Range_Not_Satisfiable, "Requested Range Not Satisfiable") \
CODE(417, Expectation_Failed, "Expectation Failed") \ CODE(417, Expectation_Failed, "Expectation Failed") \
CODE(418, I_m_a_teapot, "I'm a teapot") \
CODE(421, Misdirected_Request, "Misdirected Request") \
CODE(422, Unprocessable_Entity, "Unprocessable Entity") \
CODE(423, Locked, "Locked") \
CODE(424, Failed_Dependency, "Failed Dependency") \
CODE(426, Upgrade_Required, "Upgrade Required") \
CODE(428, Precondition_Required, "Precondition Required") \
CODE(429, Too_Many_Requests, "Too Many Requests")\
CODE(431, Request_Header_Fields_Too_Large, "Request Header Fields Too Large") \
CODE(444, Connection_Closed_Without_Response, "Connection Closed Without Response") \
CODE(451, Unavailable_For_Legal_Reasons, "Unavailable For Legal Reasons") \
CODE(499, Client_Closed_Request, "Client Closed Request") \
CODE(500, Internal_Server_Error, "Internal Server Error") \ CODE(500, Internal_Server_Error, "Internal Server Error") \
CODE(501, Not_Implemented, "Not Implemented") \ CODE(501, Not_Implemented, "Not Implemented") \
CODE(502, Bad_Gateway, "Bad Gateway") \ CODE(502, Bad_Gateway, "Bad Gateway") \
CODE(503, Service_Unavailable, "Service Unavailable") \ CODE(503, Service_Unavailable, "Service Unavailable") \
CODE(504, Gateway_Timeout, "Gateway Timeout") CODE(504, Gateway_Timeout, "Gateway Timeout") \
CODE(505, HTTP_Version_Not_Supported, "HTTP Version Not Supported")\
CODE(506, Variant_Also_Negotiates, "Variant Also Negotiates") \
CODE(507, Insufficient_Storage, "Insufficient Storage") \
CODE(508, Loop_Detected, "Loop Detected") \
CODE(510, Not_Extended, "Not Extended") \
CODE(511, Network_Authentication_Required, "Network Authentication Required")\
CODE(599, Network_Connect_Timeout_Error, "Network Connect Timeout Error")
// 3.4. Character Sets // 3.4. Character Sets
// See http://tools.ietf.org/html/rfc2978 and // See http://tools.ietf.org/html/rfc2978 and
...@@ -175,6 +199,7 @@ private: ...@@ -175,6 +199,7 @@ private:
}; };
const char* methodString(Method method); const char* methodString(Method method);
const char* versionString(Version version);
const char* codeString(Code code); const char* codeString(Code code);
std::ostream& operator<<(std::ostream& os, Version version); std::ostream& operator<<(std::ostream& os, Version version);
......
...@@ -211,6 +211,10 @@ inline bool operator==(const MediaType& lhs, const MediaType& rhs) { ...@@ -211,6 +211,10 @@ inline bool operator==(const MediaType& lhs, const MediaType& rhs) {
lhs.suffix() == rhs.suffix(); lhs.suffix() == rhs.suffix();
} }
inline bool operator!=(const MediaType& lhs, const MediaType& rhs){
return !operator==(lhs, rhs);
}
} // namespace Mime } // namespace Mime
} // namespace Http } // namespace Http
} // namespace Pistache } // namespace Pistache
......
...@@ -28,35 +28,37 @@ set(bin_install_dir "bin/") ...@@ -28,35 +28,37 @@ set(bin_install_dir "bin/")
target_link_libraries(pistache pthread) target_link_libraries(pistache pthread)
install( if (PISTACHE_INSTALL)
TARGETS pistache install(
EXPORT ${targets_export_name} TARGETS pistache
ARCHIVE DESTINATION ${lib_install_dir} EXPORT ${targets_export_name}
LIBRARY DESTINATION ${lib_install_dir} ARCHIVE DESTINATION ${lib_install_dir}
RUNTIME DESTINATION ${bin_install_dir} LIBRARY DESTINATION ${lib_install_dir}
INCLUDES DESTINATION ${include_install_dir}) RUNTIME DESTINATION ${bin_install_dir}
INCLUDES DESTINATION ${include_install_dir})
install(
DIRECTORY "${PROJECT_SOURCE_DIR}/include/pistache" install(
DESTINATION ${include_install_dir} DIRECTORY "${PROJECT_SOURCE_DIR}/include/pistache"
FILES_MATCHING PATTERN "*.*h") DESTINATION ${include_install_dir}
install(TARGETS pistache FILES_MATCHING PATTERN "*.*h")
EXPORT PistacheTargets install(TARGETS pistache
DESTINATION lib) EXPORT PistacheTargets
install(EXPORT PistacheTargets DESTINATION lib)
install(EXPORT PistacheTargets
DESTINATION "lib/cmake/pistache"
EXPORT_LINK_INTERFACE_LIBRARIES
COMPONENT cmake-config
)
include(CMakePackageConfigHelpers)
configure_package_config_file(
"PistacheConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/PistacheConfig.cmake"
INSTALL_DESTINATION "lib/cmake/pistache"
)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/PistacheConfig.cmake"
DESTINATION "lib/cmake/pistache" DESTINATION "lib/cmake/pistache"
EXPORT_LINK_INTERFACE_LIBRARIES
COMPONENT cmake-config COMPONENT cmake-config
) )
endif()
include(CMakePackageConfigHelpers)
configure_package_config_file(
"PistacheConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/PistacheConfig.cmake"
INSTALL_DESTINATION "lib/cmake/pistache"
)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/PistacheConfig.cmake"
DESTINATION "lib/cmake/pistache"
COMPONENT cmake-config
)
...@@ -315,17 +315,14 @@ namespace Private { ...@@ -315,17 +315,14 @@ namespace Private {
headerRevert.ignore(); headerRevert.ignore();
} }
if (!cursor.advance(2)) return State::Again;
revert.ignore(); revert.ignore();
return State::Next; return State::Next;
} }
State State
BodyStep::apply(StreamCursor& cursor) { BodyStep::apply(StreamCursor& cursor) {
if (message->body_.empty()) {
/* If this is the first time we are reading the body, skip the CRLF */
if (!cursor.advance(2)) return State::Again;
}
auto cl = message->headers_.tryGet<Header::ContentLength>(); auto cl = message->headers_.tryGet<Header::ContentLength>();
auto te = message->headers_.tryGet<Header::TransferEncoding>(); auto te = message->headers_.tryGet<Header::TransferEncoding>();
......
...@@ -119,10 +119,10 @@ MediaType::parseRaw(const char* str, size_t len) { ...@@ -119,10 +119,10 @@ MediaType::parseRaw(const char* str, size_t len) {
// //
// Watch out, this pattern is repeated throughout the function // Watch out, this pattern is repeated throughout the function
do { do {
#define TYPE(val, s) \ #define TYPE(val, s) \
if (match_raw(s, sizeof s - 1, cursor)) { \ if (match_string(s, sizeof s - 1, cursor, CaseSensitivity::Insensitive)) { \
top = Type::val; \ top = Type::val; \
break; \ break; \
} }
MIME_TYPES MIME_TYPES
#undef TYPE #undef TYPE
...@@ -146,10 +146,10 @@ MediaType::parseRaw(const char* str, size_t len) { ...@@ -146,10 +146,10 @@ MediaType::parseRaw(const char* str, size_t len) {
sub = Subtype::Vendor; sub = Subtype::Vendor;
} else { } else {
do { do {
#define SUB_TYPE(val, s) \ #define SUB_TYPE(val, s) \
if (match_raw(s, sizeof s - 1, cursor)) { \ if (match_string(s, sizeof s - 1, cursor, CaseSensitivity::Insensitive)) { \
sub = Subtype::val; \ sub = Subtype::val; \
break; \ break; \
} }
MIME_SUBTYPES MIME_SUBTYPES
#undef SUB_TYPE #undef SUB_TYPE
...@@ -176,10 +176,10 @@ MediaType::parseRaw(const char* str, size_t len) { ...@@ -176,10 +176,10 @@ MediaType::parseRaw(const char* str, size_t len) {
StreamCursor::Token suffixToken(cursor); StreamCursor::Token suffixToken(cursor);
do { do {
#define SUFFIX(val, s, _) \ #define SUFFIX(val, s, _) \
if (match_raw(s, sizeof s - 1, cursor)) { \ if (match_string(s, sizeof s - 1, cursor, CaseSensitivity::Insensitive)) { \
suffix = Suffix::val; \ suffix = Suffix::val; \
break; \ break; \
} }
MIME_SUFFIXES MIME_SUFFIXES
#undef SUFFIX #undef SUFFIX
......
...@@ -84,11 +84,13 @@ Address::Address(Ipv4 ip, Port port) ...@@ -84,11 +84,13 @@ Address::Address(Ipv4 ip, Port port)
Address Address
Address::fromUnix(struct sockaddr* addr) { Address::fromUnix(struct sockaddr* addr) {
struct sockaddr_in *in_addr = reinterpret_cast<struct sockaddr_in *>(addr); struct sockaddr_in *in_addr = reinterpret_cast<struct sockaddr_in *>(addr);
std::string host = TRY_RET(inet_ntoa(in_addr->sin_addr));
char *host = inet_ntoa(in_addr->sin_addr);
assert(addr);
int port = ntohs(in_addr->sin_port); int port = ntohs(in_addr->sin_port);
return Address(std::move(host), port); return Address(host, port);
} }
std::string std::string
......
...@@ -172,28 +172,27 @@ Route::match(const Http::Request& req) const ...@@ -172,28 +172,27 @@ Route::match(const Http::Request& req) const
std::tuple<bool, std::vector<TypedParam>, std::vector<TypedParam>> std::tuple<bool, std::vector<TypedParam>, std::vector<TypedParam>>
Route::match(const std::string& req) const Route::match(const std::string& req) const
{ {
#define UNMATCH() std::make_tuple(false, std::vector<TypedParam>(), std::vector<TypedParam>()) static const auto NoMatch = std::make_tuple(false, std::vector<TypedParam>(), std::vector<TypedParam>());
auto reqFragments = Fragment::fromUrl(req); auto reqFragments = Fragment::fromUrl(req);
if (reqFragments.size() > fragments_.size()) if (reqFragments.size() > fragments_.size())
return UNMATCH(); return NoMatch;
std::vector<TypedParam> params; std::vector<TypedParam> params;
std::vector<TypedParam> splats; std::vector<TypedParam> splats;
for (std::vector<Fragment>::size_type i = 0; i < fragments_.size(); ++i) { for (std::vector<Fragment>::size_type i = 0; i < fragments_.size(); ++i) {
const auto& fragment = fragments_[i]; const auto& fragment = fragments_[i];
if (i >= reqFragments.size()) { if (i >= reqFragments.size()) {
if (fragment.isOptional()) if (fragment.isOptional())
continue; continue;
return UNMATCH(); return NoMatch;
} }
const auto& reqFragment = reqFragments[i]; const auto& reqFragment = reqFragments[i];
if (!fragment.match(reqFragment)) if (!fragment.match(reqFragment))
return UNMATCH(); return NoMatch;
if (fragment.isParameter()) { if (fragment.isParameter()) {
params.push_back(TypedParam(fragment.value(), reqFragment.value())); params.push_back(TypedParam(fragment.value(), reqFragment.value()));
...@@ -203,8 +202,6 @@ Route::match(const std::string& req) const ...@@ -203,8 +202,6 @@ Route::match(const std::string& req) const
} }
#undef UNMATCH
return make_tuple(true, std::move(params), std::move(splats)); return make_tuple(true, std::move(params), std::move(splats));
} }
......
...@@ -15,3 +15,4 @@ pistache_test(typeid_test) ...@@ -15,3 +15,4 @@ pistache_test(typeid_test)
pistache_test(router_test) pistache_test(router_test)
pistache_test(cookie_test) pistache_test(cookie_test)
pistache_test(view_test) pistache_test(view_test)
pistache_test(http_parsing_test)
#include "gtest/gtest.h"
#include <pistache/http.h>
using namespace Pistache;
// @Todo: Add an easy to use fixture to inject data for parsing tests.
TEST(http_parsing_test, should_parse_http_request_in_two_packets_issue_160)
{
Http::Private::Parser<Http::Request> parser;
auto feed = [&](const char* data)
{
parser.feed(data, std::strlen(data));
};
// First, we feed the parser with a Request-Line
feed("GET /hello HTTP/1.1\r\n");
ASSERT_EQ(parser.parse(), Http::Private::State::Again);
// @Todo @Completeness We should also assert that we are in the correct step. However, the step is currently not
// exposed by the parser. Since the parser is supposed to stay "private", we could either directly expose the step
// or return it from the parse() method.
// Let's now put some headers
feed("User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36\r\n");
feed("Host: localhost\r\n");
feed("Content-Length: 5\r\n");
feed("\r\n");
ASSERT_EQ(parser.parse(), Http::Private::State::Again);
// Finally, we finish the body
feed("HELLO");
ASSERT_EQ(parser.parse(), Http::Private::State::Done);
}
\ No newline at end of file
...@@ -14,6 +14,7 @@ TEST(mime_test, basic_test) { ...@@ -14,6 +14,7 @@ TEST(mime_test, basic_test) {
ASSERT_EQ(m1.toString(), "text/plain"); ASSERT_EQ(m1.toString(), "text/plain");
ASSERT_EQ(m1, MIME(Text, Plain)); ASSERT_EQ(m1, MIME(Text, Plain));
ASSERT_NE(m1, MIME(Application, Json));
auto m2 = MIME3(Application, Xhtml, Xml); auto m2 = MIME3(Application, Xhtml, Xml);
ASSERT_EQ(m2.toString(), "application/xhtml+xml"); ASSERT_EQ(m2.toString(), "application/xhtml+xml");
...@@ -137,3 +138,20 @@ TEST(mime_test, invalid_parsing) { ...@@ -137,3 +138,20 @@ TEST(mime_test, invalid_parsing) {
ASSERT_THROW(MediaType::fromString("text/html; q=0.21; charset="), HttpError); ASSERT_THROW(MediaType::fromString("text/html; q=0.21; charset="), HttpError);
ASSERT_THROW(MediaType::fromString("text/html; q=0.21; charset=ISO-8859-4; "), HttpError); ASSERT_THROW(MediaType::fromString("text/html; q=0.21; charset=ISO-8859-4; "), HttpError);
} }
TEST(mime_test, should_parse_case_insensitive_issue_179)
{
parse("Application/Json", [](const Mime::MediaType& mime) {
ASSERT_EQ(mime, MIME(Application, Json));
ASSERT_TRUE(mime.q().isEmpty());
});
parse("aPpliCAtion/Xhtml+XML", [](const MediaType &mime) {
ASSERT_EQ(mime, MediaType(Type::Application, Subtype::Xhtml, Suffix::Xml));
ASSERT_TRUE(mime.q().isEmpty());
});
parse("Application/Xhtml+XML; q=0.78", [](const MediaType& mime) {
ASSERT_EQ(mime.q().getOrElse(Q(0)), Q(78));
});
}
\ No newline at end of file
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