ppovb5fc7/gazebo/plugins/rest_web/RestApi.cc

381 lines
10 KiB
C++

/*
* Copyright (C) 2015 Open Source Robotics Foundation
*
* Licensed 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.
*
*/
#include <iostream>
#include <cstring>
#include <stdlib.h>
#include <curl/curl.h>
#include <inttypes.h>
#include "RestApi.hh"
using namespace gazebo;
// This code is adapted from the curl C examples
// (http://curl.haxx.se/libcurl/c/)
// mostly modified to adhere to the Gazebo code check tool.
// It implements the following features:
// - HTML POST, sending and receiving data
// - Authentication (Basic Auth)
// - https (SSL, but accepting non signed certificates)
// You can enable this flag to get more curl details
// about each request (will provide SSL negociation and TCP dumps of the
// requests and responses)
bool trace_requests = false;
struct data
{
// 1 or 0
char trace_ascii;
};
/////////////////////////////////////////////////
static void DumpRequest(const char *_text,
FILE *_stream,
unsigned char *_ptr,
size_t _size,
char _nohex)
{
size_t i;
size_t c;
unsigned int width = 0x10;
if (_nohex)
{
// without the hex output, we can fit more on screen
width = 0x40;
}
int64_t s = _size;
fprintf(_stream,
"%s, %10.10" PRId64 " bytes (0x%8.8" PRIx64 ")\n", _text, s, s);
for (i = 0; i < _size; i += width)
{
fprintf(_stream, "%4.4zu: ", i);
if (!_nohex)
{
// hex not disabled, show it
for (c = 0; c < width; ++c)
{
if (i+c < _size)
fprintf(_stream, "%02x ", _ptr[i+c]);
else
fputs(" ", _stream);
}
}
for (c = 0; (c < width) && (i+c < _size); ++c)
{
// check for 0D0A; if found, skip past and start a new line of output
if (_nohex && (i+c+1 < _size) && _ptr[i+c] == 0x0D && _ptr[i+c+1] == 0x0A)
{
i+=(c+2-width);
break;
}
fprintf(_stream, "%c",
(_ptr[i+c] >= 0x20) && (_ptr[i+c] < 0x80)?_ptr[i+c]:'.');
// check again for 0D0A, to avoid an extra \n if it's at width
if (_nohex && (i+c+2 < _size) && _ptr[i+c+1] == 0x0D &&
_ptr[i+c+2] == 0x0A)
{
i+=(c+3-width);
break;
}
}
fputc('\n', _stream);
}
fflush(_stream);
}
/////////////////////////////////////////////////
// Callback given to curl that outputs data about the request
static int TraceRequest(CURL *_handle,
curl_infotype _type,
char *_data,
size_t _size,
void *_userp)
{
struct data *config = (struct data *)_userp;
const char *text;
// prevent compiler warning
(void)_handle;
switch (_type)
{
case CURLINFO_TEXT:
if (trace_requests)
fprintf(stderr, "== Info: %s", _data);
// in case a new one is introduced to shock us
default:
return 0;
case CURLINFO_HEADER_OUT:
text = "=> Send header";
break;
case CURLINFO_DATA_OUT:
text = "=> Send data";
break;
case CURLINFO_SSL_DATA_OUT:
text = "=> Send SSL data";
break;
case CURLINFO_HEADER_IN:
text = "<= Recv header";
break;
case CURLINFO_DATA_IN:
text = "<= Recv data";
break;
case CURLINFO_SSL_DATA_IN:
text = "<= Recv SSL data";
break;
}
if (trace_requests)
{
DumpRequest(text, stderr,
(unsigned char *)(_data), _size, config->trace_ascii);
}
return 0;
}
// private data structure used to
// read libcurl response
struct MemoryStruct {
char *memory;
size_t size;
};
/////////////////////////////////////////////////
// callback for libcurl when data is read from http response
static size_t WriteMemoryCallback(void *_contents,
size_t _size,
size_t _nmemb,
void *_userp)
{
size_t realsize = _size * _nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)_userp;
size_t newsize = mem->size + realsize + 1;
mem->memory = static_cast<char *> (realloc(mem->memory, newsize));
if (mem->memory == NULL)
{
// out of memory!
gzerr << "not enough memory (realloc returned NULL)" << std::endl;
return 0;
}
memcpy(&(mem->memory[mem->size]), _contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
/////////////////////////////////////////////////
RestApi::RestApi()
:isLoggedIn(false)
{
}
/////////////////////////////////////////////////
RestApi::~RestApi()
{
curl_global_cleanup();
}
/////////////////////////////////////////////////
void RestApi::PostJsonData(const char *_route, const char *_json)
{
Post post;
post.route = _route;
post.json = _json;
{
boost::mutex::scoped_lock lock(this->postsMutex);
this->posts.push_back(post);
}
this->SendUnpostedPosts();
}
/////////////////////////////////////////////////
std::string RestApi::Login(const std::string &_urlStr,
const std::string &_route,
const std::string &_userStr,
const std::string &_passStr)
{
this->isLoggedIn = false;
this->url = _urlStr;
this->user = _userStr;
this->pass = _passStr;
// at this point we want to test the (user supplied) login data
// so we're hitting the server on the login route ('/login')
this->loginRoute = _route;
std::string resp;
gzmsg << "login route: " << this->loginRoute << std::endl;
resp = this->Request(loginRoute, "");
gzmsg << "login response: " << resp << std::endl;
this->isLoggedIn = true;
this->SendUnpostedPosts();
return resp;
}
/////////////////////////////////////////////////
void RestApi::Logout()
{
this->isLoggedIn = false;
gzmsg << "Logout" << std::endl;
}
/////////////////////////////////////////////////
void RestApi::SendUnpostedPosts()
{
if (this->isLoggedIn)
{
while (!this->posts.empty())
{
Post post;
{
boost::mutex::scoped_lock lock(this->postsMutex);
post = this->posts.front();
// You can generate a similar request on the cmd line like so:
// curl --verbose --connect-timeout 5 -X POST
// -H \"Content-Type: application/json \" -k --user"
this->Request(post.route, post.json);
this->posts.pop_front();
}
}
}
else
{
gzmsg << posts.size() << " post(s) queued to be sent" << std::endl;
}
}
/////////////////////////////////////////////////
std::string RestApi::GetUser() const
{
return this->user;
}
/////////////////////////////////////////////////
std::string RestApi::Request(const std::string &_reqUrl,
const std::string &_postJsonStr)
{
if (this->url.empty())
throw RestException("A URL must be specified for web service");
if (this->user.empty())
{
std::string e = "No user specified for the web service. Please login.";
throw RestException(e.c_str());
}
// build full url (with server)
std::string path = url + _reqUrl;
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, path.c_str() );
// in case things go wrong
if (trace_requests)
{
gzmsg << "RestApi::Request" << std::endl;
gzmsg << " path: " << path << std::endl;
gzmsg << " data: " << _postJsonStr << std::endl;
gzmsg << std::endl;
struct data config;
config.trace_ascii = 1; // enable ascii tracing
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, TraceRequest);
curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &config);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curl, CURLOPT_HEADER, 1L);
}
struct MemoryStruct chunk;
// will be grown as needed by the realloc above
chunk.memory = static_cast<char*>(malloc(1));
chunk.size = 0; // no data at this point
bool secure = false;
if (!secure)
{
// skip peer verification
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
// skip host verification
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
}
// send all data to this function
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
// we pass our 'chunk' struct to the callback function
curl_easy_setopt(curl,
CURLOPT_WRITEDATA,
static_cast<void *>(&chunk));
// some servers don't like requests that are made without a user-agent
// field, so we provide one
curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
// set user name and password for the authentication
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
std::string userpass = this->user + ":" + this->pass;
curl_easy_setopt(curl, CURLOPT_USERPWD, userpass.c_str());
// connection timeout 10 sec
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10);
// is this a POST?
struct curl_slist *slist = NULL;
if (!_postJsonStr.empty())
{
curl_easy_setopt(curl, CURLOPT_UPLOAD, 0L); // disable PUT
curl_easy_setopt(curl, CURLOPT_POST, 1); // enable POST
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, _postJsonStr.c_str());
slist = curl_slist_append(slist, "Content-Type: application/json");
slist = curl_slist_append(slist, "charsets: utf-8");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
}
CURLcode res;
res = curl_easy_perform(curl);
// get HTTP response code
int64_t http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl);
if (res != CURLE_OK)
{
gzerr << "Request to " << url << " failed: "
<< curl_easy_strerror(res) << std::endl;
throw RestException(curl_easy_strerror(res));
}
// copy the data into a string
std::string response(chunk.memory, chunk.size);
if (http_code != 200)
{
gzerr << "Request to " << url << " error: " << response << std::endl;
throw RestException(response.c_str());
}
// clean up
curl_slist_free_all(slist);
if (chunk.memory)
free(chunk.memory);
return response;
}