Using native sendfile from OS for HttpServer (#4351)

* add possibility to use native sendFile from OS for HttpServerResponse
add option POCO_USE_SENDFILE_FOR_HTTPSERVER, default - OFF
add test for HttpServer - testFile
add define fro Config.h POCO_USE_SENDFILE_FOR_HTTPSERVER

* my fail, be carefull with macro and brackets

* replace option POCO_USE_SENDFILE_FOR_HTTPSERVER with compiletime
detected macro POCO_HAVE_SENDFILE
replace types for sendFile with platform depended
wrap possibility of using sendFile with macro, if sendFile doesn't exist
in OS, then all methods don't exist

* remove option POCO_USE_SENDFILE_FOR_HTTPSERVER from ci.yml

* wrap testSendFile in the suite with define POCO_HAVE_SENDFILE

* try fix compile problem with emscripten

* oh, emscripten again

* fix logical error in testSendFile

* fix problem with cmake-specific macro when usinf make-project

* revert types from platform depended to Poco::Int64 and Poco::UInt64
for sendfile
This commit is contained in:
Alexander B 2024-09-01 21:45:33 +03:00 committed by GitHub
parent 73df3689bf
commit 710c2a41f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 145 additions and 29 deletions

View File

@ -249,6 +249,7 @@ option(POCO_ENABLE_STD_MUTEX "Set to OFF|NO using mutex from standard library (d
if (POCO_ENABLE_STD_MUTEX) if (POCO_ENABLE_STD_MUTEX)
add_definitions(-DPOCO_ENABLE_STD_MUTEX) add_definitions(-DPOCO_ENABLE_STD_MUTEX)
endif () endif ()
include(DefinePlatformSpecifc) include(DefinePlatformSpecifc)
# Collect the built libraries and include dirs, the will be used to create the PocoConfig.cmake file # Collect the built libraries and include dirs, the will be used to create the PocoConfig.cmake file

View File

@ -181,6 +181,10 @@
// #define POCO_ENABLE_STD_MUTEX // #define POCO_ENABLE_STD_MUTEX
#endif #endif
#ifndef POCO_HAVE_SENDFILE
// #define POCO_HAVE_SENDFILE
#endif
#define POCO_HAVE_CPP17_COMPILER (__cplusplus >= 201703L) #define POCO_HAVE_CPP17_COMPILER (__cplusplus >= 201703L)
// Option to silence deprecation warnings. // Option to silence deprecation warnings.

View File

@ -79,7 +79,7 @@ public:
NativeHandle nativeHandle() const; NativeHandle nativeHandle() const;
/// Returns native file descriptor handle /// Returns native file descriptor handle
Poco::UInt64 size() const; UInt64 size() const;
/// Returns file size /// Returns file size
void flushToDisk(); void flushToDisk();

View File

@ -61,7 +61,7 @@ public:
NativeHandle nativeHandle() const; NativeHandle nativeHandle() const;
/// Returns native file descriptor handle /// Returns native file descriptor handle
Poco::UInt64 size() const; UInt64 size() const;
/// Returns file size /// Returns file size
protected: protected:

View File

@ -47,7 +47,6 @@ using UInt64 = std::uint64_t;
using IntPtr = std::intptr_t; using IntPtr = std::intptr_t;
using UIntPtr = std::uintptr_t; using UIntPtr = std::uintptr_t;
#if defined(_MSC_VER) #if defined(_MSC_VER)
#if defined(_WIN64) #if defined(_WIN64)
#define POCO_PTR_IS_64_BIT 1 #define POCO_PTR_IS_64_BIT 1

View File

@ -10,6 +10,28 @@ POCO_HEADERS_AUTO(SRCS ${HDRS_G})
POCO_SOURCES_AUTO_PLAT(SRCS WIN32 src/wepoll.c) POCO_SOURCES_AUTO_PLAT(SRCS WIN32 src/wepoll.c)
POCO_HEADERS_AUTO(SRCS src/wepoll.h) POCO_HEADERS_AUTO(SRCS src/wepoll.h)
if (MSVC)
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)
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)
message(STATUS "OS has native sendfile function")
add_definitions(-DPOCO_HAVE_SENDFILE)
endif()
# Version Resource # Version Resource
if(MSVC AND BUILD_SHARED_LIBS) if(MSVC AND BUILD_SHARED_LIBS)
source_group("Resources" FILES ${PROJECT_SOURCE_DIR}/DLLVersion.rc) source_group("Resources" FILES ${PROJECT_SOURCE_DIR}/DLLVersion.rc)

