Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix StreamSocket::sendFile() #4863

Merged
merged 8 commits into from
Feb 8, 2025
8 changes: 4 additions & 4 deletions Net/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,24 @@ POCO_HEADERS_AUTO(SRCS ${HDRS_G})
POCO_SOURCES_AUTO_PLAT(SRCS WIN32 src/wepoll.c)
POCO_HEADERS_AUTO(SRCS src/wepoll.h)

if (MSVC)
if (MSVC OR MINGW)
set(HAVE_SENDFILE ON)
else()
include(CheckIncludeFiles)
include(CheckSymbolExists)
check_include_files(sys/sendfile.h HAVE_SYS_SENDFILE_H)
if(HAVE_SYS_SENDFILE_H)
check_symbol_exists(sendfile sys/sendfile.h HAVE_SENDFILE)
if (NOT DEFINED HAVE_SENDFILE)
check_symbol_exists(sendfile64 sys/sendfile.h HAVE_SENDFILE)
if (NOT HAVE_SENDFILE)
check_symbol_exists(sendfile64 sys/sendfile.h HAVE_SENDFILE64)
endif()
else()
# BSD version
check_symbol_exists(sendfile "sys/types.h;sys/socket.h;sys/uio.h" HAVE_SENDFILE)
endif()
endif()

if (DEFINED HAVE_SENDFILE)
if (HAVE_SENDFILE OR HAVE_SENDFILE64)
message(STATUS "OS has native sendfile function")
add_compile_definitions(POCO_HAVE_SENDFILE)
endif()
Expand Down
39 changes: 28 additions & 11 deletions Net/include/Poco/Net/SocketImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,25 @@ class Net_API SocketImpl: public Poco::RefCountedObject
/// The preferred way for a socket to receive urgent data
/// is by enabling the SO_OOBINLINE option.

virtual std::streamsize sendFile(Poco::FileInputStream& FileInputStream, std::streamoff offset = 0, std::streamsize count = 0);
/// Sends the contents of a file in an optimized way, if possible.
///
/// If count is != 0, sends the given number of bytes, otherwise
/// sends all bytes, starting from the given offset.
///
/// On POSIX systems, this means using sendfile() or sendfile64().
/// On Windows, this means using TransmitFile().
///
/// If neither is available, or the socket is a SecureSocketImpl()
/// (secure() returns true), falls back to reading the file
/// block by block and callind sendBytes().
///
/// Returns the number of bytes sent, which may be
/// less than the number of bytes specified.
///
/// Throws NetException (or a subclass) in case of any errors.
/// Also throws if the socket is non-blocking.

virtual int available();
/// Returns the number of bytes available that can be read
/// without causing the socket to block.
Expand Down Expand Up @@ -487,17 +506,7 @@ class Net_API SocketImpl: public Poco::RefCountedObject

bool initialized() const;
/// Returns true iff the underlying socket is initialized.
#ifdef POCO_HAVE_SENDFILE
Int64 sendFile(FileInputStream &FileInputStream, UInt64 offset = 0);
/// Sends file using system function
/// for posix systems - with sendfile[64](...)
/// for windows - with TransmitFile(...)
///
/// Returns the number of bytes sent, which may be
/// less than the number of bytes specified.
///
/// Throws NetException (or a subclass) in case of any errors.
#endif

protected:
SocketImpl();
/// Creates a SocketImpl.
Expand Down Expand Up @@ -538,6 +547,14 @@ class Net_API SocketImpl: public Poco::RefCountedObject

void checkBrokenTimeout(SelectMode mode);

std::streamsize sendFileNative(Poco::FileInputStream& FileInputStream, std::streamoff offset, std::streamsize count);
/// Implements sendFile() using an OS-specific API like
/// sendfile() or TransmitFile().

std::streamsize sendFileBlockwise(Poco::FileInputStream& FileInputStream, std::streamoff offset, std::streamsize count);
/// Implements sendFile() by reading the file blockwise and
/// calling sendBytes() for each block.

static int lastError();
/// Returns the last error code.

Expand Down
21 changes: 15 additions & 6 deletions Net/include/Poco/Net/StreamSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,17 +267,26 @@ class Net_API StreamSocket: public Socket
///
/// The preferred way for a socket to receive urgent data
/// is by enabling the SO_OOBINLINE option.
#ifdef POCO_HAVE_SENDFILE
IntPtr sendFile(FileInputStream &FileInputStream, UIntPtr offset = 0);
/// Sends file using system function
/// for posix systems - with sendfile[64](...)
/// for windows - with TransmitFile(...)

