From a57e969639575643d6961344f4904c04d8415de0 Mon Sep 17 00:00:00 2001 From: James Turner Date: Wed, 30 Sep 2015 22:12:35 -0500 Subject: [PATCH] Optional use libCurl as the HTTP client. Will permit HTTPS for packages in the future, disabled by default for the moment. --- CMakeLists.txt | 10 +- simgear/CMakeLists.txt | 5 +- simgear/io/CMakeLists.txt | 11 +- simgear/io/HTTPClient.cxx | 230 +++++++++++++++++++++++-- simgear/io/HTTPClient.hxx | 5 + simgear/io/HTTPRequest.cxx | 12 +- simgear/io/HTTPRequest.hxx | 1 + simgear/io/SVNRepository.cxx | 3 + simgear/io/test_HTTP.cxx | 268 ++++++++++++++++++++---------- simgear/package/Install.cxx | 2 + simgear/simgear_config_cmake.h.in | 1 + 11 files changed, 434 insertions(+), 114 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ae509e2..e8d493ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,6 +116,7 @@ option(ENABLE_RTI "Set to ON to build SimGear with RTI support" OFF) option(ENABLE_TESTS "Set to OFF to disable building SimGear's test applications" ON) option(ENABLE_SOUND "Set to OFF to disable building SimGear's sound support" ON) option(ENABLE_PKGUTIL "Set to ON to build the sg_pkgutil application (default)" ON) +option(ENABLE_CURL "Set to ON to use libCurl as the HTTP client backend" OFF) if (MSVC) GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_BINARY_DIR} PATH) @@ -203,6 +204,11 @@ endif(SIMGEAR_HEADLESS) find_package(ZLIB REQUIRED) +if (ENABLE_CURL) + find_package(Curl REQUIRED) + message(STATUS "Curl HTTP client: ENABLED") +endif() + if (SYSTEM_EXPAT) message(STATUS "Requested to use system Expat library, forcing SIMGEAR_SHARED to true") set(SIMGEAR_SHARED ON) @@ -368,6 +374,7 @@ include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR} ${OPENAL_INCLUDE_DIR} + ${CURL_INCLUDE_DIRS} ) add_definitions(-DHAVE_CONFIG_H) @@ -396,7 +403,8 @@ set(TEST_LIBS_INTERNAL_CORE ${WINSOCK_LIBRARY} ${RT_LIBRARY} ${DL_LIBRARY} - ${COCOA_LIBRARY}) + ${COCOA_LIBRARY} + ${CURL_LIBRARIES}) set(TEST_LIBS SimGearCore ${TEST_LIBS_INTERNAL_CORE}) if(NOT SIMGEAR_HEADLESS) diff --git a/simgear/CMakeLists.txt b/simgear/CMakeLists.txt index abb4b65a..37ef4fdc 100644 --- a/simgear/CMakeLists.txt +++ b/simgear/CMakeLists.txt @@ -1,7 +1,7 @@ file(WRITE ${PROJECT_BINARY_DIR}/simgear/version.h "#define SIMGEAR_VERSION ${SIMGEAR_VERSION}") -foreach( mylibfolder +foreach( mylibfolder bucket bvh debug @@ -122,7 +122,8 @@ target_link_libraries(SimGearCore ${DL_LIBRARY} ${EXPAT_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} - ${COCOA_LIBRARY}) + ${COCOA_LIBRARY} + ${CURL_LIBRARIES}) if(NOT SIMGEAR_HEADLESS) target_link_libraries(SimGearScene diff --git a/simgear/io/CMakeLists.txt b/simgear/io/CMakeLists.txt index b72a0040..25e79180 100644 --- a/simgear/io/CMakeLists.txt +++ b/simgear/io/CMakeLists.txt @@ -18,7 +18,6 @@ set(HEADERS HTTPFileRequest.hxx HTTPMemoryRequest.hxx HTTPRequest.hxx - HTTPContentDecode.hxx DAVMultiStatus.hxx SVNRepository.hxx SVNDirectory.hxx @@ -41,13 +40,17 @@ set(SOURCES HTTPFileRequest.cxx HTTPMemoryRequest.cxx HTTPRequest.cxx - HTTPContentDecode.cxx DAVMultiStatus.cxx SVNRepository.cxx SVNDirectory.cxx SVNReportParser.cxx ) +if (NOT ENABLE_CURL) + list(APPEND SOURCES HTTPContentDecode.cxx) + list(APPEND HEADERS HTTPContentDecode.hxx) +endif() + simgear_component(io io "${SOURCES}" "${HEADERS}") if(ENABLE_TESTS) @@ -70,8 +73,8 @@ add_executable(decode_binobj decode_binobj.cxx) target_link_libraries(decode_binobj ${TEST_LIBS}) add_executable(test_binobj test_binobj.cxx) -target_link_libraries(test_binobj ${TEST_LIBS}) - +target_link_libraries(test_binobj ${TEST_LIBS}) + add_test(binobj ${EXECUTABLE_OUTPUT_PATH}/test_binobj) endif(ENABLE_TESTS) diff --git a/simgear/io/HTTPClient.cxx b/simgear/io/HTTPClient.cxx index a51fb2f4..2a6d41e2 100644 --- a/simgear/io/HTTPClient.cxx +++ b/simgear/io/HTTPClient.cxx @@ -21,6 +21,7 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // + #include "HTTPClient.hxx" #include "HTTPFileRequest.hxx" @@ -35,8 +36,16 @@ #include #include +#include + +#if defined(ENABLE_CURL) + #include +#else + #include +#endif + #include -#include + #include #include #include @@ -68,24 +77,32 @@ typedef std::list RequestList; class Client::ClientPrivate { public: +#if defined(ENABLE_CURL) + CURLM* curlMulti; + bool haveActiveRequests; +#else + NetChannelPoller poller; +// connections by host (potentially more than one) + ConnectionDict connections; +#endif + std::string userAgent; std::string proxy; int proxyPort; std::string proxyAuth; - NetChannelPoller poller; unsigned int maxConnections; - + RequestList pendingRequests; - -// connections by host (potentially more than one) - ConnectionDict connections; - + + + SGTimeStamp timeTransferSample; unsigned int bytesTransferred; unsigned int lastTransferRate; uint64_t totalBytesDownloaded; }; - + +#if !defined(ENABLE_CURL) class Connection : public NetChat { public: @@ -623,6 +640,7 @@ private: ContentDecoder _contentDecoder; }; +#endif // of !ENABLE_CURL Client::Client() : d(new ClientPrivate) @@ -633,12 +651,24 @@ Client::Client() : d->lastTransferRate = 0; d->timeTransferSample.stamp(); d->totalBytesDownloaded = 0; - + setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION)); +#if defined(ENABLE_CURL) + static bool didInitCurlGlobal = false; + if (!didInitCurlGlobal) { + curl_global_init(CURL_GLOBAL_ALL); + didInitCurlGlobal = true; + } + + d->curlMulti = curl_multi_init(); +#endif } Client::~Client() { +#if defined(ENABLE_CURL) + curl_multi_cleanup(d->curlMulti); +#endif } void Client::setMaxConnections(unsigned int maxCon) @@ -646,12 +676,49 @@ void Client::setMaxConnections(unsigned int maxCon) if (maxCon < 1) { throw sg_range_exception("illegal HTTP::Client::setMaxConnections value"); } - + d->maxConnections = maxCon; +#if defined(ENABLE_CURL) + curl_multi_setopt(d->curlMulti, CURLMOPT_MAXCONNECTS, (long) maxCon); +#endif } void Client::update(int waitTimeout) { +#if defined(ENABLE_CURL) + int remainingActive, messagesInQueue; + curl_multi_perform(d->curlMulti, &remainingActive); + d->haveActiveRequests = (remainingActive > 0); + + CURLMsg* msg; + while ((msg = curl_multi_info_read(d->curlMulti, &messagesInQueue))) { + if (msg->msg == CURLMSG_DONE) { + Request* req; + CURL *e = msg->easy_handle; + curl_easy_getinfo(e, CURLINFO_PRIVATE, &req); + + long responseCode; + curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, &responseCode); + + if (msg->data.result == 0) { + req->responseComplete(); + } else { + fprintf(stderr, "Result: %d - %s\n", + msg->data.result, curl_easy_strerror(msg->data.result)); + req->setFailure(msg->data.result, curl_easy_strerror(msg->data.result)); + } + + curl_multi_remove_handle(d->curlMulti, e); + + // balance the reference we take in makeRequest + SGReferenced::put(req); + curl_easy_cleanup(e); + } + else { + SG_LOG(SG_IO, SG_ALERT, "CurlMSG:" << msg->msg); + } + } // of curl message processing loop +#else if (!d->poller.hasChannels() && (waitTimeout > 0)) { SGTimeStamp::sleepForMSec(waitTimeout); } else { @@ -697,6 +764,7 @@ void Client::update(int waitTimeout) makeRequest(req); } } +#endif } void Client::makeRequest(const Request_ptr& r) @@ -709,18 +777,94 @@ void Client::makeRequest(const Request_ptr& r) return; } +#if defined(ENABLE_CURL) + CURL* curlRequest = curl_easy_init(); + curl_easy_setopt(curlRequest, CURLOPT_URL, r->url().c_str()); + + // manually increase the ref count of the request + SGReferenced::get(r.get()); + curl_easy_setopt(curlRequest, CURLOPT_PRIVATE, r.get()); + // disable built-in libCurl progress feedback + curl_easy_setopt(curlRequest, CURLOPT_NOPROGRESS, 1); + + curl_easy_setopt(curlRequest, CURLOPT_WRITEFUNCTION, requestWriteCallback); + curl_easy_setopt(curlRequest, CURLOPT_WRITEDATA, r.get()); + curl_easy_setopt(curlRequest, CURLOPT_HEADERFUNCTION, requestHeaderCallback); + curl_easy_setopt(curlRequest, CURLOPT_HEADERDATA, r.get()); + + curl_easy_setopt(curlRequest, CURLOPT_USERAGENT, d->userAgent.c_str()); + + if (!d->proxy.empty()) { + curl_easy_setopt(curlRequest, CURLOPT_PROXY, d->proxy.c_str()); + curl_easy_setopt(curlRequest, CURLOPT_PROXYPORT, d->proxyPort); + + if (!d->proxyAuth.empty()) { + curl_easy_setopt(curlRequest, CURLOPT_PROXYAUTH, CURLAUTH_BASIC); + curl_easy_setopt(curlRequest, CURLOPT_PROXYUSERPWD, d->proxyAuth.c_str()); + } + } + + std::string method = boost::to_lower_copy(r->method()); + if (method == "get") { + curl_easy_setopt(curlRequest, CURLOPT_HTTPGET, 1); + } else if (method == "put") { + curl_easy_setopt(curlRequest, CURLOPT_PUT, 1); + curl_easy_setopt(curlRequest, CURLOPT_UPLOAD, 1); + } else if (method == "post") { + // see http://curl.haxx.se/libcurl/c/CURLOPT_POST.html + curl_easy_setopt(curlRequest, CURLOPT_HTTPPOST, 1); + + std::string q = r->query().substr(1); + curl_easy_setopt(curlRequest, CURLOPT_COPYPOSTFIELDS, q.c_str()); + + // reset URL to exclude query pieces + std::string urlWithoutQuery = r->url(); + std::string::size_type queryPos = urlWithoutQuery.find('?'); + urlWithoutQuery.resize(queryPos); + curl_easy_setopt(curlRequest, CURLOPT_URL, urlWithoutQuery.c_str()); + } else { + curl_easy_setopt(curlRequest, CURLOPT_CUSTOMREQUEST, r->method().c_str()); + } + + struct curl_slist* headerList = NULL; + if (r->hasBodyData() && (method != "post")) { + curl_easy_setopt(curlRequest, CURLOPT_UPLOAD, 1); + curl_easy_setopt(curlRequest, CURLOPT_INFILESIZE, r->bodyLength()); + curl_easy_setopt(curlRequest, CURLOPT_READFUNCTION, requestReadCallback); + curl_easy_setopt(curlRequest, CURLOPT_READDATA, r.get()); + std::string h = "Content-Type:" + r->bodyType(); + headerList = curl_slist_append(headerList, h.c_str()); + } + + StringMap::const_iterator it; + for (it = r->requestHeaders().begin(); it != r->requestHeaders().end(); ++it) { + std::string h = it->first + ": " + it->second; + headerList = curl_slist_append(headerList, h.c_str()); + } + + if (headerList != NULL) { + curl_easy_setopt(curlRequest, CURLOPT_HTTPHEADER, headerList); + } + + curl_multi_add_handle(d->curlMulti, curlRequest); + d->haveActiveRequests = true; + +// FIXME - premature? + r->requestStart(); + +#else if( r->url().find("http://") != 0 ) { r->setFailure(EINVAL, "only HTTP protocol is supported"); return; } - + std::string host = r->host(); int port = r->port(); if (!d->proxy.empty()) { host = d->proxy; port = d->proxyPort; } - + Connection* con = NULL; std::stringstream ss; ss << host << "-" << port; @@ -774,6 +918,7 @@ void Client::makeRequest(const Request_ptr& r) } con->queueRequest(r); +#endif } //------------------------------------------------------------------------------ @@ -829,12 +974,16 @@ void Client::setProxy( const std::string& proxy, bool Client::hasActiveRequests() const { + #if defined(ENABLE_CURL) + return d->haveActiveRequests; + #else ConnectionDict::const_iterator it = d->connections.begin(); for (; it != d->connections.end(); ++it) { if (it->second->isActive()) return true; } return false; +#endif } void Client::receivedBytes(unsigned int count) @@ -874,6 +1023,63 @@ uint64_t Client::totalBytesDownloaded() const return d->totalBytesDownloaded; } +size_t Client::requestWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata) +{ + size_t byteSize = size * nmemb; + + Request* req = static_cast(userdata); + req->processBodyBytes(ptr, byteSize); + return byteSize; +} + +size_t Client::requestReadCallback(char *ptr, size_t size, size_t nmemb, void *userdata) +{ + size_t maxBytes = size * nmemb; + Request* req = static_cast(userdata); + size_t actualBytes = req->getBodyData(ptr, 0, maxBytes); + return actualBytes; +} + +size_t Client::requestHeaderCallback(char *rawBuffer, size_t size, size_t nitems, void *userdata) +{ + size_t byteSize = size * nitems; + Request* req = static_cast(userdata); + std::string h = strutils::simplify(std::string(rawBuffer, byteSize)); + + if (req->readyState() == HTTP::Request::OPENED) { + req->responseStart(h); + return byteSize; + } + + if (h.empty()) { + // got a 100-continue reponse; restart + if (req->responseCode() == 100) { + req->setReadyState(HTTP::Request::OPENED); + return byteSize; + } + + req->responseHeadersComplete(); + return byteSize; + } + + if (req->responseCode() == 100) { + return byteSize; // skip headers associated with 100-continue status + } + + int colonPos = h.find(':'); + if (colonPos == std::string::npos) { + SG_LOG(SG_IO, SG_WARN, "malformed HTTP response header:" << h); + return byteSize; + } + + std::string key = strutils::simplify(h.substr(0, colonPos)); + std::string lkey = boost::to_lower_copy(key); + std::string value = strutils::strip(h.substr(colonPos + 1)); + + req->responseHeader(lkey, value); + return byteSize; +} + } // of namespace HTTP } // of namespace simgear diff --git a/simgear/io/HTTPClient.hxx b/simgear/io/HTTPClient.hxx index 89800159..a9b2eee6 100644 --- a/simgear/io/HTTPClient.hxx +++ b/simgear/io/HTTPClient.hxx @@ -99,6 +99,11 @@ public: */ uint64_t totalBytesDownloaded() const; private: + // libCurl callbacks + static size_t requestWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata); + static size_t requestReadCallback(char *ptr, size_t size, size_t nmemb, void *userdata); + static size_t requestHeaderCallback(char *buffer, size_t size, size_t nitems, void *userdata); + void requestFinished(Connection* con); void receivedBytes(unsigned int count); diff --git a/simgear/io/HTTPRequest.cxx b/simgear/io/HTTPRequest.cxx index 90b30719..4ed18d6e 100644 --- a/simgear/io/HTTPRequest.cxx +++ b/simgear/io/HTTPRequest.cxx @@ -133,19 +133,25 @@ void Request::responseStart(const std::string& r) const int maxSplit = 2; // HTTP/1.1 nnn reason-string string_list parts = strutils::split(r, NULL, maxSplit); if (parts.size() != 3) { - throw sg_io_exception("bad HTTP response"); + throw sg_io_exception("bad HTTP response:" + r); } - + _responseVersion = decodeHTTPVersion(parts[0]); _responseStatus = strutils::to_int(parts[1]); _responseReason = parts[2]; + + setReadyState(STATUS_RECEIVED); } //------------------------------------------------------------------------------ void Request::responseHeader(const std::string& key, const std::string& value) { - if( key == "connection" ) + if( key == "connection" ) { _willClose = (value.find("close") != std::string::npos); + } else if (key == "content-length") { + int sz = strutils::to_int(value); + setResponseLength(sz); + } _responseHeaders[key] = value; } diff --git a/simgear/io/HTTPRequest.hxx b/simgear/io/HTTPRequest.hxx index 1897a5b0..9182ed37 100644 --- a/simgear/io/HTTPRequest.hxx +++ b/simgear/io/HTTPRequest.hxx @@ -50,6 +50,7 @@ public: { UNSENT = 0, OPENED, + STATUS_RECEIVED, HEADERS_RECEIVED, LOADING, DONE, diff --git a/simgear/io/SVNRepository.cxx b/simgear/io/SVNRepository.cxx index 39114b1f..c67f6007 100644 --- a/simgear/io/SVNRepository.cxx +++ b/simgear/io/SVNRepository.cxx @@ -120,6 +120,7 @@ namespace { // anonmouse Request(repo->baseUrl, "PROPFIND"), _repo(repo) { + assert(repo); requestHeader("Depth") = "0"; setBodyData( PROPFIND_REQUEST_BODY, "application/xml; charset=\"utf-8\"" ); @@ -138,6 +139,8 @@ namespace { // anonmouse _repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET); _repo = NULL; } + + Request::responseHeadersComplete(); } virtual void onDone() diff --git a/simgear/io/test_HTTP.cxx b/simgear/io/test_HTTP.cxx index 700eace4..d7bd1280 100644 --- a/simgear/io/test_HTTP.cxx +++ b/simgear/io/test_HTTP.cxx @@ -12,6 +12,7 @@ #include #include #include +#include using std::cout; using std::cerr; @@ -54,41 +55,42 @@ public: bool failed; string bodyData; - TestRequest(const std::string& url, const std::string method = "GET") : + TestRequest(const std::string& url, const std::string method = "GET") : HTTP::Request(url, method), complete(false) { } - + std::map headers; protected: - + virtual void onDone() { complete = true; - } - + } + virtual void onFail() { failed = true; } - + virtual void gotBodyData(const char* s, int n) { //std::cout << "got body data:'" << string(s, n) << "'" < userAndPass; + strutils::decodeBase64(credentials.substr(6), userAndPass); + std::string decodedUserPass((char*) userAndPass.data(), userAndPass.size()); + + if (decodedUserPass != "johndoe:swordfish") { + std::map::const_iterator it; + for (it = requestHeaders.begin(); it != requestHeaders.end(); ++it) { + cerr << "header:" << it->first << " = " << it->second << endl; + } + + sendErrorResponse(401, false, "bad auth, not as set"); // forbidden } sendBody2(); @@ -291,43 +312,78 @@ public: sendErrorResponse(400, true, "bad content type"); return; } - + requestContentLength = strutils::to_int(requestHeaders["Content-Length"]); setByteCount(requestContentLength); state = STATE_REQUEST_BODY; + } else if ((path == "/test_put") || (path == "/test_create")) { + if (requestHeaders["Content-Type"] != "x-application/foobar") { + cerr << "bad content type: '" << requestHeaders["Content-Type"] << "'" << endl; + sendErrorResponse(400, true, "bad content type"); + return; + } + + requestContentLength = strutils::to_int(requestHeaders["Content-Length"]); + setByteCount(requestContentLength); + state = STATE_REQUEST_BODY; } else { sendErrorResponse(404, false, ""); } } - + void closeAfterSending() { state = STATE_CLOSING; closeWhenDone(); } - + void receivedBody() { state = STATE_IDLE; if (method == "POST") { parseArgs(buffer); } - + if (path == "/test_post") { if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) { sendErrorResponse(400, true, "bad arguments"); return; } - + stringstream d; d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n"; d << "\r\n"; // final CRLF to terminate the headers push(d.str().c_str()); - - cerr << "sent 204 response ok" << endl; + } else if (path == "/test_put") { + std::cerr << "sending PUT response" << std::endl; + + COMPARE(buffer, BODY3); + stringstream d; + d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n"; + d << "\r\n"; // final CRLF to terminate the headers + push(d.str().c_str()); + } else if (path == "/test_create") { + std::cerr << "sending create response" << std::endl; + + std::string entityStr = "http://localhost:2000/something.txt"; + + COMPARE(buffer, BODY3); + stringstream d; + d << "HTTP/1.1 " << 201 << " " << reasonForCode(201) << "\r\n"; + d << "Location:" << entityStr << "\r\n"; + d << "Content-Length:" << entityStr.size() << "\r\n"; + d << "\r\n"; // final CRLF to terminate the headers + d << entityStr; + + push(d.str().c_str()); + } else { + std::cerr << "weird URL " << path << std::endl; + sendErrorResponse(400, true, "bad URL:" + path); } + + buffer.clear(); } - + void sendBody2() { stringstream d; @@ -337,32 +393,36 @@ public: push(d.str().c_str()); bufferSend(body2, body2Size); } - + void sendErrorResponse(int code, bool close, string content) { cerr << "sending error " << code << " for " << path << endl; + cerr << "\tcontent:" << content << endl; + stringstream headerData; headerData << "HTTP/1.1 " << code << " " << reasonForCode(code) << "\r\n"; headerData << "Content-Length:" << content.size() << "\r\n"; headerData << "\r\n"; // final CRLF to terminate the headers push(headerData.str().c_str()); push(content.c_str()); - + if (close) { closeWhenDone(); } } - - string reasonForCode(int code) + + string reasonForCode(int code) { switch (code) { case 200: return "OK"; + case 201: return "Created"; case 204: return "no content"; case 404: return "not found"; + case 407: return "proxy authentication required"; default: return "unknown code"; } } - + State state; string buffer; string method; @@ -376,7 +436,7 @@ public: class TestServer : public NetChannel { simgear::NetChannelPoller _poller; -public: +public: TestServer() { Socket::initSockets(); @@ -384,14 +444,14 @@ public: open(); bind(NULL, 2000); // localhost, any port listen(5); - + _poller.addChannel(this); } - + virtual ~TestServer() - { + { } - + virtual bool writable (void) { return false ; } virtual void handleAccept (void) @@ -401,10 +461,10 @@ public: //cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl; TestServerChannel* chan = new TestServerChannel(); chan->setHandle(handle); - + _poller.addChannel(chan); } - + void poll() { _poller.poll(); @@ -419,13 +479,13 @@ void waitForComplete(HTTP::Client* cl, TestRequest* tr) while (start.elapsedMSec() < 10000) { cl->update(); testServer.poll(); - + if (tr->complete) { return; } SGTimeStamp::sleepForMSec(15); } - + cerr << "timed out" << endl; } @@ -435,23 +495,24 @@ void waitForFailed(HTTP::Client* cl, TestRequest* tr) while (start.elapsedMSec() < 10000) { cl->update(); testServer.poll(); - + if (tr->failed) { return; } SGTimeStamp::sleepForMSec(15); } - + cerr << "timed out waiting for failure" << endl; } int main(int argc, char* argv[]) { - + sglog().setLogLevels( SG_ALL, SG_INFO ); + HTTP::Client cl; // force all requests to use the same connection for this test - cl.setMaxConnections(1); - + cl.setMaxConnections(1); + // test URL parsing TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar"); COMPARE(tr1->scheme(), "http"); @@ -459,14 +520,14 @@ int main(int argc, char* argv[]) COMPARE(tr1->host(), "localhost.woo.zar"); COMPARE(tr1->port(), 2000); COMPARE(tr1->path(), "/test1"); - + TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png"); COMPARE(tr2->scheme(), "http"); COMPARE(tr2->hostAndPort(), "192.168.1.1"); COMPARE(tr2->host(), "192.168.1.1"); COMPARE(tr2->port(), 80); COMPARE(tr2->path(), "/test1/dir/thing/file.png"); - + // basic get request { TestRequest* tr = new TestRequest("http://localhost:2000/test1"); @@ -480,12 +541,12 @@ int main(int argc, char* argv[]) COMPARE(tr->responseBytesReceived(), strlen(BODY1)); COMPARE(tr->bodyData, string(BODY1)); } - + { TestRequest* tr = new TestRequest("http://localhost:2000/testLorem"); HTTP::Request_ptr own(tr); cl.makeRequest(tr); - + waitForComplete(&cl, tr); COMPARE(tr->responseCode(), 200); COMPARE(tr->responseReason(), string("OK")); @@ -493,7 +554,7 @@ int main(int argc, char* argv[]) COMPARE(tr->responseBytesReceived(), strlen(BODY3)); COMPARE(tr->bodyData, string(BODY3)); } - + { TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe"); HTTP::Request_ptr own(tr); @@ -501,9 +562,7 @@ int main(int argc, char* argv[]) waitForComplete(&cl, tr); COMPARE(tr->responseCode(), 200); } - - cerr << "done args" << endl; - + { TestRequest* tr = new TestRequest("http://localhost:2000/test_headers"); HTTP::Request_ptr own(tr); @@ -518,12 +577,12 @@ int main(int argc, char* argv[]) COMPARE(tr->responseBytesReceived(), strlen(BODY1)); COMPARE(tr->bodyData, string(BODY1)); } - + // larger get request for (unsigned int i=0; i> 2); } - + { TestRequest* tr = new TestRequest("http://localhost:2000/test2"); HTTP::Request_ptr own(tr); @@ -533,8 +592,8 @@ int main(int argc, char* argv[]) COMPARE(tr->responseBytesReceived(), body2Size); COMPARE(tr->bodyData, string(body2, body2Size)); } - - cerr << "testing chunked" << endl; + + cerr << "testing chunked transfer encoding" << endl; { TestRequest* tr = new TestRequest("http://localhost:2000/testchunked"); HTTP::Request_ptr own(tr); @@ -548,7 +607,7 @@ int main(int argc, char* argv[]) // check trailers made it too COMPARE(tr->headers["x-foobar"], string("wibble")); } - + // test 404 { TestRequest* tr = new TestRequest("http://localhost:2000/not-found"); @@ -615,9 +674,10 @@ int main(int argc, char* argv[]) COMPARE(tr->responseLength(), body2Size); COMPARE(tr->bodyData, string(body2, body2Size)); } - + +#if defined(ENABLE_CURL) { - cl.setProxy("localhost", 2000, "ABCDEF"); + cl.setProxy("localhost", 2000, "johndoe:swordfish"); TestRequest* tr = new TestRequest("http://www.google.com/test3"); HTTP::Request_ptr own(tr); cl.makeRequest(tr); @@ -626,81 +686,105 @@ int main(int argc, char* argv[]) COMPARE(tr->responseBytesReceived(), body2Size); COMPARE(tr->bodyData, string(body2, body2Size)); } - +#endif + // pipelining - cout << "testing HTTP 1.1 pipelineing" << endl; - + cout << "testing HTTP 1.1 pipelining" << endl; + { - + cl.setProxy("", 80); TestRequest* tr = new TestRequest("http://localhost:2000/test1"); HTTP::Request_ptr own(tr); cl.makeRequest(tr); - - + + TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem"); HTTP::Request_ptr own2(tr2); cl.makeRequest(tr2); - + TestRequest* tr3 = new TestRequest("http://localhost:2000/test1"); HTTP::Request_ptr own3(tr3); cl.makeRequest(tr3); - + waitForComplete(&cl, tr3); VERIFY(tr->complete); VERIFY(tr2->complete); COMPARE(tr->bodyData, string(BODY1)); - + COMPARE(tr2->responseLength(), strlen(BODY3)); COMPARE(tr2->responseBytesReceived(), strlen(BODY3)); COMPARE(tr2->bodyData, string(BODY3)); - + COMPARE(tr3->bodyData, string(BODY1)); } - + // multiple requests with an HTTP 1.0 server { cout << "http 1.0 multiple requests" << endl; - + cl.setProxy("", 80); TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0/A"); HTTP::Request_ptr own(tr); cl.makeRequest(tr); - + TestRequest* tr2 = new TestRequest("http://localhost:2000/test_1_0/B"); HTTP::Request_ptr own2(tr2); cl.makeRequest(tr2); - + TestRequest* tr3 = new TestRequest("http://localhost:2000/test_1_0/C"); HTTP::Request_ptr own3(tr3); cl.makeRequest(tr3); - + waitForComplete(&cl, tr3); VERIFY(tr->complete); VERIFY(tr2->complete); - + COMPARE(tr->responseLength(), strlen(BODY1)); COMPARE(tr->responseBytesReceived(), strlen(BODY1)); COMPARE(tr->bodyData, string(BODY1)); - + COMPARE(tr2->responseLength(), strlen(BODY3)); COMPARE(tr2->responseBytesReceived(), strlen(BODY3)); COMPARE(tr2->bodyData, string(BODY3)); COMPARE(tr3->bodyData, string(BODY1)); } - + // POST { - cout << "POST" << endl; + cout << "testing POST" << endl; TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST"); tr->setBodyData("", "application/x-www-form-urlencoded"); - + HTTP::Request_ptr own(tr); cl.makeRequest(tr); waitForComplete(&cl, tr); COMPARE(tr->responseCode(), 204); } - + + // PUT + { + cout << "testing PUT" << endl; + TestRequest* tr = new TestRequest("http://localhost:2000/test_put", "PUT"); + tr->setBodyData(BODY3, "x-application/foobar"); + + HTTP::Request_ptr own(tr); + cl.makeRequest(tr); + waitForComplete(&cl, tr); + COMPARE(tr->responseCode(), 204); + } + + { + cout << "testing PUT create" << endl; + TestRequest* tr = new TestRequest("http://localhost:2000/test_create", "PUT"); + tr->setBodyData(BODY3, "x-application/foobar"); + + HTTP::Request_ptr own(tr); + cl.makeRequest(tr); + waitForComplete(&cl, tr); + COMPARE(tr->responseCode(), 201); + } + // test_zero_length_content { cout << "zero-length-content-response" << endl; @@ -712,8 +796,8 @@ int main(int argc, char* argv[]) COMPARE(tr->bodyData, string()); COMPARE(tr->responseBytesReceived(), 0); } - - + + cout << "all tests passed ok" << endl; return EXIT_SUCCESS; } diff --git a/simgear/package/Install.cxx b/simgear/package/Install.cxx index d8896d29..a240be17 100644 --- a/simgear/package/Install.cxx +++ b/simgear/package/Install.cxx @@ -98,6 +98,8 @@ protected: virtual void responseHeadersComplete() { + Request::responseHeadersComplete(); + Dir d(m_extractPath); d.create(0755); diff --git a/simgear/simgear_config_cmake.h.in b/simgear/simgear_config_cmake.h.in index 0cce4b31..4e68e4ce 100644 --- a/simgear/simgear_config_cmake.h.in +++ b/simgear/simgear_config_cmake.h.in @@ -18,3 +18,4 @@ #cmakedefine SYSTEM_EXPAT #cmakedefine ENABLE_SOUND +#cmakedefine ENABLE_CURL -- 2.39.5