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
17712121
Commit
17712121
authored
Aug 27, 2018
by
Jeppe Pihl
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'upstream/master' into bugfix/fix_clang_and_gcc_warnings
parents
008a403b
99cac24c
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
339 additions
and
640 deletions
+339
-640
CMakeLists.txt
CMakeLists.txt
+2
-12
examples/http_server.cc
examples/http_server.cc
+10
-0
include/pistache/client.h
include/pistache/client.h
+2
-1
include/pistache/http.h
include/pistache/http.h
+8
-0
include/pistache/router.h
include/pistache/router.h
+70
-67
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
+14
-2
src/server/router.cc
src/server/router.cc
+134
-214
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 @
17712121
cmake_minimum_required
(
VERSION 3.0.2
)
project
(
pistache
)
include
(
CheckCXXCompilerFlag
)
set
(
CMAKE_CXX_FLAGS
"
${
CMAKE_CXX_FLAGS
}
-Wall -pedantic -Wextra -Wno-missing-field-initializers"
)
...
...
@@ -9,19 +8,9 @@ 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++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++0x"
COMPILER_SUPPORTS_CXX0X
)
# 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
)
if
(
COMPILER_SUPPORTS_CXX11
)
set
(
CMAKE_CXX_FLAGS
"
${
CMAKE_CXX_FLAGS
}
-std=c++11"
)
elseif
(
COMPILER_SUPPORTS_CXX0X
)
set
(
CMAKE_CXX_FLAGS
"
${
CMAKE_CXX_FLAGS
}
-std=c++0x"
)
...
...
@@ -95,3 +84,4 @@ endif()
DESTINATION
lib/pkgconfig/
)
examples/http_server.cc
View file @
17712121
...
...
@@ -121,6 +121,16 @@ class MyHandler : public Http::Handler {
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"
)
{
throw
std
::
runtime_error
(
"Exception thrown in the handler"
);
}
...
...
include/pistache/client.h
View file @
17712121
...
...
@@ -271,7 +271,8 @@ public:
}
RequestBuilder
&
cookie
(
const
Cookie
&
cookie
);
RequestBuilder
&
body
(
std
::
string
val
);
RequestBuilder
&
body
(
const
std
::
string
&
val
);
RequestBuilder
&
body
(
std
::
string
&&
val
);
RequestBuilder
&
timeout
(
std
::
chrono
::
milliseconds
value
);
...
...
include/pistache/http.h
View file @
17712121
...
...
@@ -314,6 +314,14 @@ public:
friend
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
{
return
headers_
;
}
...
...
include/pistache/router.h
View file @
17712121
...
...
@@ -8,15 +8,11 @@
#include <string>
#include <tuple>
#include <unordered_map>
#include <memory>
#include <pistache/http.h>
#include <pistache/http_defs.h>
#include <pistache/flags.h>
#include "pistache/string_view.h"
namespace
Pistache
{
namespace
Rest
{
...
...
@@ -24,8 +20,8 @@ class Description;
namespace
details
{
template
<
typename
T
>
struct
LexicalCast
{
static
T
cast
(
const
std
::
string
_view
&
value
)
{
std
::
istringstream
iss
(
std
::
string
(
value
.
data
(),
value
.
length
())
);
static
T
cast
(
const
std
::
string
&
value
)
{
std
::
istringstream
iss
(
value
);
T
out
;
if
(
!
(
iss
>>
out
))
throw
std
::
runtime_error
(
"Bad lexical cast"
);
...
...
@@ -34,8 +30,8 @@ namespace details {
};
template
<
>
struct
LexicalCast
<
std
::
string
_view
>
{
static
std
::
string
_view
cast
(
const
std
::
string_view
&
value
)
{
struct
LexicalCast
<
std
::
string
>
{
static
std
::
string
cast
(
const
std
::
string
&
value
)
{
return
value
;
}
};
...
...
@@ -43,7 +39,7 @@ namespace details {
class
TypedParam
{
public:
TypedParam
(
const
std
::
string
_view
&
name
,
const
std
::
string_view
&
value
)
TypedParam
(
const
std
::
string
&
name
,
const
std
::
string
&
value
)
:
name_
(
name
)
,
value_
(
value
)
{
}
...
...
@@ -53,88 +49,97 @@ public:
return
details
::
LexicalCast
<
T
>::
cast
(
value_
);
}
std
::
string
_view
name
()
const
{
std
::
string
name
()
const
{
return
name_
;
}
private:
std
::
string
_view
name_
;
std
::
string
_view
value_
;
std
::
string
name_
;
std
::
string
value_
;
};
class
Request
;
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
>
void
invokeHandler
(
Args
&&
...
args
)
const
{
void
invokeHandler
(
Args
&&
...
args
)
const
{
handler_
(
std
::
forward
<
Args
>
(
args
)...);
}
Handler
handler_
;
};
namespace
Private
{
class
RouterHandler
;
}
class
FragmentTreeNode
{
private:
enum
class
FragmentType
{
Fixed
,
Param
,
Optional
,
Splat
};
struct
Fragment
{
explicit
Fragment
(
std
::
string
value
);
/**
* Resource path are allocated on stack. To make them survive it is required to allocate them on heap.
* 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_
;
bool
match
(
const
std
::
string
&
raw
)
const
;
bool
match
(
const
Fragment
&
other
)
const
;
std
::
unordered_map
<
std
::
string_view
,
std
::
shared_ptr
<
FragmentTreeNode
>>
fixed_
;
std
::
unordered_map
<
std
::
string_view
,
std
::
shared_ptr
<
FragmentTreeNode
>>
param_
;
std
::
unordered_map
<
std
::
string_view
,
std
::
shared_ptr
<
FragmentTreeNode
>>
optional_
;
std
::
shared_ptr
<
FragmentTreeNode
>
splat_
;
std
::
shared_ptr
<
Route
>
route_
;
bool
isParameter
()
const
;
bool
isSplat
()
const
;
bool
isOptional
()
const
;
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
>>
findRoute
(
const
std
::
string_view
&
path
,
std
::
vector
<
TypedParam
>
&
params
,
std
::
vector
<
TypedParam
>
&
splats
)
const
;
static
std
::
vector
<
Fragment
>
fromUrl
(
const
std
::
string
&
url
);
public:
FragmentTreeNode
();
explicit
FragmentTreeNode
(
const
std
::
shared_ptr
<
char
>
&
resourceReference
);
private:
enum
class
Flag
{
None
=
0x0
,
Fixed
=
0x1
,
Parameter
=
Fixed
<<
1
,
Optional
=
Parameter
<<
1
,
Splat
=
Optional
<<
1
};
void
addRoute
(
const
std
::
string_view
&
path
,
const
Route
::
Handler
&
handler
,
const
std
::
shared_ptr
<
char
>
&
resourceReference
);
void
init
(
std
::
string
value
);
bool
removeRoute
(
const
std
::
string_view
&
path
)
;
void
checkInvariant
()
const
;
/**
* Finds the correct route for the given path.
* \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
* is a null pointer).
Flags
<
Flag
>
flags
;
std
::
string
value_
;
};
std
::
string
resource_
;
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
>>
findRoute
(
const
std
::
string_view
&
path
)
const
;
std
::
vector
<
Fragment
>
fragments_
;
};
namespace
Private
{
class
RouterHandler
;
}
class
Router
{
public:
enum
class
Status
{
Match
,
NotFound
};
static
Router
fromDescription
(
const
Rest
::
Description
&
desc
);
std
::
shared_ptr
<
Private
::
RouterHandler
>
...
...
@@ -148,18 +153,18 @@ public:
void
patch
(
std
::
string
resource
,
Route
::
Handler
handler
);
void
del
(
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
addNotFoundHandler
(
Route
::
Handler
handler
);
inline
bool
hasNotFoundHandler
()
{
return
notFoundHandler
!=
nullptr
;
}
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:
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
;
...
...
@@ -187,18 +192,17 @@ namespace Private {
class
Request
:
public
Http
::
Request
{
public:
friend
class
FragmentTreeNode
;
friend
class
Router
;
bool
hasParam
(
std
::
string
name
)
const
;
TypedParam
param
(
std
::
string
name
)
const
;
bool
hasParam
(
const
std
::
string
&
name
)
const
;
TypedParam
param
(
const
std
::
string
&
name
)
const
;
TypedParam
splatAt
(
size_t
index
)
const
;
std
::
vector
<
TypedParam
>
splat
()
const
;
private:
explicit
Request
(
const
Http
::
Request
&
request
,
const
Http
::
Request
&
request
,
std
::
vector
<
TypedParam
>&&
params
,
std
::
vector
<
TypedParam
>&&
splats
);
...
...
@@ -215,7 +219,6 @@ namespace Routes {
void
Patch
(
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
Remove
(
Router
&
router
,
Http
::
Method
method
,
std
::
string
resource
);
void
NotFound
(
Router
&
router
,
Route
::
Handler
handler
);
...
...
include/pistache/string_view.h
deleted
100644 → 0
View file @
008a403b
#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
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
!=
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 @
17712121
...
...
@@ -9,7 +9,7 @@ set(SOURCE_FILES
${
SERVER_SOURCE_FILES
}
${
CLIENT_SOURCE_FILES
}
${
INCLUDE_FILES
}
../include/pistache/string_view.h
)
)
add_library
(
pistache
${
SOURCE_FILES
}
)
set_target_properties
(
pistache PROPERTIES
...
...
src/client/client.cc
View file @
17712121
...
...
@@ -737,14 +737,26 @@ RequestBuilder::cookie(const Cookie& cookie) {
}
RequestBuilder
&
RequestBuilder
::
body
(
std
::
string
val
)
{
RequestBuilder
::
body
(
const
std
::
string
&
val
)
{
request_
.
body_
=
val
;
return
*
this
;
}
RequestBuilder
&
RequestBuilder
::
body
(
std
::
string
&&
val
)
{
request_
.
body_
=
std
::
move
(
val
);
return
*
this
;
}
RequestBuilder
&
RequestBuilder
::
timeout
(
std
::
chrono
::
milliseconds
timeout
)
{
timeout_
=
timeout
;
return
*
this
;
}
Async
::
Promise
<
Response
>
RequestBuilder
::
send
()
{
return
client_
->
doRequest
(
std
::
move
(
request_
)
,
timeout_
);
return
client_
->
doRequest
(
request_
,
timeout_
);
}
Client
::
Options
&
...
...
src/server/router.cc
View file @
17712121
...
...
@@ -23,18 +23,18 @@ Request::Request(
}
bool
Request
::
hasParam
(
std
::
string
name
)
const
{
Request
::
hasParam
(
const
std
::
string
&
name
)
const
{
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_
);
}
TypedParam
Request
::
param
(
std
::
string
name
)
const
{
Request
::
param
(
const
std
::
string
&
name
)
const
{
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_
))
{
...
...
@@ -58,220 +58,151 @@ Request::splat() const {
return
splats_
;
}
FragmentTreeNode
::
FragmentTreeNode
()
:
resourceRef_
(),
fixed_
(),
param_
(),
optional_
(),
splat_
(),
route_
()
{
std
::
shared_ptr
<
char
>
ptr
(
new
char
[
1
],
std
::
default_delete
<
char
[]
>
());
ptr
.
get
()[
0
]
=
'/'
;
resourceRef_
.
swap
(
ptr
);
}
FragmentTreeNode
::
FragmentTreeNode
(
const
std
::
shared_ptr
<
char
>
&
resourceReference
)
:
resourceRef_
(
resourceReference
),
fixed_
(),
param_
(),
optional_
(),
splat_
(),
route_
()
{
}
Route
::
Fragment
::
Fragment
(
std
::
string
value
)
{
if
(
value
.
empty
())
throw
std
::
runtime_error
(
"Invalid empty fragment"
);
FragmentTreeNode
::
FragmentType
FragmentTreeNode
::
getFragmentType
(
const
std
::
string_view
&
fragment
)
{
init
(
std
::
move
(
value
));
}
auto
optpos
=
fragment
.
find
(
'?'
);
if
(
fragment
[
0
]
==
':'
)
{
if
(
optpos
!=
std
::
string_view
::
npos
)
{
if
(
optpos
!=
fragment
.
length
()
-
1
)
{
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
;
bool
Route
::
Fragment
::
match
(
const
std
::
string
&
raw
)
const
{
if
(
flags
.
hasFlag
(
Flag
::
Fixed
))
{
return
raw
==
value_
;
}
if
(
optpos
!=
std
::
string_view
::
npos
)
{
throw
std
::
runtime_error
(
"Only optional parameters are currently supported"
);
else
if
(
flags
.
hasFlag
(
Flag
::
Parameter
)
||
flags
.
hasFlag
(
Flag
::
Splat
))
{
return
true
;
}
return
FragmentType
::
Fixed
;
return
false
;
}
bool
Route
::
Fragment
::
match
(
const
Fragment
&
other
)
const
{
return
match
(
other
.
value
());
}
void
FragmentTreeNode
::
addRoute
(
const
std
::
string_view
&
path
,
const
Route
::
Handler
&
handler
,
const
std
::
shared_ptr
<
char
>
&
resourceReference
)
{
if
(
path
.
length
()
==
0
)
{
throw
std
::
runtime_error
(
"Invalid zero-length URL."
);
Route
::
Fragment
::
init
(
std
::
string
value
)
{
if
(
value
[
0
]
==
':'
)
flags
.
setFlag
(
Flag
::
Parameter
);
else
if
(
value
[
0
]
==
'*'
)
{
if
(
value
.
size
()
>
1
)
throw
std
::
runtime_error
(
"Invalid splat parameter"
);
flags
.
setFlag
(
Flag
::
Splat
);
}
std
::
string_view
currPath
;
if
(
path
.
rfind
(
'/'
)
==
path
.
length
()
-
1
)
{
currPath
=
path
.
substr
(
0
,
path
.
length
()
-
1
);
}
else
{
currPath
=
path
;
else
{
flags
.
setFlag
(
Flag
::
Fixed
);
}
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
=
nullptr
;
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
collection
->
insert
(
std
::
make_pair
(
mid
,
std
::
make_shared
<
FragmentTreeNode
>
(
resourceReference
)));
}
collection
->
at
(
mid
)
->
addRoute
(
next
,
handler
,
resourceReference
);
}
}
if
(
pos
!=
value
.
size
()
-
1
)
throw
std
::
runtime_error
(
"? should be at the end of the string"
);
bool
Pistache
::
Rest
::
FragmentTreeNode
::
removeRoute
(
const
std
::
string_view
&
path
)
{
if
(
path
.
length
()
==
0
)
{
throw
std
::
runtime_error
(
"Invalid zero-length URL."
);
}
std
::
string_view
currPath
;
if
(
path
.
rfind
(
'/'
)
==
path
.
length
()
-
1
)
{
currPath
=
path
.
substr
(
0
,
path
.
length
());
value_
=
value
.
substr
(
0
,
pos
);
flags
.
setFlag
(
Flag
::
Optional
);
}
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
=
nullptr
;
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
{
const
bool
removable
=
collection
->
at
(
mid
)
->
removeRoute
(
next
);
if
(
removable
)
{
collection
->
erase
(
mid
);
}
}
catch
(
const
std
::
out_of_range
&
)
{
throw
std
::
runtime_error
(
"Requested does not exist."
);
checkInvariant
();
}
void
Route
::
Fragment
::
checkInvariant
()
const
{
auto
check
=
[
this
](
std
::
initializer_list
<
Flag
>
exclusiveFlags
)
{
for
(
auto
flag
:
exclusiveFlags
)
{
if
(
!
flags
.
hasFlag
(
flag
))
return
;
}
}
return
fixed_
.
empty
()
&&
param_
.
empty
()
&&
optional_
.
empty
()
&&
splat_
==
nullptr
&&
route_
==
nullptr
;
throw
std
::
logic_error
(
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
>>
Pistache
::
Rest
::
FragmentTreeNode
::
findRoute
(
const
std
::
string_view
&
path
,
std
::
vector
<
TypedParam
>
&
params
,
std
::
vector
<
TypedParam
>
&
splats
)
const
{
if
(
path
.
length
()
==
0
)
{
throw
std
::
runtime_error
(
"Invalid zero-length URL."
);
}
std
::
string_view
currPath
;
if
(
path
.
rfind
(
'/'
)
==
path
.
length
()
-
1
)
{
currPath
=
path
.
substr
(
0
,
path
.
length
()
-
1
);
}
else
{
currPath
=
path
;
std
::
vector
<
Route
::
Fragment
>
Route
::
Fragment
::
fromUrl
(
const
std
::
string
&
url
)
{
std
::
vector
<
Route
::
Fragment
>
fragments
;
std
::
istringstream
iss
(
url
);
std
::
string
p
;
while
(
std
::
getline
(
iss
,
p
,
'/'
))
{
if
(
p
.
empty
())
continue
;
fragments
.
push_back
(
Fragment
(
std
::
move
(
p
)));
}
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_
)
{
params
.
emplace_back
(
param
.
first
,
mid
);
auto
result
=
param
.
second
->
findRoute
(
next
,
params
,
splats
);
return
fragments
;
}
auto
route
=
std
::
get
<
0
>
(
result
);
if
(
route
!=
nullptr
)
return
result
;
else
params
.
pop_back
(
);
}
bool
Route
::
Fragment
::
isParameter
()
const
{
return
flags
.
hasFlag
(
Flag
::
Parameter
);
}
for
(
const
auto
&
optional
:
optional_
)
{
params
.
emplace_back
(
optional
.
first
,
mid
);
auto
result
=
optional
.
second
->
findRoute
(
next
,
params
,
splats
);
bool
Route
::
Fragment
::
isOptional
()
const
{
return
isParameter
()
&&
flags
.
hasFlag
(
Flag
::
Optional
);
}
auto
route
=
std
::
get
<
0
>
(
result
);
if
(
route
!=
nullptr
)
return
result
;
else
{
params
.
pop_back
();
bool
Route
::
Fragment
::
isSplat
()
const
{
return
flags
.
hasFlag
(
Flag
::
Splat
);
}
// try empty optional
params
.
emplace_back
(
optional
.
first
,
std
::
string_view
());
result
=
optional
.
second
->
findRoute
(
currPath
,
params
,
splats
);
std
::
tuple
<
bool
,
std
::
vector
<
TypedParam
>
,
std
::
vector
<
TypedParam
>>
Route
::
match
(
const
Http
::
Request
&
req
)
const
{
return
match
(
req
.
resource
());
}
route
=
std
::
get
<
0
>
(
result
);
if
(
route
!=
nullptr
)
return
result
;
else
params
.
pop_back
();
}
std
::
tuple
<
bool
,
std
::
vector
<
TypedParam
>
,
std
::
vector
<
TypedParam
>>
Route
::
match
(
const
std
::
string
&
req
)
const
{
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
)
{
splats
.
emplace_back
(
mid
,
mid
);
auto
result
=
splat_
->
findRoute
(
next
,
params
,
splats
)
;
const
auto
&
reqFragment
=
reqFragments
[
i
];
if
(
!
fragment
.
match
(
reqFragment
))
return
NoMatch
;
auto
route
=
std
::
get
<
0
>
(
result
);
if
(
route
!=
nullptr
)
return
result
;
else
splats
.
pop_back
();
if
(
fragment
.
isParameter
())
{
params
.
push_back
(
TypedParam
(
fragment
.
value
(),
reqFragment
.
value
()));
}
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
>>
Pistache
::
Rest
::
FragmentTreeNode
::
findRoute
(
const
std
::
string_view
&
path
)
const
{
std
::
vector
<
TypedParam
>
params
;
std
::
vector
<
TypedParam
>
splats
;
return
findRoute
(
path
,
params
,
splats
);
return
make_tuple
(
true
,
std
::
move
(
params
),
std
::
move
(
splats
));
}
namespace
Private
{
...
...
@@ -290,7 +221,7 @@ RouterHandler::onRequest(
auto
result
=
router
.
route
(
req
,
std
::
move
(
resp
));
/* @Feature: add support for a custom NotFound handler */
if
(
result
==
Route
::
Status
::
NotFound
)
if
(
result
==
Route
r
::
Status
::
NotFound
)
{
if
(
router
.
hasNotFoundHandler
())
{
...
...
@@ -301,6 +232,7 @@ RouterHandler::onRequest(
response
.
send
(
Http
::
Code
::
Not_Found
,
"Could not find a matching route"
);
}
}
}
// namespace Private
Router
...
...
@@ -362,12 +294,6 @@ Router::options(std::string resource, Route::Handler 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
Router
::
addCustomHandler
(
Route
::
Handler
handler
)
{
customHandlers
.
push_back
(
std
::
move
(
handler
));
...
...
@@ -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
));
}
Route
::
Status
Route
r
::
Status
Router
::
route
(
const
Http
::
Request
&
req
,
Http
::
ResponseWriter
response
)
{
auto
&
r
=
routes
[
req
.
method
()];
std
::
string_view
path
{
req
.
resource
().
data
(),
req
.
resource
().
size
()};
auto
result
=
r
.
findRoute
(
path
);
auto
route
=
std
::
get
<
0
>
(
result
);
if
(
route
!=
nullptr
)
{
auto
params
=
std
::
get
<
1
>
(
result
);
auto
splats
=
std
::
get
<
2
>
(
result
);
route
->
invokeHandler
(
Request
(
req
,
std
::
move
(
params
),
std
::
move
(
splats
)),
std
::
move
(
response
));
return
Route
::
Status
::
Match
;
for
(
const
auto
&
route
:
r
)
{
bool
match
;
std
::
vector
<
TypedParam
>
params
;
std
::
vector
<
TypedParam
>
splats
;
std
::
tie
(
match
,
params
,
splats
)
=
route
.
match
(
req
);
if
(
match
)
{
route
.
invokeHandler
(
Request
(
req
,
std
::
move
(
params
),
std
::
move
(
splats
)),
std
::
move
(
response
));
return
Router
::
Status
::
Match
;
}
}
for
(
const
auto
&
handler
:
customHandlers
)
{
auto
resp
=
response
.
clone
();
auto
handler1
=
handler
(
Request
(
req
,
std
::
vector
<
TypedParam
>
(),
std
::
vector
<
TypedParam
>
()),
std
::
move
(
resp
));
if
(
handler1
==
Route
::
Result
::
Ok
)
return
Route
::
Status
::
Match
;
auto
result
=
handler
(
Request
(
req
,
std
::
vector
<
TypedParam
>
(),
std
::
vector
<
TypedParam
>
()),
std
::
move
(
resp
));
if
(
result
==
Route
::
Result
::
Ok
)
return
Router
::
Status
::
Match
;
}
if
(
hasNotFoundHandler
())
invokeNotFoundHandler
(
req
,
std
::
move
(
response
));
return
Route
::
Status
::
NotFound
;
return
Router
::
Status
::
NotFound
;
}
void
Router
::
addRoute
(
Http
::
Method
method
,
std
::
string
resource
,
Route
::
Handler
handler
)
{
auto
&
r
=
routes
[
method
];
std
::
shared_ptr
<
char
>
ptr
(
new
char
[
resource
.
length
()],
std
::
default_delete
<
char
[]
>
());
memcpy
(
ptr
.
get
(),
resource
.
data
(),
resource
.
length
());
r
.
addRoute
(
std
::
string_view
(
ptr
.
get
(),
resource
.
length
()),
handler
,
ptr
);
r
.
push_back
(
Route
(
std
::
move
(
resource
),
method
,
std
::
move
(
handler
)));
}
namespace
Routes
{
void
Get
(
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
));
}
void
Remove
(
Router
&
router
,
Http
::
Method
method
,
std
::
string
resource
)
{
router
.
removeRoute
(
method
,
std
::
move
(
resource
));
}
void
NotFound
(
Router
&
router
,
Route
::
Handler
handler
)
{
router
.
addNotFoundHandler
(
std
::
move
(
handler
));
}
...
...
tests/CMakeLists.txt
View file @
17712121
...
...
@@ -16,4 +16,4 @@ pistache_test(router_test)
pistache_test
(
cookie_test
)
pistache_test
(
view_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 @
17712121
#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 @
17712121
...
...
@@ -12,25 +12,23 @@
using
namespace
Pistache
;
bool
match
(
const
Rest
::
FragmentTreeNode
&
routes
,
const
std
::
string
&
req
)
{
std
::
shared_ptr
<
Rest
::
Route
>
route
;
std
::
tie
(
route
,
std
::
ignore
,
std
::
ignore
)
=
routes
.
findRoute
({
req
.
data
(),
req
.
size
()});
return
route
!=
nullptr
;
bool
match
(
const
Rest
::
Route
&
route
,
const
std
::
string
&
req
)
{
return
std
::
get
<
0
>
(
route
.
match
(
req
));
}
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
::
shared_ptr
<
Rest
::
Route
>
route
;
bool
ok
;
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
)
{
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
))
{
std
::
cerr
<<
"Did not find param '"
<<
p
.
first
<<
"'"
<<
std
::
endl
;
...
...
@@ -47,14 +45,14 @@ bool matchParams(
}
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
::
shared_ptr
<
Rest
::
Route
>
route
;
bool
ok
;
std
::
vector
<
Rest
::
TypedParam
>
splats
;
std
::
tie
(
route
,
std
::
ignore
,
splats
)
=
routes
.
findRoute
({
req
.
data
(),
req
.
size
()}
);
if
(
route
==
nullptr
)
return
false
;
std
::
tie
(
ok
,
std
::
ignore
,
splats
)
=
route
.
match
(
req
);
if
(
!
ok
)
return
false
;
if
(
list
.
size
()
!=
splats
.
size
())
{
std
::
cerr
<<
"Size mismatch ("
<<
list
.
size
()
<<
" != "
<<
splats
.
size
()
<<
")"
...
...
@@ -76,57 +74,55 @@ bool matchSplat(
return
true
;
}
TEST
(
router_test
,
test_fixed_routes
)
{
Rest
::
FragmentTreeNode
routes
;
routes
.
addRoute
(
std
::
string_view
(
"/v1/hello"
),
nullptr
,
nullptr
);
Rest
::
Route
makeRoute
(
std
::
string
value
)
{
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"
));
ASSERT_FALSE
(
match
(
routes
,
"/v2/hello"
));
ASSERT_FALSE
(
match
(
routes
,
"/v1/hell0"
));
TEST
(
router_test
,
test_fixed_routes
)
{
auto
r1
=
makeRoute
(
"/v1/hello"
);
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
);
ASSERT_TRUE
(
match
(
r
outes
,
"/a/b/c"
));
auto
r2
=
makeRoute
(
"/a/b/c"
);
ASSERT_TRUE
(
match
(
r
2
,
"/a/b/c"
));
}
TEST
(
router_test
,
test_parameters
)
{
Rest
::
FragmentTreeNode
routes
;
routes
.
addRoute
(
std
::
string_view
(
"/v1/hello/:name"
),
nullptr
,
nullptr
);
ASSERT_TRUE
(
matchParams
(
routes
,
"/v1/hello/joe"
,
{
auto
r1
=
makeRoute
(
"/v1/hello/:name"
);
ASSERT_TRUE
(
matchParams
(
r1
,
"/v1/hello/joe"
,
{
{
":name"
,
"joe"
}
}));
routes
.
addRoute
(
std
::
string_view
(
"/greetings/:from/:to"
),
nullptr
,
nullptr
);
ASSERT_TRUE
(
matchParams
(
r
outes
,
"/greetings/foo/bar"
,
{
auto
r2
=
makeRoute
(
"/greetings/:from/:to"
);
ASSERT_TRUE
(
matchParams
(
r
2
,
"/greetings/foo/bar"
,
{
{
":from"
,
"foo"
},
{
":to"
,
"bar"
}
}));
}
TEST
(
router_test
,
test_optional
)
{
Rest
::
FragmentTreeNode
routes
;
routes
.
addRoute
(
std
::
string_view
(
"/get/:key?"
),
nullptr
,
nullptr
);
ASSERT_TRUE
(
match
(
routes
,
"/get"
));
ASSERT_TRUE
(
match
(
routes
,
"/get/"
));
ASSERT_TRUE
(
matchParams
(
routes
,
"/get/foo"
,
{
auto
r1
=
makeRoute
(
"/get/:key?"
);
ASSERT_TRUE
(
match
(
r1
,
"/get"
));
ASSERT_TRUE
(
match
(
r1
,
"/get/"
));
ASSERT_TRUE
(
matchParams
(
r1
,
"/get/foo"
,
{
{
":key"
,
"foo"
}
}));
ASSERT_TRUE
(
matchParams
(
r
outes
,
"/get/foo/"
,
{
ASSERT_TRUE
(
matchParams
(
r
1
,
"/get/foo/"
,
{
{
":key"
,
"foo"
}
}));
ASSERT_FALSE
(
match
(
r
outes
,
"/get/foo/bar"
));
ASSERT_FALSE
(
match
(
r
1
,
"/get/foo/bar"
));
}
TEST
(
router_test
,
test_splat
)
{
Rest
::
FragmentTreeNode
routes
;
routes
.
addRoute
(
std
::
string_view
(
"/say/*/to/*"
),
nullptr
,
nullptr
);
auto
r1
=
makeRoute
(
"/say/*/to/*"
);
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_FALSE
(
match
(
routes
,
"/say/hello/to"
));
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
ASSERT_TRUE
(
matchSplat
(
r1
,
"/say/hello/to/user"
,
{
"hello"
,
"user"
}));
ASSERT_TRUE
(
matchSplat
(
r1
,
"/say/hello/to/user/"
,
{
"hello"
,
"user"
}));
}
tests/string_view_test.cc
deleted
100644 → 0
View file @
008a403b
#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
),
1U
);
ASSERT_EQ
(
orig
.
find
(
find
,
1
),
1U
);
ASSERT_EQ
(
orig
.
find
(
find
,
2
),
std
::
size_t
(
-
1
));
ASSERT_EQ
(
orig
.
find
(
'e'
),
1U
);
ASSERT_EQ
(
orig
.
find
(
'e'
,
1
),
1U
);
ASSERT_EQ
(
orig
.
find
(
'e'
,
2
),
std
::
size_t
(
-
1
));
ASSERT_EQ
(
orig
.
find
(
'1'
),
std
::
size_t
(
-
1
));
ASSERT_EQ
(
orig
.
find
(
"est"
),
1U
);
ASSERT_EQ
(
orig
.
find
(
"est"
,
1
),
1U
);
ASSERT_EQ
(
orig
.
find
(
"est"
,
1
,
2
),
1U
);
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
),
1U
);
ASSERT_EQ
(
orig
.
rfind
(
find
,
1
),
1U
);
ASSERT_EQ
(
orig
.
rfind
(
'e'
),
1U
);
ASSERT_EQ
(
orig
.
rfind
(
'e'
,
1
),
1U
);
ASSERT_EQ
(
orig
.
rfind
(
'q'
),
std
::
size_t
(
-
1
));
ASSERT_EQ
(
orig
.
rfind
(
"est"
),
1U
);
ASSERT_EQ
(
orig
.
rfind
(
"est"
,
1
),
1U
);
ASSERT_EQ
(
orig
.
rfind
(
"est"
,
1
,
2
),
1U
);
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