Added ability to set/get recv timeouts. Fixed HTTP receive bug.

Recv timeouts can now be specified for sockets, which is the maximum amount of time to wait before returning during a receive. They will return WouldBlock if no data was received during the wait time.

Receiving a HTTP request in non-blocking mode will no longer fail.
This commit is contained in:
Fred Nicolson 2018-08-16 11:24:52 +01:00
parent decb0b10f9
commit 8a4ee937b1
9 changed files with 190 additions and 91 deletions

View File

@ -45,7 +45,7 @@ namespace fr
* @param data_size The number of bytes to try and receive. Be sure that it's not larger than data.
* @param received Will be filled with the number of bytes actually received, might be less than you requested.
* @return The status of the operation:
* 'WouldBlock' if no data has been received, and the socket is in non-blocking mode
* 'WouldBlock' if no data has been received, and the socket is in non-blocking mode or the operation has timed out
* 'Disconnected' if the socket has disconnected.
* 'Success' All the bytes you wanted have been read
*/
@ -86,6 +86,12 @@ namespace fr
*/
void verify_certificates(bool should_verify);
/*!
* Applies requested socket options to the socket.
* Should be called when a new socket is created.
*/
void reconfigure_socket() override;
/*!
* Gets the underlying socket descriptor.
*
@ -133,6 +139,7 @@ namespace fr
mbedtls_ssl_config conf;
uint32_t flags;
bool should_verify;
uint32_t receive_timeout;
};
}

View File

@ -90,7 +90,7 @@ namespace fr
* @param data Where to store the received data.
* @param data_size The number of bytes to try and receive. Be sure that it's not larger than data.
* @param received Will be filled with the number of bytes actually received, might be less than you requested.
* @return The status of the operation, if the socket has disconnected etc.
* @return The status of the operation, if the socket has disconnected etc. This is dependent on the underlying socket type.
*/
virtual Status receive_raw(void *data, size_t data_size, size_t &received) = 0;
@ -146,7 +146,7 @@ namespace fr
* @return Operation status:
* 'Disconnected' if the socket disconnected
* 'Success' if buffer_size bytes could be read successfully
* 'WouldBlock' if the socket is in blocking mode and no data could be read
* 'WouldBlock' if the socket is in blocking mode and no data could be read, or if the read timed out before any data was received
*/
Status receive_all(void *dest, size_t buffer_size);
@ -168,25 +168,6 @@ namespace fr
*/
void set_inet_version(IP version);
/*!
* Sets the maximum receivable size that may be received by the socket. This does
* not apply to receive_raw(), but only things like fr::Packet.
*
* If a client attempts to send a packet larger than sz bytes, then
* the client will be disconnected and an fr::Socket::MaxPacketSizeExceeded
* will be returned. Pass '0' to indicate no limit.
*
* This should be used to prevent potential abuse, as a client could say that
* it's going to send a 200GiB packet, which would cause the Socket to try and
* allocate that much memory to accommodate the data, which is most likely not
* desirable.
*
* By default, there is no limit (0)
*
* @param sz The maximum number of bytes that may be received in an fr::Packet
*/
void set_max_receive_size(uint32_t sz);
/*!
* Converts an fr::Socket::Status value to a printable string
*
@ -206,6 +187,48 @@ namespace fr
*/
virtual void disconnect();
/*!
* Sets the maximum receivable size that may be received by the socket. This does
* not apply to receive_raw(), but only things like fr::Packet.
*
* If a client attempts to send a packet larger than sz bytes, then
* the client will be disconnected and an fr::Socket::MaxPacketSizeExceeded
* will be returned. Pass '0' to indicate no limit.
*
* This should be used to prevent potential abuse, as a client could say that
* it's going to send a 200GiB packet, which would cause the Socket to try and
* allocate that much memory to accommodate the data, which is most likely not
* desirable.
*
* By default, there is no limit (0)
*
* @param sz The maximum number of bytes that may be received in an fr::Packet
*/
inline void set_max_receive_size(uint32_t sz)
{
max_receive_size = sz;
}
/*!
* Sets a timeout which applies when receiving data.
*
* @note When receiving framed data, such as with receive(), this timeout will apply to the underlying
* individual reads, but not for the message as a whole.
*
* @param timeout The maximum number of milliseconds to wait on a socket read before returning. Pass
* 0 (default) for no timeout.
*/
inline void set_receive_timeout(uint32_t timeout)
{
socket_read_timeout = timeout;
reconfigure_socket();
}
inline uint32_t get_receive_timeout() const
{
return socket_read_timeout;
}
/*!
* Gets the max packet size. See set_max_packet_size
* for more information. If this returns 0, then
@ -248,12 +271,13 @@ namespace fr
* Applies requested socket options to the socket.
* Should be called when a new socket is created.
*/
void reconfigure_socket();
virtual void reconfigure_socket()=0;
std::string remote_address;
bool is_blocking;
int ai_family;
uint32_t max_receive_size;
uint32_t socket_read_timeout;
};
}