std::streamsize sendFile(Poco::FileInputStream& FileInputStream, std::streamoff offset = 0, std::streamsize count = 0);
/// Sends the contents of a file in an optimized way, if possible.
///
/// If count is != 0, sends the given number of bytes, otherwise
/// sends all bytes, starting from the given offset.
///
/// On POSIX systems, this means using sendfile() or sendfile64().
/// On Windows, this means using TransmitFile().
///
/// If neither is available, or the socket is a SecureSocketImpl()
/// (secure() returns true), falls back to reading the file
/// block by block and callind sendBytes().
///
/// Returns the number of bytes sent, which may be
/// less than the number of bytes specified.
///
/// Throws NetException (or a subclass) in case of any errors.
#endif
/// Also throws if the socket is non-blocking.

StreamSocket(SocketImpl* pImpl);
/// Creates the Socket and attaches the given SocketImpl.
/// The socket takes ownership of the SocketImpl.
Expand Down
4 changes: 2 additions & 2 deletions Net/src/HTTPServerResponseImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ void HTTPServerResponseImpl::sendFile(const std::string& path, const std::string
#ifdef POCO_HAVE_SENDFILE
_pStream->flush(); // flush the HTTP headers to the socket, required by HTTP 1.0 and above

Poco::IntPtr sent = 0;
Poco::IntPtr offset = 0;
std::streamsize sent = 0;
std::streamoff offset = 0;
while (sent < length)
{
offset = sent;
Expand Down
155 changes: 115 additions & 40 deletions Net/src/SocketImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,27 @@ void SocketImpl::sendUrgent(unsigned char data)
}




std::streamsize SocketImpl::sendFile(FileInputStream& fileInputStream, std::streamoff offset, std::streamsize count)
{
if (!getBlocking()) throw NetException("sendFile() not supported for non-blocking sockets");

#ifdef POCO_HAVE_SENDFILE
if (secure())
{
return sendFileBlockwise(fileInputStream, offset, count);
}
else
{
return sendFileNative(fileInputStream, offset, count);
}
#else
return sendFileBlockwise(fileInputStream, offset, count);
#endif
}


int SocketImpl::available()
{
int result = 0;
Expand Down Expand Up @@ -1424,13 +1445,16 @@ void SocketImpl::error(int code, const std::string& arg)
throw IOException(NumberFormatter::format(code), arg, code);
}
}


#ifdef POCO_HAVE_SENDFILE
#ifdef POCO_OS_FAMILY_WINDOWS
Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64 offset)


std::streamsize SocketImpl::sendFileNative(FileInputStream& fileInputStream, std::streamoff offset, std::streamsize count)
{
FileIOS::NativeHandle fd = fileInputStream.nativeHandle();
UInt64 fileSize = fileInputStream.size();
std::streamoff sentSize = fileSize - offset;
if (count == 0) count = fileInputStream.size() - offset;
LARGE_INTEGER offsetHelper;
offsetHelper.QuadPart = offset;
OVERLAPPED overlapped;
Expand All @@ -1441,57 +1465,75 @@ Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64
if (overlapped.hEvent == nullptr)
{
int err = GetLastError();
error(err, std::string("[sendfile error]") + Error::getMessage(err));
error(err);
}
bool result = TransmitFile(_sockfd, fd, sentSize, 0, &overlapped, nullptr, 0);
bool result = TransmitFile(_sockfd, fd, count, 0, &overlapped, nullptr, 0);
if (!result)
{
int err = WSAGetLastError();
if ((err != ERROR_IO_PENDING) && (WSAGetLastError() != WSA_IO_PENDING)) {
if ((err != ERROR_IO_PENDING) && (WSAGetLastError() != WSA_IO_PENDING))
{
CloseHandle(overlapped.hEvent);
error(err, std::string("[sendfile error]") + Error::getMessage(err));
error(err);
}
WaitForSingleObject(overlapped.hEvent, INFINITE);
}
CloseHandle(overlapped.hEvent);
return sentSize;
return count;
}


#else
Int64 _sendfile(poco_socket_t sd, FileIOS::NativeHandle fd, UInt64 offset, std::streamoff sentSize)


namespace
{
Int64 sent = 0;
#ifdef __USE_LARGEFILE64
sent = sendfile64(sd, fd, (off64_t *)&offset, sentSize);
#else
#if POCO_OS == POCO_OS_LINUX && !defined(POCO_EMSCRIPTEN)
sent = sendfile(sd, fd, (off_t *)&offset, sentSize);
#elif POCO_OS == POCO_OS_MAC_OS_X
int result = sendfile(fd, sd, offset, &sentSize, nullptr, 0);
if (result < 0)
{
sent = -1;
}
else
std::streamsize sendFileUnix(poco_socket_t sd, FileIOS::NativeHandle fd, std::streamsize offset, std::streamoff count)
{
sent = sentSize;
}
#else
throw Poco::NotImplementedException("sendfile not implemented for this platform");
#endif
#endif
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
sent = 0;
}
return sent;
Int64 sent = 0;
#ifdef __USE_LARGEFILE64
sent = sendfile64(sd, fd, (off64_t*) &offset, count);
#else
#if POCO_OS == POCO_OS_LINUX && !defined(POCO_EMSCRIPTEN)
sent = sendfile(sd, fd, (off_t*) &offset, count);
#elif POCO_OS == POCO_OS_MAC_OS_X
int result = sendfile(fd, sd, offset, &count, NULL, 0);
if (result < 0)
{
sent = -1;
}
else
{
sent = count;
}
#elif POCO_OS == POCO_OS_FREE_BSD
int result = sendfile(fd, sd, offset, &count, NULL, NULL, 0);
if (result < 0)
{
sent = -1;
}
else
{
sent = count;
}
#else
throw Poco::NotImplementedException("sendfile not implemented for this platform");
#endif
#endif
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
sent = 0;
}
return sent;
}
}

Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, UInt64 offset)

