Implemented http connection + shared_impl
Shared Impl provides Java/C# style reference semantics for C++ types.
This commit is contained in:
parent
f3680c3183
commit
fb6e18ec7b
6 changed files with 400 additions and 3 deletions
|
|
@ -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
|
||||
|
|
|
|||
57
include/fc/http/connection.hpp
Normal file
57
include/fc/http/connection.hpp
Normal 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
|
||||
|
||||
82
include/fc/shared_impl.cpp
Normal file
82
include/fc/shared_impl.cpp
Normal 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
161
include/fc/shared_impl.hpp
Normal 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 }; }
|
||||
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
#ifndef _FC_SHARED_PTR_HPP_
|
||||
#define _FC_SHARED_PTR_HPP_
|
||||
#pragma once
|
||||
#include <fc/utility.hpp>
|
||||
|
||||
namespace fc {
|
||||
|
|
@ -83,4 +82,3 @@ namespace fc {
|
|||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
98
src/http_connection.cpp
Normal file
98
src/http_connection.cpp
Normal 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
|
||||
Loading…
Reference in a new issue