Files
OpenSceneGraph/src/osgPlugins/curl/ReaderWriterCURL.cpp
Robert Osfield e5a436b131 From Emmanuel Roche, "I've updated the CURL plugin to support the CURL_CONNECTTIMEOUT and CURL_TIMEOUT options.
Those two additional options can now be set using the Options::setOptionsString() function (just like the already existing OSG_CURL_PROXY & OSG_CURL_PROXYPORT options).

This is a convient solution to limit the freezing effect one may face in case the targeted server is down or too slow.

I successfully compiled and used this updated version on Windows in my application.

And by default those settings are not set (so no change in the behavior if you don't need them).
"
2009-06-10 09:24:12 +00:00

553 lines
17 KiB
C++

/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* OpenSceneGraph Public License for more details.
*/
#include <osgDB/FileUtils>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgDB/Registry>
#include <iostream>
#include <sstream>
#include <fstream>
#include <curl/curl.h>
#include <curl/types.h>
#include "ReaderWriterCURL.h"
using namespace osg_curl;
//
// StreamObject
//
EasyCurl::StreamObject::StreamObject(std::ostream* stream1, const std::string& cacheFileName):
_stream1(stream1),
_cacheFileName(cacheFileName)
{
_foutOpened = false;
}
void EasyCurl::StreamObject::write(const char* ptr, size_t realsize)
{
if (_stream1) _stream1->write(ptr, realsize);
if (!_cacheFileName.empty())
{
if (!_foutOpened)
{
osg::notify(osg::INFO)<<"Writing to cache: "<<_cacheFileName<<std::endl;
_fout.open(_cacheFileName.c_str(), std::ios::out | std::ios::binary);
_foutOpened = true;
}
if (_fout)
{
_fout.write(ptr, realsize);
}
}
}
std::string EasyCurl::getResultMimeType(const StreamObject& sp) const
{
return sp._resultMimeType;
}
size_t EasyCurl::StreamMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
{
size_t realsize = size * nmemb;
StreamObject* sp = (StreamObject*)data;
sp->write((const char*)ptr, realsize);
return realsize;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// EasyCurl
//
EasyCurl::EasyCurl()
{
osg::notify(osg::INFO)<<"EasyCurl::EasyCurl()"<<std::endl;
_previousHttpAuthentication = 0;
_connectTimeout = 0; // no timeout by default.
_timeout = 0;
_curl = curl_easy_init();
curl_easy_setopt(_curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, StreamMemoryCallback);
curl_easy_setopt(_curl, CURLOPT_FOLLOWLOCATION, 1);
}
EasyCurl::~EasyCurl()
{
osg::notify(osg::INFO)<<"EasyCurl::~EasyCurl()"<<std::endl;
if (_curl) curl_easy_cleanup(_curl);
_curl = 0;
}
osgDB::ReaderWriter::ReadResult EasyCurl::read(const std::string& proxyAddress, const std::string& fileName, StreamObject& sp, const osgDB::ReaderWriter::Options *options)
{
const osgDB::AuthenticationMap* authenticationMap = (options && options->getAuthenticationMap()) ?
options->getAuthenticationMap() :
osgDB::Registry::instance()->getAuthenticationMap();
// Set the timeout value here:
// Note that this has an effect only in a connection phase.
// WARNING: here we make the assumption that if someone starts using the timeouts settings
// he will not try to disable them afterwards (a value must be provided or the previous value is used).
if(_connectTimeout > 0)
curl_easy_setopt(_curl, CURLOPT_CONNECTTIMEOUT, _connectTimeout);
if(_timeout > 0)
curl_easy_setopt(_curl, CURLOPT_TIMEOUT, _timeout);
if(!proxyAddress.empty())
{
osg::notify(osg::INFO)<<"Setting proxy: "<<proxyAddress<<std::endl;
curl_easy_setopt(_curl, CURLOPT_PROXY, proxyAddress.c_str()); //Sets proxy address and port on libcurl
}
const osgDB::AuthenticationDetails* details = authenticationMap ?
authenticationMap->getAuthenticationDetails(fileName) :
0;
// configure/reset authentication if required.
if (details)
{
const std::string colon(":");
std::string password(details->username + colon + details->password);
curl_easy_setopt(_curl, CURLOPT_USERPWD, password.c_str());
_previousPassword = password;
// use for https.
// curl_easy_setopt(_curl, CURLOPT_KEYPASSWD, password.c_str());
#if LIBCURL_VERSION_NUM >= 0x070a07
if (details->httpAuthentication != _previousHttpAuthentication)
{
curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, details->httpAuthentication);
_previousHttpAuthentication = details->httpAuthentication;
}
#endif
}
else
{
if (!_previousPassword.empty())
{
curl_easy_setopt(_curl, CURLOPT_USERPWD, 0);
_previousPassword.clear();
}
#if LIBCURL_VERSION_NUM >= 0x070a07
// need to reset if previously set.
if (_previousHttpAuthentication!=0)
{
curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, 0);
_previousHttpAuthentication = 0;
}
#endif
}
curl_easy_setopt(_curl, CURLOPT_URL, fileName.c_str());
curl_easy_setopt(_curl, CURLOPT_WRITEDATA, (void *)&sp);
CURLcode res = curl_easy_perform(_curl);
curl_easy_setopt(_curl, CURLOPT_WRITEDATA, (void *)0);
if (res==0)
{
#if LIBCURL_VERSION_NUM >= 0x070a07
long code;
if(!proxyAddress.empty())
{
curl_easy_getinfo(_curl, CURLINFO_HTTP_CONNECTCODE, &code);
}
else
{
curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &code);
}
//If the code is greater than 400, there was an error
if (code >= 400)
{
osgDB::ReaderWriter::ReadResult::ReadStatus status;
//Distinguish between a client error and a server error
if (code < 500)
{
//A 400 level error indicates a client error
status = osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
}
else
{
//A 500 level error indicates a server error
status = osgDB::ReaderWriter::ReadResult::ERROR_IN_READING_FILE;
}
osgDB::ReaderWriter::ReadResult rr(status);
//Add the error code to the ReadResult
std::stringstream message;
message << "error code = " << code;
rr.message() = message.str();
return rr;
}
#endif
// Store the mime-type, if any. (Note: CURL manages the buffer returned by
// this call.)
char* ctbuf = NULL;
if ( curl_easy_getinfo(_curl, CURLINFO_CONTENT_TYPE, &ctbuf) == 0 && ctbuf )
{
sp._resultMimeType = ctbuf;
}
return osgDB::ReaderWriter::ReadResult::FILE_LOADED;
}
else
{
#if LIBCURL_VERSION_NUM >= 0x070c00
osg::notify(osg::NOTICE)<<"Error: libcurl read error, file="<<fileName<<" error = "<<curl_easy_strerror(res)<<std::endl;
#else
osg::notify(osg::NOTICE)<<"Error: libcurl read error, file="<<fileName<<" error no = "<<res<<std::endl;
#endif
return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// ReaderWriterCURL
//
ReaderWriterCURL::ReaderWriterCURL()
{
supportsProtocol("http","Read from http port using libcurl.");
supportsExtension("curl","Psuedo file extension, used to select curl plugin.");
supportsExtension("*","Passes all read files to other plugins to handle actual model loading.");
supportsOption("OSG_CURL_PROXY","Specify the http proxy.");
supportsOption("OSG_CURL_PROXYPORT","Specify the http proxy port.");
supportsOption("OSG_CURL_CONNECTTIMEOUT","Specify the connection timeout duration in seconds [default = 0 = not set].");
supportsOption("OSG_CURL_TIMEOUT","Specify the timeout duration of the whole transfer in seconds [default = 0 = not set].");
}
ReaderWriterCURL::~ReaderWriterCURL()
{
//osg::notify(osg::NOTICE)<<"ReaderWriterCURL::~ReaderWriterCURL()"<<std::endl;
}
osgDB::ReaderWriter::ReadResult ReaderWriterCURL::readFile(ObjectType objectType, osgDB::ReaderWriter* rw, std::istream& fin, const osgDB::ReaderWriter::Options *options) const
{
switch(objectType)
{
case(OBJECT): return rw->readObject(fin,options);
case(ARCHIVE): return rw->openArchive(fin,options);
case(IMAGE): return rw->readImage(fin,options);
case(HEIGHTFIELD): return rw->readHeightField(fin,options);
case(NODE): return rw->readNode(fin,options);
default: break;
}
return ReadResult::FILE_NOT_HANDLED;
}
osgDB::ReaderWriter::ReadResult ReaderWriterCURL::readFile(ObjectType objectType, const std::string& fullFileName, const osgDB::ReaderWriter::Options *options) const
{
if (!osgDB::containsServerAddress(fullFileName))
{
if (options && !options->getDatabasePathList().empty())
{
if (osgDB::containsServerAddress(options->getDatabasePathList().front()))
{
std::string newFileName = options->getDatabasePathList().front() + "/" + fullFileName;
return readFile(objectType, newFileName,options);
}
}
return ReadResult::FILE_NOT_HANDLED;
}
osg::notify(osg::INFO)<<"ReaderWriterCURL::readFile("<<fullFileName<<")"<<std::endl;
std::string proxyAddress, optProxy, optProxyPort;
long connectTimeout = 0;
long timeout = 0;
if (options)
{
std::istringstream iss(options->getOptionString());
std::string opt;
while (iss >> opt)
{
int index = opt.find( "=" );
if( opt.substr( 0, index ) == "OSG_CURL_PROXY" )
optProxy = opt.substr( index+1 );
else if( opt.substr( 0, index ) == "OSG_CURL_PROXYPORT" )
optProxyPort = opt.substr( index+1 );
else if( opt.substr( 0, index ) == "OSG_CURL_CONNECTTIMEOUT" )
connectTimeout = atol(opt.substr( index+1 ).c_str()); // this will return 0 in case of improper format.
else if( opt.substr( 0, index ) == "OSG_CURL_TIMEOUT" )
timeout = atol(opt.substr( index+1 ).c_str()); // this will return 0 in case of improper format.
}
//Setting Proxy by OSG Options
if(!optProxy.empty())
{
if(!optProxyPort.empty())
proxyAddress = optProxy + ":" + optProxyPort;
else
proxyAddress = optProxy + ":8080"; //Port not found, using default
}
}
std::string fileName;
std::string ext = osgDB::getFileExtension(fullFileName);
if (ext=="curl")
{
fileName = osgDB::getNameLessExtension(fullFileName);
ext = osgDB::getFileExtension(fileName);
}
else
{
fileName = fullFileName;
}
bool uncompress = false;
if (ext=="gz" || ext=="osgz" || ext=="ivez")
{
osg::notify(osg::NOTICE)<<"Compressed file type "<<ext<<std::endl;
#ifndef USE_ZLIB
// don't have zlib so can't compile compressed formats
return ReadResult::FILE_NOT_HANDLED;
#endif
uncompress = true;
if (ext=="gz")
{
ext = osgDB::getFileExtension(fileName);
fileName = osgDB::getNameLessExtension(fileName);
}
else if (ext=="osgz")
{
ext = "osg";
}
else if (ext=="ivez")
{
ext = "ive";
}
osg::notify(osg::NOTICE)<<" assuming file type "<<ext<<std::endl;
}
// Try to find a reader by file extension. If this fails, we will fetch the file
// anyway and try to get a reader via mime-type.
osgDB::ReaderWriter *reader =
osgDB::Registry::instance()->getReaderWriterForExtension( ext );
//if (!reader)
//{
// osg::notify(osg::NOTICE)<<"Error: No ReaderWriter for file "<<fileName<<std::endl;
// return ReadResult::FILE_NOT_HANDLED;
//}
osg::notify(osg::INFO)<<"CURL: Have readerwriter="<<reader<<std::endl;
const char* proxyEnvAddress = getenv("OSG_CURL_PROXY");
if (proxyEnvAddress) //Env Proxy Settings
{
const char* proxyEnvPort = getenv("OSG_CURL_PROXYPORT"); //Searching Proxy Port on Env
if(proxyEnvPort)
proxyAddress = std::string(proxyEnvAddress) + ":" + std::string(proxyEnvPort);
else
proxyAddress = std::string(proxyEnvAddress) + ":8080"; //Default
}
std::stringstream buffer;
EasyCurl::StreamObject sp(&buffer, std::string());
// setup the timeouts:
getEasyCurl().setConnectionTimeout(connectTimeout);
getEasyCurl().setTimeout(timeout);
ReadResult curlResult = getEasyCurl().read(proxyAddress, fileName, sp, options);
if (curlResult.status()==ReadResult::FILE_LOADED)
{
osg::notify(osg::INFO)<<"CURL: ReadResult::FILE_LOADED "<<std::endl;
// If we do not already have a ReaderWriter, try to find one based on the
// mime-type:
if ( !reader )
{
std::string mimeType = getEasyCurl().getResultMimeType(sp);
osg::notify(osg::INFO) << "CURL: Looking up extension for mime-type " << mimeType << std::endl;
if ( mimeType.length() > 0 )
{
reader = osgDB::Registry::instance()->getReaderWriterForMimeType(mimeType);
}
}
// If there is still no reader, fail.
if ( !reader )
{
osg::notify(osg::NOTICE)<<"Error: No ReaderWriter for file "<<fileName<<std::endl;
return ReadResult::FILE_NOT_HANDLED;
}
osg::ref_ptr<Options> local_opt = options ?
static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) :
new Options;
local_opt->getDatabasePathList().push_front(osgDB::getFilePath(fileName));
local_opt->setPluginStringData("STREAM_FILENAME",osgDB::getSimpleFileName(fileName));
local_opt->setPluginStringData("filename",fileName);
if (uncompress)
{
std::string uncompressed;
if (!read(buffer, uncompressed))
{
return ReadResult::FILE_NOT_HANDLED;
}
buffer.str(uncompressed);
}
ReadResult readResult = readFile(objectType, reader, buffer, local_opt.get() );
local_opt->getDatabasePathList().pop_front();
return readResult;
}
else
{
return curlResult;
}
}
#ifdef USE_ZLIB
#include <zlib.h>
bool ReaderWriterCURL::read(std::istream& fin, std::string& destination) const
{
#define CHUNK 16384
int ret;
unsigned have;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];
/* allocate inflate state */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
ret = inflateInit2(&strm,
15 + 32 // autodected zlib or gzip header
);
if (ret != Z_OK)
return false;
/* decompress until deflate stream ends or end of file */
do {
strm.avail_in = fin.readsome((char*)in, CHUNK);
if (fin.fail())
{
(void)inflateEnd(&strm);
return false;
}
if (strm.avail_in == 0)
break;
strm.next_in = in;
/* run inflate() on input until output buffer not full */
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
switch (ret) {
case Z_NEED_DICT:
case Z_DATA_ERROR:
case Z_MEM_ERROR:
(void)inflateEnd(&strm);
return false;
}
have = CHUNK - strm.avail_out;
destination.append((char*)out, have);
} while (strm.avail_out == 0);
/* done when inflate() says it's done */
} while (ret != Z_STREAM_END);
/* clean up and return */
(void)inflateEnd(&strm);
return ret == Z_STREAM_END ? true : false;
}
#else
bool ReaderWriterCURL::read(std::istream& fin, std::string& destination) const
{
return false;
}
#endif
bool ReaderWriterCURL::fileExists(const std::string& filename, const osgDB::Options* options) const
{
if (osgDB::containsServerAddress(filename))
{
osg::notify(osg::NOTICE)<<"Checking if file exists using curl plugin: "<<filename<<std::endl;
ReadResult result = readFile(OBJECT,filename,options);
return result.status()==osgDB::ReaderWriter::ReadResult::FILE_LOADED;
}
else
{
return ReaderWriter::fileExists(filename, options);
}
}
// now register with Registry to instantiate the above
// reader/writer.
REGISTER_OSGPLUGIN(curl, ReaderWriterCURL)