std::streamsize SocketImpl::sendFileNative(FileInputStream& fileInputStream, std::streamoff offset, std::streamsize count)
{
FileIOS::NativeHandle fd = fileInputStream.nativeHandle();
UInt64 fileSize = fileInputStream.size();
std::streamoff sentSize = fileSize - offset;
Int64 sent = 0;
if (count == 0) count = fileInputStream.size() - offset;
std::streamsize sent = 0;
struct sigaction sa, old_sa;
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
Expand All @@ -1500,20 +1542,53 @@ Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, UInt64 offset)
while (sent == 0)
{
errno = 0;
sent = _sendfile(_sockfd, fd, offset, sentSize);
sent = sendFileUnix(_sockfd, fd, offset, count);
if (sent < 0)
{
error(errno, std::string("[sendfile error]") + Error::getMessage(errno));
error(errno);
}
}
if(old_sa.sa_handler == SIG_ERR)
if (old_sa.sa_handler == SIG_ERR)
{
old_sa.sa_handler = SIG_DFL;
}
sigaction(SIGPIPE, &old_sa, nullptr);
return sent;
}


#endif // POCO_OS_FAMILY_WINDOWS
#endif // POCO_HAVE_SENDFILE


std::streamsize SocketImpl::sendFileBlockwise(FileInputStream& fileInputStream, std::streamoff offset, std::streamsize count)
{
fileInputStream.seekg(offset, std::ios_base::beg);
Poco::Buffer<char> buffer(8192);
std::size_t bufferSize = buffer.size();
if (count > 0 && bufferSize > count) bufferSize = count;

std::streamsize len = 0;
fileInputStream.read(buffer.begin(), bufferSize);
std::streamsize n = fileInputStream.gcount();
while (n > 0 && (count == 0 || len < count))
{
len += n;
sendBytes(buffer.begin(), n);
if (count > 0 && len < count)
{
const std::size_t remaining = count - len;
if (bufferSize > remaining) bufferSize = remaining;
}
if (fileInputStream)
{
fileInputStream.read(buffer.begin(), bufferSize);
n = fileInputStream.gcount();
}
else n = 0;
}
return len;
}


} } // namespace Poco::Net
10 changes: 6 additions & 4 deletions Net/src/StreamSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,12 @@ void StreamSocket::sendUrgent(unsigned char data)
{
impl()->sendUrgent(data);
}
#ifdef POCO_HAVE_SENDFILE
IntPtr StreamSocket::sendFile(FileInputStream &fileInputStream, UIntPtr offset)


std::streamsize StreamSocket::sendFile(Poco::FileInputStream& fileInputStream, std::streamoff offset, std::streamsize count)
{
return impl()->sendFile(fileInputStream, offset);
return impl()->sendFile(fileInputStream, offset, count);
}
#endif


} } // namespace Poco::Net
Loading
Loading