summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Stahl <michael.stahl@allotropia.de>2021-10-26 12:18:11 +0200
committerMichael Stahl <michael.stahl@allotropia.de>2021-11-01 18:59:48 +0100
commitebe2050da7f04e0e4b3c7d27ec25379604fc86da (patch)
tree0f52290d3b12540c097268f6a65395e8f5a8cc90
parent791f94a967560fb36cce06e673b115f3ffc707ae (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.cxx84
-rw-r--r--ucb/source/ucp/webdav-curl/CurlSession.hxx4
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;