View File

@ -477,12 +477,17 @@ public:
bool initialized() const; bool initialized() const;
/// Returns true iff the underlying socket is initialized. /// Returns true iff the underlying socket is initialized.
#ifdef POCO_HAVE_SENDFILE
Poco::Int64 sendFile(FileInputStream &FileInputStream, Poco::UInt64 offset = 0); Int64 sendFile(FileInputStream &FileInputStream, UInt64 offset = 0);
/// Sends file using system function /// Sends file using system function
/// for posix systems - with sendfile[64](...) /// for posix systems - with sendfile[64](...)
/// for windows - with TransmitFile(...) /// 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: protected:
SocketImpl(); SocketImpl();
/// Creates a SocketImpl. /// Creates a SocketImpl.

View File

@ -257,12 +257,17 @@ public:
/// ///
/// The preferred way for a socket to receive urgent data /// The preferred way for a socket to receive urgent data
/// is by enabling the SO_OOBINLINE option. /// is by enabling the SO_OOBINLINE option.
#ifdef POCO_HAVE_SENDFILE
Poco::Int64 sendFile(FileInputStream &FileInputStream, Poco::UInt64 offset = 0); IntPtr sendFile(FileInputStream &FileInputStream, UIntPtr offset = 0);
/// Sends file using system function /// Sends file using system function
/// for posix systems - with sendfile[64](...) /// for posix systems - with sendfile[64](...)
/// for windows - with TransmitFile(...) /// 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
StreamSocket(SocketImpl* pImpl); StreamSocket(SocketImpl* pImpl);
/// Creates the Socket and attaches the given SocketImpl. /// Creates the Socket and attaches the given SocketImpl.
/// The socket takes ownership of the SocketImpl. /// The socket takes ownership of the SocketImpl.

View File

@ -28,6 +28,8 @@
#include "Poco/FileStream.h" #include "Poco/FileStream.h"
#include "Poco/DateTimeFormatter.h" #include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h" #include "Poco/DateTimeFormat.h"
#include "Poco/Error.h"
#include "Poco/Net/NetException.h"
using Poco::File; using Poco::File;
@ -37,6 +39,7 @@ using Poco::StreamCopier;
using Poco::OpenFileException; using Poco::OpenFileException;
using Poco::DateTimeFormatter; using Poco::DateTimeFormatter;
using Poco::DateTimeFormat; using Poco::DateTimeFormat;
using Poco::Error;
using namespace std::string_literals; using namespace std::string_literals;
@ -129,7 +132,19 @@ void HTTPServerResponseImpl::sendFile(const std::string& path, const std::string
write(*_pStream); write(*_pStream);
if (_pRequest && _pRequest->getMethod() != HTTPRequest::HTTP_HEAD) if (_pRequest && _pRequest->getMethod() != HTTPRequest::HTTP_HEAD)
{ {
#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;
while (sent < length)
{
offset = sent;
sent += _session.socket().sendFile(istr, offset);
}
#else
StreamCopier::copyStream(istr, *_pStream); StreamCopier::copyStream(istr, *_pStream);
#endif
} }
} }
else throw OpenFileException(path); else throw OpenFileException(path);

View File

