From fb6e18ec7b5ecd5cd4cea5a6ac052d840f7b5ffe Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Wed, 24 Oct 2012 00:54:03 -0400 Subject: [PATCH] Implemented http connection + shared_impl Shared Impl provides Java/C# style reference semantics for C++ types. --- CMakeLists.txt | 1 + include/fc/http/connection.hpp | 57 ++++++++++++ include/fc/shared_impl.cpp | 82 +++++++++++++++++ include/fc/shared_impl.hpp | 161 +++++++++++++++++++++++++++++++++ include/fc/shared_ptr.hpp | 4 +- src/http_connection.cpp | 98 ++++++++++++++++++++ 6 files changed, 400 insertions(+), 3 deletions(-) create mode 100644 include/fc/http/connection.hpp create mode 100644 include/fc/shared_impl.cpp create mode 100644 include/fc/shared_impl.hpp create mode 100644 src/http_connection.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ea4172b..5d26fc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ include_directories( ${Boost_INCLUDE_DIR} ) include_directories( include ) set( sources + src/http_connection.cpp src/value.cpp src/lexical_cast.cpp src/spin_lock.cpp diff --git a/include/fc/http/connection.hpp b/include/fc/http/connection.hpp new file mode 100644 index 0000000..109923d --- /dev/null +++ b/include/fc/http/connection.hpp @@ -0,0 +1,57 @@ +#pragma once +#include +#include +#include + +namespace fc { + namespace ip { class endpoint; } + class tcp_socket; + + namespace http { + + struct header { + fc::string key; + fc::string val; + }; + + struct reply { + enum status_code { + OK = 200, + NotFound = 404, + Found = 302, + InternalServerError = 500 + }; + int status; + fc::vector
headers; + fc::vector body; + }; + + struct request { + fc::string method; + fc::string domain; + fc::string path; + fc::vector
headers; + fc::vector body; + }; + + /** + * Connections have reference semantics, all copies refer to the same + * underlying socket. + */ + class connection { + public: + // 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 ); + + // used for servers + fc::tcp_socket& get_socket(); + + http::request read_request(); + void send_reply( const http::reply& ); + + FC_REFERENCE_TYPE(connection) + }; + +} } // fc::http + diff --git a/include/fc/shared_impl.cpp b/include/fc/shared_impl.cpp new file mode 100644 index 0000000..47e97b8 --- /dev/null +++ b/include/fc/shared_impl.cpp @@ -0,0 +1,82 @@ +#pragma once +#include + +namespace fc { +template +typename shared_impl::impl& shared_impl::operator* ()const { return *_impl; } + +template +typename shared_impl::impl* shared_impl::operator-> ()const { return _impl.get(); } + +template +template +shared_impl::shared_impl( U&& u ):_impl(fc::forward(u)){} + +template +shared_impl::shared_impl( const shared_impl& u ):_impl(u._impl){} + +template +shared_impl::shared_impl( shared_impl&& u ):_impl(fc::move(u._impl)){} + +template +shared_impl& shared_impl::operator=( shared_impl&& u ) { + fc::swap(_impl,u._impl); + return *this; +} +template +shared_impl& shared_impl::operator=( const shared_impl& u ) { + _impl = u._impl; + return *this; +} + +template +bool shared_impl::operator !()const { return !_impl; } + +template +shared_impl::~shared_impl(){} + +} + +#define FC_REFERENCE_TYPE_IMPL( TYPE ) \ +template \ +TYPE::TYPE( A1&& a1 ) \ +:my( new typename fc::shared_impl::impl( fc::forward(a1) ) ){}\ +template \ +TYPE::TYPE( A1&& a1, A2&& a2 ) \ +:my( new typename fc::shared_impl::impl( fc::forward(a1), fc::forward(a2) ) ){}\ +template \ +TYPE::TYPE( A1&& a1, A2&& a2, A3&& a3 ) \ +:my( new typename fc::shared_impl::impl( fc::forward(a1), fc::forward(a2), fc::forward(a3) ) ){}\ +TYPE::TYPE( shared_impl::impl* m ) \ +:my(m){}\ +TYPE::TYPE( TYPE* c )\ +:my(fc::move(c->my)){ delete c; }\ +TYPE::TYPE( TYPE&& c )\ +:my(fc::move(c.my)){}\ +TYPE::TYPE( const TYPE& c )\ +:my(c.my){}\ +TYPE::TYPE() \ +:my( new typename fc::shared_impl::impl( ) ){}\ +TYPE::~TYPE(){}\ +bool TYPE::operator !()const { return !my; }\ +TYPE& TYPE::operator = ( const TYPE& c ) {\ + my = c.my;\ + return *this;\ +}\ +TYPE& TYPE::operator = ( TYPE&& c ) {\ + fc::swap( my._impl, c.my._impl );\ + return *this;\ +}\ +TYPE& TYPE::operator = ( TYPE* c ) {\ + fc::swap( my._impl, c->my._impl );\ + delete c;\ + return *this;\ +} \ +bool operator==( const TYPE& a, const TYPE& b ) {\ + return a.my._impl.get() == b.my._impl.get(); \ +} \ +bool operator!=( const TYPE& a, const TYPE& b ) {\ + return a.my._impl.get() != b.my._impl.get(); \ +} + + diff --git a/include/fc/shared_impl.hpp b/include/fc/shared_impl.hpp new file mode 100644 index 0000000..342ff33 --- /dev/null +++ b/include/fc/shared_impl.hpp @@ -0,0 +1,161 @@ +#pragma once +#include + +namespace fc { + /** + * @class shared_impl + * @brief used to create types with reference semantics + * + * A type with reference semantics is effectively a shared pointer except that + * it is used like a Java or C# type using '.' notation instead of -> notation. + * + * It is ideal for use with classes that almost always get managed by a shared + * pointer (sockets and long-lived resources). These types are rarely, if ever + * copied by value and often by-value copy or 'deep-copy' has no real meaning. + * + * An additional feature of shared_impl is that your classes implementation + * should be private and defined entirely in your source file. There should be + * no member variables defined in your types header. + * + * To make this design pattern work requires a lot of 'boiler-plate' code for + * handling assignments, moves, copies, etc that cannot be provided via tradtional + * means such as templates or base classes. + * + * To create a new type with reference semantics you place FC_REFERENCE_TYPE(type) + * inside the private section of 'type'. Then in your source file you will + * need to define your 'private' data. + * + * @code + * #include + * + * FC_START_SHARED_IMPL( your_namespace::your_type ) + * impl( int x, int y ); // custom constructor + * + * ... + * int member_variable; + * your private member variables / methods go here. + * ... + * FC_END_SHARED_IMPL + * #include + * @endcode + * + * + * Lastly, you will need to provide the implementation your class below. This + * implementation will need to provide the matching implementations for the + * methods declared by FC_REFERENCE_TYPE(type) in your header. To do this + * use the FC_REFERENCE_TYPE_IMPL(type) + * + * @code + * namespace your_namespace { + * FC_REFERENCE_TYPE(your_type) + * ... your methods here... + * } + * @endcode + * + * Within the body of your methods you can access your private data and members + * via using 'my->member_variable' + * + * If you want to define custom constructors for your reference type, you will + * need to implement them inside the FC_START_SHARED_IMPL block using the pattern: + * + * @code + * FC_START_SHARED_IMPL( your_namespace::your_type ) + * impl( int x, int y ){} // custom constructor + * FC_END_SHARED_IMPL + * @code + * + * A limited number (3) of arguments are supported for custom constructors. + * + * Once you have defined your reference type you can use it like so: + * + * @code + * your_type val = nullptr; // create a null type + * your_type val2 = new your_type(...); // construct a new instance, unnecessary temporary heap alloc + * your_type val3(...); // constructs a new instance, more effecient + * + * val2.your_method(); + * val2 = nullptr; // reset val2 to a null object + * + * val2 = val3; // val2 and val3 now reference the same data + * if( !!val2 ){} // val2 is not null + * else{} // val2 is null + * @endcode + * + * As you can see, when creating types with this method your code will + * look and act like a Java or C# garbage collected type. + * + * Often times your private methods will need to call your public methods, to achieve + * this you can use the following techinque: + * + * @code + * FC_START_SHARED_IMPL( your_namespace::your_type ) + * void private_method( int x, int y ){ + * auto s = self(); + * s.public_method(); + * } + * FC_END_SHARED_IMPL + * @code + * + * For performance reasons, it is best to only call 'self()' once and save the + * result to avoid unnecessary copies of shared pointers which require atomic + * operations. + * + */ + template + struct shared_impl { + struct impl; + impl& operator* ()const; + impl* operator-> ()const; + + bool operator !()const; + + template + shared_impl( U&& u ); + + shared_impl( const shared_impl& u ); + shared_impl( shared_impl&& u ); + shared_impl& operator=( shared_impl&& u ); + shared_impl& operator=( const shared_impl& u ); + + ~shared_impl(); + + fc::shared_ptr _impl; + }; + +} + +#define FC_REFERENCE_TYPE( TYPE ) \ + public:\ + TYPE(); \ + TYPE( std::nullptr_t ); \ + TYPE( TYPE* ); \ + TYPE( TYPE&& ); \ + TYPE( const TYPE& ); \ + template \ + TYPE( A1&& ); \ + template \ + TYPE( A1&&, A2&& ); \ + template \ + TYPE( A1&&, A2&&, A3&& ); \ + ~TYPE(); \ + bool operator !()const; \ + friend bool operator==( const TYPE& a, const TYPE& b ); \ + friend bool operator!=( const TYPE& a, const TYPE& b ); \ + TYPE& operator = ( const TYPE& ); \ + TYPE& operator = ( TYPE&& );\ + TYPE& operator = ( TYPE* );\ + TYPE& operator = ( std::nullptr_t );\ + private: \ + friend class shared_impl::impl; \ + TYPE( shared_impl::impl* m ); \ + shared_impl my; + +#define FC_START_SHARED_IMPL( SCOPED_TYPE ) \ +namespace fc { \ +template<> \ +struct fc::shared_impl::impl : public fc::retainable { \ + SCOPED_TYPE self() { return SCOPED_TYPE(this); } \ + + +#define FC_END_SHARED_IMPL }; } + diff --git a/include/fc/shared_ptr.hpp b/include/fc/shared_ptr.hpp index 0c97504..915dddb 100644 --- a/include/fc/shared_ptr.hpp +++ b/include/fc/shared_ptr.hpp @@ -1,5 +1,4 @@ -#ifndef _FC_SHARED_PTR_HPP_ -#define _FC_SHARED_PTR_HPP_ +#pragma once #include namespace fc { @@ -83,4 +82,3 @@ namespace fc { } } -#endif diff --git a/src/http_connection.cpp b/src/http_connection.cpp new file mode 100644 index 0000000..c072a87 --- /dev/null +++ b/src/http_connection.cpp @@ -0,0 +1,98 @@ +#include +#include +#include + + +FC_START_SHARED_IMPL(fc::http::connection) + fc::tcp_socket sock; + + int read_until( char* buffer, char* end, char c = '\n' ) { + char* p = buffer; + // try { + while( p < end && sock.read(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); + } + + fc::http::reply parse_reply() { + fc::http::reply rep; + fc::vector 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 = fc::lexical_cast(fc::string(line.data())); + s = read_until( line.data(), line.data()+line.size(), '\n' ); // DESCRIPTION + + while( (s = read_until( line.data(), line.data()+line.size(), '\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); + rep.headers.push_back(h); + if( h.key == "Content-Length" ) { + rep.body.resize( fc::lexical_cast( fc::string(h.val) ) ); + } + } + if( rep.body.size() ) sock.read( rep.body.data(), rep.body.size() ); + return rep; + } + +FC_END_SHARED_IMPL +#include + + + +namespace fc { namespace http { + +FC_REFERENCE_TYPE_IMPL( connection ) + + +// used for clients +void connection::connect_to( const fc::ip::endpoint& ep ) { + my->sock.connect_to( ep ); +} + +http::reply connection::request( const fc::string& method, + const fc::string& url, + const fc::string& body ) { + fc::stringstream req; + req << method <<" "<sock.write( head.c_str(), head.size() ); + + if( body.size() ) + my->sock.write( body.c_str(), body.size() ); + + return my->parse_reply(); +} + +// used for servers +fc::tcp_socket& connection::get_socket() { + return my->sock; +} + +http::request connection::read_request() { + http::request r; + return r; +} +void connection::send_reply( const http::reply& ) { + +} + +} } // fc::http