40 m_line_buffer(config.line_buffer_config) ,
41 m_remote_location(remote) ,
42 m_state(State::Idle) ,
44 m_has_connected(false) ,
45 m_start_timer(*this,&
GNet::
Client::onStartTimeout,es) ,
46 m_connect_timer(*this,&
GNet::
Client::onConnectTimeout,es) ,
47 m_connected_timer(*this,&
GNet::
Client::onConnectedTimeout,es) ,
48 m_response_timer(*this,&
GNet::
Client::onResponseTimeout,es) ,
49 m_idle_timer(*this,&
GNet::
Client::onIdleTimeout,es)
51 G_DEBUG(
"Client::ctor" ) ;
52 if( m_config.auto_start )
53 m_start_timer.startTimer( 0U ) ;
65 G_DEBUG(
"GNet::Client::disconnect" ) ;
67 m_start_timer.cancelTimer() ;
68 m_connect_timer.cancelTimer() ;
69 m_connected_timer.cancelTimer() ;
70 m_response_timer.cancelTimer() ;
71 m_idle_timer.cancelTimer() ;
73 m_state = State::Disconnected ;
84 return m_event_signal ;
89 return m_remote_location ;
94 if( m_socket ==
nullptr )
95 throw NotConnected() ;
101 if( m_socket ==
nullptr )
102 throw NotConnected() ;
108 m_line_buffer.clear() ;
109 m_response_timer.cancelTimer() ;
112void GNet::Client::onStartTimeout()
114 G_DEBUG(
"GNet::Client::onStartTimeout: auto-start connecting" ) ;
120 G_DEBUG(
"GNet::Client::connect: [" << m_remote_location.displayString() <<
"] "
121 <<
"(" <<
static_cast<int>(m_state) <<
")" ) ;
122 if( m_state != State::Idle )
123 throw ConnectError(
"wrong state" ) ;
126 if( m_config.connection_timeout )
127 m_connect_timer.startTimer( m_config.connection_timeout ) ;
129 m_remote_location.resolveTrivially() ;
130 if( m_remote_location.resolved() )
132 setState( State::Connecting ) ;
139 throw DnsError( error ) ;
141 setState( State::Connecting ) ;
146 setState( State::Resolving ) ;
147 if( m_resolver ==
nullptr )
150 m_resolver = std::make_unique<Resolver>( resolver_callback , m_es ) ;
152 m_resolver->start( m_remote_location ) ;
153 emit(
"resolving" ) ;
157void GNet::Client::onResolved( std::string error ,
Location location )
160 throw DnsError( error ) ;
162 G_DEBUG(
"GNet::Client::onResolved: " << location.
displayString() ) ;
163 m_remote_location.update( location.
address() , location.
name() ) ;
164 setState( State::Connecting ) ;
168void GNet::Client::startConnecting()
170 G_DEBUG(
"GNet::Client::startConnecting: local: " << m_config.local_address.displayString() ) ;
171 G_DEBUG(
"GNet::Client::startConnecting: remote: " << m_remote_location.displayString() ) ;
173 setState( State::Testing ) ;
178 m_socket = std::make_unique<StreamSocket>( m_remote_location.address().family() , m_config.stream_socket_config ) ;
179 socket().addWriteHandler( *
this , m_es ) ;
180 socket().addOtherHandler( *
this , m_es ) ;
184 EventHandler & eh = *this ;
185 SocketProtocolSink & sp_sink = *this ;
186 m_sp = std::make_unique<SocketProtocol>( eh , m_es , sp_sink , *m_socket , m_config.socket_protocol_config ) ;
190 if( m_config.bind_local_address )
191 bindLocalAddress( m_config.local_address ) ;
195 bool immediate = false ;
196 if( !socket().connect( m_remote_location.address() , &immediate ) )
197 throw ConnectError(
"cannot connect to " + m_remote_location.address().displayString() +
": " + socket().reason() ) ;
203 socket().dropWriteHandler() ;
204 m_connected_timer.startTimer( 0U ) ;
208 emit(
"connecting" ) ;
215 if( m_sp !=
nullptr )
219 else if( m_socket !=
nullptr )
221 m_socket->dropWriteHandler() ;
222 m_socket->shutdown() ;
233 return m_has_connected ;
238 onDelete( (done||m_finished) ? std::string() : reason ) ;
241void GNet::Client::emit(
const std::string & action )
243 m_event_signal.emit( std::string(action) , m_remote_location.displayString() , std::string() ) ;
246void GNet::Client::onConnectTimeout()
248 std::ostringstream ss ;
249 ss <<
"cannot connect to " << m_remote_location <<
": timed out out after " << m_config.connection_timeout <<
"s" ;
250 G_DEBUG(
"GNet::Client::onConnectTimeout: " << ss.str() ) ;
251 throw ConnectError( ss.str() ) ;
254void GNet::Client::onResponseTimeout()
256 std::ostringstream ss ;
257 ss <<
"no response after " << m_config.response_timeout <<
"s while connected to " << m_remote_location ;
258 G_DEBUG(
"GNet::Client::onResponseTimeout: response timeout: " << ss.str() ) ;
259 throw ResponseTimeout( ss.str() ) ;
262void GNet::Client::onIdleTimeout()
264 std::ostringstream ss ;
265 ss <<
"no activity after " << m_config.idle_timeout <<
"s while connected to " << m_remote_location ;
266 throw IdleTimeout( ss.str() ) ;
269void GNet::Client::onConnectedTimeout()
271 G_DEBUG(
"GNet::Client::onConnectedTimeout: immediate connection" ) ;
275void GNet::Client::writeEvent()
277 G_DEBUG(
"GNet::Client::writeEvent" ) ;
281void GNet::Client::onWriteable()
283 bool has_peer = m_state == State::Connecting && socket().getPeerAddress().first ;
284 if( m_state == State::Connected )
286 if( m_sp->writeEvent() )
289 else if( m_state == State::Testing )
291 socket().dropWriteHandler() ;
292 setState( State::Connecting ) ;
293 m_connected_timer.startTimer( 2U , 100000U ) ;
295 else if( m_state == State::Connecting && has_peer && m_remote_location.socks() )
297 setState( State::Socksing ) ;
298 m_socks = std::make_unique<Socks>( m_remote_location ) ;
299 if( m_socks->send( socket() ) )
301 socket().dropWriteHandler() ;
302 socket().addReadHandler( *
this , m_es ) ;
306 socket().addWriteHandler( *
this , m_es ) ;
307 socket().dropReadHandler() ;
310 else if( m_state == State::Connecting && has_peer )
312 socket().dropWriteHandler() ;
313 socket().addReadHandler( *
this , m_es ) ;
315 setState( State::Connected ) ;
318 else if( m_state == State::Connecting )
320 socket().dropWriteHandler() ;
321 throw ConnectError(
"cannot connect to " + m_remote_location.address().displayString() ) ;
323 else if( m_state == State::Socksing )
325 G_ASSERT( m_socks !=
nullptr ) ;
326 if( m_socks->send( socket() ) )
328 socket().dropWriteHandler() ;
329 socket().addReadHandler( *
this , m_es ) ;
331 setState( State::Connected ) ;
335 else if( m_state == State::Disconnected )
341void GNet::Client::doOnConnect()
345 if( this_.deleted() ) return ;
346 emit(
"connected" ) ;
349void GNet::Client::otherEvent( EventHandler::Reason reason )
351 if( m_state == State::Socksing || m_sp ==
nullptr )
354 m_sp->otherEvent( reason , m_config.no_throw_on_peer_disconnect ) ;
357void GNet::Client::readEvent()
359 if( m_state == State::Socksing )
361 G_ASSERT( m_socks !=
nullptr ) ;
362 bool complete = m_socks->read( socket() ) ;
365 setState( State::Connected ) ;
371 G_ASSERT( m_sp !=
nullptr ) ;
372 if( m_sp->readEvent( m_config.no_throw_on_peer_disconnect ) )
377void GNet::Client::onData(
const char * data , std::size_t size )
379 if( m_config.response_timeout && m_line_buffer.transparent() )
380 m_response_timer.cancelTimer() ;
382 if( m_config.idle_timeout )
383 m_idle_timer.startTimer( m_config.idle_timeout ) ;
385 bool fragments = m_line_buffer.transparent() ;
386 m_line_buffer.apply(
this , &Client::onDataImp , data , size , fragments ) ;
389bool GNet::Client::onDataImp(
const char * data , std::size_t size , std::size_t eolsize ,
390 std::size_t linesize ,
char c0 )
392 if( m_config.response_timeout && eolsize )
393 m_response_timer.cancelTimer() ;
395 return onReceive( data , size , eolsize , linesize , c0 ) ;
400 return m_state == State::Connected ;
403void GNet::Client::bindLocalAddress(
const Address & local_address )
407 socket().bind( local_address ) ;
410 if( local_address.
isLoopback() && !m_remote_location.address().isLoopback() )
411 G_WARNING_ONCE(
"GNet::Client::bindLocalAddress: binding the loopback address for "
412 "outgoing connections may result in connection failures" ) ;
415void GNet::Client::setState( State new_state )
417 if( new_state != State::Connecting && new_state != State::Resolving )
418 m_connect_timer.cancelTimer() ;
420 if( new_state == State::Connected )
421 m_has_connected = true ;
423 if( new_state == State::Connected && m_config.idle_timeout )
424 m_idle_timer.startTimer( m_config.idle_timeout ) ;
426 m_state = new_state ;
431 return socket().getLocalAddress() ;
436 if( m_state != State::Connected )
437 throw NotConnected() ;
439 auto pair = socket().getPeerAddress() ;
441 throw NotConnected() ;
448 if( m_state == State::Connected )
450 auto pair = socket().getPeerAddress() ;
451 if( pair.first && with_port )
452 return pair.second.displayString() ;
453 else if( pair.first )
454 return pair.second.hostPartString() ;
461 if( m_state == State::Connected )
462 return socket().getPeerAddress().second.displayString() ;
464 return "("+m_remote_location.displayString()+
")" ;
469 return m_sp->peerCertificate() ;
475 return m_sp && m_sp->secureConnectCapable() ;
481 if( m_sp ==
nullptr )
482 throw NotConnected(
"for secure-connect" ) ;
483 m_sp->secureConnect() ;
488 if( m_config.response_timeout )
489 m_response_timer.startTimer( m_config.response_timeout ) ;
490 return m_sp->send( data , 0U ) ;
495 if( m_config.response_timeout )
496 m_response_timer.startTimer( m_config.response_timeout ) ;
497 return m_sp->send( data ) ;
503 std::size_t total_size = std::accumulate( data.begin() , data.end() , std::size_t(0) ,
505 if( m_config.response_timeout && offset < total_size )
506 m_response_timer.startTimer( m_config.response_timeout ) ;
507 return m_sp->send( data , offset ) ;
514 return m_line_buffer.state() ;
518void GNet::Client::onPeerDisconnect()
525GNet::Client::Config & GNet::Client::Config::set_all_timeouts(
unsigned int all_timeouts )
noexcept
527 socket_protocol_config.secure_connection_timeout = all_timeouts ;
528 connection_timeout = all_timeouts ;
529 response_timeout = all_timeouts ;
530 idle_timeout = all_timeouts * 2U ;
The GNet::Address class encapsulates a TCP/UDP transport address.
bool isLoopback() const
Returns true if this is a loopback address.
A class for making an outgoing connection to a remote server, with support for socket-level protocols...
void disconnect()
Aborts the connection and destroys the object's internal state, resulting in a zombie object.
std::string peerCertificate() const override
Returns the peer's TLS certificate.
std::string peerAddressString(bool with_port=true) const
Returns the peer address display string or the empty string if not connected().
void secureConnect()
Starts TLS/SSL client-side negotiation.
~Client() override
Destructor.
bool secureConnectCapable() const
Returns true if currently connected and secureConnect() can be used.
G::Slot::Signal< const std::string &, const std::string &, const std::string & > & eventSignal() noexcept
Returns a signal that indicates that something interesting has happened.
Address localAddress() const override
Returns the local address.
StreamSocket & socket()
Returns a reference to the socket. Throws if not connected.
std::string connectionState() const override
Returns the connection state display string.
bool finished() const
Returns true if finish() has been called.
void finish()
Indicates that the last data has been sent and the client is expecting a peer disconnect.
LineBufferState lineBuffer() const
Returns information about the state of the internal line-buffer.
bool hasConnected() const
Returns true if ever connected().
bool connected() const
Returns true if connected to the peer.
Location remoteLocation() const
Returns a Location structure, including the result of name lookup if available.
Address peerAddress() const override
Returns the peer address.
bool send(const std::string &data)
Sends data to the peer and starts the response timer (if configured).
Client(ExceptionSink, const Location &remote_location, const Config &)
Constructor.
void connect()
Initiates a connection to the remote server.
void doOnDelete(const std::string &reason, bool done)
This should be called by the Client owner (typically ClientPtr) just before this Client object is del...
void clearInput()
Clears the input LineBuffer and cancels the response timer if running.
virtual void otherEvent(Reason)
Called for a socket-exception event, or a socket-close event on windows.
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
Provides information about the state of a line buffer.
A class that represents the remote target for out-going client connections.
std::string name() const
Returns the remote canonical name.
std::string displayString() const
Returns a string representation for logging and debug.
Address address() const
Returns the remote address.
static void removeClient(const Connection &client) noexcept
Removes a client connection.
static void addClient(const Connection &client)
Adds a client connection.
static std::string resolve(Location &)
Does synchronous name resolution.
static bool async()
Returns true if the resolver supports asynchronous operation.
A derivation of GNet::Socket for a stream socket.
An object to represent a nested execution context.
A class which acquires the process's special privileges on construction and releases them on destruct...
static bool enabled() noexcept
Returns true if test features are enabled.
A class like c++17's std::string_view.
A structure containing GNet::Client configuration parameters.
An interface used for GNet::Resolver callbacks.