diff options
author | Michael Stahl <michael.stahl@allotropia.de> | 2021-10-26 12:18:11 +0200 |
---|---|---|
committer | Michael Stahl <michael.stahl@allotropia.de> | 2021-11-01 18:59:48 +0100 |
commit | ebe2050da7f04e0e4b3c7d27ec25379604fc86da (patch) | |
tree | 0f52290d3b12540c097268f6a65395e8f5a8cc90 | |
parent | 791f94a967560fb36cce06e673b115f3ffc707ae (diff) |
ucb: webdav-curl: use curl_multi API to support read timeout
This enables passing timeout to the curl_multi_wait() function.
Change-Id: Ic0ab9afe955b3625be0a44304c69882eb896abf0
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/124219
Tested-by: Michael Stahl <michael.stahl@allotropia.de>
Reviewed-by: Michael Stahl <michael.stahl@allotropia.de>
-rw-r--r-- | ucb/source/ucp/webdav-curl/CurlSession.cxx | 84 | ||||
-rw-r--r-- | ucb/source/ucp/webdav-curl/CurlSession.hxx | 4 |
2 files changed, 84 insertions, 4 deletions
diff --git a/ucb/source/ucp/webdav-curl/CurlSession.cxx b/ucb/source/ucp/webdav-curl/CurlSession.cxx index 4c42985190ff..35e70b6d4908 100644 --- a/ucb/source/ucp/webdav-curl/CurlSession.cxx +++ b/ucb/source/ucp/webdav-curl/CurlSession.cxx @@ -137,6 +137,11 @@ static auto GetErrorString(CURLcode const rc, char const* const pErrorBuffer = n return OString::Concat("(") + OString::number(sal_Int32(rc)) + ") " + pMessage; } +static auto GetErrorStringMulti(CURLMcode const mc) -> OString +{ + return OString::Concat("(") + OString::number(sal_Int32(mc)) + ") " + curl_multi_strerror(mc); +} + // libcurl callbacks: #if OSL_DEBUG_LEVEL > 0 @@ -445,6 +450,13 @@ CurlSession::CurlSession(uno::Reference<uno::XComponentContext> const& xContext, , m_Proxy(rProxyDecider.getProxy(m_URI.GetScheme(), m_URI.GetHost(), m_URI.GetPort())) { assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https"); + m_pCurlMulti.reset(curl_multi_init()); + if (!m_pCurlMulti) + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_init failed"); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); + } m_pCurl.reset(curl_easy_init()); if (!m_pCurl) { @@ -504,10 +516,8 @@ CurlSession::CurlSession(uno::Reference<uno::XComponentContext> const& xContext, throw DAVException(DAVException::DAV_SESSION_CREATE, ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); } -#if 0 auto const readTimeout(officecfg::Inet::Settings::ReadTimeout::get(m_xContext)); - // TODO: read timeout??? does not map to this value? -#endif + m_nReadTimeout = ::std::max<int>(20, ::std::min<long>(readTimeout, 180)) * 1000; // default is infinite rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_TIMEOUT, 300L); if (rc != CURLE_OK) @@ -837,8 +847,74 @@ auto CurlProcessor::ProcessRequestImpl( assert(rc == CURLE_OK); } rSession.m_ErrorBuffer[0] = '\0'; + + // note: easy handle must be added for *every* transfer! + // otherwise it gets stuck in MSTATE_MSGSENT forever after 1st transfer + auto mc = curl_multi_add_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get()); + if (mc != CURLM_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "curl_multi_add_handle failed: " << GetErrorStringMulti(mc)); + throw DAVException( + DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + } + ::comphelper::ScopeGuard const gg([&]() { + mc = curl_multi_remove_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get()); + if (mc != CURLM_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "curl_multi_remove_handle failed: " << GetErrorStringMulti(mc)); + } + }); + // this is where libcurl actually does something - rc = curl_easy_perform(rSession.m_pCurl.get()); + rc = CURL_LAST; // clear current value + int nRunning; + do + { + mc = curl_multi_perform(rSession.m_pCurlMulti.get(), &nRunning); + if (mc != CURLM_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "curl_multi_perform failed: " << GetErrorStringMulti(mc)); + throw DAVException( + DAVException::DAV_HTTP_CONNECT, + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + } + if (nRunning == 0) + { // short request like HEAD on loopback could be done in first call + break; + } + int nFDs; + mc = curl_multi_wait(rSession.m_pCurlMulti.get(), nullptr, 0, rSession.m_nReadTimeout, + &nFDs); + if (mc != CURLM_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "curl_multi_poll failed: " << GetErrorStringMulti(mc)); + throw DAVException( + DAVException::DAV_HTTP_CONNECT, + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + } + } while (nRunning != 0); + // there should be exactly 1 CURLMsg now, but the interface is + // extensible so future libcurl versions could yield additional things + do + { + CURLMsg const* const pMsg + = curl_multi_info_read(rSession.m_pCurlMulti.get(), &nRunning); + if (pMsg && pMsg->msg == CURLMSG_DONE) + { + assert(pMsg->easy_handle == rSession.m_pCurl.get()); + rc = pMsg->data.result; + } + else + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_info_read unexpected result"); + } + } while (nRunning != 0); + // error handling part 1: libcurl errors if (rc != CURLE_OK) { diff --git a/ucb/source/ucp/webdav-curl/CurlSession.hxx b/ucb/source/ucp/webdav-curl/CurlSession.hxx index 33288b312051..67e4e3616aab 100644 --- a/ucb/source/ucp/webdav-curl/CurlSession.hxx +++ b/ucb/source/ucp/webdav-curl/CurlSession.hxx @@ -35,7 +35,11 @@ private: /// once authentication was successful, rely on m_pCurl's data bool m_isAuthenticated = false; bool m_isAuthenticatedProxy = false; + /// read timeout in milliseconds (connection timeout is stored in m_pCurl) + int m_nReadTimeout = 0; + /// libcurl multi handle + ::std::unique_ptr<CURLM, deleter_from_fn<curl_multi_cleanup>> m_pCurlMulti; /// libcurl easy handle ::std::unique_ptr<CURL, deleter_from_fn<curl_easy_cleanup>> m_pCurl; |