40 m_line_buffer(config.line_buffer_config) ,
41 m_remote_location(remote) ,
42 m_start_timer(*this,&
GNet::
Client::onStartTimeout,es) ,
43 m_connect_timer(*this,&
GNet::
Client::onConnectTimeout,es) ,
44 m_connected_timer(*this,&
GNet::
Client::onConnectedTimeout,es) ,
45 m_response_timer(*this,&
GNet::
Client::onResponseTimeout,es) ,
46 m_idle_timer(*this,&
GNet::
Client::onIdleTimeout,es)
48 G_DEBUG(
"Client::ctor" ) ;
49 if( m_config.auto_start )
50 m_start_timer.startTimer( 0U ) ;
62 G_DEBUG(
"GNet::Client::disconnect" ) ;
64 m_start_timer.cancelTimer() ;
65 m_connect_timer.cancelTimer() ;
66 m_connected_timer.cancelTimer() ;
67 m_response_timer.cancelTimer() ;
68 m_idle_timer.cancelTimer() ;
70 m_state = State::Disconnected ;
81 return m_event_signal ;
86 return m_remote_location ;
91 if( m_socket ==
nullptr )
92 throw NotConnected() ;
98 if( m_socket ==
nullptr )
99 throw NotConnected() ;
105 m_line_buffer.clear() ;
106 m_response_timer.cancelTimer() ;
109void GNet::Client::onStartTimeout()
111 G_DEBUG(
"GNet::Client::onStartTimeout: auto-start connecting" ) ;
117 G_DEBUG(
"GNet::Client::connect: [" << m_remote_location.displayString() <<
"] "
118 <<
"(" <<
static_cast<int>(m_state) <<
")" ) ;
119 if( m_state != State::Idle )
120 throw ConnectError(
"wrong state" ) ;
123 if( m_config.connection_timeout )
124 m_connect_timer.startTimer( m_config.connection_timeout ) ;
126 m_remote_location.resolveTrivially() ;
127 if( m_remote_location.resolved() )
129 setState( State::Connecting ) ;
136 throw DnsError( error ) ;
138 setState( State::Connecting ) ;
143 setState( State::Resolving ) ;
144 if( m_resolver ==
nullptr )
147 m_resolver = std::make_unique<Resolver>( resolver_callback , m_es ) ;
149 m_resolver->start( m_remote_location ) ;
150 emit(
"resolving" ) ;
154void GNet::Client::onResolved( std::string error ,
Location location )
157 throw DnsError( error ) ;
159 G_DEBUG(
"GNet::Client::onResolved: " << location.
displayString() ) ;
160 m_remote_location.update( location.
address() ) ;
161 setState( State::Connecting ) ;
165void GNet::Client::startConnecting()
167 G_DEBUG(
"GNet::Client::startConnecting: local: " << m_config.local_address.displayString() ) ;
168 G_DEBUG(
"GNet::Client::startConnecting: remote: " << m_remote_location.displayString() ) ;
170 setState( State::Testing ) ;
175 m_socket = std::make_unique<StreamSocket>( m_remote_location.address().family() , m_config.stream_socket_config ) ;
176 socket().addWriteHandler( *
this , m_es ) ;
177 socket().addOtherHandler( *
this , m_es ) ;
181 EventHandler & eh = *this ;
182 SocketProtocolSink & sp_sink = *this ;
183 m_sp = std::make_unique<SocketProtocol>( eh , m_es , sp_sink , *m_socket , m_config.socket_protocol_config ) ;
187 if( m_config.bind_local_address )
188 bindLocalAddress( m_config.local_address ) ;
192 bool immediate = false ;
193 if( !socket().connect( m_remote_location.address() , &immediate ) )
194 throw ConnectError(
"cannot connect to " + m_remote_location.address().displayString() +
": " + socket().reason() ) ;
200 socket().dropWriteHandler() ;
201 m_connected_timer.startTimer( 0U ) ;
205 emit(
"connecting" ) ;
212 if( m_sp !=
nullptr )
216 else if( m_socket !=
nullptr )
218 m_socket->dropWriteHandler() ;
219 m_socket->shutdown() ;
230 return m_has_connected ;
235 onDelete( (done||m_finished) ? std::string() : reason ) ;
238void GNet::Client::emit(
const std::string & action )
240 m_event_signal.emit( std::string(action) , m_remote_location.displayString() , std::string() ) ;
243void GNet::Client::onConnectTimeout()
245 std::ostringstream ss ;
246 ss <<
"cannot connect to " << m_remote_location <<
": timed out out after " << m_config.connection_timeout <<
"s" ;
247 G_DEBUG(
"GNet::Client::onConnectTimeout: " << ss.str() ) ;
248 throw ConnectError( ss.str() ) ;
251void GNet::Client::onResponseTimeout()
253 std::ostringstream ss ;
254 ss <<
"no response after " << m_config.response_timeout <<
"s while connected to " << m_remote_location ;
255 G_DEBUG(
"GNet::Client::onResponseTimeout: response timeout: " << ss.str() ) ;
256 throw ResponseTimeout( ss.str() ) ;
259void GNet::Client::onIdleTimeout()
261 std::ostringstream ss ;
262 ss <<
"no activity after " << m_config.idle_timeout <<
"s while connected to " << m_remote_location ;
263 throw IdleTimeout( ss.str() ) ;
266void GNet::Client::onConnectedTimeout()
268 G_DEBUG(
"GNet::Client::onConnectedTimeout: immediate connection" ) ;
272void GNet::Client::writeEvent()
274 G_DEBUG(
"GNet::Client::writeEvent" ) ;
278void GNet::Client::onWriteable()
280 bool has_peer = m_state == State::Connecting && socket().getPeerAddress().first ;
281 if( m_state == State::Connected )
283 if( m_sp->writeEvent() )
286 else if( m_state == State::Testing )
288 socket().dropWriteHandler() ;
289 setState( State::Connecting ) ;
290 m_connected_timer.startTimer( 2U , 100000U ) ;
292 else if( m_state == State::Connecting && has_peer && m_remote_location.socks() )
294 setState( State::Socksing ) ;
295 m_socks = std::make_unique<Socks>( m_remote_location ) ;
296 if( m_socks->send( socket() ) )
298 socket().dropWriteHandler() ;
299 socket().addReadHandler( *
this , m_es ) ;
303 socket().addWriteHandler( *
this , m_es ) ;
304 socket().dropReadHandler() ;
307 else if( m_state == State::Connecting && has_peer )
309 socket().dropWriteHandler() ;
310 socket().addReadHandler( *
this , m_es ) ;
312 setState( State::Connected ) ;
315 else if( m_state == State::Connecting )
317 socket().dropWriteHandler() ;
318 throw ConnectError(
"cannot connect to " + m_remote_location.address().displayString() ) ;
320 else if( m_state == State::Socksing )
322 G_ASSERT( m_socks !=
nullptr ) ;
323 if( m_socks->send( socket() ) )
325 socket().dropWriteHandler() ;
326 socket().addReadHandler( *
this , m_es ) ;
328 setState( State::Connected ) ;
332 else if( m_state == State::Disconnected )
338void GNet::Client::doOnConnect()
342 if( this_.deleted() ) return ;
343 emit(
"connected" ) ;
346void GNet::Client::otherEvent( EventHandler::Reason reason )
348 if( m_state == State::Socksing || m_sp ==
nullptr )
351 m_sp->otherEvent( reason , m_config.no_throw_on_peer_disconnect ) ;
354void GNet::Client::readEvent()
356 if( m_state == State::Socksing )
358 G_ASSERT( m_socks !=
nullptr ) ;
359 bool complete = m_socks->read( socket() ) ;
362 setState( State::Connected ) ;
368 G_ASSERT( m_sp !=
nullptr ) ;
369 if( m_sp->readEvent( m_config.no_throw_on_peer_disconnect ) )
374void GNet::Client::onData(
const char * data , std::size_t size )
376 if( m_config.response_timeout && m_line_buffer.transparent() )
377 m_response_timer.cancelTimer() ;
379 if( m_config.idle_timeout )
380 m_idle_timer.startTimer( m_config.idle_timeout ) ;
382 bool fragments = m_line_buffer.transparent() ;
383 m_line_buffer.apply(
this , &Client::onDataImp , data , size , fragments ) ;
386bool GNet::Client::onDataImp(
const char * data , std::size_t size , std::size_t eolsize ,
387 std::size_t linesize ,
char c0 )
389 if( m_config.response_timeout && eolsize )
390 m_response_timer.cancelTimer() ;
392 return onReceive( data , size , eolsize , linesize , c0 ) ;
397 return m_state == State::Connected ;
400void GNet::Client::bindLocalAddress(
const Address & local_address )
404 socket().bind( local_address ) ;
407 if( local_address.
isLoopback() && !m_remote_location.address().isLoopback() )
408 G_WARNING_ONCE(
"GNet::Client::bindLocalAddress: binding the loopback address for "
409 "outgoing connections may result in connection failures" ) ;
412void GNet::Client::setState( State new_state )
414 if( new_state != State::Connecting && new_state != State::Resolving )
415 m_connect_timer.cancelTimer() ;
417 if( new_state == State::Connected )
418 m_has_connected = true ;
420 if( new_state == State::Connected && m_config.idle_timeout )
421 m_idle_timer.startTimer( m_config.idle_timeout ) ;
423 m_state = new_state ;
428 return socket().getLocalAddress() ;
433 if( m_state != State::Connected )
434 throw NotConnected() ;
436 auto pair = socket().getPeerAddress() ;
438 throw NotConnected() ;
445 if( m_state == State::Connected )
447 auto pair = socket().getPeerAddress() ;
448 if( pair.first && with_port )
449 return pair.second.displayString() ;
450 else if( pair.first )
451 return pair.second.hostPartString() ;
458 if( m_state == State::Connected )
459 return socket().getPeerAddress().second.displayString() ;
461 return "("+m_remote_location.displayString()+
")" ;
466 return m_sp->peerCertificate() ;
472 return m_sp && m_sp->secureConnectCapable() ;
478 if( m_sp ==
nullptr )
479 throw NotConnected(
"for secure-connect" ) ;
480 m_sp->secureConnect() ;
485 if( m_config.response_timeout )
486 m_response_timer.startTimer( m_config.response_timeout ) ;
487 return m_sp->send( data , 0U ) ;
492 if( m_config.response_timeout )
493 m_response_timer.startTimer( m_config.response_timeout ) ;
494 return m_sp->send( data ) ;
500 std::size_t total_size = std::accumulate( data.begin() , data.end() , std::size_t(0) ,
501 [](std::size_t n,std::string_view s){return n+s.size();} ) ;
502 if( m_config.response_timeout && offset < total_size )
503 m_response_timer.startTimer( m_config.response_timeout ) ;
504 return m_sp->send( data , offset ) ;
511 return m_line_buffer.state() ;
515void GNet::Client::onPeerDisconnect()
522GNet::Client::Config & GNet::Client::Config::set_all_timeouts(
unsigned int all_timeouts )
noexcept
524 socket_protocol_config.secure_connection_timeout = all_timeouts ;
525 connection_timeout = all_timeouts ;
526 response_timeout = all_timeouts ;
527 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.
Client(EventState, const Location &remote_location, const Config &)
Constructor.
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).
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 lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
Provides information about the state of a line buffer.
A class that represents the remote target for out-going client connections.
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::pair< std::string, std::string > resolve(Location &, const Config &)
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 structure containing GNet::Client configuration parameters.
An interface used for GNet::Resolver callbacks.