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

Using native sendfile from OS for HttpServer #4351

Merged
merged 11 commits into from
Sep 1, 2024
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions Foundation/include/Poco/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Foundation/include/Poco/FileStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class Foundation_API FileIOS: public virtual std::ios
NativeHandle nativeHandle() const;
/// Returns native file descriptor handle

Poco::UInt64 size() const;
UInt64 size() const;
/// Returns file size

void flushToDisk();
Expand Down
2 changes: 1 addition & 1 deletion Foundation/include/Poco/FileStream_POSIX.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class Foundation_API FileStreamBuf: public BufferedBidirectionalStreamBuf
NativeHandle nativeHandle() const;
/// Returns native file descriptor handle

Poco::UInt64 size() const;
UInt64 size() const;
/// Returns file size

protected:
Expand Down
1 change: 0 additions & 1 deletion Foundation/include/Poco/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions Net/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 8 additions & 3 deletions Net/include/Poco/Net/SocketImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -477,12 +477,17 @@ class Net_API SocketImpl: public Poco::RefCountedObject

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.
Expand Down
11 changes: 8 additions & 3 deletions Net/include/Poco/Net/StreamSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,17 @@ class Net_API StreamSocket: public Socket
///
/// 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.
Expand Down
15 changes: 15 additions & 0 deletions Net/src/HTTPServerResponseImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,6 +39,7 @@ using Poco::StreamCopier;
using Poco::OpenFileException;
using Poco::DateTimeFormatter;
using Poco::DateTimeFormat;
using Poco::Error;
using namespace std::string_literals;


Expand Down Expand Up @@ -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)
bas524 marked this conversation as resolved.
Show resolved Hide resolved
{
offset = sent;
sent += _session.socket().sendFile(istr, offset);
}
#else
StreamCopier::copyStream(istr, *_pStream);
#endif
}
}
else throw OpenFileException(path);
Expand Down
26 changes: 16 additions & 10 deletions Net/src/SocketImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -1372,12 +1372,12 @@ void SocketImpl::error(int code, const std::string& arg)
throw IOException(NumberFormatter::format(code), arg, code);
}
}

#ifdef POCO_HAVE_SENDFILE
bas524 marked this conversation as resolved.
Show resolved Hide resolved
#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;
Expand All @@ -1388,25 +1388,26 @@ 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)
{
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);
}
CloseHandle(overlapped.hEvent);
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
Expand All @@ -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));
bas524 marked this conversation as resolved.
Show resolved Hide resolved
}
}
signal(SIGPIPE, sigPrev != SIG_ERR ? sigPrev : SIG_DFL);
return sent;
}
#endif // POCO_OS_FAMILY_WINDOWS
#endif // POCO_HAVE_SENDFILE

} } // namespace Poco::Net
6 changes: 3 additions & 3 deletions Net/src/StreamSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
57 changes: 55 additions & 2 deletions Net/testsuite/src/HTTPServerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>


Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
}
};
}
Expand Down Expand Up @@ -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()
{
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions Net/testsuite/src/HTTPServerTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class HTTPServerTest: public CppUnit::TestCase
void testAuth();
void testNotImpl();
void testBuffer();
void testFile();
void testChunkedTrailer();

void setUp();
Expand Down
Loading
Loading