/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* $Rev$ $Date$ */ #ifndef tuscany_curl_hpp #define tuscany_curl_hpp /** * CURL HTTP client functions. */ #include #include #include #include #include "list.hpp" #include "value.hpp" #include "element.hpp" #include "monad.hpp" #include "../atom/atom.hpp" #include "../json/json.hpp" namespace tuscany { namespace http { /** * Set to true to log HTTP content. */ bool logContent = false; /** * CURL library context, one per process. */ class CURLContext { public: CURLContext() { curl_global_init(CURL_GLOBAL_ALL); } ~CURLContext() { curl_global_cleanup(); } }; CURLContext curlContext; /** * Represents a CURL session handle. */ class CURLHandle { public: CURLHandle() : h(curl_easy_init()) { } ~CURLHandle() { curl_easy_cleanup(h); } operator CURL*() const { return h; } private: CURL* h; }; /** * Context passed to the read callback function. */ class CURLReadContext { public: CURLReadContext(const list& ilist) : ilist(ilist) { } list ilist; }; /** * Called by CURL to read data to send. */ size_t readCallback(void *ptr, size_t size, size_t nmemb, void *data) { CURLReadContext& rcx = *static_cast(data); if (isNil(rcx.ilist)) return 0; rcx.ilist = fragment(rcx.ilist, size * nmemb); const std::string s = car(rcx.ilist); rcx.ilist = cdr(rcx.ilist); s.copy((char*)ptr, s.length()); return s.length(); } /** * Context passed to CURL write callback function. */ template class CURLWriteContext { public: CURLWriteContext(const lambda& reduce, const R& accum) : reduce(reduce), accum(accum) { } const lambda reduce; R accum; }; /** * Called by CURL to write received data. */ template size_t writeCallback(void *ptr, size_t size, size_t nmemb, void *data) { CURLWriteContext& wcx = *(static_cast*> (data)); const size_t realsize = size * nmemb; wcx.accum = wcx.reduce(std::string((const char*)ptr, realsize), wcx.accum); return realsize; } /** * Called by CURL to write received header data. */ template size_t headerCallback(void *ptr, size_t size, size_t nmemb, void *data) { CURLWriteContext& wcx = *(static_cast*> (data)); const size_t realsize = size * nmemb; wcx.accum = wcx.reduce(std::string((const char*)ptr, realsize), wcx.accum); return realsize; } /** * Apply an HTTP verb to a list containing a list of headers and a list of content, and * a reduce function used to process the response. */ curl_slist* headers(curl_slist* cl, const list& h) { if (isNil(h)) return cl; return headers(curl_slist_append(cl, std::string(car(h)).c_str()), cdr(h)); } template const failable, std::string> apply(const list >& req, const lambda& reduce, const R& initial, const std::string& url, const std::string& verb, const CURLHandle& ch) { // Init the curl session curl_easy_reset(ch); curl_easy_setopt(ch, CURLOPT_USERAGENT, "libcurl/1.0"); //TODO use HTTP chunking, for now just convert request to a single string std::ostringstream os; write(cadr(req), os); const std::string s = os.str(); const int sz = s.length(); if (sz < 1400) curl_easy_setopt(ch, CURLOPT_TCP_NODELAY, true); // Setup the read, header and write callbacks CURLReadContext rcx(mklist(s)); curl_easy_setopt(ch, CURLOPT_READFUNCTION, (size_t (*)(void*, size_t, size_t, void*))readCallback); curl_easy_setopt(ch, CURLOPT_READDATA, &rcx); CURLWriteContext hcx(reduce, initial); curl_easy_setopt(ch, CURLOPT_HEADERFUNCTION, (size_t (*)(void*, size_t, size_t, void*))headerCallback); curl_easy_setopt(ch, CURLOPT_HEADERDATA, &hcx); CURLWriteContext wcx(reduce, initial); curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, (size_t (*)(void*, size_t, size_t, void*))writeCallback); curl_easy_setopt(ch, CURLOPT_WRITEDATA, &wcx); // Set the request headers curl_slist* hl = headers(NULL, car(req)); if (hl != NULL) curl_easy_setopt(ch, CURLOPT_HTTPHEADER, hl); // Apply the HTTP verb curl_easy_setopt(ch, CURLOPT_URL, url.c_str()); if (verb == "POST") { curl_easy_setopt(ch, CURLOPT_POST, true); curl_easy_setopt(ch, CURLOPT_POSTFIELDSIZE, sz); } else if (verb == "PUT") { curl_easy_setopt(ch, CURLOPT_UPLOAD, true); curl_easy_setopt(ch, CURLOPT_INFILESIZE, sz); } else if (verb == "DELETE") curl_easy_setopt(ch, CURLOPT_CUSTOMREQUEST, "DELETE"); const CURLcode rc = curl_easy_perform(ch); if (hl != NULL) curl_slist_free_all(hl); // Return the HTTP return code or content if (rc) return mkfailure, std::string>(curl_easy_strerror(rc)); long httprc; curl_easy_getinfo (ch, CURLINFO_RESPONSE_CODE, &httprc); if (httprc != 200 && httprc != 201) { std::ostringstream es; es << "HTTP code " << httprc; return mkfailure, std::string>(es.str()); } return mklist(hcx.accum, wcx.accum); } /** * Evaluate an expression remotely, at the given URL. */ const failable evalExpr(const value& expr, const std::string& url, const CURLHandle& ch) { // Convert expression to a JSON-RPC request json::JSONContext cx; const failable, std::string> jsreq = jsonRequest(1, car(expr), cdr(expr), cx); if (!hasValue(jsreq)) return mkfailure(reason(jsreq)); if (logContent) { std::cout<< "content: " << std::endl; write(jsreq, std::cout); std::cout<< std::endl; std::cout.flush(); } // POST it to the URL const list h = mklist("Content-Type: application/json-rpc"); const failable >, std::string> res = apply >(mklist >(h, jsreq), rcons, list(), url, "POST", ch); if (!hasValue(res)) return mkfailure(reason(res)); // Return result if (logContent) { std::cout << "content:" << std::endl; write(cadr >(res), std::cout); std::cout << std::endl; } const list val = elementsToValues(json::readJSON(cadr >(res), cx)); return cadr(cadr(val)); } /** * HTTP GET, return the resource at the given URL. */ template const failable, std::string> get(const lambda& reduce, const R& initial, const std::string& url, const CURLHandle& ch) { const list > req = mklist(list(), list()); return apply(req, reduce, initial, url, "GET", ch); } /** * HTTP GET, return a list of values representing the resource at the given URL. */ const failable get(const std::string& url, const CURLHandle& ch) { // Get the contents of the resource at the given URL const failable >, std::string> res = get >(rcons, list(), url, ch); if (!hasValue(res)) return mkfailure(reason(res)); const list > ls = res; const std::string ct; if (ct.find("application/atom+xml") != std::string::npos) { // TODO Return an ATOM feed } // Return the content as a string value std::ostringstream os; write(reverse(cadr(ls)), os); return value(os.str()); } /** * HTTP POST. */ const failable post(const value& val, const std::string& url, const CURLHandle& ch) { // Convert value to an ATOM entry const failable, std::string> entry = atom::writeATOMEntry(atom::entryValuesToElements(val)); if (!hasValue(entry)) return mkfailure(reason(entry)); if (logContent) { std::cout << "content:" << std::endl; write(list(entry), std::cout); std::cout << std::endl; } // POST it to the URL const list h = mklist("Content-Type: application/atom+xml"); const list > req = mklist >(h, entry); const failable >, std::string> res = apply >(req, rcons, list(), url, "POST", ch); if (!hasValue(res)) return mkfailure(reason(res)); return value(true); } /** * HTTP PUT. */ const failable put(const value& val, const std::string& url, const CURLHandle& ch) { // Convert value to an ATOM entry const failable, std::string> entry = atom::writeATOMEntry(atom::entryValuesToElements(val)); if (!hasValue(entry)) return mkfailure(reason(entry)); if (logContent) { std::cout << "content:" << std::endl; write(list(entry), std::cout); std::cout << std::endl; } // PUT it to the URL const list h = mklist("Content-Type: application/atom+xml"); const list > req = mklist >(h, entry); const failable >, std::string> res = apply >(req, rcons, list(), url, "PUT", ch); if (!hasValue(res)) return mkfailure(reason(res)); return value(true); } /** * HTTP DELETE. */ const failable del(const std::string& url, const CURLHandle& ch) { const list > req = mklist(list(), list()); const failable >, std::string> res = apply >(req, rcons, list(), url, "DELETE", ch); if (!hasValue(res)) return mkfailure(reason(res)); return value(true); } /** * HTTP client proxy function. */ struct proxy { proxy(const std::string& url, const CURLHandle& ch) : url(url), ch(ch) { } const value operator()(const list& args) const { failable val = evalExpr(args, url, ch); if (!hasValue(val)) return value(); return val; } const std::string url; const CURLHandle& ch; }; } } #endif /* tuscany_curl_hpp */