Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
pistache
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Libraries
pistache
Commits
997c9cf5
Commit
997c9cf5
authored
Aug 26, 2018
by
Ian Roddis
Browse files
Options
Browse Files
Download
Plain Diff
Rebasing on master
parents
e898c622
99cac24c
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
327 additions
and
635 deletions
+327
-635
CMakeLists.txt
CMakeLists.txt
+2
-12
examples/http_server.cc
examples/http_server.cc
+10
-0
include/pistache/http.h
include/pistache/http.h
+9
-1
include/pistache/router.h
include/pistache/router.h
+68
-65
include/pistache/string_view.h
include/pistache/string_view.h
+0
-231
src/CMakeLists.txt
src/CMakeLists.txt
+1
-1
src/client/client.cc
src/client/client.cc
+7
-1
src/server/router.cc
src/server/router.cc
+132
-212
tests/CMakeLists.txt
tests/CMakeLists.txt
+1
-1
tests/http_client_test.cc
tests/http_client_test.cc
+56
-0
tests/router_test.cc
tests/router_test.cc
+41
-45
tests/string_view_test.cc
tests/string_view_test.cc
+0
-66
No files found.
CMakeLists.txt
View file @
997c9cf5
cmake_minimum_required
(
VERSION 3.0.2
)
cmake_minimum_required
(
VERSION 3.0.2
)
project
(
pistache
)
project
(
pistache
)
include
(
CheckCXXCompilerFlag
)
include
(
CheckCXXCompilerFlag
)
option
(
PISTACHE_BUILD_TESTS
"build tests alongside the project"
OFF
)
option
(
PISTACHE_BUILD_TESTS
"build tests alongside the project"
OFF
)
...
@@ -13,19 +12,9 @@ if (NOT PISTACHE_MAX_PAYLOAD)
...
@@ -13,19 +12,9 @@ if (NOT PISTACHE_MAX_PAYLOAD)
endif
()
endif
()
add_definitions
(
-DMAX_PAYLOAD=
${
PISTACHE_MAX_PAYLOAD
}
)
add_definitions
(
-DMAX_PAYLOAD=
${
PISTACHE_MAX_PAYLOAD
}
)
CHECK_CXX_COMPILER_FLAG
(
"-std=c++17"
COMPILER_SUPPORTS_CXX17
)
CHECK_CXX_COMPILER_FLAG
(
"-std=c++14"
COMPILER_SUPPORTS_CXX14
)
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
)
# Clang exports C++17 in std::experimental namespace (tested on Clang 5 and 6).
# This gives an error on date.h external library.
# Following workaround forces Clang to compile at best with C++14
if
(
COMPILER_SUPPORTS_CXX17 AND NOT CMAKE_CXX_COMPILER_ID STREQUAL
"Clang"
)
set
(
CMAKE_CXX_FLAGS
"
${
CMAKE_CXX_FLAGS
}
-std=c++17"
)
elseif
(
COMPILER_SUPPORTS_CXX14
)
set
(
CMAKE_CXX_FLAGS
"
${
CMAKE_CXX_FLAGS
}
-std=c++14"
)
elseif
(
COMPILER_SUPPORTS_CXX11
)
set
(
CMAKE_CXX_FLAGS
"
${
CMAKE_CXX_FLAGS
}
-std=c++11"
)
set
(
CMAKE_CXX_FLAGS
"
${
CMAKE_CXX_FLAGS
}
-std=c++11"
)
elseif
(
COMPILER_SUPPORTS_CXX0X
)
elseif
(
COMPILER_SUPPORTS_CXX0X
)
set
(
CMAKE_CXX_FLAGS
"
${
CMAKE_CXX_FLAGS
}
-std=c++0x"
)
set
(
CMAKE_CXX_FLAGS
"
${
CMAKE_CXX_FLAGS
}
-std=c++0x"
)
...
@@ -99,3 +88,4 @@ endif()
...
@@ -99,3 +88,4 @@ endif()
DESTINATION
DESTINATION
lib/pkgconfig/
lib/pkgconfig/
)
)
examples/http_server.cc
View file @
997c9cf5
...
@@ -121,6 +121,16 @@ class MyHandler : public Http::Handler {
...
@@ -121,6 +121,16 @@ class MyHandler : public Http::Handler {
response
.
send
(
Http
::
Code
::
Method_Not_Allowed
);
response
.
send
(
Http
::
Code
::
Method_Not_Allowed
);
}
}
}
}
else
if
(
req
.
resource
()
==
"/stream_binary"
)
{
auto
stream
=
response
.
stream
(
Http
::
Code
::
Ok
);
char
binary_data
[]
=
"some
\0\r\n
data
\n
"
;
size_t
chunk_size
=
14
;
for
(
size_t
i
=
0
;
i
<
10
;
++
i
)
{
stream
.
write
(
binary_data
,
chunk_size
);
stream
.
flush
();
}
stream
.
ends
();
}
else
if
(
req
.
resource
()
==
"/exception"
)
{
else
if
(
req
.
resource
()
==
"/exception"
)
{
throw
std
::
runtime_error
(
"Exception thrown in the handler"
);
throw
std
::
runtime_error
(
"Exception thrown in the handler"
);
}
}
...
...
include/pistache/http.h
View file @
997c9cf5
...
@@ -101,7 +101,7 @@ namespace Uri {
...
@@ -101,7 +101,7 @@ namespace Uri {
bool
has
(
const
std
::
string
&
name
)
const
;
bool
has
(
const
std
::
string
&
name
)
const
;
// Return empty string or "?key1=value1&key2=value2" if query exist
// Return empty string or "?key1=value1&key2=value2" if query exist
std
::
string
as_str
()
const
;
std
::
string
as_str
()
const
;
void
clear
()
{
void
clear
()
{
params
.
clear
();
params
.
clear
();
}
}
...
@@ -317,6 +317,14 @@ public:
...
@@ -317,6 +317,14 @@ public:
friend
friend
ResponseStream
&
operator
<<
(
ResponseStream
&
stream
,
const
T
&
val
);
ResponseStream
&
operator
<<
(
ResponseStream
&
stream
,
const
T
&
val
);
std
::
streamsize
write
(
const
char
*
data
,
std
::
streamsize
sz
)
{
std
::
ostream
os
(
&
buf_
);
os
<<
std
::
hex
<<
sz
<<
crlf
;
os
.
write
(
data
,
sz
);
os
<<
crlf
;
return
sz
;
}
const
Header
::
Collection
&
headers
()
const
{
const
Header
::
Collection
&
headers
()
const
{
return
headers_
;
return
headers_
;
}
}
...
...
include/pistache/router.h
View file @
997c9cf5
...
@@ -8,15 +8,11 @@
...
@@ -8,15 +8,11 @@
#include <string>
#include <string>
#include <tuple>
#include <tuple>
#include <unordered_map>
#include <memory>
#include <pistache/http.h>
#include <pistache/http.h>
#include <pistache/http_defs.h>
#include <pistache/http_defs.h>
#include <pistache/flags.h>
#include <pistache/flags.h>
#include "pistache/string_view.h"
namespace
Pistache
{
namespace
Pistache
{
namespace
Rest
{
namespace
Rest
{
...
@@ -24,8 +20,8 @@ class Description;
...
@@ -24,8 +20,8 @@ class Description;
namespace
details
{
namespace
details
{
template
<
typename
T
>
struct
LexicalCast
{
template
<
typename
T
>
struct
LexicalCast
{
static
T
cast
(
const
std
::
string
_view
&
value
)
{
static
T
cast
(
const
std
::
string
&
value
)
{
std
::
istringstream
iss
(
std
::
string
(
value
.
data
(),
value
.
length
())
);
std
::
istringstream
iss
(
value
);
T
out
;
T
out
;
if
(
!
(
iss
>>
out
))
if
(
!
(
iss
>>
out
))
throw
std
::
runtime_error
(
"Bad lexical cast"
);
throw
std
::
runtime_error
(
"Bad lexical cast"
);
...
@@ -34,8 +30,8 @@ namespace details {
...
@@ -34,8 +30,8 @@ namespace details {
};
};
template
<
>
template
<
>
struct
LexicalCast
<
std
::
string
_view
>
{
struct
LexicalCast
<
std
::
string
>
{
static
std
::
string
_view
cast
(
const
std
::
string_view
&
value
)
{
static
std
::
string
cast
(
const
std
::
string
&
value
)
{
return
value
;
return
value
;
}
}
};
};
...
@@ -43,7 +39,7 @@ namespace details {
...
@@ -43,7 +39,7 @@ namespace details {
class
TypedParam
{
class
TypedParam
{
public:
public:
TypedParam
(
const
std
::
string
_view
&
name
,
const
std
::
string_view
&
value
)
TypedParam
(
const
std
::
string
&
name
,
const
std
::
string
&
value
)
:
name_
(
name
)
:
name_
(
name
)
,
value_
(
value
)
,
value_
(
value
)
{
}
{
}
...
@@ -53,88 +49,97 @@ public:
...
@@ -53,88 +49,97 @@ public:
return
details
::
LexicalCast
<
T
>::
cast
(
value_
);
return
details
::
LexicalCast
<
T
>::
cast
(
value_
);
}
}
std
::
string
_view
name
()
const
{
std
::
string
name
()
const
{
return
name_
;
return
name_
;
}
}
private:
private:
std
::
string
_view
name_
;
std
::
string
name_
;
std
::
string
_view
value_
;
std
::
string
value_
;
};
};
class
Request
;
class
Request
;
struct
Route
{
struct
Route
{
enum
class
Result
{
Ok
,
Failure
};
enum
class
Status
{
Match
,
NotFound
}
;
typedef
std
::
function
<
Result
(
const
Request
&
,
Http
::
ResponseWriter
)
>
Handler
;
enum
class
Result
{
Ok
,
Failure
};
Route
(
std
::
string
resource
,
Http
::
Method
method
,
Handler
handler
)
:
resource_
(
std
::
move
(
resource
))
,
method_
(
method
)
,
handler_
(
std
::
move
(
handler
))
,
fragments_
(
Fragment
::
fromUrl
(
resource_
))
{
}
typedef
std
::
function
<
Result
(
const
Request
,
Http
::
ResponseWriter
)
>
Handler
;
std
::
tuple
<
bool
,
std
::
vector
<
TypedParam
>
,
std
::
vector
<
TypedParam
>>
match
(
const
Http
::
Request
&
req
)
const
;
explicit
Route
(
Route
::
Handler
handler
)
:
handler_
(
std
::
move
(
handler
))
{
}
std
::
tuple
<
bool
,
std
::
vector
<
TypedParam
>
,
std
::
vector
<
TypedParam
>>
match
(
const
std
::
string
&
req
)
const
;
template
<
typename
...
Args
>
template
<
typename
...
Args
>
void
invokeHandler
(
Args
&&
...
args
)
const
{
void
invokeHandler
(
Args
&&
...
args
)
const
{
handler_
(
std
::
forward
<
Args
>
(
args
)...);
handler_
(
std
::
forward
<
Args
>
(
args
)...);
}
}
Handler
handler_
;
};
namespace
Private
{
class
RouterHandler
;
}
class
FragmentTreeNode
{
private:
private:
enum
class
FragmentType
{
struct
Fragment
{
Fixed
,
Param
,
Optional
,
Splat
explicit
Fragment
(
std
::
string
value
);
};
/**
bool
match
(
const
std
::
string
&
raw
)
const
;
* Resource path are allocated on stack. To make them survive it is required to allocate them on heap.
bool
match
(
const
Fragment
&
other
)
const
;
* Since the main idea between string_view is to have constant substring and thus a reduced number of
* char* allocated on heap, this shared_ptr is used to know if a string_view can be destroyed (after a
* route removal).
*/
std
::
shared_ptr
<
char
>
resourceRef_
;
std
::
unordered_map
<
std
::
string_view
,
std
::
shared_ptr
<
FragmentTreeNode
>>
fixed_
;
bool
isParameter
()
const
;
std
::
unordered_map
<
std
::
string_view
,
std
::
shared_ptr
<
FragmentTreeNode
>>
param_
;
bool
isSplat
()
const
;
std
::
unordered_map
<
std
::
string_view
,
std
::
shared_ptr
<
FragmentTreeNode
>>
optional_
;
bool
isOptional
()
const
;
std
::
shared_ptr
<
FragmentTreeNode
>
splat_
;
std
::
shared_ptr
<
Route
>
route_
;
static
FragmentType
getFragmentType
(
const
std
::
string_view
&
fragment
);
std
::
string
value
()
const
{
return
value_
;
}
std
::
tuple
<
std
::
shared_ptr
<
Route
>
,
std
::
vector
<
TypedParam
>
,
std
::
vector
<
TypedParam
>>
static
std
::
vector
<
Fragment
>
fromUrl
(
const
std
::
string
&
url
);
findRoute
(
const
std
::
string_view
&
path
,
std
::
vector
<
TypedParam
>
&
params
,
std
::
vector
<
TypedParam
>
&
splats
)
const
;
public:
private:
FragmentTreeNode
();
enum
class
Flag
{
explicit
FragmentTreeNode
(
const
std
::
shared_ptr
<
char
>
&
resourceReference
);
None
=
0x0
,
Fixed
=
0x1
,
Parameter
=
Fixed
<<
1
,
Optional
=
Parameter
<<
1
,
Splat
=
Optional
<<
1
};
void
addRoute
(
const
std
::
string_view
&
path
,
const
Route
::
Handler
&
handler
,
void
init
(
std
::
string
value
);
const
std
::
shared_ptr
<
char
>
&
resourceReference
);
bool
removeRoute
(
const
std
::
string_view
&
path
)
;
void
checkInvariant
()
const
;
/**
Flags
<
Flag
>
flags
;
* Finds the correct route for the given path.
std
::
string
value_
;
* \param[in] path Requested resource path (usually derived from request)
};
* \throws std::runtime_error An empty path was given
* \return Found route with its resolved parameters and splats (if no route is found, first element of the tuple
std
::
string
resource_
;
* is a null pointer).
Http
::
Method
method_
;
Handler
handler_
;
/* @Performance: since we know that resource_ will live as long as the vector underneath,
* we would benefit from std::experimental::string_view to store fragments.
*
* We could use string_view instead of allocating strings everytime. However, string_view is
* only available in c++17, so I might have to come with my own lightweight implementation of
* it
*/
*/
std
::
tuple
<
std
::
shared_ptr
<
Route
>
,
std
::
vector
<
TypedParam
>
,
std
::
vector
<
TypedParam
>>
std
::
vector
<
Fragment
>
fragments_
;
findRoute
(
const
std
::
string_view
&
path
)
const
;
};
};
namespace
Private
{
class
RouterHandler
;
}
class
Router
{
class
Router
{
public:
public:
enum
class
Status
{
Match
,
NotFound
};
static
Router
fromDescription
(
const
Rest
::
Description
&
desc
);
static
Router
fromDescription
(
const
Rest
::
Description
&
desc
);
std
::
shared_ptr
<
Private
::
RouterHandler
>
std
::
shared_ptr
<
Private
::
RouterHandler
>
...
@@ -148,18 +153,18 @@ public:
...
@@ -148,18 +153,18 @@ public:
void
patch
(
std
::
string
resource
,
Route
::
Handler
handler
);
void
patch
(
std
::
string
resource
,
Route
::
Handler
handler
);
void
del
(
std
::
string
resource
,
Route
::
Handler
handler
);
void
del
(
std
::
string
resource
,
Route
::
Handler
handler
);
void
options
(
std
::
string
resource
,
Route
::
Handler
handler
);
void
options
(
std
::
string
resource
,
Route
::
Handler
handler
);
void
removeRoute
(
Http
::
Method
method
,
std
::
string
resource
);
void
addCustomHandler
(
Route
::
Handler
handler
);
void
addCustomHandler
(
Route
::
Handler
handler
);
void
addNotFoundHandler
(
Route
::
Handler
handler
);
void
addNotFoundHandler
(
Route
::
Handler
handler
);
inline
bool
hasNotFoundHandler
()
{
return
notFoundHandler
!=
nullptr
;
}
inline
bool
hasNotFoundHandler
()
{
return
notFoundHandler
!=
nullptr
;
}
void
invokeNotFoundHandler
(
const
Http
::
Request
&
req
,
Http
::
ResponseWriter
resp
)
const
;
void
invokeNotFoundHandler
(
const
Http
::
Request
&
req
,
Http
::
ResponseWriter
resp
)
const
;
Route
::
Status
route
(
const
Http
::
Request
&
request
,
Http
::
ResponseWriter
response
);
Status
route
(
const
Http
::
Request
&
request
,
Http
::
ResponseWriter
response
);
private:
private:
void
addRoute
(
Http
::
Method
method
,
std
::
string
resource
,
Route
::
Handler
handler
);
void
addRoute
(
Http
::
Method
method
,
std
::
string
resource
,
Route
::
Handler
handler
);
std
::
unordered_map
<
Http
::
Method
,
FragmentTreeNode
>
routes
;
std
::
unordered_map
<
Http
::
Method
,
std
::
vector
<
Route
>
>
routes
;
std
::
vector
<
Route
::
Handler
>
customHandlers
;
std
::
vector
<
Route
::
Handler
>
customHandlers
;
...
@@ -187,7 +192,6 @@ namespace Private {
...
@@ -187,7 +192,6 @@ namespace Private {
class
Request
:
public
Http
::
Request
{
class
Request
:
public
Http
::
Request
{
public:
public:
friend
class
FragmentTreeNode
;
friend
class
Router
;
friend
class
Router
;
bool
hasParam
(
const
std
::
string
&
name
)
const
;
bool
hasParam
(
const
std
::
string
&
name
)
const
;
...
@@ -198,7 +202,7 @@ public:
...
@@ -198,7 +202,7 @@ public:
private:
private:
explicit
Request
(
explicit
Request
(
const
Http
::
Request
&
request
,
const
Http
::
Request
&
request
,
std
::
vector
<
TypedParam
>&&
params
,
std
::
vector
<
TypedParam
>&&
params
,
std
::
vector
<
TypedParam
>&&
splats
);
std
::
vector
<
TypedParam
>&&
splats
);
...
@@ -215,7 +219,6 @@ namespace Routes {
...
@@ -215,7 +219,6 @@ namespace Routes {
void
Patch
(
Router
&
router
,
std
::
string
resource
,
Route
::
Handler
handler
);
void
Patch
(
Router
&
router
,
std
::
string
resource
,
Route
::
Handler
handler
);
void
Delete
(
Router
&
router
,
std
::
string
resource
,
Route
::
Handler
handler
);
void
Delete
(
Router
&
router
,
std
::
string
resource
,
Route
::
Handler
handler
);
void
Options
(
Router
&
router
,
std
::
string
resource
,
Route
::
Handler
handler
);
void
Options
(
Router
&
router
,
std
::
string
resource
,
Route
::
Handler
handler
);
void
Remove
(
Router
&
router
,
Http
::
Method
method
,
std
::
string
resource
);
void
NotFound
(
Router
&
router
,
Route
::
Handler
handler
);
void
NotFound
(
Router
&
router
,
Route
::
Handler
handler
);
...
...
include/pistache/string_view.h
deleted
100644 → 0
View file @
e898c622
#pragma once
#define CUSTOM_STRING_VIEW 1
#if __cplusplus >= 201703L
# if defined(__has_include)
# if __has_include(<string_view>)
# undef CUSTOM_STRING_VIEW
# include <string_view>
# elif __has_include(<experimental/string_view>)
# undef CUSTOM_STRING_VIEW
# include <experimental/string_view>
namespace
std
{
typedef
experimental
::
string_view
string_view
;
}
# endif
# endif
#endif
#ifdef CUSTOM_STRING_VIEW
#include <endian.h>
#include <string>
#include <stdexcept>
#include <cstring>
typedef
std
::
size_t
size_type
;
namespace
std
{
class
string_view
{
private:
const
char
*
data_
;
size_type
size_
;
public:
static
constexpr
size_type
npos
=
size_type
(
-
1
);
constexpr
string_view
()
noexcept
:
data_
(
nullptr
),
size_
(
0
)
{
}
constexpr
string_view
(
const
string_view
&
other
)
noexcept
=
default
;
constexpr
string_view
(
const
char
*
s
,
size_type
count
)
:
data_
(
s
),
size_
(
count
)
{
}
string_view
(
const
char
*
s
)
:
data_
(
s
),
size_
(
strlen
(
s
))
{
}
string_view
substr
(
size_type
pos
,
size_type
count
=
npos
)
const
{
if
(
pos
>
size_
)
{
throw
std
::
out_of_range
(
"Out of range."
);
}
size_type
rcount
=
std
::
min
(
count
,
size_
-
pos
);
return
{
data_
+
pos
,
rcount
};
}
constexpr
size_type
size
()
const
noexcept
{
return
size_
;
}
constexpr
size_type
length
()
const
noexcept
{
return
size_
;
}
constexpr
const
char
operator
[](
size_type
pos
)
const
{
return
data_
[
pos
];
}
constexpr
const
char
*
data
()
const
noexcept
{
return
data_
;
}
bool
operator
==
(
const
string_view
&
rhs
)
const
{
return
(
!
std
::
lexicographical_compare
(
data_
,
data_
+
size_
,
rhs
.
data_
,
rhs
.
data_
+
rhs
.
size_
)
&&
!
std
::
lexicographical_compare
(
rhs
.
data_
,
rhs
.
data_
+
rhs
.
size_
,
data_
,
data_
+
size_
));
}
string_view
&
operator
=
(
const
string_view
&
view
)
noexcept
=
default
;
size_type
find
(
string_view
v
,
size_type
pos
=
0
)
const
noexcept
{
for
(;
size_
-
pos
>=
v
.
size_
;
pos
++
)
{
string_view
compare
=
substr
(
pos
,
v
.
size_
);
if
(
v
==
compare
)
{
return
pos
;
}
}
return
npos
;
}
size_type
find
(
char
ch
,
size_type
pos
=
0
)
const
noexcept
{
return
find
(
string_view
(
&
ch
,
1
),
pos
);
}
size_type
find
(
const
char
*
s
,
size_type
pos
,
size_type
count
)
const
{
return
find
(
string_view
(
s
,
count
),
pos
);
}
size_type
find
(
const
char
*
s
,
size_type
pos
=
0
)
const
{
return
find
(
string_view
(
s
),
pos
);
}
size_type
rfind
(
string_view
v
,
size_type
pos
=
npos
)
const
noexcept
{
if
(
pos
>=
size_
)
{
pos
=
size_
-
v
.
size_
;
}
for
(;
pos
>=
0
&&
pos
!=
npos
;
pos
--
)
{
string_view
compare
=
substr
(
pos
,
v
.
size_
);
if
(
v
==
compare
)
{
return
pos
;
}
}
return
npos
;
}
size_type
rfind
(
char
c
,
size_type
pos
=
npos
)
const
noexcept
{
return
rfind
(
string_view
(
&
c
,
1
),
pos
);
}
size_type
rfind
(
const
char
*
s
,
size_type
pos
,
size_type
count
)
const
{
return
rfind
(
string_view
(
s
,
count
),
pos
);
}
size_type
rfind
(
const
char
*
s
,
size_type
pos
=
npos
)
const
{
return
rfind
(
string_view
(
s
),
pos
);
}
constexpr
bool
empty
()
const
noexcept
{
return
size_
==
0
;
}
};
template
<
>
struct
hash
<
string_view
>
{
//-----------------------------------------------------------------------------
// MurmurHash3 was written by Austin Appleby, and is placed in the public
// domain. The author hereby disclaims copyright to this source code.
private:
#ifdef __GNUC__
#define FORCE_INLINE __attribute__((always_inline)) inline
#else
#define FORCE_INLINE inline
#endif
static
FORCE_INLINE
std
::
uint32_t
Rotl32
(
const
std
::
uint32_t
x
,
const
std
::
int8_t
r
)
{
return
(
x
<<
r
)
|
(
x
>>
(
32
-
r
));
}
static
FORCE_INLINE
std
::
uint32_t
Getblock
(
const
std
::
uint32_t
*
p
,
const
int
i
)
{
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return
p
[
i
];
#else
std
::
uint32_t
temp_number
=
p
[
i
];
uint8_t
(
&
number
)[
4
]
=
*
reinterpret_cast
<
uint8_t
(
*
)[
4
]
>
(
&
temp_number
);
std
::
swap
(
number
[
0
],
number
[
3
]);
std
::
swap
(
number
[
1
],
number
[
2
]);
return
temp_number
;
#endif
}
static
FORCE_INLINE
uint32_t
fmix32
(
uint32_t
h
)
{
h
^=
h
>>
16
;
h
*=
0x85ebca6b
;
h
^=
h
>>
13
;
h
*=
0xc2b2ae35
;
h
^=
h
>>
16
;
return
h
;
}
public:
size_type
operator
()(
const
string_view
&
str
)
const
{
const
size_t
len
=
str
.
length
();
const
uint8_t
*
data
=
reinterpret_cast
<
const
uint8_t
*>
(
str
.
data
());
const
int
nblocks
=
static_cast
<
int
>
(
len
/
4
);
uint32_t
h1
=
0
;
// seed
const
uint32_t
c1
=
0xcc9e2d51U
;
const
uint32_t
c2
=
0x1b873593U
;
const
uint32_t
*
blocks
=
reinterpret_cast
<
const
uint32_t
*>
(
data
+
nblocks
*
4
);
for
(
auto
i
=
-
nblocks
;
i
;
++
i
)
{
uint32_t
k1
=
Getblock
(
blocks
,
i
);
k1
*=
c1
;
k1
=
Rotl32
(
k1
,
15
);
k1
*=
c2
;
h1
^=
k1
;
h1
=
Rotl32
(
h1
,
13
);
h1
=
h1
*
5
+
0xe6546b64
;
}
const
uint8_t
*
tail
=
data
+
nblocks
*
4
;
uint32_t
k1
=
0
;
switch
(
len
&
3
)
{
case
3
:
k1
^=
tail
[
2
]
<<
16
;
case
2
:
k1
^=
tail
[
1
]
<<
8
;
case
1
:
k1
^=
tail
[
0
];
k1
*=
c1
;
k1
=
Rotl32
(
k1
,
15
);
k1
*=
c2
;
h1
^=
k1
;
default:
break
;
}
h1
^=
len
;
h1
=
fmix32
(
h1
);
return
hash
<
int
>
()(
h1
);
}
#undef FORCE_INLINE
};
}
#endif
src/CMakeLists.txt
View file @
997c9cf5
...
@@ -9,7 +9,7 @@ set(SOURCE_FILES
...
@@ -9,7 +9,7 @@ set(SOURCE_FILES
${
SERVER_SOURCE_FILES
}
${
SERVER_SOURCE_FILES
}
${
CLIENT_SOURCE_FILES
}
${
CLIENT_SOURCE_FILES
}
${
INCLUDE_FILES
}
${
INCLUDE_FILES
}
../include/pistache/string_view.h
)
)
add_library
(
pistache
${
SOURCE_FILES
}
)
add_library
(
pistache
${
SOURCE_FILES
}
)
set_target_properties
(
pistache PROPERTIES
set_target_properties
(
pistache PROPERTIES
...
...
src/client/client.cc
View file @
997c9cf5
...
@@ -745,9 +745,15 @@ RequestBuilder::body(std::string&& val) {
...
@@ -745,9 +745,15 @@ RequestBuilder::body(std::string&& val) {
return
*
this
;
return
*
this
;
}
}
RequestBuilder
&
RequestBuilder
::
timeout
(
std
::
chrono
::
milliseconds
timeout
)
{
timeout_
=
timeout
;
return
*
this
;
}
Async
::
Promise
<
Response
>
Async
::
Promise
<
Response
>
RequestBuilder
::
send
()
{
RequestBuilder
::
send
()
{
return
client_
->
doRequest
(
std
::
move
(
request_
)
,
timeout_
);
return
client_
->
doRequest
(
request_
,
timeout_
);
}
}
Client
::
Options
&
Client
::
Options
&
...
...
src/server/router.cc
View file @
997c9cf5
...
@@ -25,7 +25,7 @@ Request::Request(
...
@@ -25,7 +25,7 @@ Request::Request(
bool
bool
Request
::
hasParam
(
const
std
::
string
&
name
)
const
{
Request
::
hasParam
(
const
std
::
string
&
name
)
const
{
auto
it
=
std
::
find_if
(
params_
.
begin
(),
params_
.
end
(),
[
&
](
const
TypedParam
&
param
)
{
auto
it
=
std
::
find_if
(
params_
.
begin
(),
params_
.
end
(),
[
&
](
const
TypedParam
&
param
)
{
return
param
.
name
()
==
std
::
string_view
(
name
.
data
(),
name
.
length
())
;
return
param
.
name
()
==
name
;
});
});
return
it
!=
std
::
end
(
params_
);
return
it
!=
std
::
end
(
params_
);
...
@@ -34,7 +34,7 @@ Request::hasParam(const std::string& name) const {
...
@@ -34,7 +34,7 @@ Request::hasParam(const std::string& name) const {
TypedParam
TypedParam
Request
::
param
(
const
std
::
string
&
name
)
const
{
Request
::
param
(
const
std
::
string
&
name
)
const
{
auto
it
=
std
::
find_if
(
params_
.
begin
(),
params_
.
end
(),
[
&
](
const
TypedParam
&
param
)
{
auto
it
=
std
::
find_if
(
params_
.
begin
(),
params_
.
end
(),
[
&
](
const
TypedParam
&
param
)
{
return
param
.
name
()
==
std
::
string_view
(
name
.
data
(),
name
.
length
())
;
return
param
.
name
()
==
name
;
});
});
if
(
it
==
std
::
end
(
params_
))
{
if
(
it
==
std
::
end
(
params_
))
{
...
@@ -58,220 +58,151 @@ Request::splat() const {
...
@@ -58,220 +58,151 @@ Request::splat() const {
return
splats_
;
return
splats_
;
}
}
FragmentTreeNode
::
FragmentTreeNode
()
:
resourceRef_
(),
fixed_
(),
param_
(),
optional_
(),
splat_
(),
route_
()
{
Route
::
Fragment
::
Fragment
(
std
::
string
value
)
std
::
shared_ptr
<
char
>
ptr
(
new
char
[
1
],
std
::
default_delete
<
char
[]
>
());
{
ptr
.
get
()[
0
]
=
'/'
;
if
(
value
.
empty
())
resourceRef_
.
swap
(
ptr
);
throw
std
::
runtime_error
(
"Invalid empty fragment"
);
}
FragmentTreeNode
::
FragmentTreeNode
(
const
std
::
shared_ptr
<
char
>
&
resourceReference
)
:
resourceRef_
(
resourceReference
),
fixed_
(),
param_
(),
optional_
(),
splat_
(),
route_
()
{
}
FragmentTreeNode
::
FragmentType
init
(
std
::
move
(
value
));
FragmentTreeNode
::
getFragmentType
(
const
std
::
string_view
&
fragment
)
{
}
auto
optpos
=
fragment
.
find
(
'?'
);
bool
if
(
fragment
[
0
]
==
':'
)
{
Route
::
Fragment
::
match
(
const
std
::
string
&
raw
)
const
{
if
(
optpos
!=
std
::
string_view
::
npos
)
{
if
(
flags
.
hasFlag
(
Flag
::
Fixed
))
{
if
(
optpos
!=
fragment
.
length
()
-
1
)
{
return
raw
==
value_
;
throw
std
::
runtime_error
(
"? should be at the end of the string"
);
}
return
FragmentType
::
Optional
;
}
return
FragmentType
::
Param
;
}
else
if
(
fragment
[
0
]
==
'*'
)
{
if
(
fragment
.
length
()
>
1
)
{
throw
std
::
runtime_error
(
"Invalid splat parameter"
);
}
return
FragmentType
::
Splat
;
}
}
else
if
(
flags
.
hasFlag
(
Flag
::
Parameter
)
||
flags
.
hasFlag
(
Flag
::
Splat
))
{
if
(
optpos
!=
std
::
string_view
::
npos
)
{
return
true
;
throw
std
::
runtime_error
(
"Only optional parameters are currently supported"
);
}
}
return
FragmentType
::
Fixed
;
return
false
;
}
bool
Route
::
Fragment
::
match
(
const
Fragment
&
other
)
const
{
return
match
(
other
.
value
());
}
}
void
void
FragmentTreeNode
::
addRoute
(
const
std
::
string_view
&
path
,
Route
::
Fragment
::
init
(
std
::
string
value
)
{
const
Route
::
Handler
&
handler
,
if
(
value
[
0
]
==
':'
)
const
std
::
shared_ptr
<
char
>
&
resourceReference
)
{
flags
.
setFlag
(
Flag
::
Parameter
);
if
(
path
.
length
()
==
0
)
{
else
if
(
value
[
0
]
==
'*'
)
{
throw
std
::
runtime_error
(
"Invalid zero-length URL."
);
if
(
value
.
size
()
>
1
)
throw
std
::
runtime_error
(
"Invalid splat parameter"
);
flags
.
setFlag
(
Flag
::
Splat
);
}
}
std
::
string_view
currPath
;
else
{
if
(
path
.
rfind
(
'/'
)
==
path
.
length
()
-
1
)
{
flags
.
setFlag
(
Flag
::
Fixed
);
currPath
=
path
.
substr
(
0
,
path
.
length
()
-
1
);
}
else
{
currPath
=
path
;
}
}
if
(
currPath
.
find
(
'/'
)
==
std
::
string_view
::
npos
)
{
// current leaf requested
if
(
route_
!=
nullptr
)
{
throw
std
::
runtime_error
(
"Requested route already exist."
);
}
else
{
route_
=
std
::
make_shared
<
Route
>
(
handler
);
}
}
else
{
// recursion to correct descendant
const
auto
pos
=
currPath
.
find
(
'/'
,
1
);
const
auto
next
=
(
pos
==
std
::
string_view
::
npos
)
?
currPath
.
substr
(
1
)
:
currPath
.
substr
(
pos
);
// complete lower path
auto
mid
=
(
pos
==
std
::
string_view
::
npos
)
?
currPath
.
substr
(
1
)
:
currPath
.
substr
(
1
,
currPath
.
find
(
'/'
,
pos
)
-
1
);
// middle resource name
std
::
unordered_map
<
std
::
string_view
,
std
::
shared_ptr
<
FragmentTreeNode
>>
*
collection
;
const
auto
fragmentType
=
getFragmentType
(
mid
);
switch
(
fragmentType
)
{
case
FragmentType
:
:
Fixed
:
collection
=
&
fixed_
;
break
;
case
FragmentType
:
:
Param
:
collection
=
&
param_
;
break
;
case
FragmentType
:
:
Optional
:
mid
=
mid
.
substr
(
0
,
mid
.
length
()
-
1
);
collection
=
&
optional_
;
break
;
case
FragmentType
:
:
Splat
:
if
(
splat_
==
nullptr
)
{
splat_
=
std
::
make_shared
<
FragmentTreeNode
>
(
resourceReference
);
}
splat_
->
addRoute
(
next
,
handler
,
resourceReference
);
return
;
}
// Let's search for any '?'
auto
pos
=
value
.
find
(
'?'
);
if
(
pos
!=
std
::
string
::
npos
)
{
if
(
value
[
0
]
!=
':'
)
throw
std
::
runtime_error
(
"Only optional parameters are currently supported"
);
if
(
collection
->
count
(
mid
)
==
0
)
{
// direct subpath does not exist
if
(
pos
!=
value
.
size
()
-
1
)
collection
->
insert
(
std
::
make_pair
(
mid
,
std
::
make_shared
<
FragmentTreeNode
>
(
resourceReference
)));
throw
std
::
runtime_error
(
"? should be at the end of the string"
);
}
collection
->
at
(
mid
)
->
addRoute
(
next
,
handler
,
resourceReference
);
}
}
bool
Pistache
::
Rest
::
FragmentTreeNode
::
removeRoute
(
const
std
::
string_view
&
path
)
{
value_
=
value
.
substr
(
0
,
pos
);
if
(
path
.
length
()
==
0
)
{
flags
.
setFlag
(
Flag
::
Optional
);
throw
std
::
runtime_error
(
"Invalid zero-length URL."
);
}
std
::
string_view
currPath
;
if
(
path
.
rfind
(
'/'
)
==
path
.
length
()
-
1
)
{
currPath
=
path
.
substr
(
0
,
path
.
length
());
}
else
{
}
else
{
currPath
=
path
;
value_
=
std
::
move
(
value
)
;
}
}
if
(
currPath
.
find
(
'/'
)
==
std
::
string_view
::
npos
)
{
// current leaf requested
route_
.
reset
();
}
else
{
// recursion to correct descendant
const
auto
pos
=
currPath
.
find
(
'/'
,
1
);
const
auto
next
=
(
pos
==
std
::
string_view
::
npos
)
?
currPath
.
substr
(
1
)
:
currPath
.
substr
(
pos
);
// complete lower path
auto
mid
=
(
pos
==
std
::
string_view
::
npos
)
?
currPath
.
substr
(
1
)
:
currPath
.
substr
(
1
,
currPath
.
find
(
'/'
,
pos
)
-
1
);
// middle resource name
std
::
unordered_map
<
std
::
string_view
,
std
::
shared_ptr
<
FragmentTreeNode
>>
*
collection
;
auto
fragmentType
=
getFragmentType
(
mid
);
switch
(
fragmentType
)
{
case
FragmentType
:
:
Fixed
:
collection
=
&
fixed_
;
break
;
case
FragmentType
:
:
Param
:
collection
=
&
param_
;
break
;
case
FragmentType
:
:
Optional
:
mid
=
mid
.
substr
(
0
,
mid
.
length
()
-
1
);
collection
=
&
optional_
;
break
;
case
FragmentType
:
:
Splat
:
return
splat_
->
removeRoute
(
next
);
}
try
{
checkInvariant
();
const
bool
removable
=
collection
->
at
(
mid
)
->
removeRoute
(
next
);
}
if
(
removable
)
{
collection
->
erase
(
mid
);
void
}
Route
::
Fragment
::
checkInvariant
()
const
{
}
catch
(
const
std
::
out_of_range
&
)
{
auto
check
=
[
this
](
std
::
initializer_list
<
Flag
>
exclusiveFlags
)
{
throw
std
::
runtime_error
(
"Requested does not exist."
);
for
(
auto
flag
:
exclusiveFlags
)
{
if
(
!
flags
.
hasFlag
(
flag
))
return
;
}
}
}
return
fixed_
.
empty
()
&&
param_
.
empty
()
&&
throw
std
::
logic_error
(
optional_
.
empty
()
&&
splat_
==
nullptr
&&
route_
==
nullptr
;
std
::
string
(
"Invariant violated: invalid combination of flags for fragment "
)
+
value_
);
};
check
({
Flag
::
Fixed
,
Flag
::
Optional
});
check
({
Flag
::
Fixed
,
Flag
::
Parameter
});
check
({
Flag
::
Splat
,
Flag
::
Fixed
});
check
({
Flag
::
Splat
,
Flag
::
Optional
});
check
({
Flag
::
Splat
,
Flag
::
Parameter
});
}
}
std
::
tuple
<
std
::
shared_ptr
<
Route
>
,
std
::
vector
<
TypedParam
>
,
std
::
vector
<
TypedParam
>>
std
::
vector
<
Route
::
Fragment
>
Pistache
::
Rest
::
FragmentTreeNode
::
findRoute
(
Route
::
Fragment
::
fromUrl
(
const
std
::
string
&
url
)
{
const
std
::
string_view
&
path
,
std
::
vector
<
Route
::
Fragment
>
fragments
;
std
::
vector
<
TypedParam
>
&
params
,
std
::
vector
<
TypedParam
>
&
splats
)
const
{
std
::
istringstream
iss
(
url
);
if
(
path
.
length
()
==
0
)
{
std
::
string
p
;
throw
std
::
runtime_error
(
"Invalid zero-length URL."
);
}
while
(
std
::
getline
(
iss
,
p
,
'/'
))
{
std
::
string_view
currPath
;
if
(
p
.
empty
())
continue
;
if
(
path
.
rfind
(
'/'
)
==
path
.
length
()
-
1
)
{
currPath
=
path
.
substr
(
0
,
path
.
length
()
-
1
);
fragments
.
push_back
(
Fragment
(
std
::
move
(
p
)));
}
else
{
currPath
=
path
;
}
}
if
(
currPath
.
find
(
'/'
)
==
std
::
string_view
::
npos
)
{
// current leaf requested, or empty final optional
// in case of more than one optional at this point, as it is an ambuiguity,
// it is resolved by using the first optional
if
(
!
optional_
.
empty
())
{
auto
optional
=
optional_
.
begin
();
params
.
emplace_back
(
optional
->
first
,
std
::
string_view
());
return
optional
->
second
->
findRoute
(
currPath
,
params
,
splats
);
}
else
return
std
::
make_tuple
(
route_
,
std
::
move
(
params
),
std
::
move
(
splats
));
}
else
{
// recursion to correct descendant
const
auto
pos
=
currPath
.
find
(
'/'
,
1
);
const
auto
next
=
(
pos
==
std
::
string_view
::
npos
)
?
currPath
.
substr
(
1
)
:
currPath
.
substr
(
pos
);
// complete lower path
auto
mid
=
(
pos
==
std
::
string_view
::
npos
)
?
currPath
.
substr
(
1
)
:
currPath
.
substr
(
1
,
currPath
.
find
(
'/'
,
pos
)
-
1
);
// middle resource name
if
(
fixed_
.
count
(
mid
)
!=
0
)
{
auto
result
=
fixed_
.
at
(
mid
)
->
findRoute
(
next
,
params
,
splats
);
auto
route
=
std
::
get
<
0
>
(
result
);
if
(
route
!=
nullptr
)
return
result
;
}
for
(
const
auto
&
param
:
param_
)
{
return
fragments
;
params
.
emplace_back
(
param
.
first
,
mid
);
}
auto
result
=
param
.
second
->
findRoute
(
next
,
params
,
splats
);
auto
route
=
std
::
get
<
0
>
(
result
);
bool
if
(
route
!=
nullptr
)
return
result
;
Route
::
Fragment
::
isParameter
()
const
{
else
params
.
pop_back
(
);
return
flags
.
hasFlag
(
Flag
::
Parameter
);
}
}
for
(
const
auto
&
optional
:
optional_
)
{
bool
params
.
emplace_back
(
optional
.
first
,
mid
);
Route
::
Fragment
::
isOptional
()
const
{
auto
result
=
optional
.
second
->
findRoute
(
next
,
params
,
splats
);
return
isParameter
()
&&
flags
.
hasFlag
(
Flag
::
Optional
);
}
auto
route
=
std
::
get
<
0
>
(
result
);
bool
if
(
route
!=
nullptr
)
return
result
;
Route
::
Fragment
::
isSplat
()
const
{
else
{
return
flags
.
hasFlag
(
Flag
::
Splat
);
params
.
pop_back
();
}
// try empty optional
std
::
tuple
<
bool
,
std
::
vector
<
TypedParam
>
,
std
::
vector
<
TypedParam
>>
params
.
emplace_back
(
optional
.
first
,
std
::
string_view
());
Route
::
match
(
const
Http
::
Request
&
req
)
const
result
=
optional
.
second
->
findRoute
(
currPath
,
params
,
splats
);
{
return
match
(
req
.
resource
());
}
route
=
std
::
get
<
0
>
(
result
);
std
::
tuple
<
bool
,
std
::
vector
<
TypedParam
>
,
std
::
vector
<
TypedParam
>>
if
(
route
!=
nullptr
)
return
result
;
Route
::
match
(
const
std
::
string
&
req
)
const
else
params
.
pop_back
();
{
}
static
const
auto
NoMatch
=
std
::
make_tuple
(
false
,
std
::
vector
<
TypedParam
>
(),
std
::
vector
<
TypedParam
>
());
auto
reqFragments
=
Fragment
::
fromUrl
(
req
);
if
(
reqFragments
.
size
()
>
fragments_
.
size
())
return
NoMatch
;
std
::
vector
<
TypedParam
>
params
;
std
::
vector
<
TypedParam
>
splats
;
for
(
std
::
vector
<
Fragment
>::
size_type
i
=
0
;
i
<
fragments_
.
size
();
++
i
)
{
const
auto
&
fragment
=
fragments_
[
i
];
if
(
i
>=
reqFragments
.
size
())
{
if
(
fragment
.
isOptional
())
continue
;
return
NoMatch
;
}
}
if
(
splat_
!=
nullptr
)
{
const
auto
&
reqFragment
=
reqFragments
[
i
];
splats
.
emplace_back
(
mid
,
mid
);
if
(
!
fragment
.
match
(
reqFragment
))
auto
result
=
splat_
->
findRoute
(
next
,
params
,
splats
)
;
return
NoMatch
;
auto
route
=
std
::
get
<
0
>
(
result
);
if
(
fragment
.
isParameter
())
{
if
(
route
!=
nullptr
)
return
result
;
params
.
push_back
(
TypedParam
(
fragment
.
value
(),
reqFragment
.
value
()));
else
splats
.
pop_back
();
}
else
if
(
fragment
.
isSplat
())
{
splats
.
push_back
(
TypedParam
(
reqFragment
.
value
(),
reqFragment
.
value
()));
}
}
return
std
::
make_tuple
(
nullptr
,
std
::
vector
<
TypedParam
>
(),
std
::
vector
<
TypedParam
>
());
}
}
}
std
::
tuple
<
std
::
shared_ptr
<
Route
>
,
std
::
vector
<
TypedParam
>
,
std
::
vector
<
TypedParam
>>
return
make_tuple
(
true
,
std
::
move
(
params
),
std
::
move
(
splats
));
Pistache
::
Rest
::
FragmentTreeNode
::
findRoute
(
const
std
::
string_view
&
path
)
const
{
std
::
vector
<
TypedParam
>
params
;
std
::
vector
<
TypedParam
>
splats
;
return
findRoute
(
path
,
params
,
splats
);
}
}
namespace
Private
{
namespace
Private
{
...
@@ -290,7 +221,7 @@ RouterHandler::onRequest(
...
@@ -290,7 +221,7 @@ RouterHandler::onRequest(
auto
result
=
router
.
route
(
req
,
std
::
move
(
resp
));
auto
result
=
router
.
route
(
req
,
std
::
move
(
resp
));
/* @Feature: add support for a custom NotFound handler */
/* @Feature: add support for a custom NotFound handler */
if
(
result
==
Route
::
Status
::
NotFound
)
if
(
result
==
Route
r
::
Status
::
NotFound
)
{
{
if
(
router
.
hasNotFoundHandler
())
if
(
router
.
hasNotFoundHandler
())
{
{
...
@@ -301,6 +232,7 @@ RouterHandler::onRequest(
...
@@ -301,6 +232,7 @@ RouterHandler::onRequest(
response
.
send
(
Http
::
Code
::
Not_Found
,
"Could not find a matching route"
);
response
.
send
(
Http
::
Code
::
Not_Found
,
"Could not find a matching route"
);
}
}
}
}
}
// namespace Private
}
// namespace Private
Router
Router
...
@@ -362,12 +294,6 @@ Router::options(std::string resource, Route::Handler handler) {
...
@@ -362,12 +294,6 @@ Router::options(std::string resource, Route::Handler handler) {
addRoute
(
Http
::
Method
::
Options
,
std
::
move
(
resource
),
std
::
move
(
handler
));
addRoute
(
Http
::
Method
::
Options
,
std
::
move
(
resource
),
std
::
move
(
handler
));
}
}
void
Router
::
removeRoute
(
Http
::
Method
method
,
std
::
string
resource
)
{
auto
&
r
=
routes
[
method
];
r
.
removeRoute
(
std
::
string_view
(
resource
.
data
(),
resource
.
length
()));
}
void
void
Router
::
addCustomHandler
(
Route
::
Handler
handler
)
{
Router
::
addCustomHandler
(
Route
::
Handler
handler
)
{
customHandlers
.
push_back
(
std
::
move
(
handler
));
customHandlers
.
push_back
(
std
::
move
(
handler
));
...
@@ -384,39 +310,37 @@ Router::invokeNotFoundHandler(const Http::Request &req, Http::ResponseWriter res
...
@@ -384,39 +310,37 @@ Router::invokeNotFoundHandler(const Http::Request &req, Http::ResponseWriter res
notFoundHandler
(
Rest
::
Request
(
std
::
move
(
req
),
std
::
vector
<
TypedParam
>
(),
std
::
vector
<
TypedParam
>
()),
std
::
move
(
resp
));
notFoundHandler
(
Rest
::
Request
(
std
::
move
(
req
),
std
::
vector
<
TypedParam
>
(),
std
::
vector
<
TypedParam
>
()),
std
::
move
(
resp
));
}
}
Route
::
Status
Route
r
::
Status
Router
::
route
(
const
Http
::
Request
&
req
,
Http
::
ResponseWriter
response
)
{
Router
::
route
(
const
Http
::
Request
&
req
,
Http
::
ResponseWriter
response
)
{
auto
&
r
=
routes
[
req
.
method
()];
auto
&
r
=
routes
[
req
.
method
()];
for
(
const
auto
&
route
:
r
)
{
std
::
string_view
path
{
req
.
resource
().
data
(),
req
.
resource
().
size
()};
bool
match
;
auto
result
=
r
.
findRoute
(
path
);
std
::
vector
<
TypedParam
>
params
;
std
::
vector
<
TypedParam
>
splats
;
auto
route
=
std
::
get
<
0
>
(
result
);
std
::
tie
(
match
,
params
,
splats
)
=
route
.
match
(
req
);
if
(
route
!=
nullptr
)
{
if
(
match
)
{
auto
params
=
std
::
get
<
1
>
(
result
);
route
.
invokeHandler
(
Request
(
req
,
std
::
move
(
params
),
std
::
move
(
splats
)),
std
::
move
(
response
));
auto
splats
=
std
::
get
<
2
>
(
result
);
return
Router
::
Status
::
Match
;
route
->
invokeHandler
(
Request
(
req
,
std
::
move
(
params
),
std
::
move
(
splats
)),
std
::
move
(
response
));
}
return
Route
::
Status
::
Match
;
}
}
for
(
const
auto
&
handler
:
customHandlers
)
{
for
(
const
auto
&
handler
:
customHandlers
)
{
auto
resp
=
response
.
clone
();
auto
resp
=
response
.
clone
();
auto
handler1
=
handler
(
Request
(
req
,
std
::
vector
<
TypedParam
>
(),
std
::
vector
<
TypedParam
>
()),
std
::
move
(
resp
));
auto
result
=
handler
(
Request
(
req
,
std
::
vector
<
TypedParam
>
(),
std
::
vector
<
TypedParam
>
()),
std
::
move
(
resp
));
if
(
handler1
==
Route
::
Result
::
Ok
)
return
Route
::
Status
::
Match
;
if
(
result
==
Route
::
Result
::
Ok
)
return
Router
::
Status
::
Match
;
}
}
if
(
hasNotFoundHandler
())
invokeNotFoundHandler
(
req
,
std
::
move
(
response
));
return
Router
::
Status
::
NotFound
;
return
Route
::
Status
::
NotFound
;
}
}
void
void
Router
::
addRoute
(
Http
::
Method
method
,
std
::
string
resource
,
Route
::
Handler
handler
)
Router
::
addRoute
(
Http
::
Method
method
,
std
::
string
resource
,
Route
::
Handler
handler
)
{
{
auto
&
r
=
routes
[
method
];
auto
&
r
=
routes
[
method
];
std
::
shared_ptr
<
char
>
ptr
(
new
char
[
resource
.
length
()],
std
::
default_delete
<
char
[]
>
());
r
.
push_back
(
Route
(
std
::
move
(
resource
),
method
,
std
::
move
(
handler
)));
memcpy
(
ptr
.
get
(),
resource
.
data
(),
resource
.
length
());
r
.
addRoute
(
std
::
string_view
(
ptr
.
get
(),
resource
.
length
()),
handler
,
ptr
);
}
}
namespace
Routes
{
namespace
Routes
{
void
Get
(
Router
&
router
,
std
::
string
resource
,
Route
::
Handler
handler
)
{
void
Get
(
Router
&
router
,
std
::
string
resource
,
Route
::
Handler
handler
)
{
...
@@ -443,10 +367,6 @@ void Options(Router& router, std::string resource, Route::Handler handler) {
...
@@ -443,10 +367,6 @@ void Options(Router& router, std::string resource, Route::Handler handler) {
router
.
options
(
std
::
move
(
resource
),
std
::
move
(
handler
));
router
.
options
(
std
::
move
(
resource
),
std
::
move
(
handler
));
}
}
void
Remove
(
Router
&
router
,
Http
::
Method
method
,
std
::
string
resource
)
{
router
.
removeRoute
(
method
,
std
::
move
(
resource
));
}
void
NotFound
(
Router
&
router
,
Route
::
Handler
handler
)
{
void
NotFound
(
Router
&
router
,
Route
::
Handler
handler
)
{
router
.
addNotFoundHandler
(
std
::
move
(
handler
));
router
.
addNotFoundHandler
(
std
::
move
(
handler
));
}
}
...
...
tests/CMakeLists.txt
View file @
997c9cf5
...
@@ -16,4 +16,4 @@ pistache_test(router_test)
...
@@ -16,4 +16,4 @@ 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
)
pistache_test
(
http_parsing_test
)
pistache_test
(
string_view
_test
)
pistache_test
(
http_client
_test
)
tests/http_client_test.cc
0 → 100644
View file @
997c9cf5
#include "gtest/gtest.h"
#include <pistache/http.h>
#include <pistache/client.h>
#include <pistache/endpoint.h>
#include <chrono>
using
namespace
Pistache
;
struct
HelloHandler
:
public
Http
::
Handler
{
HTTP_PROTOTYPE
(
HelloHandler
)
void
onRequest
(
const
Http
::
Request
&
request
,
Http
::
ResponseWriter
writer
)
{
writer
.
send
(
Http
::
Code
::
Ok
,
"Hello, World!"
);
}
};
TEST
(
request_builder
,
multiple_send_test
)
{
const
std
::
string
address
=
"localhost:9080"
;
Http
::
Endpoint
server
(
address
);
auto
server_opts
=
Http
::
Endpoint
::
options
().
threads
(
1
);
server
.
init
(
server_opts
);
server
.
setHandler
(
Http
::
make_handler
<
HelloHandler
>
());
server
.
serveThreaded
();
Http
::
Client
client
;
auto
client_opts
=
Http
::
Client
::
options
().
threads
(
1
).
maxConnectionsPerHost
(
1
);
client
.
init
(
client_opts
);
std
::
vector
<
Async
::
Promise
<
Http
::
Response
>>
responses
;
const
int
RESPONSE_SIZE
=
3
;
int
response_counter
=
0
;
auto
rb
=
client
.
get
(
address
);
for
(
int
i
=
0
;
i
<
RESPONSE_SIZE
;
++
i
)
{
auto
response
=
rb
.
send
();
response
.
then
([
&
](
Http
::
Response
rsp
)
{
if
(
rsp
.
code
()
==
Http
::
Code
::
Ok
)
++
response_counter
;
},
Async
::
IgnoreException
);
responses
.
push_back
(
std
::
move
(
response
));
}
auto
sync
=
Async
::
whenAll
(
responses
.
begin
(),
responses
.
end
());
Async
::
Barrier
<
std
::
vector
<
Http
::
Response
>>
barrier
(
sync
);
barrier
.
wait_for
(
std
::
chrono
::
seconds
(
5
));
server
.
shutdown
();
client
.
shutdown
();
ASSERT_TRUE
(
response_counter
==
RESPONSE_SIZE
);
}
\ No newline at end of file
tests/router_test.cc
View file @
997c9cf5
...
@@ -12,25 +12,23 @@
...
@@ -12,25 +12,23 @@
using
namespace
Pistache
;
using
namespace
Pistache
;
bool
match
(
const
Rest
::
FragmentTreeNode
&
routes
,
const
std
::
string
&
req
)
{
bool
match
(
const
Rest
::
Route
&
route
,
const
std
::
string
&
req
)
{
std
::
shared_ptr
<
Rest
::
Route
>
route
;
return
std
::
get
<
0
>
(
route
.
match
(
req
));
std
::
tie
(
route
,
std
::
ignore
,
std
::
ignore
)
=
routes
.
findRoute
({
req
.
data
(),
req
.
size
()});
return
route
!=
nullptr
;
}
}
bool
matchParams
(
bool
matchParams
(
const
Rest
::
FragmentTreeNode
&
routes
,
const
std
::
string
&
req
,
const
Rest
::
Route
&
route
,
const
std
::
string
&
req
,
std
::
initializer_list
<
std
::
pair
<
std
::
string
,
std
::
string
>>
list
)
std
::
initializer_list
<
std
::
pair
<
std
::
string
,
std
::
string
>>
list
)
{
{
std
::
shared_ptr
<
Rest
::
Route
>
route
;
bool
ok
;
std
::
vector
<
Rest
::
TypedParam
>
params
;
std
::
vector
<
Rest
::
TypedParam
>
params
;
std
::
tie
(
route
,
params
,
std
::
ignore
)
=
routes
.
findRoute
({
req
.
data
(),
req
.
size
()}
);
std
::
tie
(
ok
,
params
,
std
::
ignore
)
=
route
.
match
(
req
);
if
(
route
==
nullptr
)
return
false
;
if
(
!
ok
)
return
false
;
for
(
const
auto
&
p
:
list
)
{
for
(
const
auto
&
p
:
list
)
{
auto
it
=
std
::
find_if
(
params
.
begin
(),
params
.
end
(),
[
&
](
const
Rest
::
TypedParam
&
param
)
{
auto
it
=
std
::
find_if
(
params
.
begin
(),
params
.
end
(),
[
&
](
const
Rest
::
TypedParam
&
param
)
{
return
param
.
name
()
==
std
::
string_view
(
p
.
first
.
data
(),
p
.
first
.
size
())
;
return
param
.
name
()
==
p
.
first
;
});
});
if
(
it
==
std
::
end
(
params
))
{
if
(
it
==
std
::
end
(
params
))
{
std
::
cerr
<<
"Did not find param '"
<<
p
.
first
<<
"'"
<<
std
::
endl
;
std
::
cerr
<<
"Did not find param '"
<<
p
.
first
<<
"'"
<<
std
::
endl
;
...
@@ -47,14 +45,14 @@ bool matchParams(
...
@@ -47,14 +45,14 @@ bool matchParams(
}
}
bool
matchSplat
(
bool
matchSplat
(
const
Rest
::
FragmentTreeNode
&
routes
,
const
std
::
string
&
req
,
const
Rest
::
Route
&
route
,
const
std
::
string
&
req
,
std
::
initializer_list
<
std
::
string
>
list
)
std
::
initializer_list
<
std
::
string
>
list
)
{
{
std
::
shared_ptr
<
Rest
::
Route
>
route
;
bool
ok
;
std
::
vector
<
Rest
::
TypedParam
>
splats
;
std
::
vector
<
Rest
::
TypedParam
>
splats
;
std
::
tie
(
route
,
std
::
ignore
,
splats
)
=
routes
.
findRoute
({
req
.
data
(),
req
.
size
()}
);
std
::
tie
(
ok
,
std
::
ignore
,
splats
)
=
route
.
match
(
req
);
if
(
route
==
nullptr
)
return
false
;
if
(
!
ok
)
return
false
;
if
(
list
.
size
()
!=
splats
.
size
())
{
if
(
list
.
size
()
!=
splats
.
size
())
{
std
::
cerr
<<
"Size mismatch ("
<<
list
.
size
()
<<
" != "
<<
splats
.
size
()
<<
")"
std
::
cerr
<<
"Size mismatch ("
<<
list
.
size
()
<<
" != "
<<
splats
.
size
()
<<
")"
...
@@ -76,57 +74,55 @@ bool matchSplat(
...
@@ -76,57 +74,55 @@ bool matchSplat(
return
true
;
return
true
;
}
}
TEST
(
router_test
,
test_fixed_routes
)
{
Rest
::
Route
Rest
::
FragmentTreeNode
routes
;
makeRoute
(
std
::
string
value
)
{
routes
.
addRoute
(
std
::
string_view
(
"/v1/hello"
),
nullptr
,
nullptr
);
auto
noop
=
[](
const
Http
::
Request
&
,
Http
::
Response
)
{
return
Rest
::
Route
::
Result
::
Ok
;
};
return
Rest
::
Route
(
value
,
Http
::
Method
::
Get
,
noop
);
}
ASSERT_TRUE
(
match
(
routes
,
"/v1/hello"
));
TEST
(
router_test
,
test_fixed_routes
)
{
ASSERT_FALSE
(
match
(
routes
,
"/v2/hello"
));
auto
r1
=
makeRoute
(
"/v1/hello"
);
ASSERT_FALSE
(
match
(
routes
,
"/v1/hell0"
));
ASSERT_TRUE
(
match
(
r1
,
"/v1/hello"
));
ASSERT_FALSE
(
match
(
r1
,
"/v2/hello"
));
ASSERT_FALSE
(
match
(
r1
,
"/v1/hell0"
));
routes
.
addRoute
(
std
::
string_view
(
"/a/b/c"
),
nullptr
,
nullptr
);
auto
r2
=
makeRoute
(
"/a/b/c"
);
ASSERT_TRUE
(
match
(
r
outes
,
"/a/b/c"
));
ASSERT_TRUE
(
match
(
r
2
,
"/a/b/c"
));
}
}
TEST
(
router_test
,
test_parameters
)
{
TEST
(
router_test
,
test_parameters
)
{
Rest
::
FragmentTreeNode
routes
;
auto
r1
=
makeRoute
(
"/v1/hello/:name"
);
routes
.
addRoute
(
std
::
string_view
(
"/v1/hello/:name"
),
nullptr
,
nullptr
);
ASSERT_TRUE
(
matchParams
(
r1
,
"/v1/hello/joe"
,
{
ASSERT_TRUE
(
matchParams
(
routes
,
"/v1/hello/joe"
,
{
{
":name"
,
"joe"
}
{
":name"
,
"joe"
}
}));
}));
routes
.
addRoute
(
std
::
string_view
(
"/greetings/:from/:to"
),
nullptr
,
nullptr
);
auto
r2
=
makeRoute
(
"/greetings/:from/:to"
);
ASSERT_TRUE
(
matchParams
(
r
outes
,
"/greetings/foo/bar"
,
{
ASSERT_TRUE
(
matchParams
(
r
2
,
"/greetings/foo/bar"
,
{
{
":from"
,
"foo"
},
{
":from"
,
"foo"
},
{
":to"
,
"bar"
}
{
":to"
,
"bar"
}
}));
}));
}
}
TEST
(
router_test
,
test_optional
)
{
TEST
(
router_test
,
test_optional
)
{
Rest
::
FragmentTreeNode
routes
;
auto
r1
=
makeRoute
(
"/get/:key?"
);
routes
.
addRoute
(
std
::
string_view
(
"/get/:key?"
),
nullptr
,
nullptr
);
ASSERT_TRUE
(
match
(
r1
,
"/get"
));
ASSERT_TRUE
(
match
(
r1
,
"/get/"
));
ASSERT_TRUE
(
match
(
routes
,
"/get"
));
ASSERT_TRUE
(
matchParams
(
r1
,
"/get/foo"
,
{
ASSERT_TRUE
(
match
(
routes
,
"/get/"
));
ASSERT_TRUE
(
matchParams
(
routes
,
"/get/foo"
,
{
{
":key"
,
"foo"
}
{
":key"
,
"foo"
}
}));
}));
ASSERT_TRUE
(
matchParams
(
r
outes
,
"/get/foo/"
,
{
ASSERT_TRUE
(
matchParams
(
r
1
,
"/get/foo/"
,
{
{
":key"
,
"foo"
}
{
":key"
,
"foo"
}
}));
}));
ASSERT_FALSE
(
match
(
r
outes
,
"/get/foo/bar"
));
ASSERT_FALSE
(
match
(
r
1
,
"/get/foo/bar"
));
}
}
TEST
(
router_test
,
test_splat
)
{
TEST
(
router_test
,
test_splat
)
{
Rest
::
FragmentTreeNode
routes
;
auto
r1
=
makeRoute
(
"/say/*/to/*"
);
routes
.
addRoute
(
std
::
string_view
(
"/say/*/to/*"
),
nullptr
,
nullptr
);
ASSERT_TRUE
(
match
(
r1
,
"/say/hello/to/user"
));
ASSERT_FALSE
(
match
(
r1
,
"/say/hello/to"
));
ASSERT_FALSE
(
match
(
r1
,
"/say/hello/to/user/please"
));
ASSERT_TRUE
(
match
(
routes
,
"/say/hello/to/user"
));
ASSERT_TRUE
(
matchSplat
(
r1
,
"/say/hello/to/user"
,
{
"hello"
,
"user"
}));
ASSERT_FALSE
(
match
(
routes
,
"/say/hello/to"
));
ASSERT_TRUE
(
matchSplat
(
r1
,
"/say/hello/to/user/"
,
{
"hello"
,
"user"
}));
ASSERT_FALSE
(
match
(
routes
,
"/say/hello/to/user/please"
));
}
ASSERT_TRUE
(
matchSplat
(
routes
,
"/say/hello/to/user"
,
{
"hello"
,
"user"
}));
ASSERT_TRUE
(
matchSplat
(
routes
,
"/say/hello/to/user/"
,
{
"hello"
,
"user"
}));
}
\ No newline at end of file
tests/string_view_test.cc
deleted
100644 → 0
View file @
e898c622
#include "gtest/gtest.h"
#include <pistache/string_view.h>
TEST
(
string_view_test
,
substr_test
)
{
std
::
string_view
orig
(
"test"
);
std
::
string_view
targ
(
"est"
);
std
::
string_view
sub
=
orig
.
substr
(
1
);
ASSERT_EQ
(
sub
,
targ
);
sub
=
orig
.
substr
(
1
,
10
);
ASSERT_EQ
(
sub
,
targ
);
ASSERT_THROW
(
orig
.
substr
(
6
),
std
::
out_of_range
);
}
TEST
(
string_view_test
,
find_test
)
{
std
::
string_view
orig
(
"test"
);
std
::
string_view
find
(
"est"
);
ASSERT_EQ
(
orig
.
find
(
find
),
1
);
ASSERT_EQ
(
orig
.
find
(
find
,
1
),
1
);
ASSERT_EQ
(
orig
.
find
(
find
,
2
),
std
::
size_t
(
-
1
));
ASSERT_EQ
(
orig
.
find
(
'e'
),
1
);
ASSERT_EQ
(
orig
.
find
(
'e'
,
1
),
1
);
ASSERT_EQ
(
orig
.
find
(
'e'
,
2
),
std
::
size_t
(
-
1
));
ASSERT_EQ
(
orig
.
find
(
'1'
),
std
::
size_t
(
-
1
));
ASSERT_EQ
(
orig
.
find
(
"est"
),
1
);
ASSERT_EQ
(
orig
.
find
(
"est"
,
1
),
1
);
ASSERT_EQ
(
orig
.
find
(
"est"
,
1
,
2
),
1
);
ASSERT_EQ
(
orig
.
find
(
"set"
),
std
::
size_t
(
-
1
));
ASSERT_EQ
(
orig
.
find
(
"est"
,
2
),
std
::
size_t
(
-
1
));
ASSERT_EQ
(
orig
.
find
(
"est"
,
2
,
2
),
std
::
size_t
(
-
1
));
}
TEST
(
string_view_test
,
rfind_test
)
{
std
::
string_view
orig
(
"test"
);
std
::
string_view
find
(
"est"
);
ASSERT_EQ
(
orig
.
rfind
(
find
),
1
);
ASSERT_EQ
(
orig
.
rfind
(
find
,
1
),
1
);
ASSERT_EQ
(
orig
.
rfind
(
'e'
),
1
);
ASSERT_EQ
(
orig
.
rfind
(
'e'
,
1
),
1
);
ASSERT_EQ
(
orig
.
rfind
(
'q'
),
std
::
size_t
(
-
1
));
ASSERT_EQ
(
orig
.
rfind
(
"est"
),
1
);
ASSERT_EQ
(
orig
.
rfind
(
"est"
,
1
),
1
);
ASSERT_EQ
(
orig
.
rfind
(
"est"
,
1
,
2
),
1
);
ASSERT_EQ
(
orig
.
rfind
(
"set"
),
std
::
size_t
(
-
1
));
}
TEST
(
string_view_test
,
emptiness
)
{
std
::
string_view
e1
;
std
::
string_view
e2
(
""
);
std
::
string_view
e3
(
"test"
,
0
);
std
::
string_view
ne
(
"test"
);
ASSERT_TRUE
(
e1
.
empty
());
ASSERT_TRUE
(
e2
.
empty
());
ASSERT_TRUE
(
e3
.
empty
());
ASSERT_FALSE
(
ne
.
empty
());
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment