diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cc5cfa391..d8ef0cb406 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/Foundation/include/Poco/Config.h b/Foundation/include/Poco/Config.h index a0168008a3..bd57425dfc 100644 --- a/Foundation/include/Poco/Config.h +++ b/Foundation/include/Poco/Config.h @@ -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. diff --git a/Foundation/include/Poco/FileStream.h b/Foundation/include/Poco/FileStream.h index 662d63e28e..7acd1e2227 100644 --- a/Foundation/include/Poco/FileStream.h +++ b/Foundation/include/Poco/FileStream.h @@ -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(); diff --git a/Foundation/include/Poco/FileStream_POSIX.h b/Foundation/include/Poco/FileStream_POSIX.h index 0c35b0a07d..a31aa8cd34 100644 --- a/Foundation/include/Poco/FileStream_POSIX.h +++ b/Foundation/include/Poco/FileStream_POSIX.h @@ -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: diff --git a/Foundation/include/Poco/Types.h b/Foundation/include/Poco/Types.h index ed1a386228..965c4553dc 100644 --- a/Foundation/include/Poco/Types.h +++ b/Foundation/include/Poco/Types.h @@ -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 diff --git a/Net/CMakeLists.txt b/Net/CMakeLists.txt index 229cdc991b..034f02502d 100644 --- a/Net/CMakeLists.txt +++ b/Net/CMakeLists.txt @@ -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) diff --git a/Net/include/Poco/Net/SocketImpl.h b/Net/include/Poco/Net/SocketImpl.h index 501488e052..2a71877e16 100644 --- a/Net/include/Poco/Net/SocketImpl.h +++ b/Net/include/Poco/Net/SocketImpl.h @@ -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. diff --git a/Net/include/Poco/Net/StreamSocket.h b/Net/include/Poco/Net/StreamSocket.h index a71b87acd5..0b219a0310 100644 --- a/Net/include/Poco/Net/StreamSocket.h +++ b/Net/include/Poco/Net/StreamSocket.h @@ -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. diff --git a/Net/src/HTTPServerResponseImpl.cpp b/Net/src/HTTPServerResponseImpl.cpp index a127b14c34..ce4fd5bf36 100644 --- a/Net/src/HTTPServerResponseImpl.cpp +++ b/Net/src/HTTPServerResponseImpl.cpp @@ -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); diff --git a/Net/src/SocketImpl.cpp b/Net/src/SocketImpl.cpp index 34cad7d074..5a3c7b1197 100644 --- a/Net/src/SocketImpl.cpp +++ b/Net/src/SocketImpl.cpp @@ -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 #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 diff --git a/Net/src/StreamSocket.cpp b/Net/src/StreamSocket.cpp index 7741654ed7..806c56516a 100644 --- a/Net/src/StreamSocket.cpp +++ b/Net/src/StreamSocket.cpp @@ -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 diff --git a/Net/testsuite/src/HTTPServerTest.cpp b/Net/testsuite/src/HTTPServerTest.cpp index 3379188ca6..d39695e3b9 100644 --- a/Net/testsuite/src/HTTPServerTest.cpp +++ b/Net/testsuite/src/HTTPServerTest.cpp @@ -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 @@ -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; diff --git a/Net/testsuite/src/HTTPServerTest.h b/Net/testsuite/src/HTTPServerTest.h index 3ba58b24b8..84e4f550b4 100644 --- a/Net/testsuite/src/HTTPServerTest.h +++ b/Net/testsuite/src/HTTPServerTest.h @@ -38,6 +38,7 @@ class HTTPServerTest: public CppUnit::TestCase void testAuth(); void testNotImpl(); void testBuffer(); + void testFile(); void testChunkedTrailer(); void setUp(); diff --git a/Net/testsuite/src/SocketStreamTest.cpp b/Net/testsuite/src/SocketStreamTest.cpp index 5a453b4449..aef327a3dc 100644 --- a/Net/testsuite/src/SocketStreamTest.cpp +++ b/Net/testsuite/src/SocketStreamTest.cpp @@ -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; } diff --git a/Net/testsuite/src/SocketStreamTest.h b/Net/testsuite/src/SocketStreamTest.h index c97a77e6f2..69d5172adf 100644 --- a/Net/testsuite/src/SocketStreamTest.h +++ b/Net/testsuite/src/SocketStreamTest.h @@ -27,7 +27,9 @@ class SocketStreamTest: public CppUnit::TestCase void testStreamEcho(); void testLargeStreamEcho(); void testEOF(); +#ifdef POCO_HAVE_SENDFILE void testSendFile(); +#endif void setUp(); void tearDown();