-
-
Save xeroc/a0046d3c61b2fcdb7f7eed5661597a78 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/CMakeLists.txt b/CMakeLists.txt | |
index 73e6b1b..08862ab 100644 | |
--- a/CMakeLists.txt | |
+++ b/CMakeLists.txt | |
@@ -25,6 +25,12 @@ SET( DEFAULT_EXECUTABLE_INSTALL_DIR bin/ ) | |
SET( CMAKE_DEBUG_POSTFIX _debug ) | |
SET( BUILD_SHARED_LIBS NO ) | |
SET( ECC_IMPL secp256k1 CACHE STRING "secp256k1 or openssl or mixed" ) | |
+SET( FC_USE_FULL_ZLIB FALSE CACHE BOOL "TRUE to try to use full zlib for compression, FALSE to use miniz.c") | |
+ | |
+if( FC_USE_FULL_ZLIB ) | |
+ find_package( ZLIB REQUIRED ) | |
+ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DFC_USE_FULL_ZLIB") | |
+endif() | |
set(platformBitness 32) | |
if(CMAKE_SIZEOF_VOID_P EQUAL 8) | |
@@ -142,17 +148,6 @@ find_package(OpenSSL REQUIRED) | |
set( CMAKE_FIND_LIBRARY_SUFFIXES ${ORIGINAL_LIB_SUFFIXES} ) | |
-# We are now building in support for deflate compression into our websockets layer by default, | |
-# which requires zlib. Aside from that, all of fc compiles without zlib, so this could be | |
-# made optional without much effort | |
-# (important exception, apple: as of 10.10 yosemite, the OpenSSL static libraries shipped with | |
-# os x have a dependency on zlib) | |
-# On a side note, fc's fc::zlib_compress() function uses a separate implementation of zlib | |
-# from the miniz library. If we're comfortable requiring an external zlib, we can | |
-# reimplement fc::zlib_compress() to call the real zlib, and remove miniz.c from our | |
-# repository. | |
-find_package( ZLIB REQUIRED ) | |
- | |
option( UNITY_BUILD OFF ) | |
set( fc_sources | |
@@ -215,6 +210,7 @@ set( fc_sources | |
src/crypto/sha256.cpp | |
src/crypto/sha224.cpp | |
src/crypto/sha512.cpp | |
+ src/crypto/md5.cpp | |
src/crypto/dh.cpp | |
src/crypto/blowfish.cpp | |
src/crypto/elliptic_common.cpp | |
@@ -249,11 +245,10 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/git_revision.cpp.in" "${CMAKE_CU | |
list(APPEND sources "${CMAKE_CURRENT_BINARY_DIR}/git_revision.cpp") | |
list(APPEND sources ${fc_headers}) | |
-add_subdirectory( vendor/websocketpp EXCLUDE_FROM_ALL ) | |
+add_subdirectory( vendor/websocketpp ) | |
add_subdirectory( vendor/udt4 ) | |
-setup_library( fc SOURCES ${sources} LIBRARY_TYPE STATIC ) | |
-install( DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/" DESTINATION include ) | |
+setup_library( fc SOURCES ${sources} LIBRARY_TYPE STATIC DONT_INSTALL_LIBRARY ) | |
# begin readline stuff | |
find_package(Curses) | |
@@ -317,7 +312,7 @@ target_include_directories(fc | |
${CMAKE_CURRENT_SOURCE_DIR}/vendor/udt4/src | |
${CMAKE_CURRENT_SOURCE_DIR}/vendor/websocketpp | |
${CMAKE_CURRENT_SOURCE_DIR}/vendor/secp256k1-zkp | |
- ${ZLIB_INCLUDE_DIR} | |
+ ${ZLIB_INCLUDE_DIRS} | |
) | |
#target_link_libraries( fc PUBLIC udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library} ${ECC_LIB} ) | |
@@ -486,6 +481,14 @@ if(WIN32) | |
endif(WIN32) | |
+IF(APPLE) | |
+ # As of 10.10 yosemite, the OpenSSL static libraries shipped with os x have a dependency | |
+ # on zlib, so any time you link in openssl you also need to link zlib. . We really want to detect whether openssl was configured with the --no-zlib | |
+ # option or not when it was built, but that's difficult to do in practice, so we | |
+ # just always try to link it in on mac. | |
+ find_package( ZLIB REQUIRED ) | |
+ENDIF(APPLE) | |
+ | |
SET(OPENSSL_CONF_TARGET ) | |
IF(DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY) | |
SET (OPENSSL_CONF_TARGET ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) | |
diff --git a/include/fc/asio.hpp b/include/fc/asio.hpp | |
index 9310774..57da7a1 100644 | |
--- a/include/fc/asio.hpp | |
+++ b/include/fc/asio.hpp | |
@@ -39,17 +39,8 @@ namespace asio { | |
std::shared_ptr<const char> _buffer; | |
}; | |
- //void read_write_handler( const promise<size_t>::ptr& p, | |
- // const boost::system::error_code& ec, | |
- // size_t bytes_transferred ); | |
- void read_write_handler_ec( promise<size_t>* p, | |
- boost::system::error_code* oec, | |
- const boost::system::error_code& ec, | |
- size_t bytes_transferred ); | |
void error_handler( const promise<void>::ptr& p, | |
- const boost::system::error_code& ec ); | |
- void error_handler_ec( promise<boost::system::error_code>* p, | |
- const boost::system::error_code& ec ); | |
+ const boost::system::error_code& ec ); | |
template<typename C> | |
struct non_blocking { | |
@@ -57,14 +48,14 @@ namespace asio { | |
bool operator()( C& c, bool s ) { c.non_blocking(s); return true; } | |
}; | |
- #if WIN32 // windows stream handles do not support non blocking! | |
- template<> | |
- struct non_blocking<boost::asio::windows::stream_handle> { | |
- typedef boost::asio::windows::stream_handle C; | |
- bool operator()( C& ) { return false; } | |
- bool operator()( C&, bool ) { return false; } | |
+#if WIN32 // windows stream handles do not support non blocking! | |
+ template<> | |
+ struct non_blocking<boost::asio::windows::stream_handle> { | |
+ typedef boost::asio::windows::stream_handle C; | |
+ bool operator()( C& ) { return false; } | |
+ bool operator()( C&, bool ) { return false; } | |
}; | |
- #endif | |
+#endif | |
} | |
/** | |
* @return the default boost::asio::io_service for use with fc::asio | |
diff --git a/include/fc/compress/zlib.hpp b/include/fc/compress/zlib.hpp | |
index 10a3a0e..baf01f5 100644 | |
--- a/include/fc/compress/zlib.hpp | |
+++ b/include/fc/compress/zlib.hpp | |
@@ -1,10 +1,16 @@ | |
#pragma once | |
#include <fc/string.hpp> | |
+#ifdef FC_USE_FULL_ZLIB | |
+# include <fc/filesystem.hpp> | |
+#endif | |
namespace fc | |
{ | |
string zlib_compress(const string& in); | |
+#ifdef FC_USE_FULL_ZLIB | |
+ void gzip_compress_file(const path& input_filename, const path& output_filename); | |
+#endif | |
} // namespace fc | |
diff --git a/include/fc/crypto/elliptic.hpp b/include/fc/crypto/elliptic.hpp | |
index dd60da7..99cd68a 100644 | |
--- a/include/fc/crypto/elliptic.hpp | |
+++ b/include/fc/crypto/elliptic.hpp | |
@@ -123,7 +123,7 @@ namespace fc { | |
fc::sha512 get_shared_secret( const public_key& pub )const; | |
// signature sign( const fc::sha256& digest )const; | |
- compact_signature sign_compact( const fc::sha256& digest, bool require_canonical = true )const; | |
+ compact_signature sign_compact( const fc::sha256& digest )const; | |
// bool verify( const fc::sha256& digest, const signature& sig ); | |
public_key get_public_key()const; | |
diff --git a/include/fc/crypto/hash_ctr_rng.hpp b/include/fc/crypto/hash_ctr_rng.hpp | |
deleted file mode 100644 | |
index 878cf77..0000000 | |
--- a/include/fc/crypto/hash_ctr_rng.hpp | |
+++ /dev/null | |
@@ -1,107 +0,0 @@ | |
-#pragma once | |
- | |
-#include <boost/multiprecision/integer.hpp> | |
- | |
-namespace fc { | |
- | |
-/** | |
- * Always returns 0. Useful for testing. | |
- */ | |
-class nullary_rng | |
-{ | |
- public: | |
- nullary_rng() {} | |
- virtual ~nullary_rng() {} | |
- | |
- template< typename T > T operator()( T max ) | |
- { return T(0); } | |
-} ; | |
- | |
-/** | |
- * The hash_ctr_rng generates bits using a hash function in counter (CTR) | |
- * mode. | |
- */ | |
-template<class HashClass, int SeedLength> | |
-class hash_ctr_rng | |
-{ | |
- public: | |
- hash_ctr_rng( const char* seed, uint64_t counter = 0 ) | |
- : _counter( counter ), _current_offset( 0 ) | |
- { | |
- memcpy( _seed, seed, SeedLength ); | |
- _reset_current_value(); | |
- return; | |
- } | |
- | |
- virtual ~hash_ctr_rng() {} | |
- | |
- uint64_t get_bits( uint8_t count ) | |
- { | |
- uint64_t result = 0; | |
- uint64_t mask = 1; | |
- // grab the requested number of bits | |
- while( count > 0 ) | |
- { | |
- result |= | |
- ( | |
- ( | |
- ( | |
- _current_value.data()[ (_current_offset >> 3) & 0x1F ] | |
- & ( 1 << (_current_offset & 0x07) ) | |
- ) | |
- != 0 | |
- ) ? mask : 0 | |
- ); | |
- mask += mask; | |
- --count; | |
- ++_current_offset; | |
- if( _current_offset == (_current_value.data_size() << 3) ) | |
- { | |
- _counter++; | |
- _current_offset = 0; | |
- _reset_current_value(); | |
- } | |
- } | |
- return result; | |
- } | |
- | |
- uint64_t operator()( uint64_t bound ) | |
- { | |
- if( bound <= 1 ) | |
- return 0; | |
- uint8_t bitcount = boost::multiprecision::detail::find_msb( bound ) + 1; | |
- | |
- // probability of loop exiting is >= 1/2, so probability of | |
- // running N times is bounded above by (1/2)^N | |
- while( true ) | |
- { | |
- uint64_t result = get_bits( bitcount ); | |
- if( result < bound ) | |
- return result; | |
- } | |
- } | |
- | |
- // convenience method which does casting for types other than uint64_t | |
- template< typename T > T operator()( T bound ) | |
- { return (T) ( (*this)(uint64_t( bound )) ); } | |
- | |
- void _reset_current_value() | |
- { | |
- // internal implementation detail, called to update | |
- // _current_value when _counter changes | |
- typename HashClass::encoder enc; | |
- enc.write( _seed , SeedLength ); | |
- enc.write( (char *) &_counter, 8 ); | |
- _current_value = enc.result(); | |
- return; | |
- } | |
- | |
- uint64_t _counter; | |
- char _seed[ SeedLength ]; | |
- HashClass _current_value; | |
- uint16_t _current_offset; | |
- | |
- static const int seed_length = SeedLength; | |
-}; | |
- | |
-} // end namespace fc | |
diff --git a/include/fc/crypto/md5.hpp b/include/fc/crypto/md5.hpp | |
new file mode 100644 | |
index 0000000..06141f7 | |
--- /dev/null | |
+++ b/include/fc/crypto/md5.hpp | |
@@ -0,0 +1,76 @@ | |
+#pragma once | |
+#include <fc/fwd.hpp> | |
+#include <fc/string.hpp> | |
+ | |
+namespace fc | |
+{ | |
+ | |
+class md5 | |
+{ | |
+ public: | |
+ md5(); | |
+ explicit md5( const string& hex_str ); | |
+ | |
+ string str()const; | |
+ operator string()const; | |
+ | |
+ char* data()const; | |
+ size_t data_size()const { return 128 / 8; } | |
+ | |
+ static md5 hash( const char* d, uint32_t dlen ); | |
+ static md5 hash( const string& ); | |
+ | |
+ template<typename T> | |
+ static md5 hash( const T& t ) | |
+ { | |
+ md5::encoder e; | |
+ e << t; | |
+ return e.result(); | |
+ } | |
+ | |
+ class encoder | |
+ { | |
+ public: | |
+ encoder(); | |
+ ~encoder(); | |
+ | |
+ void write( const char* d, uint32_t dlen ); | |
+ void put( char c ) { write( &c, 1 ); } | |
+ void reset(); | |
+ md5 result(); | |
+ | |
+ private: | |
+ struct impl; | |
+ fc::fwd<impl,216> my; | |
+ }; | |
+ | |
+ template<typename T> | |
+ inline friend T& operator<<( T& ds, const md5& ep ) { | |
+ ds.write( ep.data(), sizeof(ep) ); | |
+ return ds; | |
+ } | |
+ | |
+ template<typename T> | |
+ inline friend T& operator>>( T& ds, md5& ep ) { | |
+ ds.read( ep.data(), sizeof(ep) ); | |
+ return ds; | |
+ } | |
+ friend md5 operator << ( const md5& h1, uint32_t i ); | |
+ friend bool operator == ( const md5& h1, const md5& h2 ); | |
+ friend bool operator != ( const md5& h1, const md5& h2 ); | |
+ friend md5 operator ^ ( const md5& h1, const md5& h2 ); | |
+ friend bool operator >= ( const md5& h1, const md5& h2 ); | |
+ friend bool operator > ( const md5& h1, const md5& h2 ); | |
+ friend bool operator < ( const md5& h1, const md5& h2 ); | |
+ | |
+ uint64_t _hash[2]; | |
+}; | |
+ | |
+ class variant; | |
+ void to_variant( const md5& bi, variant& v ); | |
+ void from_variant( const variant& v, md5& bi ); | |
+ | |
+} // fc | |
+ | |
+#include <fc/reflect/reflect.hpp> | |
+FC_REFLECT_TYPENAME( fc::md5 ) | |
diff --git a/include/fc/exception/exception.hpp b/include/fc/exception/exception.hpp | |
index 028b172..35d9f27 100644 | |
--- a/include/fc/exception/exception.hpp | |
+++ b/include/fc/exception/exception.hpp | |
@@ -449,7 +449,7 @@ namespace fc | |
FC_RETHROW_EXCEPTION( er, LOG_LEVEL, FORMAT, __VA_ARGS__ ); \ | |
} catch( const std::exception& e ) { \ | |
fc::exception fce( \ | |
- FC_LOG_MESSAGE( LOG_LEVEL, "${what}: " FORMAT,__VA_ARGS__("what",e.what())), \ | |
+ BOOST_PP_EXPAND(FC_LOG_MESSAGE( LOG_LEVEL, "${what}: " FORMAT,__VA_ARGS__("what",e.what()))), \ | |
fc::std_exception_code,\ | |
typeid(e).name(), \ | |
e.what() ) ; throw fce;\ | |
diff --git a/include/fc/io/raw.hpp b/include/fc/io/raw.hpp | |
index 46dcf4b..bc5cd22 100644 | |
--- a/include/fc/io/raw.hpp | |
+++ b/include/fc/io/raw.hpp | |
@@ -324,13 +324,13 @@ namespace fc { | |
struct if_enum<fc::true_type> { | |
template<typename Stream, typename T> | |
static inline void pack( Stream& s, const T& v ) { | |
- fc::raw::pack(s, signed_int((int32_t)v)); | |
+ fc::raw::pack(s, (int64_t)v); | |
} | |
template<typename Stream, typename T> | |
static inline void unpack( Stream& s, T& v ) { | |
- signed_int temp; | |
+ int64_t temp; | |
fc::raw::unpack(s, temp); | |
- v = (T)temp.value; | |
+ v = (T)temp; | |
} | |
}; | |
diff --git a/include/fc/log/file_appender.hpp b/include/fc/log/file_appender.hpp | |
index a05fcd3..70a6e0a 100644 | |
--- a/include/fc/log/file_appender.hpp | |
+++ b/include/fc/log/file_appender.hpp | |
@@ -18,6 +18,7 @@ class file_appender : public appender { | |
bool rotate = false; | |
microseconds rotation_interval; | |
microseconds rotation_limit; | |
+ bool rotation_compression = false; | |
}; | |
file_appender( const variant& args ); | |
~file_appender(); | |
@@ -31,4 +32,4 @@ class file_appender : public appender { | |
#include <fc/reflect/reflect.hpp> | |
FC_REFLECT( fc::file_appender::config, | |
- (format)(filename)(flush)(rotate)(rotation_interval)(rotation_limit) ) | |
+ (format)(filename)(flush)(rotate)(rotation_interval)(rotation_limit)(rotation_compression) ) | |
diff --git a/include/fc/log/log_message.hpp b/include/fc/log/log_message.hpp | |
index 1928a98..c17c0e6 100644 | |
--- a/include/fc/log/log_message.hpp | |
+++ b/include/fc/log/log_message.hpp | |
@@ -8,6 +8,14 @@ | |
#include <fc/shared_ptr.hpp> | |
#include <memory> | |
+#include <boost/preprocessor/seq/for_each.hpp> | |
+#include <boost/preprocessor/stringize.hpp> | |
+#include <boost/preprocessor/control/if.hpp> | |
+#include <boost/preprocessor/comparison/equal.hpp> | |
+#include <boost/preprocessor/facilities/expand.hpp> | |
+#include <boost/preprocessor/variadic/size.hpp> | |
+#include <boost/preprocessor/seq/variadic_seq_to_seq.hpp> | |
+ | |
namespace fc | |
{ | |
namespace detail | |
@@ -157,6 +165,15 @@ FC_REFLECT_TYPENAME( fc::log_message ); | |
* @param FORMAT A const char* string containing zero or more references to keys as "${key}" | |
* @param ... A set of key/value pairs denoted as ("key",val)("key2",val2)... | |
*/ | |
-#define FC_LOG_MESSAGE( LOG_LEVEL, FORMAT, ... ) \ | |
- fc::log_message( FC_LOG_CONTEXT(LOG_LEVEL), FORMAT, fc::mutable_variant_object()__VA_ARGS__ ) | |
+#define FC_LOG_MESSAGE_GENERATE_PARAMETER_NAME(VALUE) BOOST_PP_LPAREN() BOOST_PP_STRINGIZE(VALUE), fc::variant(VALUE) BOOST_PP_RPAREN() | |
+#define FC_LOG_MESSAGE_DONT_GENERATE_PARAMETER_NAME(NAME, VALUE) BOOST_PP_LPAREN() NAME, fc::variant(VALUE) BOOST_PP_RPAREN() | |
+#define FC_LOG_MESSAGE_GENERATE_PARAMETER_NAMES_IF_NEEDED(r, data, PARAMETER_AND_MAYBE_NAME) BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_VARIADIC_SIZE PARAMETER_AND_MAYBE_NAME,1),FC_LOG_MESSAGE_GENERATE_PARAMETER_NAME,FC_LOG_MESSAGE_DONT_GENERATE_PARAMETER_NAME)PARAMETER_AND_MAYBE_NAME | |
+ | |
+#define FC_LOG_MESSAGE_STRING_ONLY(LOG_LEVEL, FORMAT) \ | |
+ fc::log_message(FC_LOG_CONTEXT(LOG_LEVEL), FORMAT, fc::variant_object()) | |
+#define FC_LOG_MESSAGE_WITH_SUBSTITUTIONS(LOG_LEVEL, FORMAT, ...) \ | |
+ fc::log_message(FC_LOG_CONTEXT(LOG_LEVEL), FORMAT, fc::mutable_variant_object() BOOST_PP_SEQ_FOR_EACH(FC_LOG_MESSAGE_GENERATE_PARAMETER_NAMES_IF_NEEDED, _, BOOST_PP_VARIADIC_SEQ_TO_SEQ(__VA_ARGS__))) | |
+ | |
+#define FC_LOG_MESSAGE(LOG_LEVEL, ...) \ | |
+ BOOST_PP_EXPAND(BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__),1),FC_LOG_MESSAGE_STRING_ONLY,FC_LOG_MESSAGE_WITH_SUBSTITUTIONS)(LOG_LEVEL,__VA_ARGS__)) | |
diff --git a/include/fc/log/logger.hpp b/include/fc/log/logger.hpp | |
index 9005d75..8c1020d 100644 | |
--- a/include/fc/log/logger.hpp | |
+++ b/include/fc/log/logger.hpp | |
@@ -155,6 +155,8 @@ namespace fc | |
#define FC_FORMAT_ARG_PARAMS( ... )\ | |
BOOST_PP_SEQ_FOR_EACH( FC_FORMAT_ARGS, v, __VA_ARGS__ ) | |
+#define ddump( SEQ ) \ | |
+ dlog( FC_FORMAT(SEQ), FC_FORMAT_ARG_PARAMS(SEQ) ) | |
#define idump( SEQ ) \ | |
ilog( FC_FORMAT(SEQ), FC_FORMAT_ARG_PARAMS(SEQ) ) | |
#define wdump( SEQ ) \ | |
@@ -176,4 +178,4 @@ namespace fc | |
# define ilog(...) FC_MULTILINE_MACRO_BEGIN FC_MULTILINE_MACRO_END | |
# undef dlog | |
# define dlog(...) FC_MULTILINE_MACRO_BEGIN FC_MULTILINE_MACRO_END | |
-#endif | |
\ No newline at end of file | |
+#endif | |
diff --git a/include/fc/network/http/connection.hpp b/include/fc/network/http/connection.hpp | |
index 3c8de77..153a47e 100644 | |
--- a/include/fc/network/http/connection.hpp | |
+++ b/include/fc/network/http/connection.hpp | |
@@ -60,7 +60,7 @@ namespace fc { | |
~connection(); | |
// used for clients | |
void connect_to( const fc::ip::endpoint& ep ); | |
- http::reply request( const fc::string& method, const fc::string& url, const fc::string& body = std::string(), const headers& = headers()); | |
+ http::reply request( const fc::string& method, const fc::string& url, const fc::string& body = std::string(), const headers& = headers(), const fc::string& content_type = "application/json"); | |
// used for servers | |
fc::tcp_socket& get_socket()const; | |
diff --git a/include/fc/network/http/websocket.hpp b/include/fc/network/http/websocket.hpp | |
index bcb022e..c0c7266 100644 | |
--- a/include/fc/network/http/websocket.hpp | |
+++ b/include/fc/network/http/websocket.hpp | |
@@ -8,7 +8,8 @@ | |
namespace fc { namespace http { | |
namespace detail { | |
- class abstract_websocket_server; | |
+ class websocket_server_impl; | |
+ class websocket_tls_server_impl; | |
class websocket_client_impl; | |
class websocket_tls_client_impl; | |
} // namespace detail; | |
@@ -41,7 +42,7 @@ namespace fc { namespace http { | |
class websocket_server | |
{ | |
public: | |
- websocket_server(bool enable_permessage_deflate = true); | |
+ websocket_server(); | |
~websocket_server(); | |
void on_connection( const on_connection_handler& handler); | |
@@ -50,16 +51,16 @@ namespace fc { namespace http { | |
void start_accept(); | |
private: | |
- std::unique_ptr<detail::abstract_websocket_server> my; | |
+ friend class detail::websocket_server_impl; | |
+ std::unique_ptr<detail::websocket_server_impl> my; | |
}; | |
class websocket_tls_server | |
{ | |
public: | |
- websocket_tls_server(const std::string& server_pem = std::string(), | |
- const std::string& ssl_password = std::string(), | |
- bool enable_permessage_deflate = false); | |
+ websocket_tls_server( const std::string& server_pem = std::string(), | |
+ const std::string& ssl_password = std::string()); | |
~websocket_tls_server(); | |
void on_connection( const on_connection_handler& handler); | |
@@ -68,7 +69,8 @@ namespace fc { namespace http { | |
void start_accept(); | |
private: | |
- std::unique_ptr<detail::abstract_websocket_server> my; | |
+ friend class detail::websocket_tls_server_impl; | |
+ std::unique_ptr<detail::websocket_tls_server_impl> my; | |
}; | |
class websocket_client | |
diff --git a/include/fc/network/url.hpp b/include/fc/network/url.hpp | |
index 6f8c745..4d6066c 100644 | |
--- a/include/fc/network/url.hpp | |
+++ b/include/fc/network/url.hpp | |
@@ -48,6 +48,7 @@ namespace fc { | |
ostring pass()const; | |
opath path()const; | |
ovariant_object args()const; | |
+ std::string args_as_string()const; | |
fc::optional<uint16_t> port()const; | |
private: | |
diff --git a/include/fc/optional.hpp b/include/fc/optional.hpp | |
index bb760a5..dfb725d 100644 | |
--- a/include/fc/optional.hpp | |
+++ b/include/fc/optional.hpp | |
@@ -18,11 +18,9 @@ namespace fc { | |
* fc::optional adds less than 400. | |
*/ | |
template<typename T> | |
- class optional | |
+ class optional | |
{ | |
public: | |
- typedef T value_type; | |
- | |
optional():_valid(false){} | |
~optional(){ reset(); } | |
diff --git a/include/fc/variant.hpp b/include/fc/variant.hpp | |
index 11ab95a..3b86fcb 100644 | |
--- a/include/fc/variant.hpp | |
+++ b/include/fc/variant.hpp | |
@@ -93,10 +93,10 @@ namespace fc | |
template<typename K, typename T> | |
void from_variant( const variant& var, fc::flat_map<K,T>& vo ); | |
- template<typename K, typename T> | |
- void to_variant( const std::map<K,T>& var, variant& vo ); | |
- template<typename K, typename T> | |
- void from_variant( const variant& var, std::map<K,T>& vo ); | |
+ template<typename K, typename T, typename C> | |
+ void to_variant( const std::map<K,T, C>& var, variant& vo ); | |
+ template<typename K, typename T, typename C> | |
+ void from_variant( const variant& var, std::map<K,T,C>& vo ); | |
template<typename K, typename T> | |
void to_variant( const std::multimap<K,T>& var, variant& vo ); | |
template<typename K, typename T> | |
@@ -134,6 +134,8 @@ namespace fc | |
#ifdef __APPLE__ | |
void to_variant( size_t s, variant& v ); | |
+ #elif defined(_MSC_VER) | |
+ void to_variant( unsigned long s, variant& v); | |
#elif !defined(_MSC_VER) | |
void to_variant( long long int s, variant& v ); | |
void to_variant( unsigned long long int s, variant& v ); | |
@@ -401,8 +403,8 @@ namespace fc | |
vo.insert( itr->as< std::pair<K,T> >() ); | |
} | |
- template<typename K, typename T> | |
- void to_variant( const std::map<K, T>& var, variant& vo ) | |
+ template<typename K, typename T, typename C> | |
+ void to_variant( const std::map<K, T, C>& var, variant& vo ) | |
{ | |
std::vector< variant > vars(var.size()); | |
size_t i = 0; | |
@@ -410,8 +412,8 @@ namespace fc | |
vars[i] = fc::variant(*itr); | |
vo = vars; | |
} | |
- template<typename K, typename T> | |
- void from_variant( const variant& var, std::map<K, T>& vo ) | |
+ template<typename K, typename T, typename C> | |
+ void from_variant( const variant& var, std::map<K, T, C>& vo ) | |
{ | |
const variants& vars = var.get_array(); | |
vo.clear(); | |
@@ -529,6 +531,11 @@ namespace fc | |
#ifdef __APPLE__ | |
inline void to_variant( size_t s, variant& v ) { v = variant(uint64_t(s)); } | |
#endif | |
+ | |
+ #ifdef _MSC_VER | |
+ inline void to_variant( unsigned long s, variant& v) { v = variant(uint64_t(s)); } | |
+ #endif | |
+ | |
template<typename T> | |
void to_variant( const std::shared_ptr<T>& var, variant& vo ) | |
{ | |
diff --git a/include/fc/variant_object.hpp b/include/fc/variant_object.hpp | |
index 5a39c80..bd68791 100644 | |
--- a/include/fc/variant_object.hpp | |
+++ b/include/fc/variant_object.hpp | |
@@ -202,7 +202,7 @@ namespace fc | |
mutable_variant_object( string key, T&& val ) | |
:_key_value( new std::vector<entry>() ) | |
{ | |
- set( std::move(key), variant(forward<T>(val)) ); | |
+ set( std::move(key), variant(fc::forward<T>(val)) ); | |
} | |
mutable_variant_object( mutable_variant_object&& ); | |
diff --git a/src/asio.cpp b/src/asio.cpp | |
index 0319eaa..7843336 100644 | |
--- a/src/asio.cpp | |
+++ b/src/asio.cpp | |
@@ -11,11 +11,12 @@ namespace fc { | |
read_write_handler::read_write_handler(const promise<size_t>::ptr& completion_promise) : | |
_completion_promise(completion_promise) | |
{ | |
- // assert(false); // to detect anywhere we're not passing in a shared buffer | |
+ //assert(false); // to detect anywhere we're not passing in a shared buffer | |
} | |
+ | |
void read_write_handler::operator()(const boost::system::error_code& ec, size_t bytes_transferred) | |
{ | |
- // assert(false); // to detect anywhere we're not passing in a shared buffer | |
+ //assert(false); // to detect anywhere we're not passing in a shared buffer | |
if( !ec ) | |
_completion_promise->set_value(bytes_transferred); | |
else if( ec == boost::asio::error::eof ) | |
@@ -23,11 +24,13 @@ namespace fc { | |
else | |
_completion_promise->set_exception( fc::exception_ptr( new fc::exception( FC_LOG_MESSAGE( error, "${message} ", ("message", boost::system::system_error(ec).what())) ) ) ); | |
} | |
- read_write_handler_with_buffer::read_write_handler_with_buffer(const promise<size_t>::ptr& completion_promise, | |
+ | |
+ read_write_handler_with_buffer::read_write_handler_with_buffer(const promise<size_t>::ptr& completion_promise, | |
const std::shared_ptr<const char>& buffer) : | |
_completion_promise(completion_promise), | |
_buffer(buffer) | |
{} | |
+ | |
void read_write_handler_with_buffer::operator()(const boost::system::error_code& ec, size_t bytes_transferred) | |
{ | |
if( !ec ) | |
@@ -38,57 +41,53 @@ namespace fc { | |
_completion_promise->set_exception( fc::exception_ptr( new fc::exception( FC_LOG_MESSAGE( error, "${message} ", ("message", boost::system::system_error(ec).what())) ) ) ); | |
} | |
- void read_write_handler_ec( promise<size_t>* p, boost::system::error_code* oec, const boost::system::error_code& ec, size_t bytes_transferred ) { | |
- p->set_value(bytes_transferred); | |
- *oec = ec; | |
- } | |
void error_handler( const promise<void>::ptr& p, | |
- const boost::system::error_code& ec ) { | |
- if( !ec ) | |
- p->set_value(); | |
- else | |
- { | |
- if( ec == boost::asio::error::eof ) | |
- { | |
+ const boost::system::error_code& ec ) | |
+ { | |
+ if( !ec ) | |
+ p->set_value(); | |
+ else | |
+ { | |
+ if( ec == boost::asio::error::eof ) | |
+ { | |
p->set_exception( fc::exception_ptr( new fc::eof_exception( | |
- FC_LOG_MESSAGE( error, "${message} ", ("message", boost::system::system_error(ec).what())) ) ) ); | |
- } | |
- else | |
- { | |
- //elog( "${message} ", ("message", boost::system::system_error(ec).what())); | |
+ FC_LOG_MESSAGE( error, "${message} ", ("message", boost::system::system_error(ec).what())) ) ) ); | |
+ } | |
+ else | |
+ { | |
+ //elog( "${message} ", ("message", boost::system::system_error(ec).what())); | |
p->set_exception( fc::exception_ptr( new fc::exception( | |
- FC_LOG_MESSAGE( error, "${message} ", ("message", boost::system::system_error(ec).what())) ) ) ); | |
- } | |
- } | |
+ FC_LOG_MESSAGE( error, "${message} ", ("message", boost::system::system_error(ec).what())) ) ) ); | |
+ } | |
} | |
+ } | |
- void error_handler_ec( promise<boost::system::error_code>* p, | |
- const boost::system::error_code& ec ) { | |
- p->set_value(ec); | |
+ template<typename EndpointType, typename IteratorType> | |
+ void resolve_handler(const typename promise<std::vector<EndpointType> >::ptr& p, | |
+ const boost::system::error_code& ec, | |
+ IteratorType itr) | |
+ { | |
+ if( !ec ) | |
+ { | |
+ std::vector<EndpointType> eps; | |
+ while( itr != IteratorType() ) | |
+ { | |
+ eps.push_back(*itr); | |
+ ++itr; | |
+ } | |
+ p->set_value( eps ); | |
} | |
- | |
- template<typename EndpointType, typename IteratorType> | |
- void resolve_handler( | |
- const typename promise<std::vector<EndpointType> >::ptr& p, | |
- const boost::system::error_code& ec, | |
- IteratorType itr) { | |
- if( !ec ) { | |
- std::vector<EndpointType> eps; | |
- while( itr != IteratorType() ) { | |
- eps.push_back(*itr); | |
- ++itr; | |
- } | |
- p->set_value( eps ); | |
- } else { | |
- //elog( "%s", boost::system::system_error(ec).what() ); | |
- //p->set_exception( fc::copy_exception( boost::system::system_error(ec) ) ); | |
+ else | |
+ { | |
+ //elog( "%s", boost::system::system_error(ec).what() ); | |
+ //p->set_exception( fc::copy_exception( boost::system::system_error(ec) ) ); | |
p->set_exception( | |
fc::exception_ptr( new fc::exception( | |
FC_LOG_MESSAGE( error, "process exited with: ${message} ", | |
- ("message", boost::system::system_error(ec).what())) ) ) ); | |
- } | |
+ ("message", boost::system::system_error(ec).what())) ) ) ); | |
} | |
- } | |
+ } | |
+ } // end namespace detail | |
struct default_io_service_scope | |
{ | |
diff --git a/src/compress/zlib.cpp b/src/compress/zlib.cpp | |
index dd9d23b..a7099ba 100644 | |
--- a/src/compress/zlib.cpp | |
+++ b/src/compress/zlib.cpp | |
@@ -1,9 +1,77 @@ | |
#include <fc/compress/zlib.hpp> | |
-#include "miniz.c" | |
+#ifdef FC_USE_FULL_ZLIB | |
+# include <zlib.h> | |
+# include <memory> | |
+# include <fstream> | |
+#else | |
+# include "miniz.c" | |
+#endif | |
namespace fc | |
{ | |
+#ifdef FC_USE_FULL_ZLIB | |
+ string zlib_compress(const string& in) | |
+ { | |
+ unsigned long bufferLen = compressBound(in.size()); | |
+ std::unique_ptr<char[]> buffer(new char[bufferLen]); | |
+ compress((unsigned char*)buffer.get(), &bufferLen, (const unsigned char*)in.c_str(), in.size()); | |
+ string result(buffer.get(), bufferLen); | |
+ return result; | |
+ } | |
+ | |
+ void gzip_compress_file(const path& input_filename, const path& output_filename) | |
+ { | |
+ std::ifstream infile(input_filename.generic_string().c_str(), std::ios::binary); | |
+ std::ofstream outfile(output_filename.generic_string().c_str(), std::ios::out | std::ios::binary); | |
+ unsigned bufferLen = 1024 * 1024; | |
+ std::unique_ptr<char[]> inputBuffer(new char[bufferLen]); | |
+ std::unique_ptr<char[]> outputBuffer(new char[bufferLen]); | |
+ | |
+ z_stream outputStream; | |
+ outputStream.zalloc = 0; | |
+ outputStream.zfree = 0; | |
+ outputStream.opaque = 0; | |
+ int windowBits = 15; | |
+ int GZIP_ENCODING = 16; | |
+ | |
+ deflateInit2(&outputStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, windowBits | GZIP_ENCODING, | |
+ 8, Z_DEFAULT_STRATEGY); | |
+ | |
+ if (infile) | |
+ { | |
+ do | |
+ { | |
+ infile.read(inputBuffer.get(), bufferLen); | |
+ int bytesRead = infile.gcount(); | |
+ if (bytesRead == 0) | |
+ break; | |
+ outputStream.avail_in = bytesRead; | |
+ outputStream.next_in = (unsigned char*)inputBuffer.get(); | |
+ do | |
+ { | |
+ outputStream.avail_out = bufferLen; | |
+ outputStream.next_out = (unsigned char*)outputBuffer.get(); | |
+ deflate(&outputStream, Z_NO_FLUSH); | |
+ int compressedBytesGenerated = bufferLen - outputStream.avail_out; | |
+ outfile.write(outputBuffer.get(), compressedBytesGenerated); | |
+ } | |
+ while (outputStream.avail_out == 0); | |
+ } | |
+ while (infile); | |
+ } | |
+ do | |
+ { | |
+ outputStream.avail_out = bufferLen; | |
+ outputStream.next_out = (unsigned char*)outputBuffer.get(); | |
+ deflate(&outputStream, Z_FINISH); | |
+ int compressedBytesGenerated = bufferLen - outputStream.avail_out; | |
+ outfile.write(outputBuffer.get(), compressedBytesGenerated); | |
+ } | |
+ while (outputStream.avail_out == 0); | |
+ deflateEnd(&outputStream); | |
+ } | |
+#else | |
string zlib_compress(const string& in) | |
{ | |
size_t compressed_message_length; | |
@@ -12,4 +80,5 @@ namespace fc | |
free(compressed_message); | |
return result; | |
} | |
+#endif | |
} | |
diff --git a/src/crypto/elliptic_impl_priv.cpp b/src/crypto/elliptic_impl_priv.cpp | |
index 585ffde..ad79ebe 100644 | |
--- a/src/crypto/elliptic_impl_priv.cpp | |
+++ b/src/crypto/elliptic_impl_priv.cpp | |
@@ -85,7 +85,7 @@ namespace fc { namespace ecc { | |
return secp256k1_nonce_function_default( nonce32, msg32, key32, *extra, nullptr ); | |
} | |
- compact_signature private_key::sign_compact( const fc::sha256& digest, bool require_canonical )const | |
+ compact_signature private_key::sign_compact( const fc::sha256& digest )const | |
{ | |
FC_ASSERT( my->_key != empty_priv ); | |
compact_signature result; | |
@@ -94,7 +94,7 @@ namespace fc { namespace ecc { | |
do | |
{ | |
FC_ASSERT( secp256k1_ecdsa_sign_compact( detail::_get_context(), (unsigned char*) digest.data(), (unsigned char*) result.begin() + 1, (unsigned char*) my->_key.data(), extended_nonce_function, &counter, &recid )); | |
- } while( require_canonical && !public_key::is_canonical( result ) ); | |
+ } while( !public_key::is_canonical( result ) ); | |
result.begin()[0] = 27 + 4 + recid; | |
return result; | |
} | |
diff --git a/src/crypto/md5.cpp b/src/crypto/md5.cpp | |
new file mode 100644 | |
index 0000000..fd304eb | |
--- /dev/null | |
+++ b/src/crypto/md5.cpp | |
@@ -0,0 +1,96 @@ | |
+#include <fc/crypto/hex.hpp> | |
+#include <fc/fwd_impl.hpp> | |
+#include <openssl/md5.h> | |
+#include <string.h> | |
+#include <fc/crypto/md5.hpp> | |
+#include <fc/variant.hpp> | |
+ | |
+namespace fc { | |
+ | |
+ md5::md5() { memset( _hash, 0, sizeof(_hash) ); } | |
+ md5::md5( const string& hex_str ) { | |
+ fc::from_hex( hex_str, (char*)_hash, sizeof(_hash) ); | |
+ } | |
+ | |
+ string md5::str()const { | |
+ return fc::to_hex( (char*)_hash, sizeof(_hash) ); | |
+ } | |
+ md5::operator string()const { return str(); } | |
+ | |
+ char* md5::data()const { return (char*)&_hash[0]; } | |
+ | |
+ | |
+ struct md5::encoder::impl { | |
+ MD5_CTX ctx; | |
+ }; | |
+ | |
+ md5::encoder::~encoder() {} | |
+ md5::encoder::encoder() { | |
+ reset(); | |
+ } | |
+ | |
+ md5 md5::hash( const char* d, uint32_t dlen ) { | |
+ encoder e; | |
+ e.write(d,dlen); | |
+ return e.result(); | |
+ } | |
+ md5 md5::hash( const string& s ) { | |
+ return hash( s.c_str(), s.size() ); | |
+ } | |
+ | |
+ void md5::encoder::write( const char* d, uint32_t dlen ) { | |
+ MD5_Update( &my->ctx, d, dlen); | |
+ } | |
+ md5 md5::encoder::result() { | |
+ md5 h; | |
+ MD5_Final((uint8_t*)h.data(), &my->ctx ); | |
+ return h; | |
+ } | |
+ void md5::encoder::reset() { | |
+ MD5_Init( &my->ctx); | |
+ } | |
+ | |
+ md5 operator << ( const md5& h1, uint32_t i ) { | |
+ md5 result; | |
+ uint8_t* r = (uint8_t*)result._hash; | |
+ uint8_t* s = (uint8_t*)h1._hash; | |
+ for( uint32_t p = 0; p < sizeof(h1._hash)-1; ++p ) | |
+ r[p] = s[p] << i | (s[p+1]>>(8-i)); | |
+ r[63] = s[63] << i; | |
+ return result; | |
+ } | |
+ md5 operator ^ ( const md5& h1, const md5& h2 ) { | |
+ md5 result; | |
+ result._hash[0] = h1._hash[0] ^ h2._hash[0]; | |
+ result._hash[1] = h1._hash[1] ^ h2._hash[1]; | |
+ return result; | |
+ } | |
+ bool operator >= ( const md5& h1, const md5& h2 ) { | |
+ return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) >= 0; | |
+ } | |
+ bool operator > ( const md5& h1, const md5& h2 ) { | |
+ return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) > 0; | |
+ } | |
+ bool operator < ( const md5& h1, const md5& h2 ) { | |
+ return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) < 0; | |
+ } | |
+ bool operator != ( const md5& h1, const md5& h2 ) { | |
+ return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) != 0; | |
+ } | |
+ bool operator == ( const md5& h1, const md5& h2 ) { | |
+ return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) == 0; | |
+ } | |
+ | |
+ void to_variant( const md5& bi, variant& v ) | |
+ { | |
+ v = std::vector<char>( (const char*)&bi, ((const char*)&bi) + sizeof(bi) ); | |
+ } | |
+ void from_variant( const variant& v, md5& bi ) | |
+ { | |
+ std::vector<char> ve = v.as< std::vector<char> >(); | |
+ if( ve.size() ) | |
+ memcpy(&bi, ve.data(), fc::min<size_t>(ve.size(),sizeof(bi)) ); | |
+ else | |
+ memset( &bi, char(0), sizeof(bi) ); | |
+ } | |
+} | |
diff --git a/src/log/file_appender.cpp b/src/log/file_appender.cpp | |
index 85b69c6..204686a 100644 | |
--- a/src/log/file_appender.cpp | |
+++ b/src/log/file_appender.cpp | |
@@ -5,6 +5,9 @@ | |
#include <fc/thread/scoped_lock.hpp> | |
#include <fc/thread/thread.hpp> | |
#include <fc/variant.hpp> | |
+#ifdef FC_USE_FULL_ZLIB | |
+# include <fc/compress/zlib.hpp> | |
+#endif | |
#include <boost/thread/mutex.hpp> | |
#include <iomanip> | |
#include <queue> | |
@@ -12,6 +15,8 @@ | |
namespace fc { | |
+ static const string compression_extension( ".gz" ); | |
+ | |
class file_appender::impl : public fc::retainable | |
{ | |
public: | |
@@ -22,6 +27,7 @@ namespace fc { | |
private: | |
future<void> _rotation_task; | |
time_point_sec _current_file_start_time; | |
+ std::unique_ptr<thread> _compression_thread; | |
time_point_sec get_file_start_time( const time_point_sec& timestamp, const microseconds& interval ) | |
{ | |
@@ -30,6 +36,28 @@ namespace fc { | |
return time_point_sec( (uint32_t)(file_number * interval_seconds) ); | |
} | |
+ void compress_file( const fc::path& filename ) | |
+ { | |
+#ifdef FC_USE_FULL_ZLIB | |
+ FC_ASSERT( cfg.rotate && cfg.rotation_compression ); | |
+ FC_ASSERT( _compression_thread ); | |
+ if( !_compression_thread->is_current() ) | |
+ { | |
+ _compression_thread->async( [this, filename]() { compress_file( filename ); }, "compress_file" ).wait(); | |
+ return; | |
+ } | |
+ | |
+ try | |
+ { | |
+ gzip_compress_file( filename, filename.parent_path() / (filename.filename().string() + compression_extension) ); | |
+ remove_all( filename ); | |
+ } | |
+ catch( ... ) | |
+ { | |
+ } | |
+#endif | |
+ } | |
+ | |
public: | |
impl( const config& c) : cfg( c ) | |
{ | |
@@ -38,8 +66,10 @@ namespace fc { | |
FC_ASSERT( cfg.rotation_interval >= seconds( 1 ) ); | |
FC_ASSERT( cfg.rotation_limit >= cfg.rotation_interval ); | |
- | |
- | |
+#ifdef FC_USE_FULL_ZLIB | |
+ if( cfg.rotation_compression ) | |
+ _compression_thread.reset( new thread( "compression") ); | |
+#endif | |
_rotation_task = async( [this]() { rotate_files( true ); }, "rotate_files(1)" ); | |
} | |
@@ -107,11 +137,16 @@ namespace fc { | |
fc::time_point_sec current_timestamp = fc::time_point_sec::from_iso_string( current_timestamp_str ); | |
if( current_timestamp < start_time ) | |
{ | |
- if( current_timestamp < limit_time || file_size( current_filename ) <= 0 ) | |
+ if( current_timestamp < limit_time || file_size( link_filename.parent_path() / itr->filename() ) <= 0 ) | |
{ | |
remove_all( *itr ); | |
continue; | |
} | |
+ if( !cfg.rotation_compression ) | |
+ continue; | |
+ if( current_filename.find( compression_extension ) != string::npos ) | |
+ continue; | |
+ compress_file( *itr ); | |
} | |
} | |
catch (const fc::canceled_exception&) | |
@@ -134,7 +169,8 @@ namespace fc { | |
format( "${timestamp} ${thread_name} ${context} ${file}:${line} ${method} ${level}] ${message}" ), | |
filename(p), | |
flush(true), | |
- rotate(false) | |
+ rotate(false), | |
+ rotation_compression(false) | |
{} | |
file_appender::file_appender( const variant& args ) : | |
@@ -161,7 +197,11 @@ namespace fc { | |
{ | |
std::stringstream line; | |
//line << (m.get_context().get_timestamp().time_since_epoch().count() % (1000ll*1000ll*60ll*60))/1000 <<"ms "; | |
- line << string(m.get_context().get_timestamp()) << " "; | |
+ //line << string(m.get_context().get_timestamp()) << " "; | |
+ time_point timestamp = m.get_context().get_timestamp(); | |
+ line << string(timestamp); | |
+ uint64_t milliseconds = (timestamp.time_since_epoch().count() % 1000000) / 1000; | |
+ line << "." << std::setw(3) << std::setfill('0') << milliseconds << std::setfill(' ') << " "; | |
line << std::setw( 21 ) << (m.get_context().get_thread_name().substr(0,9) + string(":") + m.get_context().get_task_name()).c_str() << " "; | |
string method_name = m.get_context().get_method(); | |
diff --git a/src/network/http/http_connection.cpp b/src/network/http/http_connection.cpp | |
index 309866d..7a1e076 100644 | |
--- a/src/network/http/http_connection.cpp | |
+++ b/src/network/http/http_connection.cpp | |
@@ -9,67 +9,137 @@ | |
#include <fc/io/stdio.hpp> | |
#include <fc/network/url.hpp> | |
#include <boost/algorithm/string.hpp> | |
- | |
+#include <boost/range/algorithm_ext/push_back.hpp> | |
class fc::http::connection::impl | |
{ | |
public: | |
fc::tcp_socket sock; | |
fc::ip::endpoint ep; | |
- impl() { | |
- } | |
+ impl() {} | |
- int read_until( char* buffer, char* end, char c = '\n' ) { | |
- char* p = buffer; | |
- // try { | |
- while( p < end && 1 == sock.readsome(p,1) ) { | |
- if( *p == c ) { | |
- *p = '\0'; | |
- return (p - buffer)-1; | |
- } | |
- ++p; | |
- } | |
- // } catch ( ... ) { | |
- // elog("%s", fc::current_exception().diagnostic_information().c_str() ); | |
- //elog( "%s", fc::except_str().c_str() ); | |
- // } | |
- return (p-buffer); | |
+ size_t read_until(std::shared_ptr<char> buffer, size_t buffer_length, char c = '\n') | |
+ { | |
+ size_t offset = 0; | |
+ while (offset < buffer_length && | |
+ sock.readsome(buffer, 1, offset) == 1) | |
+ { | |
+ if (buffer.get()[offset] == c) | |
+ { | |
+ buffer.get()[offset] = 0; | |
+ return offset; | |
+ } | |
+ ++offset; | |
+ } | |
+ return offset; | |
} | |
- fc::http::reply parse_reply() { | |
+ fc::http::reply parse_reply() | |
+ { | |
fc::http::reply rep; | |
- try { | |
- std::vector<char> line(1024*8); | |
- int s = read_until( line.data(), line.data()+line.size(), ' ' ); // HTTP/1.1 | |
- s = read_until( line.data(), line.data()+line.size(), ' ' ); // CODE | |
- rep.status = static_cast<int>(to_int64(fc::string(line.data()))); | |
- s = read_until( line.data(), line.data()+line.size(), '\n' ); // DESCRIPTION | |
+ fc::oexception parsing_exception; | |
+ try | |
+ { | |
+ const size_t buffer_length = 1024 * 8; | |
+ std::shared_ptr<char> line(new char[buffer_length], [](char* p){ delete[] p; }); | |
+ read_until(line, buffer_length, ' '); // HTTP/1.1 | |
+ size_t bytes_read = read_until(line, buffer_length, ' '); // CODE | |
+ rep.status = static_cast<int>(to_int64(fc::string(line.get(), bytes_read))); | |
+ read_until(line, buffer_length, '\n'); // DESCRIPTION | |
- while( (s = read_until( line.data(), line.data()+line.size(), '\n' )) > 1 ) { | |
+ fc::optional<size_t> content_length; | |
+ bool is_chunked = false; | |
+ while( (bytes_read = read_until(line, buffer_length, '\n')) > 1 ) | |
+ { | |
fc::http::header h; | |
- char* end = line.data(); | |
- while( *end != ':' )++end; | |
- h.key = fc::string(line.data(),end); | |
- ++end; // skip ':' | |
- ++end; // skip space | |
- char* skey = end; | |
- while( *end != '\r' ) ++end; | |
- h.val = fc::string(skey,end); | |
+ std::string line_string(line.get(), bytes_read); | |
+ size_t colon_pos = line_string.find(": "); | |
+ if (colon_pos != std::string::npos) | |
+ { | |
+ h.key = line_string.substr(0, colon_pos); | |
+ size_t value_start_pos = colon_pos + 2; | |
+ size_t carriage_return_pos = line_string.find('\r', value_start_pos); | |
+ if (carriage_return_pos != std::string::npos) | |
+ h.val = line_string.substr(value_start_pos, carriage_return_pos - value_start_pos); | |
+ else | |
+ h.val = line_string.substr(value_start_pos); | |
+ } | |
rep.headers.push_back(h); | |
- if( boost::iequals(h.key, "Content-Length") ) { | |
- rep.body.resize( static_cast<size_t>(to_uint64( fc::string(h.val) ) )); | |
+ if( boost::iequals(h.key, "Content-Length") ) | |
+ content_length = static_cast<size_t>(to_uint64( fc::string(h.val) )); | |
+ if( boost::iequals(h.key, "Transfer-Encoding") && | |
+ boost::iequals(fc::string(h.val), "chunked") ) | |
+ is_chunked = true; | |
+ } | |
+ | |
+ if (is_chunked) | |
+ { | |
+ do | |
+ { | |
+ // Chunked means we get a hexadecimal number of bytes on a line, followed by the content | |
+ bytes_read = read_until(line, buffer_length, '\n'); //read chunk length | |
+ std::string line_string(line.get(), bytes_read); | |
+ if (line_string.size() > 0 && line_string[line_string.size() - 1] == '\r') | |
+ line_string.erase(line_string.size() - 1); | |
+ unsigned length; | |
+ if (sscanf(line_string.c_str(), "%x", &length) != 1) | |
+ FC_THROW("Invalid content length: ${length}", ("length", line_string)); | |
+ content_length = length; | |
+ if (*content_length) | |
+ { | |
+ std::shared_ptr<char> temp_data(new char[*content_length], [](char* p){ delete[] p; }); | |
+ sock.read(temp_data, *content_length, 0); | |
+ boost::push_back(rep.body, std::make_pair(temp_data.get(), temp_data.get() + *content_length)); | |
+ read_until(line, buffer_length, '\n'); //discard cr/lf after each chunk | |
+ } | |
} | |
+ while (*content_length != 0); | |
} | |
- if( rep.body.size() ) { | |
- sock.read( rep.body.data(), rep.body.size() ); | |
+ | |
+ if (content_length) | |
+ { | |
+ if (*content_length) | |
+ { | |
+ std::shared_ptr<char> temp_data(new char[*content_length], [](char* p){ delete[] p; }); | |
+ sock.read(temp_data, *content_length, 0); | |
+ boost::push_back(rep.body, std::make_pair(temp_data.get(), temp_data.get() + *content_length)); | |
+ } | |
} | |
+ else //just read until closed if no content length or chunking | |
+ { | |
+ while (true) | |
+ { | |
+ try | |
+ { | |
+ sock.read(line, 1, 0); | |
+ rep.body.push_back(line.get()[0]); | |
+ } | |
+ catch (const fc::canceled_exception&) | |
+ { | |
+ throw; | |
+ } | |
+ catch (const fc::eof_exception&) | |
+ { | |
+ break; | |
+ } | |
+ } | |
+ } | |
+ | |
return rep; | |
- } catch ( fc::exception& e ) { | |
- elog( "${exception}", ("exception",e.to_detail_string() ) ); | |
- sock.close(); | |
- rep.status = http::reply::InternalServerError; | |
- return rep; | |
} | |
+ catch (const fc::canceled_exception&) | |
+ { | |
+ throw; | |
+ } | |
+ catch (const fc::exception& e) | |
+ { | |
+ parsing_exception = e; | |
+ } | |
+ assert(parsing_exception); // the only way we get here is if the last catch falls through | |
+ elog("${exception}", ("exception", parsing_exception->to_detail_string())); | |
+ sock.close(); | |
+ rep.status = http::reply::InternalServerError; | |
+ return rep; | |
} | |
}; | |
@@ -77,54 +147,76 @@ class fc::http::connection::impl | |
namespace fc { namespace http { | |
- connection::connection() | |
- :my( new connection::impl() ){} | |
- connection::~connection(){} | |
+connection::connection() : | |
+ my( new connection::impl() ) | |
+{} | |
+connection::~connection(){} | |
// used for clients | |
-void connection::connect_to( const fc::ip::endpoint& ep ) { | |
+void connection::connect_to( const fc::ip::endpoint& ep ) | |
+{ | |
my->sock.close(); | |
my->sock.connect_to( my->ep = ep ); | |
} | |
http::reply connection::request( const fc::string& method, | |
- const fc::string& url, | |
- const fc::string& body, const headers& he ) { | |
- | |
+ const fc::string& url, | |
+ const fc::string& body, | |
+ const headers& he, | |
+ const fc::string& content_type ) | |
+{ | |
fc::url parsed_url(url); | |
- if( !my->sock.is_open() ) { | |
+ if( !my->sock.is_open() ) | |
+ { | |
wlog( "Re-open socket!" ); | |
my->sock.connect_to( my->ep ); | |
} | |
- try { | |
- fc::stringstream req; | |
- req << method <<" "<<parsed_url.path()->generic_string()<<" HTTP/1.1\r\n"; | |
- req << "Host: "<<*parsed_url.host()<<"\r\n"; | |
- req << "Content-Type: application/json\r\n"; | |
- for( auto i = he.begin(); i != he.end(); ++i ) | |
- { | |
- req << i->key <<": " << i->val<<"\r\n"; | |
- } | |
- if( body.size() ) req << "Content-Length: "<< body.size() << "\r\n"; | |
- req << "\r\n"; | |
- fc::string head = req.str(); | |
+ try | |
+ { | |
+ fc::stringstream req; | |
+ req << method << " " << parsed_url.path()->generic_string() << parsed_url.args_as_string() << " HTTP/1.1\r\n"; | |
+ req << "Host: " << *parsed_url.host() << "\r\n"; | |
+ req << "Content-Type: " << content_type << "\r\n"; | |
+ for( auto i = he.begin(); i != he.end(); ++i ) | |
+ req << i->key << ": " << i->val << "\r\n"; | |
+ if( body.size() ) | |
+ req << "Content-Length: "<< body.size() << "\r\n"; | |
+ req << "\r\n"; | |
- my->sock.write( head.c_str(), head.size() ); | |
- // fc::cerr.write( head.c_str() ); | |
+ { | |
+ fc::string head = req.str(); | |
+ std::shared_ptr<char> write_buffer(new char[head.size()], [](char* p){ delete[] p; }); | |
+ std::copy(head.begin(), head.end(), write_buffer.get()); | |
+ my->sock.write(write_buffer, head.size(), 0); | |
+ //elog("Sending header ${head}", ("head", head)); | |
+ // fc::cerr.write( head.c_str() ); | |
+ } | |
- if( body.size() ) { | |
- my->sock.write( body.c_str(), body.size() ); | |
- // fc::cerr.write( body.c_str() ); | |
- } | |
- // fc::cerr.flush(); | |
+ if( body.size() ) | |
+ { | |
+ std::shared_ptr<char> write_buffer(new char[body.size()], [](char* p){ delete[] p; }); | |
+ std::copy(body.begin(), body.end(), write_buffer.get()); | |
+ my->sock.write(write_buffer, body.size(), 0); | |
+ //elog("Sending body ${body}", ("body", body)); | |
+ //fc::cerr.write( body.c_str() ); | |
+ } | |
+ // fc::cerr.flush(); | |
- return my->parse_reply(); | |
- } catch ( ... ) { | |
- my->sock.close(); | |
- FC_THROW_EXCEPTION( exception, "Error Sending HTTP Request" ); // TODO: provide more info | |
- // return http::reply( http::reply::InternalServerError ); // TODO: replace with connection error | |
+ return my->parse_reply(); | |
+ } | |
+ catch (const fc::canceled_exception&) | |
+ { | |
+ throw; | |
} | |
+ catch (...) | |
+ { | |
+ // fall through | |
+ } | |
+ // the only way we get here is if we encountered catch(...) | |
+ my->sock.close(); | |
+ FC_THROW_EXCEPTION( exception, "Error Sending HTTP Request" ); // TODO: provide more info | |
+ // return http::reply( http::reply::InternalServerError ); // TODO: replace with connection error | |
} | |
// used for servers | |
@@ -132,73 +224,97 @@ fc::tcp_socket& connection::get_socket()const { | |
return my->sock; | |
} | |
-http::request connection::read_request()const { | |
+http::request connection::read_request()const { | |
http::request req; | |
- std::vector<char> line(1024*8); | |
- int s = my->read_until( line.data(), line.data()+line.size(), ' ' ); // METHOD | |
- req.method = line.data(); | |
- s = my->read_until( line.data(), line.data()+line.size(), ' ' ); // PATH | |
- req.path = line.data(); | |
- s = my->read_until( line.data(), line.data()+line.size(), '\n' ); // HTTP/1.0 | |
+ const size_t buffer_length = 1024 * 8; | |
+ std::shared_ptr<char> line(new char[buffer_length], [](char* p){ delete[] p; }); | |
+ size_t bytes_read = my->read_until(line, buffer_length, ' '); // METHOD | |
+ req.method = std::string(line.get(), bytes_read); | |
+ bytes_read = my->read_until(line, buffer_length, ' '); // PATH | |
+ req.path = std::string(line.get(), bytes_read); | |
+ bytes_read = my->read_until(line, buffer_length, '\n'); // HTTP/1.0 | |
- while( (s = my->read_until( line.data(), line.data()+line.size(), '\n' )) > 1 ) { | |
+ while( (bytes_read = my->read_until(line, buffer_length, '\n')) > 1 ) | |
+ { | |
fc::http::header h; | |
- char* end = line.data(); | |
- while( *end != ':' )++end; | |
- h.key = fc::string(line.data(),end); | |
- ++end; // skip ':' | |
- ++end; // skip space | |
- char* skey = end; | |
- while( *end != '\r' ) ++end; | |
- h.val = fc::string(skey,end); | |
- req.headers.push_back(h); | |
- if( boost::iequals(h.key, "Content-Length")) { | |
- auto s = static_cast<size_t>(to_uint64( fc::string(h.val) ) ); | |
- FC_ASSERT( s < 1024*1024 ); | |
- req.body.resize( static_cast<size_t>(to_uint64( fc::string(h.val) ) )); | |
+ std::string line_string(line.get(), bytes_read); | |
+ size_t colon_pos = line_string.find(": "); | |
+ if (colon_pos != std::string::npos) | |
+ { | |
+ h.key = line_string.substr(0, colon_pos); | |
+ size_t value_start_pos = colon_pos + 2; | |
+ size_t carriage_return_pos = line_string.find('\r', value_start_pos); | |
+ if (carriage_return_pos != std::string::npos) | |
+ h.val = line_string.substr(value_start_pos, carriage_return_pos - value_start_pos); | |
+ else | |
+ h.val = line_string.substr(value_start_pos); | |
} | |
- if( boost::iequals(h.key, "Host") ) { | |
- req.domain = h.val; | |
+ req.headers.push_back(h); | |
+ if( boost::iequals(h.key, "Content-Length")) | |
+ { | |
+ size_t content_length = static_cast<size_t>(to_uint64( fc::string(h.val) ) ); | |
+ FC_ASSERT(content_length < 1024*1024); | |
+ req.body.resize( static_cast<size_t>(to_uint64( fc::string(h.val) ) )); | |
} | |
+ if( boost::iequals(h.key, "Host") ) | |
+ req.domain = h.val; | |
} | |
// TODO: some common servers won't give a Content-Length, they'll use | |
// Transfer-Encoding: chunked. handle that here. | |
- if( req.body.size() ) { | |
- my->sock.read( req.body.data(), req.body.size() ); | |
+ if( req.body.size() ) | |
+ { | |
+ std::shared_ptr<char> body_buffer(new char[req.body.size()], [](char* p){ delete[] p; }); | |
+ my->sock.read(body_buffer, req.body.size(), 0); | |
+ std::copy(body_buffer.get(), body_buffer.get() + req.body.size(), req.body.data()); | |
} | |
+ | |
return req; | |
} | |
-fc::string request::get_header( const fc::string& key )const { | |
- for( auto itr = headers.begin(); itr != headers.end(); ++itr ) { | |
- if( boost::iequals(itr->key, key) ) { return itr->val; } | |
- } | |
+fc::string request::get_header( const fc::string& key )const | |
+{ | |
+ for( auto itr = headers.begin(); itr != headers.end(); ++itr ) | |
+ if( boost::iequals(itr->key, key) ) | |
+ return itr->val; | |
return fc::string(); | |
} | |
-std::vector<header> parse_urlencoded_params( const fc::string& f ) { | |
+ | |
+std::vector<header> parse_urlencoded_params( const fc::string& f ) | |
+{ | |
int num_args = 0; | |
- for( size_t i = 0; i < f.size(); ++i ) { | |
- if( f[i] == '=' ) ++num_args; | |
- } | |
+ for( size_t i = 0; i < f.size(); ++i ) | |
+ if( f[i] == '=' ) | |
+ ++num_args; | |
+ | |
std::vector<header> h(num_args); | |
int arg = 0; | |
- for( size_t i = 0; i < f.size(); ++i ) { | |
- while( f[i] != '=' && i < f.size() ) { | |
- if( f[i] == '%' ) { | |
+ for( size_t i = 0; i < f.size(); ++i ) | |
+ { | |
+ while( f[i] != '=' && i < f.size() ) | |
+ { | |
+ if( f[i] == '%' ) | |
+ { | |
h[arg].key += char((fc::from_hex(f[i+1]) << 4) | fc::from_hex(f[i+2])); | |
i += 3; | |
- } else { | |
- h[arg].key += f[i]; | |
- ++i; | |
+ } | |
+ else | |
+ { | |
+ h[arg].key += f[i]; | |
+ ++i; | |
} | |
} | |
++i; | |
- while( i < f.size() && f[i] != '&' ) { | |
- if( f[i] == '%' ) { | |
+ | |
+ while( i < f.size() && f[i] != '&' ) | |
+ { | |
+ if( f[i] == '%' ) | |
+ { | |
h[arg].val += char((fc::from_hex(f[i+1]) << 4) | fc::from_hex(f[i+2])); | |
i += 3; | |
- } else { | |
+ } | |
+ else | |
+ { | |
h[arg].val += f[i] == '+' ? ' ' : f[i]; | |
++i; | |
} | |
diff --git a/src/network/http/http_server.cpp b/src/network/http/http_server.cpp | |
index 648fc54..0fb399d 100644 | |
--- a/src/network/http/http_server.cpp | |
+++ b/src/network/http/http_server.cpp | |
@@ -16,24 +16,38 @@ namespace fc { namespace http { | |
:body_bytes_sent(0),body_length(0),con(c),handle_next_req(cont) | |
{} | |
- void send_header() { | |
+ void send_header() | |
+ { | |
//ilog( "sending header..." ); | |
fc::stringstream ss; | |
ss << "HTTP/1.1 " << rep.status << " "; | |
- switch( rep.status ) { | |
- case fc::http::reply::OK: ss << "OK\r\n"; break; | |
- case fc::http::reply::RecordCreated: ss << "Record Created\r\n"; break; | |
- case fc::http::reply::NotFound: ss << "Not Found\r\n"; break; | |
- case fc::http::reply::Found: ss << "Found\r\n"; break; | |
- default: ss << "Internal Server Error\r\n"; break; | |
- } | |
- for( uint32_t i = 0; i < rep.headers.size(); ++i ) { | |
- ss << rep.headers[i].key <<": "<<rep.headers[i].val <<"\r\n"; | |
+ switch( rep.status ) | |
+ { | |
+ case fc::http::reply::OK: | |
+ ss << "OK\r\n"; | |
+ break; | |
+ case fc::http::reply::RecordCreated: | |
+ ss << "Record Created\r\n"; | |
+ break; | |
+ case fc::http::reply::NotFound: | |
+ ss << "Not Found\r\n"; | |
+ break; | |
+ case fc::http::reply::Found: | |
+ ss << "Found\r\n"; | |
+ break; | |
+ default: | |
+ ss << "Internal Server Error\r\n"; | |
+ break; | |
} | |
- ss << "Content-Length: "<<body_length<<"\r\n\r\n"; | |
- auto s = ss.str(); | |
+ for( uint32_t i = 0; i < rep.headers.size(); ++i ) | |
+ ss << rep.headers[i].key << ": " << rep.headers[i].val << "\r\n"; | |
+ ss << "Content-Length: " << body_length << "\r\n\r\n"; | |
+ std::string s = ss.str(); | |
+ std::shared_ptr<char> write_buffer(new char[s.size()], [](char* p){ delete[] p; }); | |
+ std::copy(s.begin(), s.end(), write_buffer.get()); | |
+ | |
//fc::cerr<<s<<"\n"; | |
- con->get_socket().write( s.c_str(), s.size() ); | |
+ con->get_socket().write(write_buffer, s.size(), 0); | |
} | |
http::reply rep; | |
@@ -184,7 +198,9 @@ namespace fc { namespace http { | |
my->send_header(); | |
} | |
my->body_bytes_sent += len; | |
- my->con->get_socket().write( data, static_cast<size_t>(len) ); | |
+ std::shared_ptr<char> write_buffer(new char[len], [](char* p){ delete[] p; }); | |
+ std::copy(data, data + len, write_buffer.get()); | |
+ my->con->get_socket().write(write_buffer, static_cast<size_t>(len), 0); | |
if( my->body_bytes_sent == int64_t(my->body_length) ) { | |
if( false || my->handle_next_req ) { | |
ilog( "handle next request..." ); | |
diff --git a/src/network/http/websocket.cpp b/src/network/http/websocket.cpp | |
index b8a3d3c..ef880ff 100644 | |
--- a/src/network/http/websocket.cpp | |
+++ b/src/network/http/websocket.cpp | |
@@ -1,19 +1,8 @@ | |
#include <fc/network/http/websocket.hpp> | |
- | |
-#ifndef WIN32 | |
-// websocket++ currently does not build correctly with permessage deflate enabled | |
-// since chrome does not work with websocketpp's implementation of permessage-deflate | |
-// yet, I'm just disabling it on windows instead of trying to fix the build error. | |
-# define ENABLE_WEBSOCKET_PERMESSAGE_DEFLATE | |
-#endif | |
- | |
#include <websocketpp/config/asio_client.hpp> | |
#include <websocketpp/config/asio.hpp> | |
#include <websocketpp/server.hpp> | |
#include <websocketpp/config/asio_client.hpp> | |
-#ifdef ENABLE_WEBSOCKET_PERMESSAGE_DEFLATE | |
-# include <websocketpp/extensions/permessage_deflate/enabled.hpp> | |
-#endif | |
#include <websocketpp/client.hpp> | |
#include <websocketpp/logger/stub.hpp> | |
@@ -30,11 +19,12 @@ | |
namespace fc { namespace http { | |
namespace detail { | |
+ | |
struct asio_with_stub_log : public websocketpp::config::asio { | |
+ | |
typedef asio_with_stub_log type; | |
typedef asio base; | |
- //// All boilerplate copying the base class's config, except as noted | |
typedef base::concurrency_type concurrency_type; | |
typedef base::request_type request_type; | |
@@ -43,8 +33,15 @@ namespace fc { namespace http { | |
typedef base::message_type message_type; | |
typedef base::con_msg_manager_type con_msg_manager_type; | |
typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; | |
- | |
- /// Custom Logging policies, use do-nothing log::stub instead of log::basic | |
+ | |
+ /// Custom Logging policies | |
+ /*typedef websocketpp::log::syslog<concurrency_type, | |
+ websocketpp::log::elevel> elog_type; | |
+ typedef websocketpp::log::syslog<concurrency_type, | |
+ websocketpp::log::alevel> alog_type; | |
+ */ | |
+ //typedef base::alog_type alog_type; | |
+ //typedef base::elog_type elog_type; | |
typedef websocketpp::log::stub elog_type; | |
typedef websocketpp::log::stub alog_type; | |
@@ -63,16 +60,13 @@ namespace fc { namespace http { | |
typedef websocketpp::transport::asio::endpoint<transport_config> | |
transport_type; | |
- // override default value of 5 sec timeout | |
static const long timeout_open_handshake = 0; | |
}; | |
+ struct asio_tls_with_stub_log : public websocketpp::config::asio_tls { | |
-#ifdef ENABLE_WEBSOCKET_PERMESSAGE_DEFLATE | |
- struct asio_with_stub_log_and_deflate : public websocketpp::config::asio { | |
- typedef asio_with_stub_log_and_deflate type; | |
- typedef asio base; | |
+ typedef asio_with_stub_log type; | |
+ typedef asio_tls base; | |
- //// All boilerplate copying the base class's config, except as noted | |
typedef base::concurrency_type concurrency_type; | |
typedef base::request_type request_type; | |
@@ -82,7 +76,14 @@ namespace fc { namespace http { | |
typedef base::con_msg_manager_type con_msg_manager_type; | |
typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; | |
- /// Custom Logging policies, use do-nothing log::stub instead of log::basic | |
+ /// Custom Logging policies | |
+ /*typedef websocketpp::log::syslog<concurrency_type, | |
+ websocketpp::log::elevel> elog_type; | |
+ typedef websocketpp::log::syslog<concurrency_type, | |
+ websocketpp::log::alevel> alog_type; | |
+ */ | |
+ //typedef base::alog_type alog_type; | |
+ //typedef base::elog_type elog_type; | |
typedef websocketpp::log::stub elog_type; | |
typedef websocketpp::log::stub alog_type; | |
@@ -94,97 +95,54 @@ namespace fc { namespace http { | |
typedef type::elog_type elog_type; | |
typedef type::request_type request_type; | |
typedef type::response_type response_type; | |
- typedef websocketpp::transport::asio::basic_socket::endpoint | |
- socket_type; | |
+ typedef websocketpp::transport::asio::tls_socket::endpoint socket_type; | |
}; | |
typedef websocketpp::transport::asio::endpoint<transport_config> | |
transport_type; | |
- /// enable the permessage_compress extension | |
- struct permessage_deflate_config {}; | |
- typedef websocketpp::extensions::permessage_deflate::enabled | |
- <permessage_deflate_config> permessage_deflate_type; | |
- | |
- // override default value of 5 sec timeout | |
static const long timeout_open_handshake = 0; | |
}; | |
-#endif ENABLE_WEBSOCKET_PERMESSAGE_DEFLATE | |
- | |
struct asio_tls_stub_log : public websocketpp::config::asio_tls { | |
- typedef asio_tls_stub_log type; | |
- typedef asio_tls base; | |
+ typedef asio_tls_stub_log type; | |
+ typedef asio_tls base; | |
- //// All boilerplate copying the base class's config, except as noted | |
- typedef base::concurrency_type concurrency_type; | |
+ typedef base::concurrency_type concurrency_type; | |
- typedef base::request_type request_type; | |
- typedef base::response_type response_type; | |
+ typedef base::request_type request_type; | |
+ typedef base::response_type response_type; | |
- typedef base::message_type message_type; | |
- typedef base::con_msg_manager_type con_msg_manager_type; | |
- typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; | |
+ typedef base::message_type message_type; | |
+ typedef base::con_msg_manager_type con_msg_manager_type; | |
+ typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; | |
- /// Custom Logging policies, use do-nothing log::stub instead of log::basic | |
- typedef websocketpp::log::stub elog_type; | |
- typedef websocketpp::log::stub alog_type; | |
+ //typedef base::alog_type alog_type; | |
+ //typedef base::elog_type elog_type; | |
+ typedef websocketpp::log::stub elog_type; | |
+ typedef websocketpp::log::stub alog_type; | |
- typedef base::rng_type rng_type; | |
+ typedef base::rng_type rng_type; | |
- struct transport_config : public base::transport_config { | |
- typedef type::concurrency_type concurrency_type; | |
- typedef type::alog_type alog_type; | |
- typedef type::elog_type elog_type; | |
- typedef type::request_type request_type; | |
- typedef type::response_type response_type; | |
- typedef websocketpp::transport::asio::tls_socket::endpoint socket_type; | |
- }; | |
+ struct transport_config : public base::transport_config { | |
+ typedef type::concurrency_type concurrency_type; | |
+ typedef type::alog_type alog_type; | |
+ typedef type::elog_type elog_type; | |
+ typedef type::request_type request_type; | |
+ typedef type::response_type response_type; | |
+ typedef websocketpp::transport::asio::tls_socket::endpoint socket_type; | |
+ }; | |
- typedef websocketpp::transport::asio::endpoint<transport_config> | |
- transport_type; | |
+ typedef websocketpp::transport::asio::endpoint<transport_config> | |
+ transport_type; | |
}; | |
-#ifdef ENABLE_WEBSOCKET_PERMESSAGE_DEFLATE | |
- struct asio_tls_stub_log_and_deflate : public websocketpp::config::asio_tls { | |
- typedef asio_tls_stub_log_and_deflate type; | |
- typedef asio_tls base; | |
- //// All boilerplate copying the base class's config, except as noted | |
- typedef base::concurrency_type concurrency_type; | |
- typedef base::request_type request_type; | |
- typedef base::response_type response_type; | |
- typedef base::message_type message_type; | |
- typedef base::con_msg_manager_type con_msg_manager_type; | |
- typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; | |
- | |
- /// Custom Logging policies, use do-nothing log::stub instead of log::basic | |
- typedef websocketpp::log::stub elog_type; | |
- typedef websocketpp::log::stub alog_type; | |
- | |
- typedef base::rng_type rng_type; | |
- | |
- struct transport_config : public base::transport_config { | |
- typedef type::concurrency_type concurrency_type; | |
- typedef type::alog_type alog_type; | |
- typedef type::elog_type elog_type; | |
- typedef type::request_type request_type; | |
- typedef type::response_type response_type; | |
- typedef websocketpp::transport::asio::tls_socket::endpoint socket_type; | |
- }; | |
- | |
- typedef websocketpp::transport::asio::endpoint<transport_config> | |
- transport_type; | |
- | |
- /// enable the permessage_compress extension | |
- struct permessage_deflate_config {}; | |
- typedef websocketpp::extensions::permessage_deflate::enabled | |
- <permessage_deflate_config> permessage_deflate_type; | |
- }; | |
-#endif | |
using websocketpp::connection_hdl; | |
+ typedef websocketpp::server<asio_with_stub_log> websocket_server_type; | |
+ typedef websocketpp::server<asio_tls_stub_log> websocket_tls_server_type; | |
template<typename T> | |
class websocket_connection_impl : public websocket_connection | |
@@ -215,19 +173,7 @@ namespace fc { namespace http { | |
typedef websocketpp::lib::shared_ptr<boost::asio::ssl::context> context_ptr; | |
- class abstract_websocket_server | |
- { | |
- public: | |
- virtual ~abstract_websocket_server() {} | |
- | |
- virtual void on_connection( const on_connection_handler& handler) = 0; | |
- virtual void listen( uint16_t port ) = 0; | |
- virtual void listen( const fc::ip::endpoint& ep ) = 0; | |
- virtual void start_accept() = 0; | |
- }; | |
- | |
- template <typename config> | |
- class websocket_server_impl : public abstract_websocket_server | |
+ class websocket_server_impl | |
{ | |
public: | |
websocket_server_impl() | |
@@ -239,15 +185,15 @@ namespace fc { namespace http { | |
_server.set_reuse_addr(true); | |
_server.set_open_handler( [&]( connection_hdl hdl ){ | |
_server_thread.async( [&](){ | |
- websocket_connection_ptr new_con = std::make_shared<websocket_connection_impl<typename websocketpp::server<config>::connection_ptr>>( _server.get_con_from_hdl(hdl) ); | |
+ auto new_con = std::make_shared<websocket_connection_impl<websocket_server_type::connection_ptr>>( _server.get_con_from_hdl(hdl) ); | |
_on_connection( _connections[hdl] = new_con ); | |
}).wait(); | |
}); | |
- _server.set_message_handler( [&]( connection_hdl hdl, typename websocketpp::server<config>::message_ptr msg ){ | |
+ _server.set_message_handler( [&]( connection_hdl hdl, websocket_server_type::message_ptr msg ){ | |
_server_thread.async( [&](){ | |
auto current_con = _connections.find(hdl); | |
assert( current_con != _connections.end() ); | |
- //wdump(("server")(msg->get_payload())); | |
+ idump(("server")(msg->get_payload())); | |
//std::cerr<<"recv: "<<msg->get_payload()<<"\n"; | |
auto payload = msg->get_payload(); | |
std::shared_ptr<websocket_connection> con = current_con->second; | |
@@ -260,20 +206,20 @@ namespace fc { namespace http { | |
_server.set_http_handler( [&]( connection_hdl hdl ){ | |
_server_thread.async( [&](){ | |
- auto current_con = std::make_shared<websocket_connection_impl<typename websocketpp::server<config>::connection_ptr>>( _server.get_con_from_hdl(hdl) ); | |
+ auto current_con = std::make_shared<websocket_connection_impl<websocket_server_type::connection_ptr>>( _server.get_con_from_hdl(hdl) ); | |
_on_connection( current_con ); | |
auto con = _server.get_con_from_hdl(hdl); | |
con->defer_http_response(); | |
std::string request_body = con->get_request_body(); | |
- //wdump(("server")(request_body)); | |
+ wdump(("server")(request_body)); | |
fc::async([current_con, request_body, con] { | |
std::string response = current_con->on_http(request_body); | |
- con->set_body( response ); | |
- con->set_status( websocketpp::http::status_code::ok ); | |
+ con->set_body( response ); | |
+ con->set_status( websocketpp::http::status_code::ok ); | |
con->send_http_response(); | |
- current_con->closed(); | |
+ current_con->closed(); | |
}, "call on_http"); | |
}).wait(); | |
}); | |
@@ -328,62 +274,132 @@ namespace fc { namespace http { | |
if( _closed ) _closed->wait(); | |
} | |
- void on_connection( const on_connection_handler& handler ) override | |
- { | |
- _on_connection = handler; | |
- } | |
- | |
- void listen( uint16_t port ) override | |
- { | |
- _server.listen(port); | |
- } | |
- | |
- void listen( const fc::ip::endpoint& ep ) override | |
- { | |
- _server.listen( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4(uint32_t(ep.get_address())),ep.port()) ); | |
- } | |
- | |
- void start_accept() override | |
- { | |
- _server.start_accept(); | |
- } | |
- | |
typedef std::map<connection_hdl, websocket_connection_ptr,std::owner_less<connection_hdl> > con_map; | |
con_map _connections; | |
fc::thread& _server_thread; | |
- websocketpp::server<config> _server; | |
+ websocket_server_type _server; | |
on_connection_handler _on_connection; | |
fc::promise<void>::ptr _closed; | |
uint32_t _pending_messages = 0; | |
}; | |
- template <typename config> | |
- class websocket_tls_server_impl : public websocket_server_impl<config> | |
+ class websocket_tls_server_impl | |
{ | |
public: | |
websocket_tls_server_impl( const string& server_pem, const string& ssl_password ) | |
+ :_server_thread( fc::thread::current() ) | |
{ | |
- this->_server.set_tls_init_handler( [=]( websocketpp::connection_hdl hdl ) -> context_ptr { | |
- context_ptr ctx = websocketpp::lib::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::tlsv1); | |
- try { | |
- ctx->set_options(boost::asio::ssl::context::default_workarounds | | |
- boost::asio::ssl::context::no_sslv2 | | |
- boost::asio::ssl::context::no_sslv3 | | |
- boost::asio::ssl::context::single_dh_use); | |
- ctx->set_password_callback([=](std::size_t max_length, boost::asio::ssl::context::password_purpose){ return ssl_password;}); | |
- ctx->use_certificate_chain_file(server_pem); | |
- ctx->use_private_key_file(server_pem, boost::asio::ssl::context::pem); | |
- } catch (std::exception& e) { | |
- std::cout << e.what() << std::endl; | |
- } | |
- return ctx; | |
+ //if( server_pem.size() ) | |
+ { | |
+ _server.set_tls_init_handler( [=]( websocketpp::connection_hdl hdl ) -> context_ptr { | |
+ context_ptr ctx = websocketpp::lib::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::tlsv1); | |
+ try { | |
+ ctx->set_options(boost::asio::ssl::context::default_workarounds | | |
+ boost::asio::ssl::context::no_sslv2 | | |
+ boost::asio::ssl::context::no_sslv3 | | |
+ boost::asio::ssl::context::single_dh_use); | |
+ ctx->set_password_callback([=](std::size_t max_length, boost::asio::ssl::context::password_purpose){ return ssl_password;}); | |
+ ctx->use_certificate_chain_file(server_pem); | |
+ ctx->use_private_key_file(server_pem, boost::asio::ssl::context::pem); | |
+ } catch (std::exception& e) { | |
+ std::cout << e.what() << std::endl; | |
+ } | |
+ return ctx; | |
+ }); | |
+ } | |
+ | |
+ _server.clear_access_channels( websocketpp::log::alevel::all ); | |
+ _server.init_asio(&fc::asio::default_io_service()); | |
+ _server.set_reuse_addr(true); | |
+ _server.set_open_handler( [&]( connection_hdl hdl ){ | |
+ _server_thread.async( [&](){ | |
+ auto new_con = std::make_shared<websocket_connection_impl<websocket_tls_server_type::connection_ptr>>( _server.get_con_from_hdl(hdl) ); | |
+ _on_connection( _connections[hdl] = new_con ); | |
+ }).wait(); | |
+ }); | |
+ _server.set_message_handler( [&]( connection_hdl hdl, websocket_server_type::message_ptr msg ){ | |
+ _server_thread.async( [&](){ | |
+ auto current_con = _connections.find(hdl); | |
+ assert( current_con != _connections.end() ); | |
+ auto received = msg->get_payload(); | |
+ std::shared_ptr<websocket_connection> con = current_con->second; | |
+ fc::async([con,received](){ con->on_message( received ); }); | |
+ }).wait(); | |
+ }); | |
+ | |
+ _server.set_http_handler( [&]( connection_hdl hdl ){ | |
+ _server_thread.async( [&](){ | |
+ | |
+ auto current_con = std::make_shared<websocket_connection_impl<websocket_tls_server_type::connection_ptr>>( _server.get_con_from_hdl(hdl) ); | |
+ try{ | |
+ _on_connection( current_con ); | |
+ | |
+ auto con = _server.get_con_from_hdl(hdl); | |
+ idump(("server")(con->get_request_body())); | |
+ auto response = current_con->on_http( con->get_request_body() ); | |
+ | |
+ con->set_body( response ); | |
+ con->set_status( websocketpp::http::status_code::ok ); | |
+ } catch ( const fc::exception& e ) | |
+ { | |
+ edump((e.to_detail_string())); | |
+ } | |
+ current_con->closed(); | |
+ | |
+ }).wait(); | |
+ }); | |
+ | |
+ _server.set_close_handler( [&]( connection_hdl hdl ){ | |
+ _server_thread.async( [&](){ | |
+ _connections[hdl]->closed(); | |
+ _connections.erase( hdl ); | |
+ }).wait(); | |
+ }); | |
+ | |
+ _server.set_fail_handler( [&]( connection_hdl hdl ){ | |
+ if( _server.is_listening() ) | |
+ { | |
+ _server_thread.async( [&](){ | |
+ if( _connections.find(hdl) != _connections.end() ) | |
+ { | |
+ _connections[hdl]->closed(); | |
+ _connections.erase( hdl ); | |
+ } | |
+ }).wait(); | |
+ } | |
}); | |
} | |
+ ~websocket_tls_server_impl() | |
+ { | |
+ if( _server.is_listening() ) | |
+ _server.stop_listening(); | |
+ auto cpy_con = _connections; | |
+ for( auto item : cpy_con ) | |
+ _server.close( item.first, 0, "server exit" ); | |
+ } | |
+ | |
+ typedef std::map<connection_hdl, websocket_connection_ptr,std::owner_less<connection_hdl> > con_map; | |
+ | |
+ con_map _connections; | |
+ fc::thread& _server_thread; | |
+ websocket_tls_server_type _server; | |
+ on_connection_handler _on_connection; | |
+ fc::promise<void>::ptr _closed; | |
}; | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
typedef websocketpp::client<asio_with_stub_log> websocket_client_type; | |
typedef websocketpp::client<asio_tls_stub_log> websocket_tls_client_type; | |
@@ -401,7 +417,7 @@ namespace fc { namespace http { | |
_client.clear_access_channels( websocketpp::log::alevel::all ); | |
_client.set_message_handler( [&]( connection_hdl hdl, message_ptr msg ){ | |
_client_thread.async( [&](){ | |
- wdump((msg->get_payload())); | |
+ idump((msg->get_payload())); | |
//std::cerr<<"recv: "<<msg->get_payload()<<"\n"; | |
auto received = msg->get_payload(); | |
fc::async( [=](){ | |
@@ -456,7 +472,7 @@ namespace fc { namespace http { | |
_client.clear_access_channels( websocketpp::log::alevel::all ); | |
_client.set_message_handler( [&]( connection_hdl hdl, message_ptr msg ){ | |
_client_thread.async( [&](){ | |
- wdump((msg->get_payload())); | |
+ idump((msg->get_payload())); | |
_connection->on_message( msg->get_payload() ); | |
}).wait(); | |
}); | |
@@ -465,7 +481,7 @@ namespace fc { namespace http { | |
{ | |
try { | |
_client_thread.async( [&](){ | |
- wlog(". ${p}", ("p",uint64_t(_connection.get()))); | |
+ ilog(". ${p}", ("p",uint64_t(_connection.get()))); | |
if( !_shutting_down && !_closed && _connection ) | |
_connection->closed(); | |
_connection.reset(); | |
@@ -509,7 +525,7 @@ namespace fc { namespace http { | |
{ | |
if(_connection ) | |
{ | |
- wlog("."); | |
+ ilog("."); | |
_shutting_down = true; | |
_connection->close(0, "client closed"); | |
_closed->wait(); | |
@@ -526,79 +542,57 @@ namespace fc { namespace http { | |
} // namespace detail | |
- websocket_server::websocket_server(bool enable_permessage_deflate /* = true */) : | |
- my( | |
-#ifdef ENABLE_WEBSOCKET_PERMESSAGE_DEFLATE | |
- enable_permessage_deflate ? | |
- (detail::abstract_websocket_server*)new detail::websocket_server_impl<detail::asio_with_stub_log_and_deflate> : | |
-#endif | |
- (detail::abstract_websocket_server*)new detail::websocket_server_impl<detail::asio_with_stub_log> ) | |
- { | |
-#ifndef ENABLE_WEBSOCKET_PERMESSAGE_DEFLATE | |
- if (enable_permessage_deflate) | |
- elog("Websocket permessage-deflate requested but not enabled during compile"); | |
-#endif | |
- } | |
+ websocket_server::websocket_server():my( new detail::websocket_server_impl() ) {} | |
websocket_server::~websocket_server(){} | |
void websocket_server::on_connection( const on_connection_handler& handler ) | |
{ | |
- my->on_connection(handler); | |
+ my->_on_connection = handler; | |
} | |
void websocket_server::listen( uint16_t port ) | |
{ | |
- my->listen(port); | |
+ my->_server.listen(port); | |
} | |
void websocket_server::listen( const fc::ip::endpoint& ep ) | |
{ | |
- my->listen(ep); | |
+ my->_server.listen( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4(uint32_t(ep.get_address())),ep.port()) ); | |
} | |
void websocket_server::start_accept() { | |
- my->start_accept(); | |
+ my->_server.start_accept(); | |
} | |
- websocket_tls_server::websocket_tls_server(const string& server_pem, | |
- const string& ssl_password, | |
- bool enable_permessage_deflate /* = true */) : | |
- my( | |
-#ifdef ENABLE_WEBSOCKET_PERMESSAGE_DEFLATE | |
- enable_permessage_deflate ? | |
- (detail::abstract_websocket_server*)new detail::websocket_tls_server_impl<detail::asio_tls_stub_log_and_deflate>(server_pem, ssl_password) : | |
-#endif | |
- (detail::abstract_websocket_server*)new detail::websocket_tls_server_impl<detail::asio_tls_stub_log>(server_pem, ssl_password) ) | |
- { | |
-#ifndef ENABLE_WEBSOCKET_PERMESSAGE_DEFLATE | |
- if (enable_permessage_deflate) | |
- elog("Websocket permessage-deflate requested but not enabled during compile"); | |
-#endif | |
- } | |
+ websocket_tls_server::websocket_tls_server( const string& server_pem, const string& ssl_password ):my( new detail::websocket_tls_server_impl(server_pem, ssl_password) ) {} | |
websocket_tls_server::~websocket_tls_server(){} | |
void websocket_tls_server::on_connection( const on_connection_handler& handler ) | |
{ | |
- my->on_connection(handler); | |
+ my->_on_connection = handler; | |
} | |
void websocket_tls_server::listen( uint16_t port ) | |
{ | |
- my->listen(port); | |
+ my->_server.listen(port); | |
} | |
void websocket_tls_server::listen( const fc::ip::endpoint& ep ) | |
{ | |
- my->listen(ep); | |
+ my->_server.listen( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4(uint32_t(ep.get_address())),ep.port()) ); | |
} | |
- void websocket_tls_server::start_accept() | |
- { | |
- my->start_accept(); | |
+ void websocket_tls_server::start_accept() { | |
+ my->_server.start_accept(); | |
} | |
+ websocket_tls_client::websocket_tls_client():my( new detail::websocket_tls_client_impl() ) {} | |
+ websocket_tls_client::~websocket_tls_client(){ } | |
+ | |
+ | |
+ | |
websocket_client::websocket_client():my( new detail::websocket_client_impl() ),smy(new detail::websocket_tls_client_impl()) {} | |
websocket_client::~websocket_client(){ } | |
diff --git a/src/network/url.cpp b/src/network/url.cpp | |
index 635dd2d..d7d6339 100644 | |
--- a/src/network/url.cpp | |
+++ b/src/network/url.cpp | |
@@ -60,9 +60,22 @@ namespace fc | |
_path = fc::path( "/" ) / _lpath; | |
#endif | |
std::getline( ss, _largs ); | |
- if( _args.valid() && _args->size() ) | |
+ if( _largs.size() ) | |
{ | |
- // TODO: args = fc::move(_args); | |
+ mutable_variant_object new_args; | |
+ std::istringstream args_stream(_largs); | |
+ std::string _larg; | |
+ while (std::getline(args_stream, _larg, '&')) | |
+ { | |
+ std::string::size_type equals_pos = _larg.find('='); | |
+ if (equals_pos != std::string::npos) | |
+ { | |
+ std::string key = _larg.substr(0, equals_pos); | |
+ std::string value = _larg.substr(equals_pos + 1); | |
+ new_args[key] = value; | |
+ } | |
+ } | |
+ _args = new_args; | |
} | |
} | |
@@ -88,18 +101,21 @@ namespace fc | |
url::operator string()const | |
{ | |
std::stringstream ss; | |
- ss<<my->_proto<<"://"; | |
- if( my->_user.valid() ) { | |
+ ss << my->_proto << "://"; | |
+ if( my->_user.valid() ) | |
+ { | |
ss << *my->_user; | |
- if( my->_pass.valid() ) { | |
- ss<<":"<<*my->_pass; | |
- } | |
- ss<<"@"; | |
+ if( my->_pass.valid() ) | |
+ ss << ":" << *my->_pass; | |
+ ss << "@"; | |
} | |
- if( my->_host.valid() ) ss<<*my->_host; | |
- if( my->_port.valid() ) ss<<":"<<*my->_port; | |
- if( my->_path.valid() ) ss<<my->_path->generic_string(); | |
- // if( my->_args ) ss<<"?"<<*my->_args; | |
+ if( my->_host.valid() ) | |
+ ss << *my->_host; | |
+ if( my->_port.valid() ) | |
+ ss << ":" << *my->_port; | |
+ if( my->_path.valid() ) | |
+ ss << my->_path->generic_string(); | |
+ ss << args_as_string(); | |
return ss.str(); | |
} | |
@@ -189,6 +205,21 @@ namespace fc | |
{ | |
return my->_args; | |
} | |
+ std::string url::args_as_string()const | |
+ { | |
+ std::ostringstream ss; | |
+ if( my->_args ) | |
+ { | |
+ bool first = true; | |
+ for (auto iter = my->_args->begin(); iter != my->_args->end(); ++iter) | |
+ { | |
+ ss << (first ? "?" : "&"); | |
+ first = false; | |
+ ss << iter->key() << "=" << iter->value().as_string(); | |
+ } | |
+ } | |
+ return ss.str(); | |
+ } | |
fc::optional<uint16_t> url::port()const | |
{ | |
return my->_port; | |
diff --git a/tests/compress/compress.cpp b/tests/compress/compress.cpp | |
index a30255e..a155d54 100644 | |
--- a/tests/compress/compress.cpp | |
+++ b/tests/compress/compress.cpp | |
@@ -38,9 +38,9 @@ BOOST_AUTO_TEST_CASE(smaz_test) | |
BOOST_CHECK_EQUAL( decomp, line ); | |
} | |
+#ifndef FC_USE_FULL_ZLIB | |
extern "C" { | |
- | |
enum | |
{ | |
TINFL_FLAG_PARSE_ZLIB_HEADER = 1, | |
@@ -85,5 +85,6 @@ BOOST_AUTO_TEST_CASE(zlib_test) | |
std::string decomp = zlib_decompress( compressed ); | |
BOOST_CHECK_EQUAL( decomp, line ); | |
} | |
+#endif | |
BOOST_AUTO_TEST_SUITE_END() | |
diff --git a/vendor/websocketpp b/vendor/websocketpp | |
index 378437a..c5510d6 160000 | |
--- a/vendor/websocketpp | |
+++ b/vendor/websocketpp | |
@@ -1 +1 @@ | |
-Subproject commit 378437aecdcb1dfe62096ffd5d944bf1f640ccc3 | |
+Subproject commit c5510d6de04917812b910a8dd44735c1f17061d9 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment