348 строки
12 KiB
C++
348 строки
12 KiB
C++
/******************************************************************************
|
|
*
|
|
* Project: WMS Client Driver
|
|
* Purpose: Implementation of Dataset and RasterBand classes for WMS
|
|
* and other similar services.
|
|
* Author: Adam Nowacki, nowak@xpam.de
|
|
*
|
|
******************************************************************************
|
|
* Copyright (c) 2007, Adam Nowacki
|
|
* Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
|
|
* Copyright (c) 2016, Lucian Plesea
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
#include "wmsdriver.h"
|
|
#include <algorithm>
|
|
|
|
#if !CURL_AT_LEAST_VERSION(7, 28, 0)
|
|
// Needed for curl_multi_wait()
|
|
#error Need libcurl version 7.28.0 or newer
|
|
// 7.28 was released in Oct 2012
|
|
#endif
|
|
|
|
static size_t WriteFunc(void *buffer, size_t count, size_t nmemb, void *req)
|
|
{
|
|
WMSHTTPRequest *psRequest = reinterpret_cast<WMSHTTPRequest *>(req);
|
|
size_t size = count * nmemb;
|
|
|
|
if (size == 0)
|
|
return 0;
|
|
|
|
const size_t required_size = psRequest->nDataLen + size + 1;
|
|
if (required_size > psRequest->nDataAlloc)
|
|
{
|
|
size_t new_size = required_size * 2;
|
|
if (new_size < 512)
|
|
new_size = 512;
|
|
psRequest->nDataAlloc = new_size;
|
|
GByte *pabyNewData = reinterpret_cast<GByte *>(
|
|
VSIRealloc(psRequest->pabyData, new_size));
|
|
if (pabyNewData == nullptr)
|
|
{
|
|
VSIFree(psRequest->pabyData);
|
|
psRequest->pabyData = nullptr;
|
|
psRequest->Error.Printf(
|
|
"Out of memory allocating %u bytes for HTTP data buffer.",
|
|
static_cast<unsigned int>(new_size));
|
|
psRequest->nDataAlloc = 0;
|
|
psRequest->nDataLen = 0;
|
|
return 0;
|
|
}
|
|
psRequest->pabyData = pabyNewData;
|
|
}
|
|
memcpy(psRequest->pabyData + psRequest->nDataLen, buffer, size);
|
|
psRequest->nDataLen += size;
|
|
psRequest->pabyData[psRequest->nDataLen] = 0;
|
|
return nmemb;
|
|
}
|
|
|
|
// Process curl errors
|
|
static void ProcessCurlErrors(CURLMsg *msg, WMSHTTPRequest *pasRequest,
|
|
int nRequestCount)
|
|
{
|
|
CPLAssert(msg != nullptr);
|
|
CPLAssert(msg->msg == CURLMSG_DONE);
|
|
|
|
// in case of local file error: update status code
|
|
if (msg->data.result == CURLE_FILE_COULDNT_READ_FILE)
|
|
{
|
|
// identify current request
|
|
for (int current_req_i = 0; current_req_i < nRequestCount;
|
|
++current_req_i)
|
|
{
|
|
WMSHTTPRequest *const psRequest = &pasRequest[current_req_i];
|
|
if (psRequest->m_curl_handle != msg->easy_handle)
|
|
continue;
|
|
|
|
// sanity check for local files
|
|
if (STARTS_WITH(psRequest->URL.c_str(), "file://"))
|
|
{
|
|
psRequest->nStatus = 404;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Builds a curl request
|
|
void WMSHTTPInitializeRequest(WMSHTTPRequest *psRequest)
|
|
{
|
|
psRequest->nStatus = 0;
|
|
psRequest->pabyData = nullptr;
|
|
psRequest->nDataLen = 0;
|
|
psRequest->nDataAlloc = 0;
|
|
|
|
psRequest->m_curl_handle = curl_easy_init();
|
|
if (psRequest->m_curl_handle == nullptr)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"CPLHTTPInitializeRequest(): Unable to create CURL handle.");
|
|
return;
|
|
}
|
|
|
|
if (!psRequest->Range.empty())
|
|
{
|
|
CPL_IGNORE_RET_VAL(curl_easy_setopt(
|
|
psRequest->m_curl_handle, CURLOPT_RANGE, psRequest->Range.c_str()));
|
|
}
|
|
|
|
CPL_IGNORE_RET_VAL(curl_easy_setopt(psRequest->m_curl_handle,
|
|
CURLOPT_WRITEDATA, psRequest));
|
|
CPL_IGNORE_RET_VAL(curl_easy_setopt(psRequest->m_curl_handle,
|
|
CURLOPT_WRITEFUNCTION, WriteFunc));
|
|
|
|
psRequest->m_curl_error.resize(CURL_ERROR_SIZE + 1);
|
|
CPL_IGNORE_RET_VAL(curl_easy_setopt(psRequest->m_curl_handle,
|
|
CURLOPT_ERRORBUFFER,
|
|
&psRequest->m_curl_error[0]));
|
|
|
|
psRequest->m_headers = static_cast<struct curl_slist *>(CPLHTTPSetOptions(
|
|
psRequest->m_curl_handle, psRequest->URL.c_str(), psRequest->options));
|
|
const char *pszAccept = CSLFetchNameValue(psRequest->options, "ACCEPT");
|
|
if (pszAccept)
|
|
{
|
|
psRequest->m_headers = curl_slist_append(
|
|
psRequest->m_headers, CPLSPrintf("Accept: %s", pszAccept));
|
|
}
|
|
if (psRequest->m_headers != nullptr)
|
|
{
|
|
CPL_IGNORE_RET_VAL(curl_easy_setopt(psRequest->m_curl_handle,
|
|
CURLOPT_HTTPHEADER,
|
|
psRequest->m_headers));
|
|
}
|
|
}
|
|
|
|
WMSHTTPRequest::~WMSHTTPRequest()
|
|
{
|
|
if (m_curl_handle != nullptr)
|
|
curl_easy_cleanup(m_curl_handle);
|
|
if (m_headers != nullptr)
|
|
curl_slist_free_all(m_headers);
|
|
if (pabyData != nullptr)
|
|
CPLFree(pabyData);
|
|
}
|
|
|
|
//
|
|
// Like CPLHTTPFetch, but multiple requests in parallel
|
|
// By default it uses 5 connections
|
|
//
|
|
CPLErr WMSHTTPFetchMulti(WMSHTTPRequest *pasRequest, int nRequestCount)
|
|
{
|
|
CPLErr ret = CE_None;
|
|
CURLM *curl_multi = nullptr;
|
|
int max_conn;
|
|
int i, conn_i;
|
|
|
|
CPLAssert(nRequestCount >= 0);
|
|
if (nRequestCount == 0)
|
|
return CE_None;
|
|
|
|
const char *max_conn_opt =
|
|
CSLFetchNameValue(const_cast<char **>(pasRequest->options), "MAXCONN");
|
|
max_conn =
|
|
(max_conn_opt == nullptr) ? 5 : MAX(1, MIN(atoi(max_conn_opt), 1000));
|
|
|
|
// If the first url starts with vsimem, assume all do and defer to
|
|
// CPLHTTPFetch
|
|
if (STARTS_WITH(pasRequest[0].URL.c_str(), "/vsimem/") &&
|
|
/* Disabled by default for potential security issues */
|
|
CPLTestBool(CPLGetConfigOption("CPL_CURL_ENABLE_VSIMEM", "FALSE")))
|
|
{
|
|
for (i = 0; i < nRequestCount; i++)
|
|
{
|
|
CPLHTTPResult *psResult =
|
|
CPLHTTPFetch(pasRequest[i].URL.c_str(),
|
|
const_cast<char **>(pasRequest[i].options));
|
|
pasRequest[i].pabyData = psResult->pabyData;
|
|
pasRequest[i].nDataLen = psResult->nDataLen;
|
|
pasRequest[i].Error =
|
|
psResult->pszErrBuf ? psResult->pszErrBuf : "";
|
|
// Conventions are different between this module and cpl_http...
|
|
if (psResult->pszErrBuf != nullptr &&
|
|
strcmp(psResult->pszErrBuf, "HTTP error code : 404") == 0)
|
|
pasRequest[i].nStatus = 404;
|
|
else
|
|
pasRequest[i].nStatus = 200;
|
|
pasRequest[i].ContentType =
|
|
psResult->pszContentType ? psResult->pszContentType : "";
|
|
// took ownership of content, we're done with the rest
|
|
psResult->pabyData = nullptr;
|
|
psResult->nDataLen = 0;
|
|
CPLHTTPDestroyResult(psResult);
|
|
}
|
|
return CE_None;
|
|
}
|
|
|
|
curl_multi = curl_multi_init();
|
|
if (curl_multi == nullptr)
|
|
{
|
|
CPLError(CE_Fatal, CPLE_AppDefined,
|
|
"CPLHTTPFetchMulti(): Unable to create CURL multi-handle.");
|
|
}
|
|
|
|
// add at most max_conn requests
|
|
int torun = std::min(nRequestCount, max_conn);
|
|
for (conn_i = 0; conn_i < torun; ++conn_i)
|
|
{
|
|
WMSHTTPRequest *const psRequest = &pasRequest[conn_i];
|
|
CPLDebug("HTTP", "Requesting [%d/%d] %s", conn_i + 1, nRequestCount,
|
|
pasRequest[conn_i].URL.c_str());
|
|
curl_multi_add_handle(curl_multi, psRequest->m_curl_handle);
|
|
}
|
|
|
|
void *old_handler = CPLHTTPIgnoreSigPipe();
|
|
int still_running;
|
|
do
|
|
{
|
|
CURLMcode mc;
|
|
do
|
|
{
|
|
mc = curl_multi_perform(curl_multi, &still_running);
|
|
} while (CURLM_CALL_MULTI_PERFORM == mc);
|
|
|
|
// Pick up messages, clean up the completed ones, add more
|
|
int msgs_in_queue = 0;
|
|
do
|
|
{
|
|
CURLMsg *m = curl_multi_info_read(curl_multi, &msgs_in_queue);
|
|
if (m && (m->msg == CURLMSG_DONE))
|
|
{
|
|
ProcessCurlErrors(m, pasRequest, nRequestCount);
|
|
|
|
curl_multi_remove_handle(curl_multi, m->easy_handle);
|
|
if (conn_i < nRequestCount)
|
|
{
|
|
auto psRequest = &pasRequest[conn_i];
|
|
CPLDebug("HTTP", "Requesting [%d/%d] %s", conn_i + 1,
|
|
nRequestCount, pasRequest[conn_i].URL.c_str());
|
|
curl_multi_add_handle(curl_multi, psRequest->m_curl_handle);
|
|
++conn_i;
|
|
still_running = 1; // Still have request pending
|
|
}
|
|
}
|
|
} while (msgs_in_queue);
|
|
|
|
if (CURLM_OK == mc)
|
|
{
|
|
int numfds;
|
|
curl_multi_wait(curl_multi, nullptr, 0, 100, &numfds);
|
|
}
|
|
} while (still_running || conn_i != nRequestCount);
|
|
|
|
// process any message still in queue
|
|
CURLMsg *msg;
|
|
int msgs_in_queue;
|
|
do
|
|
{
|
|
msg = curl_multi_info_read(curl_multi, &msgs_in_queue);
|
|
if (msg != nullptr)
|
|
{
|
|
if (msg->msg == CURLMSG_DONE)
|
|
{
|
|
ProcessCurlErrors(msg, pasRequest, nRequestCount);
|
|
}
|
|
}
|
|
} while (msg != nullptr);
|
|
|
|
CPLHTTPRestoreSigPipeHandler(old_handler);
|
|
|
|
if (conn_i != nRequestCount)
|
|
{ // something gone really really wrong
|
|
// oddly built libcurl or perhaps absence of network interface
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"WMSHTTPFetchMulti(): conn_i != nRequestCount, this should "
|
|
"never happen ...");
|
|
nRequestCount = conn_i;
|
|
ret = CE_Failure;
|
|
}
|
|
|
|
for (i = 0; i < nRequestCount; ++i)
|
|
{
|
|
WMSHTTPRequest *const psRequest = &pasRequest[i];
|
|
|
|
long response_code;
|
|
curl_easy_getinfo(psRequest->m_curl_handle, CURLINFO_RESPONSE_CODE,
|
|
&response_code);
|
|
// for local files, don't update the status code if one is already set
|
|
if (!(psRequest->nStatus != 0 &&
|
|
STARTS_WITH(psRequest->URL.c_str(), "file://")))
|
|
psRequest->nStatus = static_cast<int>(response_code);
|
|
|
|
char *content_type = nullptr;
|
|
curl_easy_getinfo(psRequest->m_curl_handle, CURLINFO_CONTENT_TYPE,
|
|
&content_type);
|
|
psRequest->ContentType = content_type ? content_type : "";
|
|
|
|
if (psRequest->Error.empty())
|
|
psRequest->Error = &psRequest->m_curl_error[0];
|
|
|
|
/* In the case of a file:// URL, curl will return a status == 0, so if
|
|
* there's no */
|
|
/* error returned, patch the status code to be 200, as it would be for
|
|
* http:// */
|
|
if (psRequest->nStatus == 0 && psRequest->Error.empty() &&
|
|
STARTS_WITH(psRequest->URL.c_str(), "file://"))
|
|
psRequest->nStatus = 200;
|
|
|
|
// If there is an error with no error message, use the content if it is
|
|
// text
|
|
if (psRequest->Error.empty() && psRequest->nStatus != 0 &&
|
|
psRequest->nStatus != 200 &&
|
|
strstr(psRequest->ContentType, "text") &&
|
|
psRequest->pabyData != nullptr)
|
|
psRequest->Error =
|
|
reinterpret_cast<const char *>(psRequest->pabyData);
|
|
|
|
CPLDebug(
|
|
"HTTP", "Request [%d] %s : status = %d, type = %s, error = %s", i,
|
|
psRequest->URL.c_str(), psRequest->nStatus,
|
|
!psRequest->ContentType.empty() ? psRequest->ContentType.c_str()
|
|
: "(null)",
|
|
!psRequest->Error.empty() ? psRequest->Error.c_str() : "(null)");
|
|
|
|
curl_multi_remove_handle(curl_multi, pasRequest->m_curl_handle);
|
|
}
|
|
|
|
curl_multi_cleanup(curl_multi);
|
|
|
|
return ret;
|
|
}
|