@ -56,7 +56,7 @@
using sighandler_t = sig_t; using sighandler_t = sig_t;
#endif #endif
#if POCO_OS == POCO_OS_LINUX && !defined(POCO_EMSCRIPTEN) #if POCO_OS == POCO_OS_LINUX && defined(POCO_HAVE_SENDFILE) && !defined(POCO_EMSCRIPTEN)
#include <sys/sendfile.h> #include <sys/sendfile.h>
#endif #endif
@ -1372,12 +1372,12 @@ void SocketImpl::error(int code, const std::string& arg)
throw IOException(NumberFormatter::format(code), arg, code); throw IOException(NumberFormatter::format(code), arg, code);
} }
} }
#ifdef POCO_HAVE_SENDFILE
#ifdef POCO_OS_FAMILY_WINDOWS #ifdef POCO_OS_FAMILY_WINDOWS
Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64 offset) Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64 offset)
{ {
FileIOS::NativeHandle fd = fileInputStream.nativeHandle(); FileIOS::NativeHandle fd = fileInputStream.nativeHandle();
Poco::UInt64 fileSize = fileInputStream.size(); UInt64 fileSize = fileInputStream.size();
std::streamoff sentSize = fileSize - offset; std::streamoff sentSize = fileSize - offset;
LARGE_INTEGER offsetHelper; LARGE_INTEGER offsetHelper;
offsetHelper.QuadPart = offset; offsetHelper.QuadPart = offset;
@ -1388,7 +1388,8 @@ Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64
overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (overlapped.hEvent == nullptr) if (overlapped.hEvent == nullptr)
{ {
return -1; int err = GetLastError();
error(err, std::string("[sendfile error]") + Error::getMessage(err));
} }
bool result = TransmitFile(_sockfd, fd, sentSize, 0, &overlapped, nullptr, 0); bool result = TransmitFile(_sockfd, fd, sentSize, 0, &overlapped, nullptr, 0);
if (!result) if (!result)
@ -1396,7 +1397,7 @@ Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64
int err = WSAGetLastError(); int err = WSAGetLastError();
if ((err != ERROR_IO_PENDING) && (WSAGetLastError() != WSA_IO_PENDING)) { if ((err != ERROR_IO_PENDING) && (WSAGetLastError() != WSA_IO_PENDING)) {
CloseHandle(overlapped.hEvent); CloseHandle(overlapped.hEvent);
error(err, Error::getMessage(err)); error(err, std::string("[sendfile error]") + Error::getMessage(err));
} }
WaitForSingleObject(overlapped.hEvent, INFINITE); WaitForSingleObject(overlapped.hEvent, INFINITE);
} }
@ -1404,9 +1405,9 @@ Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64
return sentSize; return sentSize;
} }
#else #else
Poco::Int64 _sendfile(poco_socket_t sd, FileIOS::NativeHandle fd, Poco::UInt64 offset,std::streamoff sentSize) Int64 _sendfile(poco_socket_t sd, FileIOS::NativeHandle fd, UInt64 offset, std::streamoff sentSize)
{ {
Poco::Int64 sent = 0; Int64 sent = 0;
#ifdef __USE_LARGEFILE64 #ifdef __USE_LARGEFILE64
sent = sendfile64(sd, fd, (off64_t *)&offset, sentSize); sent = sendfile64(sd, fd, (off64_t *)&offset, sentSize);
#else #else
@ -1433,21 +1434,26 @@ Poco::Int64 _sendfile(poco_socket_t sd, FileIOS::NativeHandle fd, Poco::UInt64 o
return sent; return sent;
} }
Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64 offset) Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, UInt64 offset)
{ {
FileIOS::NativeHandle fd = fileInputStream.nativeHandle(); FileIOS::NativeHandle fd = fileInputStream.nativeHandle();
Poco::UInt64 fileSize = fileInputStream.size(); UInt64 fileSize = fileInputStream.size();
std::streamoff sentSize = fileSize - offset; std::streamoff sentSize = fileSize - offset;
Poco::Int64 sent = 0; Int64 sent = 0;
sighandler_t sigPrev = signal(SIGPIPE, SIG_IGN); sighandler_t sigPrev = signal(SIGPIPE, SIG_IGN);
while (sent == 0) while (sent == 0)
{ {
errno = 0; errno = 0;
sent = _sendfile(_sockfd, fd, offset, sentSize); sent = _sendfile(_sockfd, fd, offset, sentSize);
if (sent < 0)
{
error(errno, std::string("[sendfile error]") + Error::getMessage(errno));
}
} }
signal(SIGPIPE, sigPrev != SIG_ERR ? sigPrev : SIG_DFL); signal(SIGPIPE, sigPrev != SIG_ERR ? sigPrev : SIG_DFL);
return sent; return sent;
} }
#endif // POCO_OS_FAMILY_WINDOWS #endif // POCO_OS_FAMILY_WINDOWS
#endif // POCO_HAVE_SENDFILE
} } // namespace Poco::Net } } // namespace Poco::Net

