Implemented http connection + shared_impl

Shared Impl provides Java/C# style reference semantics for C++ types.
This commit is contained in:
Daniel Larimer 2012-10-24 00:54:03 -04:00
parent f3680c3183
commit fb6e18ec7b
6 changed files with 400 additions and 3 deletions

View file

@ -45,6 +45,7 @@ include_directories( ${Boost_INCLUDE_DIR} )
include_directories( include ) include_directories( include )
set( sources set( sources
src/http_connection.cpp
src/value.cpp src/value.cpp
src/lexical_cast.cpp src/lexical_cast.cpp
src/spin_lock.cpp src/spin_lock.cpp

View file

@ -0,0 +1,57 @@
#pragma once
#include <fc/vector.hpp>
#include <fc/string.hpp>
#include <fc/shared_impl.hpp>
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<header> headers;
fc::vector<char> body;
};
struct request {
fc::string method;
fc::string domain;
fc::string path;
fc::vector<header> headers;
fc::vector<char> 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

View file

@ -0,0 +1,82 @@
#pragma once
#include <fc/shared_impl.hpp>
namespace fc {
template<typename T>
typename shared_impl<T>::impl& shared_impl<T>::operator* ()const { return *_impl; }
template<typename T>
typename shared_impl<T>::impl* shared_impl<T>::operator-> ()const { return _impl.get(); }
template<typename T>
template<typename U>
shared_impl<T>::shared_impl( U&& u ):_impl(fc::forward<U>(u)){}
template<typename T>
shared_impl<T>::shared_impl( const shared_impl<T>& u ):_impl(u._impl){}
template<typename T>
shared_impl<T>::shared_impl( shared_impl<T>&& u ):_impl(fc::move(u._impl)){}
template<typename T>
shared_impl<T>& shared_impl<T>::operator=( shared_impl<T>&& u ) {
fc::swap(_impl,u._impl);
return *this;
}
template<typename T>
shared_impl<T>& shared_impl<T>::operator=( const shared_impl<T>& u ) {
_impl = u._impl;
return *this;
}
template<typename T>
bool shared_impl<T>::operator !()const { return !_impl; }
template<typename T>
shared_impl<T>::~shared_impl(){}
}
#define FC_REFERENCE_TYPE_IMPL( TYPE ) \
template<typename A1> \
TYPE::TYPE( A1&& a1 ) \
:my( new typename fc::shared_impl<TYPE>::impl( fc::forward<A1>(a1) ) ){}\
template<typename A1,typename A2> \
TYPE::TYPE( A1&& a1, A2&& a2 ) \
:my( new typename fc::shared_impl<TYPE>::impl( fc::forward<A1>(a1), fc::forward<A2>(a2) ) ){}\
template<typename A1,typename A2, typename A3> \
TYPE::TYPE( A1&& a1, A2&& a2, A3&& a3 ) \
:my( new typename fc::shared_impl<TYPE>::impl( fc::forward<A1>(a1), fc::forward<A2>(a2), fc::forward<A3>(a3) ) ){}\
TYPE::TYPE( shared_impl<TYPE>::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<TYPE>::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(); \
}

161
include/fc/shared_impl.hpp Normal file
View file

@ -0,0 +1,161 @@
#pragma once
#include <fc/shared_ptr.hpp>
namespace fc {
/**
* @class shared_impl<T>
* @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<T> 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 <your_type.hpp>
*
* 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 <fc/shared_impl.cpp>
* @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<typename T>
struct shared_impl {
struct impl;
impl& operator* ()const;
impl* operator-> ()const;
bool operator !()const;
template<typename U>
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<shared_impl::impl> _impl;
};
}
#define FC_REFERENCE_TYPE( TYPE ) \
public:\
TYPE(); \
TYPE( std::nullptr_t ); \
TYPE( TYPE* ); \
TYPE( TYPE&& ); \
TYPE( const TYPE& ); \
template<typename A1> \
TYPE( A1&& ); \
template<typename A1,typename A2> \
TYPE( A1&&, A2&& ); \
template<typename A1,typename A2, typename A3> \
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<TYPE>::impl; \
TYPE( shared_impl<TYPE>::impl* m ); \
shared_impl<TYPE> my;
#define FC_START_SHARED_IMPL( SCOPED_TYPE ) \
namespace fc { \
template<> \
struct fc::shared_impl<SCOPED_TYPE>::impl : public fc::retainable { \
SCOPED_TYPE self() { return SCOPED_TYPE(this); } \
#define FC_END_SHARED_IMPL }; }

View file

@ -1,5 +1,4 @@
#ifndef _FC_SHARED_PTR_HPP_ #pragma once
#define _FC_SHARED_PTR_HPP_
#include <fc/utility.hpp> #include <fc/utility.hpp>
namespace fc { namespace fc {
@ -83,4 +82,3 @@ namespace fc {
} }
} }
#endif

98
src/http_connection.cpp Normal file
View file

@ -0,0 +1,98 @@
#include <fc/http/connection.hpp>
#include <fc/tcp_socket.hpp>
#include <fc/sstream.hpp>
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<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 = fc::lexical_cast<int>(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<int>( fc::string(h.val) ) );
}
}
if( rep.body.size() ) sock.read( rep.body.data(), rep.body.size() );
return rep;
}
FC_END_SHARED_IMPL
#include <fc/shared_impl.cpp>
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 <<" "<<url<<" HTTP/1.1\r\n";
if( body.size() ) req << "Content-Length: "<< body.size() << "\r\n";
req << "\r\n";
fc::string head = req.str();
my->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