I've never really done anything multithreaded or asynchronous in c++, I only used cURL to do single synchronous requests so far.
In order to better visualize what I'm trying to do, I wrote a simple Javascript which would do what I want to do with cURL in C++.
function AddRequest( method, url, data, id ) {
var httpObj = new ActiveXObject("Msxml2.XMLHTTP.6.0"); //new XMLHttpRequest();
httpObj.onreadystatechange = function() {
if (httpObj.readyState == 4)
ResponseCallback( httpObj, id );
};
httpObj.Open( method, url, true );
httpObj.Send( data );
}
function ResponseCallback( httpObj, id ) {
WScript.Echo( id ); //alert( id );
WScript.Echo( httpObj.ResponseText ); //alert( httpObj.ResponseText );
}
//It could now be used like this:
AddRequest("GET","http://example.com/","",1);
AddRequest("GET","https://www.facebook.com","",2);
WScript.Echo( "all requests sent" ); //alert( "all requests sent" );
//these requests are all done at the same time
//and every time a request has finished it calls the ResponseCallback() function,
//telling it which request has finished
CURL just seems to be COMPLETELY different and unnecessary more complicated than XmlHttpRequest, even though both are just sending http requests...
Here is my first approach (based on the answer of hogren):
#include "stdafx.hpp"
#include <iostream> //#include <stdio.h>
#include <curl.h>
#include <pthread.h>
#include <map>
#include <string>
using namespace std;
bool printing = false; //will allow us to prevent prints overlapping each other
struct requestStruct { //will allow us to pass more than one argument to the threaded functions
int id;
const char* url;
const char* method;
const char* body;
map<const char*, const char*> headers;
const char* proxy;
int timeout;
};
struct responseStruct { //will allow us to return more than one value from the Request function
long statusCode;
//map<const char*, const char*> headers;
const char* body;
};
size_t writeToString(void *ptr, size_t size, size_t count, void *stream) {
((string*)stream)->append((char*)ptr, 0, size* count);
return size* count;
}
static void *ResponseCallback(int id, struct responseStruct *response) {
long statusCode = response -> statusCode;
//map<const char*, const char*> headers = response -> headers;
const char* body = response -> body;
//while (printing) {} //wait for other threads to stop printing
printing = true; //tell other threads to not print anything
cout << id << " response received! Code: " << statusCode << endl << body << endl;
printing = false; //tell other threads printing is okay again
return NULL;
}
struct responseStruct HttpRequest(const char* url, const char* method, const char* body, map<const char*, const char*> &headers, const char* proxy, long timeout) {
CURL *curl;
curl = curl_easy_init();
long statusCode = 0;
map<const char*, const char*> respHeaders;
string respBody;
string _url(url);
string _method(method);
string _proxy(proxy);
struct curl_slist *headerList = NULL;
string headerString;
curl_easy_setopt(curl, CURLOPT_URL, url); //set url
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method); //set method
for (auto header=headers.begin(); header!=headers.end(); ++header) { //make header list
headerString = header->first;
headerString.append(": ").append(header->second);
headerList = curl_slist_append(headerList, headerString.c_str());
//cout << headerString << '\n';
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerList); //set headers
if (_method == "POST" || _method == "PUT" || _method == "DELETE") //set body if the request method would allow it
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
if (_url.find(string("https://")) != string::npos) //set ssl verifypeer if it's an https url
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
if (_proxy != "") //set proxy
curl_easy_setopt(curl, CURLOPT_PROXY, proxy);
if (timeout != 0) //set timeout
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); //follow redirects
//curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, writeToString);
//curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &respHeaders); //to receive response headers
//??
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeToString);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &respBody); //to receive response body
curl_easy_perform(curl); //send the request
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode); //get status code
struct responseStruct response;
response.statusCode = statusCode;
//response.headers;
response.body = respBody.c_str();
curl_easy_cleanup(curl);
return response;
}
static void *AddRequest( void *arguments ) {
// get arguments:
struct requestStruct *args = (struct requestStruct*)arguments;
int id = args->id;
const char* url = args->url;
const char* method = args->method;
const char* body = args->body;
map<const char*, const char*> headers = args->headers;
const char* proxy = args->proxy;
int timeout = args->timeout;
// print arguments:
//while (printing) {} //wait for other threads to stop printing
//printing = true; //tell other threads to not print anything
// cout << id << endl << url << endl << method << endl;
//printing = false; //tell the other threads it's okay to print again now
struct responseStruct response = HttpRequest(url, method, body, headers, proxy, timeout);
ResponseCallback(id,&response);
pthread_exit(0);
return NULL;
}
int main() {
//map<const char*, const char*> headers;
//headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0";
//struct responseStruct response = HttpRequest("https://facebook.com", "GET", "", headers, "localhost:8888", 6000);
//cout << response.body << endl;
pthread_t threads[3];
struct requestStruct reqArguments[3];
map<const char*, const char*> headers;
headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0";
const char* proxy = "";
reqArguments[0].id = 0;
reqArguments[0].url = "https://www.facebook.com/";
reqArguments[0].method = "GET";
reqArguments[0].headers = headers;
reqArguments[0].body = "";
reqArguments[0].proxy = proxy;
reqArguments[0].timeout = 6000;
pthread_create(&threads[0], NULL, &AddRequest, (void *)&reqArguments[0]); //create a thread on AddRequest() passing a full struct of arguments
reqArguments[1].id = 1;
reqArguments[1].url = "https://www.facebook.com/";
reqArguments[1].method = "GET";
reqArguments[1].headers = headers;
reqArguments[1].body = "";
reqArguments[1].proxy = proxy;
reqArguments[1].timeout = 6000;
pthread_create(&threads[1], NULL, &AddRequest, (void *)&reqArguments[1]); //create a thread on AddRequest() passing a full struct of arguments
reqArguments[2].id = 2;
reqArguments[2].url = "https://www.facebook.com/";
reqArguments[2].method = "GET";
reqArguments[2].headers = headers;
reqArguments[2].body = "";
reqArguments[2].proxy = proxy;
reqArguments[2].timeout = 6000;
pthread_create(&threads[2], NULL, &AddRequest, (void *)&reqArguments[2]); //create a thread on AddRequest() passing a full struct of arguments
getchar(); //prevent console from closing instantly
return 0;
}
I'm not really sure if I'm doing the whole pthread thing correctly..
There are some issues:
1. For some reason only the first request succeeds the others aren't even sent.
UNLESS I uncomment the first 4 lines of the main function which will do a direct request without a new thread, but I obviously don't want to use that code.
2. The HttpRequest() function doesn't return the response html code properly, I only receive garbage.
I think issue 2 might be a pointer related issue with the return struct of HttpRequest(), but I wasn't able to fix it. :(
3. My last and not that important problem is that I don't know how to receive the response headers and put them in a map.
Btw: I'm compiling with Visual C++ 2010 and I'm debugging the http traffic with Fiddler.
EDIT : This is your code that I corrected.
There was not really an error. But after several tests, I saw that to launch several curl_perform in the same time cause issues. So I added a delay (5000ms is large, you can reduce it).
And pthread_exit() caused prolems with response error.
#include "stdafx.hpp"
#include <iostream> //#include <stdio.h>
#include <curl/curl.h>
#include <pthread.h>
#include <map>
#include <string>
using namespace std;
bool printing = false; //will allow us to prevent prints overlapping each other
#if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) || defined(__WINDOWS__) || defined(__TOS_WIN__)
#include <windows.h>
inline void delay( unsigned long ms )
{
Sleep( ms );
}
#else /* presume POSIX */
#include <unistd.h>
inline void delay( unsigned long ms )
{
usleep( ms * 1000 );
}
#endif
struct requestStruct { //will allow us to pass more than one argument to the threaded functions
int id;
const char* url;
const char* method;
const char* body;
map<const char*, const char*> headers;
const char* proxy;
int timeout;
};
struct responseStruct { //will allow us to return more than one value from the Request function
long statusCode;
//map<const char*, const char*> headers;
const char* body;
};
size_t writeToString(void *ptr, size_t size, size_t count, void *stream) {
((string*)stream)->append((char*)ptr, 0, size* count);
return size* count;
}
static void *ResponseCallback(int id, struct responseStruct *response) {
long statusCode = response -> statusCode;
//map<const char*, const char*> headers = response -> headers;
const char* body = response -> body;
//while (printing) {} //wait for other threads to stop printing
printing = true; //tell other threads to not print anything
cout << id << " response received! Code: " << statusCode << endl << body << endl;
printing = false; //tell other threads printing is okay again
return NULL;
}
struct responseStruct HttpRequest(const char* url, const char* method, const char* body, map<const char*, const char*> &headers, const char* proxy, long timeout) {
CURL *curl;
curl = curl_easy_init();
long statusCode = 0;
map<const char*, const char*> respHeaders;
string respBody;
string _url(url);
string _method(method);
string _proxy(proxy);
struct curl_slist *headerList = NULL;
string headerString;
curl_easy_setopt(curl, CURLOPT_URL, url); //set url
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method); //set method
for (std::map<const char*, const char*>::iterator header=headers.begin(); header!=headers.end(); ++header) { //make header list
headerString = header->first;
headerString.append(": ").append(header->second);
headerList = curl_slist_append(headerList, headerString.c_str());
//cout << headerString << '\n';
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerList); //set headers
if (_method == "POST" || _method == "PUT" || _method == "DELETE") //set body if the request method would allow it
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
if (_url.find(string("https://")) != string::npos) //set ssl verifypeer if it's an https url
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
if (_proxy != "") //set proxy
curl_easy_setopt(curl, CURLOPT_PROXY, proxy);
if (timeout != 0) //set timeout
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); //follow redirects
//curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, writeToString);
//curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &respHeaders); //to receive response headers
//??
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeToString);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &respBody); //to receive response body
static int i=0;
delay(5000*(i++));
std::cout << "url: " << _url << ";" << std::endl;
curl_easy_perform(curl); //send the request
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode); //get status code
struct responseStruct response;
response.statusCode = statusCode;
//response.headers;
response.body = respBody.c_str();
curl_easy_cleanup(curl);
return response;
}
static void *AddRequest( void *arguments ) {
// get arguments:
struct requestStruct *args = (struct requestStruct*)arguments;
int id = args->id;
const char* url = args->url;
const char* method = args->method;
const char* body = args->body;
map<const char*, const char*> headers = args->headers;
const char* proxy = args->proxy;
int timeout = args->timeout;
// print arguments:
//while (printing) {} //wait for other threads to stop printing
//printing = true; //tell other threads to not print anything
// cout << id << endl << url << endl << method << endl;
//printing = false; //tell the other threads it's okay to print again now
struct responseStruct response = HttpRequest(url, method, body, headers, proxy, timeout);
ResponseCallback(id,&response);
/* this code cause trouble (no response code) */
//pthread_exit(0);
return NULL;
}
int main() {
//map<const char*, const char*> headers;
//headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0";
//struct responseStruct response = HttpRequest("https://facebook.com", "GET", "", headers, "localhost:8888", 6000);
//cout << response.body << endl;
pthread_t threads[3];
struct requestStruct reqArguments[3];
map<const char*, const char*> headers;
headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0";
const char* proxy = "";
reqArguments[0].id = 0;
reqArguments[0].url = "https://www.duckduckgo.com/";
reqArguments[0].method = "GET";
reqArguments[0].headers = headers;
reqArguments[0].body = "";
reqArguments[0].proxy = proxy;
reqArguments[0].timeout = 6000;
pthread_create(&threads[0], NULL, &AddRequest, (void *)&reqArguments[0]); //create a thread on AddRequest() passing a full struct of arguments
reqArguments[1].id = 1;
reqArguments[1].url = "https://www.google.com/";
reqArguments[1].method = "GET";
reqArguments[1].headers = headers;
reqArguments[1].body = "";
reqArguments[1].proxy = proxy;
reqArguments[1].timeout = 6000;
pthread_create(&threads[1], NULL, &AddRequest, (void *)&reqArguments[1]); //create a thread on AddRequest() passing a full struct of arguments
reqArguments[2].id = 2;
reqArguments[2].url = "https://www.facebook.com/";
reqArguments[2].method = "GET";
reqArguments[2].headers = headers;
reqArguments[2].body = "";
reqArguments[2].proxy = proxy;
reqArguments[2].timeout = 6000;
pthread_create(&threads[2], NULL, &AddRequest, (void *)&reqArguments[2]); //create a thread on AddRequest() passing a full struct of arguments
// getchar();
// that is cleaner
for (int i=0; i<3; ++i) {
int rc = pthread_join(threads[i], NULL);
printf("In main: thread %d is complete\n", i);
}
return 0;
}
For the last question about headers, please post an other question on stackoverflow. Because there are yet many subjects in one (I think).
And a little advice, work with objects is very more easy to write and to read code sources.
END EDIT
This is a copy of the official example to make multi-threading with libcurl :
http://curl.haxx.se/libcurl/c/multithread.html
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel#haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
/* A multi-threaded example that uses pthreads extensively to fetch
* X remote files at once */
#include <stdio.h>
#include <pthread.h>
#include <curl/curl.h>
#define NUMT 4
/*
List of URLs to fetch.
If you intend to use a SSL-based protocol here you MUST setup the OpenSSL
callback functions as described here:
http://www.openssl.org/docs/crypto/threads.html#DESCRIPTION
*/
const char * const urls[NUMT]= {
"http://curl.haxx.se/",
"ftp://cool.haxx.se/",
"http://www.contactor.se/",
"www.haxx.se"
};
static void *pull_one_url(void *url)
{
CURL *curl;
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_perform(curl); /* ignores error */
curl_easy_cleanup(curl);
return NULL;
}
/*
int pthread_create(pthread_t *new_thread_ID,
const pthread_attr_t *attr,
void * (*start_func)(void *), void *arg);
*/
int main(int argc, char **argv)
{
pthread_t tid[NUMT];
int i;
int error;
/* Must initialize libcurl before any threads are started */
curl_global_init(CURL_GLOBAL_ALL);
for(i=0; i< NUMT; i++) {
error = pthread_create(&tid[i],
NULL, /* default attributes please */
pull_one_url,
(void *)urls[i]);
if(0 != error)
fprintf(stderr, "Couldn't run thread number %d, errno %d\n", i, error);
else
fprintf(stderr, "Thread %d, gets %s\n", i, urls[i]);
}
/* now wait for all threads to terminate */
for(i=0; i< NUMT; i++) {
error = pthread_join(tid[i], NULL);
fprintf(stderr, "Thread %d terminated\n", i);
}
return 0;
}
You can, for a interactive use, transform the urls Array to a vector.
I hope that it will help you !
Related
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed last month.
Improve this question
I'm trying to create c++ code that download data from some URLs, but it's throwing a write access violation:
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
#endif
#include <curl/curl.h>
#include <string>
static const char* urls[] = {
"http://www.example.com",
"http://www.example1.com",
};
#define MAX_PARALLEL 10 /* number of simultaneous transfers */
#define NUM_URLS sizeof(urls)/sizeof(char *)
static size_t write_cb(void* ptr, size_t size, size_t nmemb, void* buffer)
{
((std::string*)buffer)->append((char*)ptr, nmemb);
return nmemb;
}
static void add_transfer(CURLM* cm, int i, int* left)
{
CURL* eh = curl_easy_init();
curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(eh, CURLOPT_URL, urls[i]);
curl_easy_setopt(eh, CURLOPT_PRIVATE, urls[i]);
curl_easy_setopt(eh, CURLOPT_WRITEDATA, &write_cb);
curl_easy_setopt(eh, CURLOPT_VERBOSE, 1L);
curl_multi_add_handle(cm, eh);
(*left)++;
}
int main(void)
{
CURLM* cm;
unsigned int transfers = 0;
int msgs_left = -1;
int left = 0;
curl_global_init(CURL_GLOBAL_ALL);
cm = curl_multi_init();
/* Limit the amount of simultaneous connections curl should allow: */
curl_multi_setopt(cm, CURLMOPT_MAXCONNECTS, (long)MAX_PARALLEL);
for (transfers = 0; transfers < MAX_PARALLEL && transfers < NUM_URLS;
transfers++)
add_transfer(cm, transfers, &left);
do {
int still_alive = 1;
curl_multi_perform(cm, &still_alive);
CURLMsg* msg;
int queued;
CURLMcode mc = curl_multi_perform(cm, &still_alive);
if (cm)
/* wait for activity, timeout or "nothing" */
mc = curl_multi_poll(cm, NULL, 0, 1000, NULL);
if (mc)
break;
do {
msg = curl_multi_info_read(cm, &queued);
if (msg) {
if (msg->msg == CURLMSG_DONE) {
/* a transfer ended */
fprintf(stderr, "Transfer completed\n");
}
}
} while (msg);
if (left)
curl_multi_wait(cm, NULL, 0, 1000, NULL);
} while (left);
curl_multi_cleanup(cm);
curl_global_cleanup();
return EXIT_SUCCESS;
}
Its crashing on the line:
_Mypair._Myval2._Mysize = _Old_size + _Count;
The full error message is:
Exception thrown: write access violation.
this was 0x7FF7941D39D0.
How can I make this code download each Url data without any error?
In your write_cb() callback, you are expecting the buffer parameter to point at a std::string object, but in add_transfer() you are setting CURLOPT_WRITEDATA to point at write_cb itself rather than at a std::string object.
Try something more like this instead:
struct url_info {
const char* url;
std::string data;
};
static url_info urls[] = {
{"http://www.example.com", ""},
{"http://www.example1.com", ""}
};
static const int NUM_URLS = sizeof(urls)/sizeof(urls[0]);
static size_t write_cb(void* ptr, size_t size, size_t nmemb, void* buffer)
{
size_t result = size * nmemb;
static_cast<std::string*>(buffer)->append(static_cast<char*>(ptr), result);
return result;
}
static void add_transfer(CURLM* cm, int i, int* left)
{
...
curl_easy_setopt(eh, CURLOPT_URL, urls[i].url);
curl_easy_setopt(eh, CURLOPT_WRITEDATA, &urls[i].data);
curl_easy_setopt(eh, CURLOPT_PRIVATE, &urls[i]);
...
}
int main(void)
{
...
while ((msg = curl_multi_info_read(cm, &queued)) != NULL) {
if (msg->msg == CURLMSG_DONE) {
/* a transfer ended */
url_info *info;
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, reinterpret_cast<char**>(&info));
// use info->url and info->data as needed...
std::cerr << "Transfer completed from " << info->url << ", bytes received: " << info->data.size() << "\n";
}
}
...
}
I am using visual studio 2019.
When I try to run this from a new project it works totally fine. Sends the email and doesn't give any error.
But when I try to run this from my server code it gives this error:
To be more precise:
Project1/DEBUG works fine.
Project1/Release works fine.
Project2/DEBUG works fine.
Project2/Release gives runtime error.
"Exception thrown: read access violation.
this was 0xF. occurred"
And compiler is redirected to "xstring" code page, line 2873.
part from xstring
basic_string& assign(_In_reads_(_Count) const _Elem* const _Ptr, _CRT_GUARDOVERFLOW const size_type _Count) {
// assign [_Ptr, _Ptr + _Count)
if (_Count <= _Mypair._Myval2._Myres) { // LINE 2873
_Elem* const _Old_ptr = _Mypair._Myval2._Myptr();
_Mypair._Myval2._Mysize = _Count;
_Traits::move(_Old_ptr, _Ptr, _Count);
_Traits::assign(_Old_ptr[_Count], _Elem());
return *this;
}
I've tried with totally same code (with copy paste) but still no use.
Project properties are the same. I've checked it several times for any change in properties but there is not even a single difference.
email.h
#pragma warning(disable : 4996) //_CRT_SECURE_NO_WARNINGS
#ifndef EMAIL_H
#define EMAIL_H
#include <vector>
#include <string>
#include "curl/curl.h"
// Enable one of the below depending on your operating system
#define WINDOWS_H
//#define LINUX_H
class Email
{
public:
// default constructor
Email();
// sets who the email is going to
void setTo(const std::string to);
// sets who the email came from
void setFrom(const std::string from);
// sets the cc
void setCc(const std::string to);
// sets the subject of the email
void setSubject(const std::string subject);
// set the body of the email
void setBody(const std::string body);
// sets the smtp username
void setSMTP_username(const std::string username);
// sets the smtp password
void setSMTP_password(const std::string password);
// sets the SMTP HOST
void setSMTP_host(const std::string host);
// adds a binary attachment to the email
void addAttachment(const std::string filepath);
// removes an attachment from the email (Not implemented yet)
void removeAttachment(const std::string filepath);
// removes all attachments
void removeAllAttachments();
// contructs the final email
void constructEmail();
// clears the contents of the email
void clearEmailContents();
// void send email
int send() const; // returns a CURL error code
// dumps the email contents for debugging
void dump() const;
private:
// smtp information
std::string smtp_user;
std::string smtp_password;
std::string smtp_host;
// email data
std::string to;
std::string from;
std::string cc;
std::string subject;
std::string body;
// vector which stores the email data
std::vector<std::string> email_contents;
std::vector<std::string> attachments;
// length of the above vector
int numberOfLines;
};
#endif
email.cpp
#include <iostream>
#include <ctime>
#include <string.h>
// #include "stdafx.h"
#include "email.h"
#define MAX_LEN 255 // this must be divisible by 3 otherwise the SMTP server won't be able to decode the attachment properly
#define ENCODED_LEN 341
// offsets into the email template
#define BOUNDARY 5
#define END_LINE 8
#define ATTACH_TYPE 10
#define ATTACH_TRANSFER 11
#define ATTACH_DEPOSITION 12
#define END_OF_TRANSMISSION_BOUNDARY 15
#define END_OF_TRANSMISSION 16
#ifdef WINDOWS_H
#define DIR_CHAR '\\'
#else
#define DIR_CHAR '/'
#endif
using namespace std;
// base64 encoding functions
void base64_encode(char* input_buf, char* output_buf, size_t input_size);
void encodeblock(char* in, char* out, int len);
// callback function
static size_t payload_source(void* ptr, size_t size, size_t nmemb, void* userp);
// base64 encoding table
const char b64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// global vector this is TEMP
vector<string> global_vec;
// struct used by the CURL callback
struct upload_status {
int lines_read;
};
static const char* email_template[] = {
/* i = 0 */
"User-Agent: DXF Viewer agent\r\n",
"MIME-Version: 1.0\r\n",
"Content-Type: multipart/mixed;\r\n",
" boundary=\"------------030203080101020302070708\"\r\n",
"\r\nThis is a multi-part message in MIME format.\r\n",
"--------------030203080101020302070708\r\n", // use this type of boundary for subsequent attachments
"Content-Type: text/plain; charset=utf-8; format=flowed\r\n",
"Content-Transfer-Encoding: 7bit\r\n",
/* i = 8 (BODY) */
"\r\n", /* empty line to divide headers from body, see RFC5322 */
"--------------030203080101020302070708\r\n", /* i = 9 */
/* Template for binary attachments i = 10 */
"Content-Type: application/octet-stream\r\n",
"Content-Transfer-Encoding: base64\r\n",
"Content-Disposition: attachment;\r\n",
/* Filename here */
"\r\n",
/* Encoded base64 contents here */
"\r\n",
"--------------030203080101020302070708--\r\n", // this type of boundary indicates that there are no more parts i = 15
"\r\n.\r\n",
NULL
};
Email::Email()
{
// empty constructor
}
void Email::setTo(const string to)
{
this->to = to;
}
void Email::setFrom(const string from)
{
this->from = from;
}
void Email::setCc(const string cc)
{
this->cc = cc;
}
void Email::setSubject(const string subject)
{
this->subject = subject;
}
void Email::setBody(const string body)
{
this->body = body;
}
void Email::setSMTP_username(const string user)
{
this->smtp_user = user;
}
void Email::setSMTP_password(const string pass)
{
this->smtp_password = pass;
}
void Email::setSMTP_host(const string hostname)
{
this->smtp_host = hostname;
}
void Email::addAttachment(const string file_path)
{
/*FILE* fp = NULL;
char buffer[MAX_LEN + 1] = { 0 };
char encodedBuffer[ENCODED_LEN] = { 0 };
char tempBuffer[MAX_LEN] = { 0 };
char* filename = NULL;
unsigned int fileSize = 0;
unsigned int bytesCopied = 0;
int bytesToCopy = 0;
int bytesRead = 0;
fp = fopen(file_path.c_str(), "rb");
if (fp) {
// get the file size
fseek(fp, 0, SEEK_END);
fileSize = ftell(fp);
fseek(fp, 0, SEEK_SET);
// copy the attachment header
string type(email_template[ATTACH_TYPE]);
this->attachments.push_back(type);
string encodingType(email_template[ATTACH_TRANSFER]);
this->attachments.push_back(encodingType);
string deposition(email_template[ATTACH_DEPOSITION]);
this->attachments.push_back(deposition);
// extract the filename from the path
filename = (char*)strrchr(file_path.c_str(), DIR_CHAR);
if (filename != NULL) {
filename += 1;
// push the filename
snprintf(tempBuffer, MAX_LEN, " filename=\"%s\"\r\n", filename);
string filename(tempBuffer);
this->attachments.push_back(filename);
string endLine(email_template[END_LINE]);
this->attachments.push_back(endLine);
// copy the file MAX_LEN bytes at a time into the attachments vector
while (bytesCopied < fileSize) {
// determine how many bytes to read
if (bytesCopied + MAX_LEN > fileSize) {
bytesToCopy = fileSize - bytesCopied;
}
else {
bytesToCopy = MAX_LEN;
}
// read from the file
memset(buffer, 0, MAX_LEN + 1);
bytesRead = fread(buffer, sizeof(char), bytesToCopy, fp);
// encoded the data read
memset(encodedBuffer, 0, ENCODED_LEN);
base64_encode(buffer, encodedBuffer, bytesRead);
// setup the encoded string so that we can push it to attachments
string line(encodedBuffer);
line += endLine;
this->attachments.push_back(line);
// update the number of bytes we have copied
bytesCopied += bytesToCopy;
}
this->attachments.push_back(endLine);
string boundary(email_template[BOUNDARY]);
this->attachments.push_back(boundary);
}
else {
removeAllAttachments();
cout << "Failed to extract filename!" << endl;
}
// close the file
fclose(fp);
}
else {
cout << "Unable to open file." << endl;
}
*/
}
void Email::constructEmail(void)
{
int i = 0;
char buffer[MAX_LEN];
string boundary(email_template[BOUNDARY]);
// time stuff
time_t rawtime;
struct tm* timeinfo;
// store all the email contents in a vector
// TO:
snprintf(buffer, MAX_LEN, "To: %s\r\n", this->to.c_str());
string line1(buffer);
this->email_contents.push_back(line1);
// FROM:
memset(buffer, 0, MAX_LEN);
snprintf(buffer, MAX_LEN, "From: %s\r\n", this->from.c_str());
string line2(buffer);
this->email_contents.push_back(line2);
// CC:
memset(buffer, 0, MAX_LEN);
snprintf(buffer, MAX_LEN, "Cc: %s\r\n", this->cc.c_str());
string line3(buffer);
if (this->cc.length() > 0) {
this->email_contents.push_back(line3);
}
// Subject:
memset(buffer, 0, MAX_LEN);
snprintf(buffer, MAX_LEN, "Subject: %s\r\n", this->subject.c_str());
string line4(buffer);
this->email_contents.push_back(line4);
// time:
time(&rawtime);
timeinfo = localtime(&rawtime);
memset(buffer, 0, MAX_LEN);
strftime(buffer, sizeof(buffer), "%d/%m/%Y %I:%M:%S +1100\r\n", timeinfo);
string time_str(buffer);
this->email_contents.push_back(time_str);
cout << time_str << endl;
for (i = 0; i < END_LINE; i++) { // other stuff e.g user-agent etc
string line(email_template[i]);
this->email_contents.push_back(line);
}
// add in the body
string endLine(email_template[END_LINE]);
this->email_contents.push_back(endLine);
memset(buffer, 0, MAX_LEN);
snprintf(buffer, MAX_LEN, "%s", this->body.c_str()); // Body:
string line5(buffer);
this->email_contents.push_back(line5); // body
this->email_contents.push_back(endLine); // \r\n
this->email_contents.push_back(boundary); // boundary
// add in the attachments
for (i = 0; i < attachments.size(); i++) {
this->email_contents.push_back(this->attachments[i]);
}
// add the last boundary with the two hyphens
string lastBoundary(email_template[END_OF_TRANSMISSION_BOUNDARY]);
this->email_contents.push_back(lastBoundary);
// specify that we don't want to send any more data
string endTransmission(email_template[END_OF_TRANSMISSION]);
this->email_contents.push_back(endTransmission);
}
/*
This function was taken and modified from:
https://curl.haxx.se/libcurl/c/smtp-ssl.html
*/
int Email::send(void) const
{
CURL* curl;
CURLcode res = CURLE_OK;
struct curl_slist* recipients = NULL;
struct upload_status upload_ctx;
upload_ctx.lines_read = 0;
curl = curl_easy_init();
global_vec = this->email_contents;
if (curl) {
/* Set username and password */
curl_easy_setopt(curl, CURLOPT_USERNAME, this->smtp_user.c_str());
curl_easy_setopt(curl, CURLOPT_PASSWORD, this->smtp_password.c_str());
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); // allows emails to be sent
/* This is the URL for your mailserver. Note the use of smtps:// rather
* than smtp:// to request a SSL based connection. */
curl_easy_setopt(curl, CURLOPT_URL, this->smtp_host.c_str());
/* If you want to connect to a site who isn't using a certificate that is
* signed by one of the certs in the CA bundle you have, you can skip the
* verification of the server's certificate. This makes the connection
* A LOT LESS SECURE.
*
* If you have a CA cert for the server stored someplace else than in the
* default bundle, then the CURLOPT_CAPATH option might come handy for
* you. */
#ifdef SKIP_PEER_VERIFICATION
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
#endif
/* If the site you're connecting to uses a different host name that what
* they have mentioned in their server certificate's commonName (or
* subjectAltName) fields, libcurl will refuse to connect. You can skip
* this check, but this will make the connection less secure. */
#ifdef SKIP_HOSTNAME_VERIFICATION
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
#endif
/* Note that this option isn't strictly required, omitting it will result
* in libcurl sending the MAIL FROM command with empty sender data. All
* autoresponses should have an empty reverse-path, and should be directed
* to the address in the reverse-path which triggered them. Otherwise,
* they could cause an endless loop. See RFC 5321 Section 4.5.5 for more
* details.
*/
curl_easy_setopt(curl, CURLOPT_MAIL_FROM, this->from.c_str());
/* Add two recipients, in this particular case they correspond to the
* To: and Cc: addressees in the header, but they could be any kind of
* recipient. */
recipients = curl_slist_append(recipients, this->to.c_str());
if (this->cc.length() > 0) {
recipients = curl_slist_append(recipients, this->cc.c_str());
}
curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
/* We're using a callback function to specify the payload (the headers and
* body of the message). You could just use the CURLOPT_READDATA option to
* specify a FILE pointer to read from. */
curl_easy_setopt(curl, CURLOPT_READFUNCTION, payload_source);
curl_easy_setopt(curl, CURLOPT_READDATA, &upload_ctx);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
/* Since the traffic will be encrypted, it is very useful to turn on debug
* information within libcurl to see what is happening during the
* transfer */
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
/* Send the message */
res = curl_easy_perform(curl);
/* Check for errors */
if (res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
/* Free the list of recipients */
curl_slist_free_all(recipients);
/* Always cleanup */
curl_easy_cleanup(curl);
}
return (int)res;
}
void Email::removeAllAttachments()
{
this->attachments.clear();
}
void Email::clearEmailContents()
{
this->email_contents.clear();
this->attachments.clear();
}
void Email::dump(void) const
{
int i = 0;
cout << "Email contents: " << endl;
for (i = 0; i < this->email_contents.size(); i++) {
cout << this->email_contents[i];
if (i == 20) {
break;
}
}
cout << "\n\nEmail attachments: " << endl;
for (i = 0; i < this->attachments.size(); i++) {
cout << this->attachments[i];
if (i == 5) {
break;
}
}
}
void base64_encode(char* input_buf, char* output_buf, size_t input_size)
{
char in[3];
char out[4];
size_t len = input_size;
*output_buf = 0;
for (size_t i = 0; i < len; )
{
int buf3_len = 0;
for (int j = 0; j < 3; ++j)
{
in[j] = input_buf[i++];
if (i > len)
in[j] = 0;
else
buf3_len++;
}
if (len > 0)
{
encodeblock(in, out, buf3_len);
strncat(output_buf, out, 4);
}
}
}
void encodeblock(char* in, char* out, int len)
{
out[0] = (char)b64_table[(int)(in[0] >> 2)];
out[1] = (char)b64_table[(int)(((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4))];
out[2] = (char)(len > 1 ? b64_table[(int)(((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6))] : '=');
out[3] = (char)(len > 2 ? b64_table[(int)(in[2] & 0x3f)] : '=');
}
static size_t payload_source(void* ptr, size_t size, size_t nmemb, void* userp)
{
struct upload_status* upload_ctx = (struct upload_status*)userp;
const char* data;
if ((size == 0) || (nmemb == 0) || ((size * nmemb) < 1)) {
return 0;
}
if (upload_ctx->lines_read >= 0 && upload_ctx->lines_read < global_vec.size())
{
data = global_vec[upload_ctx->lines_read].c_str();
if (data) {
size_t len = strlen(data);
memcpy(ptr, data, len);
upload_ctx->lines_read++;
return len;
}
}
return 0;
}
main.cpp
#include <iostream>
#include "email.h"
using namespace std;
int main(void)
{
Email e;
int curlError = 0;
// e.dump();
e.setTo("receiver#example.com");
e.setFrom("sender#example.com");
e.setSubject("Test Email");
e.setCc("");
e.setBody("Do not reply to this email");
e.setSMTP_host("smtp://smtp.mail.com:587");
e.setSMTP_username("sender#example.com");
e.setSMTP_password("sender_password");
e.addAttachment("");
// e.addAttachment("email.h");
// e.addAttachment("main.cpp");
e.constructEmail();
e.dump();
curlError = e.send();
if (curlError) {
cout << "Error sending email!" << endl;
}
else {
cout << "Email sent successfully!" << endl;
}
return 0;
}
What is the reason this same code runs perfectly on one project but doesn't work on another?
EDIT:
error happens in Email::send(void) on the line :
global_vec2 = this->email_contents;
but I cannot try/catch it. it doesn't throws exception.
EDIT2 :
Problem is caused by static vector. It doesn't exist, doesn't initialize. I don't understand what's causing this project to prevent static std::vectors from being created.
I have data in a JSON object that I can't seem to get to.
Error:
> testGetprice3.o: testGetprice3.cpp:71: int getData(): Assertion
> `document["success"].IsString()' failed.
I have tried switching the datatypes, it only errors.
How do I correctly access my JSON object data?
#include "rapidjson/include/rapidjson/document.h"
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <curl/curl.h>
#include <unistd.h>
#include <unordered_map>
#include <string>
using namespace rapidjson;
struct myData
{
std::fstream *file;
std::string *str;
};
size_t write_data(void *ptr, size_t size, size_t nmemb, myData *data)
{
size_t numBytes = size * nmemb;
if (data->file)
data->file->write((char*)ptr, numBytes);
if (data->str)
*data->str += std::string((char*)ptr, numBytes);
return numBytes;
}
//function to get coin data and perform analysis
int getData()
{
int count = 0;
//begin non terminating loop
while(true)
{
count++;
CURL *curl = curl_easy_init();
if (curl)
{
curl_easy_setopt(curl, CURLOPT_URL, "http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid=155");
std::fstream file("/home/coinz/cryptsy/myfile.txt", std::ios_base::out | std::ios_base::ate);
std::string json;
myData data;
data.file = &file;
data.str = &json;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
/* Perform the request, res will get the return code */
CURLcode res = curl_easy_perform(curl);
/* Check for errors */
if (res != CURLE_OK)
{
std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
}
else
{
file << std::endl;
//begin deserialization
Document document;
document.Parse(json.c_str());
assert(document.HasMember("success"));
assert(document["success"].IsString());
//std::cout << "The Last Traded Price is = " << document["lasttradeprice"].GetString() << std::endl;
}
/* always cleanup */
curl_easy_cleanup(curl);
}
//timer for URL request. *ADUJST ME AS DESIRED*
usleep(10000000);
}
return 0;
}
//Le Main
int main(void)
{
getData();
}
Looking at your JSON data, the success member is an int (as it is not surrounded by quote marks).
More info on JSON types can be found here: http://en.wikipedia.org/wiki/JSON#Data_types.2C_syntax_and_example
If the success member was a string, the JSON data would look like this: {"success":"1",...
Therefore when you call assert(document["success"].IsString()) it causes the error, as the success member is not a string, but an int.
Since the success member is an int, change the assertion to test whether it's an int - assert(document["success"].IsInt())
You stated you changed datatypes, but did not state which ones you tried. From the URL you are using, success is being returned as "success":1, which is an int.
Change:
assert(document["success"].IsString());
to:
assert(document["success"].IsInt());
Disclaimer: I am not asking anyone to debug this code, I am more interested to know if anyone sees that I am using libcurl improperly, because as far as I can tell, I am following the documentation exactly.
The problem is in the MakeRequest() method. At curl_easy_perform(), I get std output of
* About to connect() to dynamodb.us-east-1.amazonaws.com port 80 (#0)
* Trying 72.21.195.244... * connected
Then a segfault.
Here is the stack trace:
Thread [1] 30267 [core: 0] (Suspended : Signal : SIGSEGV:Segmentation fault)
Curl_getformdata() at 0x7ffff79069bb
Curl_http() at 0x7ffff790b178
Curl_do() at 0x7ffff791a298
Curl_do_perform() at 0x7ffff7925457
CurlHttpClient::MakeRequest() at CurlHttpClient.cpp:91 0x7ffff7ba17f5
AWSClient::MakeRequest() at AWSClient.cpp:54 0x7ffff7bbac4d
DynamoDbV2Client::GetItem() at DynamoDbV2Client.cpp:34 0x7ffff7bb7380
GetItemResultTest_TestLiveRequest_Test::TestBody() at GetItemResultTest.cpp:88 0x43db5a
testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void>() at gtest-all.cc:3,562 0x46502f
testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>() at gtest-all.cc:3,598 0x4602f6
<...more frames...>
Here is the code in question.
#include "http/curl/CurlHttpClient.h"
#include "http/standard/StandardHttpResponse.h"
#include "utils/StringUtils.h"
#include <curl/curl.h>
#include <sstream>
#include <algorithm>
#include <functional>
#include <vector>
bool CurlHttpClient::isInit = false;
void SetOptCodeForHttpMethod(CURL* requestHandle, HttpMethod method)
{
switch (method)
{
case GET:
curl_easy_setopt(requestHandle, CURLOPT_HTTPGET, 1);
break;
case POST:
curl_easy_setopt(requestHandle, CURLOPT_HTTPPOST, 1);
break;
case PUT:
curl_easy_setopt(requestHandle, CURLOPT_PUT, 1);
break;
default:
curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, "DELETE");
break;
}
}
CurlHttpClient::CurlHttpClient()
{
if (!isInit)
{
isInit = true;
curl_global_init(CURL_GLOBAL_ALL);
}
}
CurlHttpClient::~CurlHttpClient()
{
}
HttpResponse* CurlHttpClient::MakeRequest(const HttpRequest& request) const
{
struct curl_slist* headers = NULL;
std::stringstream headerStream;
HeaderValueCollection requestHeaders = request.GetHeaders();
for (HeaderValueCollection::iterator iter = requestHeaders.begin();
iter != requestHeaders.end(); ++iter)
{
headerStream.str("");
headerStream << iter->first << ": " << iter->second;
headers = curl_slist_append(headers, headerStream.str().c_str());
}
CURL* singleRequestHandle = curl_easy_init();
HttpResponse* response = NULL;
if (singleRequestHandle)
{
if (headers)
{
curl_easy_setopt(singleRequestHandle, CURLOPT_HTTPHEADER, headers);
}
if(request.GetMethod() == HttpMethod::POST)
{
curl_easy_setopt(singleRequestHandle, CURLOPT_POSTFIELDS, request.GetUri().GetFormParameters().c_str());
}
response = new StandardHttpResponse(request);
SetOptCodeForHttpMethod(singleRequestHandle, request.GetMethod());
std::string url = request.GetURIString(false);
curl_easy_setopt(singleRequestHandle, CURLOPT_URL, url.c_str());
curl_easy_setopt(singleRequestHandle, CURLOPT_WRITEFUNCTION, &CurlHttpClient::WriteData);
curl_easy_setopt(singleRequestHandle, CURLOPT_WRITEDATA, response);
curl_easy_setopt(singleRequestHandle, CURLOPT_HEADERFUNCTION, &CurlHttpClient::WriteHeader);
curl_easy_setopt(singleRequestHandle, CURLOPT_HEADERDATA, response);
if (request.GetContentBody() != NULL)
{
curl_easy_setopt(singleRequestHandle, CURLOPT_POSTFIELDSIZE, request.GetContentBody()->tellp());
curl_easy_setopt(singleRequestHandle, CURLOPT_READFUNCTION, &CurlHttpClient::ReadBody);
curl_easy_setopt(singleRequestHandle, CURLOPT_READDATA, &request);
}
curl_easy_setopt(singleRequestHandle, CURLOPT_VERBOSE, 1L);
curl_easy_perform(singleRequestHandle);
int responseCode;
curl_easy_getinfo(singleRequestHandle, CURLINFO_RESPONSE_CODE, &responseCode);
response->SetResponseCode((HttpResponseCode) responseCode);
char* contentType = NULL;
curl_easy_getinfo(singleRequestHandle, CURLINFO_CONTENT_TYPE, &contentType);
response->SetContentType(contentType);
curl_easy_cleanup(singleRequestHandle);
}
if (headers)
{
curl_slist_free_all(headers);
}
return response;
}
size_t CurlHttpClient::WriteData(char *ptr, size_t size, size_t nmemb, void* userdata)
{
if (ptr)
{
HttpResponse* response = (HttpResponse*)userdata;
if (!response->GetResponseBody())
{
std::streambuf* strBuffer = new std::stringbuf;
response->SetResponseBody(new std::iostream(strBuffer));
}
int sizeToWrite = size * nmemb;
response->GetResponseBody()->write(ptr, sizeToWrite);
return sizeToWrite;
}
return 0;
}
size_t CurlHttpClient::WriteHeader(char *ptr, size_t size, size_t nmemb, void* userdata)
{
if (ptr)
{
HttpResponse* response = (HttpResponse*)userdata;
std::string headerLine(ptr);
std::vector<std::string> keyValuePair = StringUtils::Split(headerLine, ':');
if (keyValuePair.size() == 2)
{
std::string headerName = keyValuePair[0];
headerName = StringUtils::Trim(headerName);
std::string headerValue = keyValuePair[1];
headerValue = StringUtils::Trim(headerValue);
response->AddHeader(headerName, headerValue);
}
return size * nmemb;
}
return 0;
}
size_t CurlHttpClient::ReadBody(char* ptr, size_t size, size_t nmemb, void* userdata)
{
HttpRequest* request = (HttpRequest*)userdata;
std::shared_ptr<std::iostream> outputStream = request->GetContentBody();
if (outputStream != NULL && size * nmemb)
{
size_t written = outputStream->readsome(ptr, size * nmemb);
return written;
}
return 0;
}
For reference here is the definition for CurlHttpClient:
//Curl implementation of an http client. Right now it is only synchronous.
class CurlHttpClient : public HttpClient
{
public:
//Creates client, intializes curl handle if it hasn't been created already.
CurlHttpClient();
//cleans up curl lib
virtual ~CurlHttpClient();
//Makes request and recieves response synchronously
virtual HttpResponse* MakeRequest(const HttpRequest& request) const;
private:
//Callback to read the content from the content body of the request
static size_t ReadBody(char* ptr, size_t size, size_t nmemb, void* userdata);
//callback to write the content from the response to the response object
static size_t WriteData( char* ptr, size_t size, size_t nmemb, void* userdata);
//callback to write the headers from the response to the response
static size_t WriteHeader( char* ptr, size_t size, size_t nmemb, void* userdata);
//init flag.
static bool isInit;
};
One definite problem I see with the code is
curl_easy_setopt(requestHandle, CURLOPT_HTTPPOST, 1);
CURLOPT_HTTPPOST expects a pointer to a structure of type struct curl_httppost. Passing 1 creates a dangling pointer. You probably might want to use the CURLOPT_POST instead.
I'm having some trouble with a C++ program here. Basically I've written a simple wrapper for http requests, with the ability to do multiple requests at once.
Works absolutely fine, but when I do httpS requests, it crashes randomly in multithreaded mode. I'm using curl and posix threads.
Backtrace looks like this:
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x80996)[0x7fea9046d996]
/lib/x86_64-linux-gnu/libc.so.6(+0x82b80)[0x7fea9046fb80]
/lib/x86_64-linux-gnu/libc.so.6(realloc+0xf2)[0x7fea90470ae2]
/lib/x86_64-linux-gnu/libcrypto.so.1.0.0(CRYPTO_realloc+0x49)[0x7fea8f9c6169]
/lib/x86_64-linux-gnu/libcrypto.so.1.0.0(lh_insert+0x101)[0x7fea8fa4bfb1]
/lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0xe844e)[0x7fea8fa4e44e]
/lib/x86_64-linux-gnu/libcrypto.so.1.0.0(ERR_get_state+0xde)[0x7fea8fa4eeee]
/lib/x86_64-linux-gnu/libcrypto.so.1.0.0(ERR_clear_error+0x15)[0x7fea8fa4f065]
/usr/lib/x86_64-linux-gnu/libcurl.so.4(+0x24e79)[0x7fea90f10e79]
/usr/lib/x86_64-linux-gnu/libcurl.so.4(+0x39ea0)[0x7fea90f25ea0]
/usr/lib/x86_64-linux-gnu/libcurl.so.4(+0xf8fd)[0x7fea90efb8fd]
/usr/lib/x86_64-linux-gnu/libcurl.so.4(+0x219f5)[0x7fea90f0d9f5]
/usr/lib/x86_64-linux-gnu/libcurl.so.4(+0x35538)[0x7fea90f21538]
/usr/lib/x86_64-linux-gnu/libcurl.so.4(curl_multi_perform+0x91)[0x7fea90f21d31]
/usr/lib/x86_64-linux-gnu/libcurl.so.4(curl_easy_perform+0x107)[0x7fea90f19457]
./exbot[0x40273a]
/lib/x86_64-linux-gnu/libpthread.so.0(+0x7f6e)[0x7fea90cd6f6e]
/lib/x86_64-linux-gnu/libc.so.6(clone+0x6d)[0x7fea904e79cd]
Could this be a bug in libcrypto?
Can I somehow tell curl not to use libcrypto? Any alternatives?
It only crahes wenn using httpS requests and works fine with even 10000 simultaneous http queries.
Cheers,
Thomas
Just for completeness my code:
// simple wrapper for http requests
#ifndef _REQUEST_H_
#define _REQUEST_H_
#include <curl/curl.h>
#include <pthread.h>
#include <string>
#include <iostream>
//////////////////////////////////
// MACROS
//////////////////////////////////
#define ERR(_msg) std::cerr << __FUNCTION__ << ": " << _msg << std::endl
//////////////////////////////////
// REQUEST WRAPPER
//////////////////////////////////
typedef unsigned int uint;
class RequestWrapper
{
private: // non copyable
RequestWrapper();
RequestWrapper(const RequestWrapper &that);
RequestWrapper &operator=(const RequestWrapper &that);
public:
struct Response
{
Response() : msg(""), success(false) {}
std::string msg;
bool success;
};
static Response simpleGET(std::string url, uint timeout);
static size_t write(char *content, size_t size, size_t nmemb, void *userp);
};
//////////////////////////////////
// GET
//////////////////////////////////
inline size_t RequestWrapper::write(char *content, size_t size, size_t nmemb, void *userp)
{
std::string *buf = static_cast<std::string *>(userp);
size_t realsize = size * nmemb;
for (uint i = 0; i < realsize; ++i)
{
buf->push_back(content[i]);
}
return realsize;
}
inline RequestWrapper::Response RequestWrapper::simpleGET(std::string url, uint timeout)
{
Response resp;
CURL *curl;
CURLcode res;
std::string buf;
// send request
buf.clear();
curl = curl_easy_init();
if (!curl)
{
//ERR("libcurl init failed");
return resp;
}
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void *>(&buf));
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
res = curl_easy_perform(curl);
if(res != CURLE_OK)
{
//ERR("libcurl request failed, CODE: " << res);
return resp;
}
curl_easy_cleanup(curl);
// done
resp.msg = buf;
resp.success = true;
return resp;
}
//////////////////////////////////
// MULTITHREADED REQUEST
//////////////////////////////////
class RequestList
{
private:
std::vector<std::string> _reqs;
static void *sender(void *payload);
static pthread_mutex_t _mutex;
public:
inline void add(std::string request)
{
_reqs.push_back(request);
}
inline void clear()
{
_reqs.clear();
}
std::vector<std::string> send(uint timeout) const;
struct Payload
{
std::string url;
std::vector<std::string> *out;
uint tout, index;
Payload(std::string url,
std::vector<std::string> *out,
uint tout, uint index) : url(url), out(out), tout(tout), index(index) { }
Payload() : url(""), out(NULL), tout(0), index(0) { }
};
};
//////////////////////////////////
// SEND MT REQUEST
//////////////////////////////////
pthread_mutex_t RequestList::_mutex;
void *RequestList::sender(void *payload)
{
Payload *pl = static_cast<Payload *>(payload);
RequestWrapper::Response resp = RequestWrapper::simpleGET(pl->url, pl->tout);
pthread_mutex_lock(&_mutex);
if (resp.success)
{
pl->out->at(pl->index) = resp.msg;
std::cerr << ".";
}
else
{
std::cerr << "x";
}
pthread_mutex_unlock(&_mutex);
return NULL;
}
inline std::vector<std::string> RequestList::send(uint timeout) const
{
std::vector<std::string> resp;
resp.resize(_reqs.size());
Payload *payloads = new Payload[_reqs.size()];
pthread_t *tids = new pthread_t[_reqs.size()];
// create mutex
pthread_mutex_init(&_mutex, NULL);
// prepare payload and create thread
for (uint i = 0; i < _reqs.size(); ++i)
{
payloads[i] = Payload(_reqs[i], &resp, timeout, i);
pthread_create(&tids[i], NULL, RequestList::sender, static_cast<void *>(&payloads[i]));
}
// wait for threads to finish
for (uint i = 0; i < _reqs.size(); ++i)
{
pthread_join(tids[i], NULL);
}
std::cerr << std::endl;
//destroy mutex
pthread_mutex_destroy(&_mutex);
delete[] payloads;
delete[] tids;
return resp;
}
#endif
Libcrypto is part of OpenSSL, which is not thread-safe unless you provide the necessary callbacks. According to the documentation, on a POSIX-compliant system (which has thread-local errno) the default thread-id implementation is acceptable, so you just need a locking function:
void locking_function(int mode, int n, const char *file, int line);
This function will need to maintain a set of CRYPTO_num_locks() mutexes, and lock or unlocks the n-th mutex depending on the value of mode. You can read the documentation for more details. The libcurl website actually has some sample code showing how to do this.
Alternatively, you can build libcurl with a different SSL library that is thread safe, such as GnuTLS.