View File

@ -212,10 +212,10 @@ void StreamSocket::sendUrgent(unsigned char data)
{ {
impl()->sendUrgent(data); impl()->sendUrgent(data);
} }
#ifdef POCO_HAVE_SENDFILE
Poco::Int64 StreamSocket::sendFile(FileInputStream &fileInputStream, Poco::UInt64 offset) IntPtr StreamSocket::sendFile(FileInputStream &fileInputStream, UIntPtr offset)
{ {
return impl()->sendFile(fileInputStream, offset); return impl()->sendFile(fileInputStream, offset);
} }
#endif
} } // namespace Poco::Net } } // namespace Poco::Net

View File

@ -24,6 +24,9 @@
#include "Poco/Net/HTTPServerSession.h" #include "Poco/Net/HTTPServerSession.h"
#include "Poco/Net/ServerSocket.h" #include "Poco/Net/ServerSocket.h"
#include "Poco/StreamCopier.h" #include "Poco/StreamCopier.h"
#include "Poco/Path.h"
#include "Poco/FileStream.h"
#include "Poco/File.h"
#include <sstream> #include <sstream>
@ -40,10 +43,15 @@ using Poco::Net::HTTPServerResponse;
using Poco::Net::HTTPMessage; using Poco::Net::HTTPMessage;
using Poco::Net::ServerSocket; using Poco::Net::ServerSocket;
using Poco::StreamCopier; using Poco::StreamCopier;
using Poco::Path;
using Poco::File;
using Poco::FileOutputStream;
namespace namespace
{ {
static const int sendFileSize = 64000;
class EchoBodyRequestHandler: public HTTPRequestHandler class EchoBodyRequestHandler: public HTTPRequestHandler
{ {
public: public:
@ -106,6 +114,28 @@ namespace
} }
}; };
class FileRequestHandler: public HTTPRequestHandler
{
public:
void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response)
{
std::string payload(sendFileSize, 'x');
Poco::Path testFilePath = Poco::Path::temp().append("test.http.server.sendfile.txt");
const std::string fileName = testFilePath.toString();
{
File f(fileName);
if (f.exists())
{
f.remove();
}
}
FileOutputStream fout(fileName);
fout << payload;
fout.close();
response.sendFile(fileName, "text/plain");
}
};
class TrailerRequestHandler: public HTTPRequestHandler class TrailerRequestHandler: public HTTPRequestHandler
{ {
public: public:
@ -138,8 +168,10 @@ namespace
return new BufferRequestHandler; return new BufferRequestHandler;
else if (request.getURI() == "/trailer") else if (request.getURI() == "/trailer")
return new TrailerRequestHandler; return new TrailerRequestHandler;
else if (request.getURI() == "/file")
return new FileRequestHandler;
else else
return 0; return nullptr;
} }
}; };
} }
@ -534,6 +566,26 @@ void HTTPServerTest::testBuffer()
assertTrue (rbody == "xxxxxxxxxx"); assertTrue (rbody == "xxxxxxxxxx");
} }
void HTTPServerTest::testFile()
{
std::string payload(sendFileSize, 'x');
ServerSocket svs(0);
HTTPServerParams* pParams = new HTTPServerParams;
pParams->setKeepAlive(false);
HTTPServer srv(new RequestHandlerFactory, svs, pParams);
srv.start();
HTTPClientSession cs("127.0.0.1", svs.address().port());
HTTPRequest request("GET", "/file");
cs.sendRequest(request);
HTTPResponse response;
std::string rbody;
cs.receiveResponse(response) >> rbody;
assertTrue (response.getStatus() == HTTPResponse::HTTP_OK);
assertTrue (rbody == payload);
}
void HTTPServerTest::testChunkedTrailer() void HTTPServerTest::testChunkedTrailer()
{ {
@ -585,6 +637,7 @@ CppUnit::Test* HTTPServerTest::suite()
CppUnit_addTest(pSuite, HTTPServerTest, testAuth); CppUnit_addTest(pSuite, HTTPServerTest, testAuth);
CppUnit_addTest(pSuite, HTTPServerTest, testNotImpl); CppUnit_addTest(pSuite, HTTPServerTest, testNotImpl);
CppUnit_addTest(pSuite, HTTPServerTest, testBuffer); CppUnit_addTest(pSuite, HTTPServerTest, testBuffer);
CppUnit_addTest(pSuite, HTTPServerTest, testFile);
CppUnit_addTest(pSuite, HTTPServerTest, testChunkedTrailer); CppUnit_addTest(pSuite, HTTPServerTest, testChunkedTrailer);
return pSuite; return pSuite;

View File

@ -38,6 +38,7 @@ public:
void testAuth(); void testAuth();
void testNotImpl(); void testNotImpl();
void testBuffer(); void testBuffer();
void testFile();
void testChunkedTrailer(); void testChunkedTrailer();
void setUp(); void setUp();

View File

@ -124,6 +124,7 @@ void SocketStreamTest::testEOF()
ss.close(); ss.close();
} }
#ifdef POCO_HAVE_SENDFILE
void SocketStreamTest::testSendFile() void SocketStreamTest::testSendFile()
{ {
const int fileSize = 64000; const int fileSize = 64000;
@ -147,11 +148,11 @@ void SocketStreamTest::testSendFile()
SocketStream str(ss); SocketStream str(ss);
Poco::UInt64 offset = 0; Poco::UIntPtr offset = 0;
Poco::Int64 sent = 0; Poco::IntPtr sent = 0;
try try
{ {
sent = ss.sendFile(fin); sent = ss.sendFile(fin);
} }
catch (Poco::NotImplementedException &) catch (Poco::NotImplementedException &)
{ {
@ -162,8 +163,7 @@ void SocketStreamTest::testSendFile()
while (sent < fileSize) while (sent < fileSize)
{ {
offset = sent; offset = sent;
sent = ss.sendFile(fin, offset); sent += ss.sendFile(fin, offset);
assertTrue(sent >= 0);
} }
str.flush(); str.flush();
assertTrue (str.good()); assertTrue (str.good());
@ -180,6 +180,7 @@ void SocketStreamTest::testSendFile()
File f(fileName); File f(fileName);
f.remove(); f.remove();
} }
#endif
void SocketStreamTest::setUp() void SocketStreamTest::setUp()
{ {
@ -198,7 +199,9 @@ CppUnit::Test* SocketStreamTest::suite()
CppUnit_addTest(pSuite, SocketStreamTest, testStreamEcho); CppUnit_addTest(pSuite, SocketStreamTest, testStreamEcho);
CppUnit_addTest(pSuite, SocketStreamTest, testLargeStreamEcho); CppUnit_addTest(pSuite, SocketStreamTest, testLargeStreamEcho);
CppUnit_addTest(pSuite, SocketStreamTest, testEOF); CppUnit_addTest(pSuite, SocketStreamTest, testEOF);
#ifdef POCO_HAVE_SENDFILE
CppUnit_addTest(pSuite, SocketStreamTest, testSendFile); CppUnit_addTest(pSuite, SocketStreamTest, testSendFile);
#endif
return pSuite; return pSuite;
} }

View File

@ -27,7 +27,9 @@ public:
void testStreamEcho(); void testStreamEcho();
void testLargeStreamEcho(); void testLargeStreamEcho();
void testEOF(); void testEOF();
#ifdef POCO_HAVE_SENDFILE
void testSendFile(); void testSendFile();
#endif
void setUp(); void setUp();
void tearDown(); void tearDown();