View File

@ -54,7 +54,7 @@ public:
* @param buffer_size The number of bytes to try and receive. Be sure that it's not larger than data.
* @param received Will be filled with the number of bytes actually received, might be less than you requested.
* @return The status of the operation:
* 'WouldBlock' if no data has been received, and the socket is in non-blocking mode
* 'WouldBlock' if no data has been received, and the socket is in non-blocking mode or operation has timed out
* 'Disconnected' if the socket has disconnected.
* 'Success' All the bytes you wanted have been read
*/
@ -83,6 +83,12 @@ public:
*/
int32_t get_socket_descriptor() const override;
/*!
* Applies requested socket options to the socket.
* Should be called when a new socket is created.
*/
void reconfigure_socket() override;
/*!
* Checks to see if we're connected to a socket or not
*

View File

@ -8,11 +8,11 @@
//Format: Major | Minor | Patch
#define FRNETLIB_VERSION_MAJOR 1
#define FRNETLIB_VERSION_MINOR 0
#define FRNETLIB_VERSION_PATCH 2
#define FRNETLIB_VERSION_MINOR 1
#define FRNETLIB_VERSION_PATCH 0
#define FRNETLIB_VERSION_NUMBER (FRNETLIB_VERSION_MAJOR * 100*100 + FRNETLIB_VERSION_MINOR * 100 + FRNETLIB_VERSION_PATCH)
#define FRNETLIB_VERSION_STRING "1.0.2"
#define FRNETLIB_VERSION_STRING_FULL "frnetlib 1.0.2"
#define FRNETLIB_VERSION_STRING "1.1.0"
#define FRNETLIB_VERSION_STRING_FULL "frnetlib 1.0.0"
#endif //FRNETLIB_VERSION_H

View File

@ -999,13 +999,15 @@ namespace fr
Socket::Status Http::receive(Socket *socket)
{
char recv_buffer[RECV_CHUNK_SIZE];
size_t received = 0;
fr::Socket::Status state;
size_t total_received = 0;
size_t received = 0;
do
{
//Receive the request
Socket::Status status = socket->receive_raw(recv_buffer, RECV_CHUNK_SIZE, received);
if(status != Socket::Success)
total_received += received;
if(status != Socket::Success && !(status == fr::Socket::WouldBlock && total_received != 0))
return status;
//Parse it

View File

@ -37,7 +37,7 @@ namespace fr
header_ended = header_end != std::string::npos;
//Ensure that the header doesn't exceed max length
if(!header_ended && body.size() > MAX_HTTP_HEADER_SIZE || header_ended && header_end > MAX_HTTP_HEADER_SIZE)
if((!header_ended && body.size() > MAX_HTTP_HEADER_SIZE) || (header_ended && header_end > MAX_HTTP_HEADER_SIZE))
{
return fr::Socket::HttpHeaderTooBig;
}

View File

@ -7,12 +7,15 @@
#include <utility>
#include <mbedtls/net_sockets.h>
#include <frnetlib/SSLSocket.h>
namespace fr
{
SSLSocket::SSLSocket(std::shared_ptr<SSLContext> ssl_context_) noexcept
: ssl_context(std::move(ssl_context_)),
should_verify(true)
should_verify(true),
receive_timeout(0)
{
//Initialise mbedtls structures
mbedtls_ssl_config_init(&conf);
@ -63,19 +66,46 @@ namespace fr
Socket::Status SSLSocket::receive_raw(void *data, size_t data_size, size_t &received)
{
int read = 0;
received = 0;
read = mbedtls_ssl_read(ssl.get(), (unsigned char *)data, data_size);
if(read == MBEDTLS_ERR_SSL_WANT_READ || read == MBEDTLS_ERR_SSL_WANT_WRITE)
return Socket::Status::WouldBlock;
if(read <= 0)
ssize_t status = 0;
if(receive_timeout == 0)
{
close_socket();
return Socket::Status::Disconnected;
status = mbedtls_ssl_read(ssl.get(), (unsigned char *)data, data_size);
if(status <= 0)
{
if(status == MBEDTLS_ERR_SSL_WANT_READ || status == MBEDTLS_ERR_SSL_WANT_WRITE)
{
return Socket::Status::WouldBlock;
}
close_socket();
return Socket::Status::Disconnected;
}
}
else
{
do
{
status = mbedtls_net_recv_timeout(ssl.get(), (unsigned char *)data, data_size, receive_timeout);
if(status <= 0)
{
if(status == MBEDTLS_ERR_SSL_TIMEOUT)
{
return Socket::Status::WouldBlock;
}
else if(status == MBEDTLS_ERR_SSL_WANT_READ)
{
continue; //try again, interrupted before anything could be received
}
close_socket();
return Socket::Status::Disconnected;
}
break;
} while(true);
}
received += read;
received = static_cast<size_t>(status);
return Socket::Status::Success;
}
@ -163,4 +193,21 @@ namespace fr
{
should_verify = should_verify_;
}
void SSLSocket::reconfigure_socket()
{
int one = 1;
#ifndef _WIN32
//Disable Nagle's algorithm
setsockopt(get_socket_descriptor(), SOL_TCP, TCP_NODELAY, (char*)&one, sizeof(one));
#else
//Disable Nagle's algorithm
setsockopt(get_socket_descriptor(), IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one));
setsockopt(get_socket_descriptor(), SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&one, sizeof(one));
//Apply receive timeout
DWORD timeout_dword = static_cast<DWORD>(get_receive_timeout());
setsockopt(socket_descriptor, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout_dword, sizeof timeout_dword);
#endif
}
}

View File

@ -15,7 +15,8 @@ namespace fr
Socket::Socket()
: is_blocking(true),
ai_family(AF_UNSPEC),
max_receive_size(0)
max_receive_size(0),
socket_read_timeout(0)
{
init_wsa();
}
@ -64,16 +65,6 @@ namespace fr
::shutdown(get_socket_descriptor(), SHUT_RDWR);
}
void Socket::reconfigure_socket()
{
//todo: Perhaps allow for these settings to be modified
int one = 1;
setsockopt(get_socket_descriptor(), SOL_TCP, TCP_NODELAY, (char*)&one, sizeof(one));
#ifdef _WIN32
setsockopt(get_socket_descriptor(), SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&one, sizeof(one));
#endif
}
void Socket::set_inet_version(Socket::IP version)
{
switch(version)
@ -92,29 +83,25 @@ namespace fr
}
}
void Socket::set_max_receive_size(uint32_t sz)
{
max_receive_size = sz;
}
const std::string &Socket::status_to_string(fr::Socket::Status status)
{
static std::vector<std::string> map = {
"Unknown",
"Success",
"Listen Failed",
"Bind Failed",
"Disconnected",
"Error",
"Would Block",
"Connection Failed",
"Handshake Failed",
"Verification Failed",
"Max packet size exceeded",
"Not enough data",
"Parse error",
"HTTP header too big",
"HTTP body too big"};
"Unknown",
"Success",
"Listen Failed",
"Bind Failed",
"Disconnected",
"Error",
"Would Block",
"Connection Failed",
"Handshake Failed",
"Verification Failed",
"Max packet size exceeded",
"Not enough data",
"Parse error",
"HTTP header too big",
"HTTP body too big"
};
if(status < 0 || status > map.size())
throw std::logic_error("Socket::status_to_string(): Invalid status value " + std::to_string(status));

View File

@ -4,6 +4,8 @@
#include <iostream>
#include <frnetlib/SocketSelector.h>
#include <frnetlib/TcpSocket.h>
#include "frnetlib/TcpSocket.h"
#define DEFAULT_SOCKET_TIMEOUT 20
@ -51,29 +53,29 @@ namespace fr
Socket::Status TcpSocket::receive_raw(void *data, size_t buffer_size, size_t &received)
{
received = 0;
//Read RECV_CHUNK_SIZE bytes into the recv buffer
int64_t status = ::recv(socket_descriptor, (char*)data, buffer_size, 0);
if(status > 0)
ssize_t status = 0;
do
{
received += status;
}
else
{
if(errno == EWOULDBLOCK || errno == EAGAIN)
status = ::recv(socket_descriptor, (char*)data, buffer_size, 0);
if(status <= 0)
{
return Socket::Status::WouldBlock;
if(errno == EWOULDBLOCK || errno == EAGAIN)
{
return Socket::Status::WouldBlock;
}
else if(errno == EINTR)
{
continue; //try again, interrupted before anything could be received
}
close_socket();
return Socket::Status::Disconnected;
}
break;
} while(true);
close_socket();
return Socket::Status::Disconnected;
}
if(received > buffer_size)
received = buffer_size;
received = static_cast<size_t>(status);
return Socket::Status::Success;
}
@ -185,4 +187,28 @@ namespace fr
{
return socket_descriptor;
}
void TcpSocket::reconfigure_socket()
{
int one = 1;
#ifndef _WIN32
//Disable Nagle's algorithm
setsockopt(get_socket_descriptor(), SOL_TCP, TCP_NODELAY, (char*)&one, sizeof(one));
//Apply receive timeout
struct timeval tv = {};
tv.tv_sec = get_receive_timeout() / 1000;
tv.tv_usec = (get_receive_timeout() % 1000) * 1000;
setsockopt(socket_descriptor, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
#else
//Disable Nagle's algorithm
setsockopt(get_socket_descriptor(), IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one));
setsockopt(get_socket_descriptor(), SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&one, sizeof(one));
//Apply receive timeout
DWORD timeout_dword = static_cast<DWORD>(get_receive_timeout());
setsockopt(socket_descriptor, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout_dword, sizeof timeout_dword);
#endif
}
}