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)
add_definitions(-DPOCO_ENABLE_STD_MUTEX)
endif ()
include(DefinePlatformSpecifc)
# 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
#endif
#ifndef POCO_HAVE_SENDFILE
// #define POCO_HAVE_SENDFILE
#endif
#define POCO_HAVE_CPP17_COMPILER (__cplusplus >= 201703L)
// Option to silence deprecation warnings.

View File

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

View File

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

View File

@ -47,7 +47,6 @@ using UInt64 = std::uint64_t;
using IntPtr = std::intptr_t;
using UIntPtr = std::uintptr_t;
#if defined(_MSC_VER)
#if defined(_WIN64)
#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_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
if(MSVC AND BUILD_SHARED_LIBS)
source_group("Resources" FILES ${PROJECT_SOURCE_DIR}/DLLVersion.rc)

View File

@ -477,12 +477,17 @@ public:
bool initialized() const;
/// Returns true iff the underlying socket is initialized.
Poco::Int64 sendFile(FileInputStream &FileInputStream, Poco::UInt64 offset = 0);
#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.

View File

@ -257,12 +257,17 @@ public:
///
/// The preferred way for a socket to receive urgent data
/// is by enabling the SO_OOBINLINE option.
Poco::Int64 sendFile(FileInputStream &FileInputStream, Poco::UInt64 offset = 0);
#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(...)
///
/// 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);
/// Creates the Socket and attaches the given SocketImpl.
/// The socket takes ownership of the SocketImpl.

View File

@ -28,6 +28,8 @@
#include "Poco/FileStream.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h"
#include "Poco/Error.h"
#include "Poco/Net/NetException.h"
using Poco::File;
@ -37,6 +39,7 @@ using Poco::StreamCopier;
using Poco::OpenFileException;
using Poco::DateTimeFormatter;
using Poco::DateTimeFormat;
using Poco::Error;
using namespace std::string_literals;
@ -129,7 +132,19 @@ void HTTPServerResponseImpl::sendFile(const std::string& path, const std::string
write(*_pStream);
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);
#endif
}
}
else throw OpenFileException(path);

View File

@ -56,7 +56,7 @@
using sighandler_t = sig_t;
#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>
#endif
@ -1372,12 +1372,12 @@ 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)
{
FileIOS::NativeHandle fd = fileInputStream.nativeHandle();
Poco::UInt64 fileSize = fileInputStream.size();
UInt64 fileSize = fileInputStream.size();
std::streamoff sentSize = fileSize - offset;
LARGE_INTEGER offsetHelper;
offsetHelper.QuadPart = offset;
@ -1388,7 +1388,8 @@ Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64
overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
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);
if (!result)
@ -1396,7 +1397,7 @@ Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64
int err = WSAGetLastError();
if ((err != ERROR_IO_PENDING) && (WSAGetLastError() != WSA_IO_PENDING)) {
CloseHandle(overlapped.hEvent);
error(err, Error::getMessage(err));
error(err, std::string("[sendfile error]") + Error::getMessage(err));
}
WaitForSingleObject(overlapped.hEvent, INFINITE);
}
@ -1404,9 +1405,9 @@ Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64
return sentSize;
}
#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
sent = sendfile64(sd, fd, (off64_t *)&offset, sentSize);
#else
@ -1433,21 +1434,26 @@ Poco::Int64 _sendfile(poco_socket_t sd, FileIOS::NativeHandle fd, Poco::UInt64 o
return sent;
}
Poco::Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, Poco::UInt64 offset)
Int64 SocketImpl::sendFile(FileInputStream &fileInputStream, UInt64 offset)
{
FileIOS::NativeHandle fd = fileInputStream.nativeHandle();
Poco::UInt64 fileSize = fileInputStream.size();
UInt64 fileSize = fileInputStream.size();
std::streamoff sentSize = fileSize - offset;
Poco::Int64 sent = 0;
Int64 sent = 0;
sighandler_t sigPrev = signal(SIGPIPE, SIG_IGN);
while (sent == 0)
{
errno = 0;
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);
return sent;
}
#endif // POCO_OS_FAMILY_WINDOWS
#endif // POCO_HAVE_SENDFILE
} } // namespace Poco::Net

View File

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

View File

@ -24,6 +24,9 @@
#include "Poco/Net/HTTPServerSession.h"
#include "Poco/Net/ServerSocket.h"
#include "Poco/StreamCopier.h"
#include "Poco/Path.h"
#include "Poco/FileStream.h"
#include "Poco/File.h"
#include <sstream>
@ -40,10 +43,15 @@ using Poco::Net::HTTPServerResponse;
using Poco::Net::HTTPMessage;
using Poco::Net::ServerSocket;
using Poco::StreamCopier;
using Poco::Path;
using Poco::File;
using Poco::FileOutputStream;
namespace
{
static const int sendFileSize = 64000;
class EchoBodyRequestHandler: public HTTPRequestHandler
{
public:
@ -105,7 +113,29 @@ namespace
response.sendBuffer(data.data(), data.length());
}
};
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
{
public:
@ -138,8 +168,10 @@ namespace
return new BufferRequestHandler;
else if (request.getURI() == "/trailer")
return new TrailerRequestHandler;
else if (request.getURI() == "/file")
return new FileRequestHandler;
else
return 0;
return nullptr;
}
};
}
@ -534,6 +566,26 @@ void HTTPServerTest::testBuffer()
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()
{
@ -585,6 +637,7 @@ CppUnit::Test* HTTPServerTest::suite()
CppUnit_addTest(pSuite, HTTPServerTest, testAuth);
CppUnit_addTest(pSuite, HTTPServerTest, testNotImpl);
CppUnit_addTest(pSuite, HTTPServerTest, testBuffer);
CppUnit_addTest(pSuite, HTTPServerTest, testFile);
CppUnit_addTest(pSuite, HTTPServerTest, testChunkedTrailer);
return pSuite;

View File

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

View File

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

View File

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