I am using libcurl to fetch json data using GET request from a webserver.
This is my sample code:
char *DownloadedResponse;
static int writer(char *data, size_t size, size_t nmemb, char *buffer_in)
{
if (buffer_in != NULL)
{
buffer_in = new char[size*nmemb];
strcpy(buffer_in,data);
DownloadedResponse = buffer_in;
return size * nmemb;
}
return 0;
}
char * DownloadJSON(string URL)
{
CURL *curl;
CURLcode res;
struct curl_slist *headers=NULL;
curl_slist_append(headers, "Accept: application/json");
curl_slist_append( headers, "Content-Type: application/json");
curl_slist_append( headers, "charsets: utf-8");
curl = curl_easy_init();
if (curl)
{
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_URL, URL.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPGET,1);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,writer);
res = curl_easy_perform(curl);
if (CURLE_OK == res)
{
char *ct;
res = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
if((CURLE_OK == res) && ct)
{
cout<<"\nresponse received: "<<DownloadedResponse;
}
else
{
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl = NULL;
return NULL;
}
}
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl = NULL;
}
Here I am able to get json data in DownloadedResponse in callback "writer" of CURLOPT_WRITEFUNCTION.
But if I print using custom pointer of CURLOPT_WRITEDATA,
char *dataPointer = NULL;
CURLcode curl_easy_setopt(curl, CURLOPT_WRITEDATA, dataPointer);
cout<<dataPointer;
Output of dataPointer is empty.
What is the issue here since i able to print json data in callback of CURLOPT_WRITEFUNCTION but not in the pointer of CURLOPT_WRITEDATA
You write a function that takes data read from the network, and writes it to where you want it.
static int writer(char *data, size_t size, size_t nmemb, char *buffer_in){
if (buffer_in != NULL) {
// very bad code which is never executed
}
return 0;
}
In order for that function to write the data, it has to know where to write it, so you tell it to write to NULL
char *dataPointer = NULL;
CURLcode curl_easy_setopt(curl, CURLOPT_WRITEDATA, dataPointer);
What value do you tell it to use as buffer_in? You pass it dataPointer, which is NULL, so you just told it buffer_in = NULL. I think instead you meant to say "the address of dataPointer", which would be &dataPointer.
Technically, I have answered your question now. You passed it NULL for the buffer, so the write function exited immediately. But there's more. Now you get to execute that really bad code in writer().
if (buffer_in != NULL)
{
// if buffer_in already has allocated memory then leak it immediately
// create a new buffer of memory to leak later
buffer_in = new char[size*nmemb];
// store the data in buffer_in
// assume it is null terminated (it is not)
// rather than using the length we already know
strcpy(buffer_in,data);
// remember buffer_in? We don't use it so assign that data pointer to a global variable.
DownloadedResponse = buffer_in;
// return size of this particular chunk of data
return size * nmemb;
}
This function MUST use the length of the data, and not assume data is null terminated (see https://curl.haxx.se/libcurl/c/CURLOPT_WRITEFUNCTION.html).
This function MUST be able to handle the data in multiple small pieces by adding them to what it has already read. You can't call new and then discard the new memory. And you can't do that anyway because you just leaked that memory -- every new must be matched with exactly one delete. In fact, you would be very well advised not to use new or delete at all, now that we have the standard library.
This function should use the buffer_in argument you give it rather than a global variable, but you can use a global variable if you want, it's just error prone. It's not literally an error like the other stuff.
The whole point of buffer_in is to give you a persistent data structure where you can accumulate the answers. It probably should be in local scope around the curl_easy_execute, so you can then just return the content from that data structure if you got CURLE_OK. I strongly recommend you write the data to std::vector, so you don't have to keep track of memory allocation. You have trouble with it, but you don't need to do it at all. Modern style says everybody has trouble with it, so just let the standard library handle it.
You claim to follow the example in the docs, which links to https://curl.haxx.se/libcurl/c/getinmemory.html If you look again, you will see what they are doing, and how your code doesn't match. In particular, they pass &chunk (the address of chunk) and then write data into chunk so they keep what was there before.
struct MemoryStruct {
char *memory;
size_t size;
};
static size_t
WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
// here is where they get access to the buffer
struct MemoryStruct *mem = (struct MemoryStruct *)userp;
In the call to curl, you will find the struct locally defined, then the remote call:
struct MemoryStruct chunk;
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
res = curl_easy_perform(curl_handle);
if (stuff)
printf("%lu bytes retrieved\n", (long)chunk.size);
Related
I am using following code to download data from an url to memory (stream). Around 2% chance, the size of the stream is zero. I can download proper data from the same failing url if I try it another time. I am not sure if this is a network issue, CPU usage issue, or it's just the code not covering some corner cases. Please advice. Thanks!
static size_t write_data(char *ptr, size_t size, size_t nmemb, void *userdata)
{
std::vector<uchar> *stream = (std::vector<uchar>*)userdata;
size_t count = size * nmemb;
stream->insert(stream->end(), ptr, ptr + count);
return count;
}
static void CurlUrl(const char* img_url, std::vector<uchar>* stream) {
CURL *curl = curl_easy_init(); // curl_global_init is called eleswhere.
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(curl, CURLOPT_URL, img_url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, stream);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10);
CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
If it didn't deliver any download data into the buffer via the callback, it means that the transfer either failed or that there was exactly zero bytes to transfer.
Check the return code from curl_easy_perform() as it might actually tell you exactly what happened.
Use CURLOPT_VERBOSE to see what's going on if (1) is not enough.
Use CURLOPT_ERRORBUFFER to get a better error description if it fails if (2) is not enough.
I would like to dowload some page content of wikitionary. I use curl in a loop. The first iteration is ok but the others give me the same result as the first. What is missing/wrong?. Thank you. This is the loop:
std::string buffer;
size_t curl_write( void *ptr, size_t size, size_t nmemb, void *stream)
{
buffer.append((char*)ptr, size*nmemb);
return size*nmemb;
}
int main(int argc, char **argv)
{
CURL *curl = curl_easy_init();
string data;
data="http://fr.wiktionary.org/w/api.php?format=json&action=query&titles=";
//Page titles are read from local file. The code is not shown to make short.
while ( not_end_of_file){
//list_of_page_title is pages requested for the current iteration.
data=data+list_of_page_title+"prop=revisions&rvprop=content";
curl_easy_setopt(curl, CURLOPT_URL, data.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write);
curl_easy_perform(curl);
curl_easy_reset(curl);
}
curl_easy_cleanup(curl);
return 0;
}
I am new to curl. May be many things are missed. Thank you for the help.
data=data+list_of_page_title will append the new title onto your previous URL instead of replacing the previous. By the end you'll have a gigantic URL full of garbage. The server is probably paying attention to the first title and ignoring the rest.
And this would be obvious if you just output your URL as the first step of debugging... "Am I requesting what I think I'm requesting?"
One problem is that you are not resetting your buffer variable.
while ( not_end_of_file){
buffer = ""; // reset buffer to empty string
//list_of_page_title is pages requested for the current iteration.
data="http://fr.wiktionary.org/w/api.php?format=json&action=query&titles=" +
list_of_page_title +
"prop=revisions&rvprop=content";
curl_easy_setopt(curl, CURLOPT_URL, data.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write);
curl_easy_perform(curl);
curl_easy_reset(curl);
}
And as Peter points out your handling of the data variable has a very similar problem.
The below code is to get response from a server using wsdl, here the problem is curl returns response but am unable to print it.
Error:
Failed writing body
Failed writing data
#include<stdio.h>
#include<string.h>
#include"../include/curl.h"
size_t write_data(void *ptr, size_t size, size_t count, void *stream)
{
/* ptr - your string variable.
stream - data chuck you received */
printf("%.*s", size, (char*)stream);
}
int main()
{
int res=0,i=0;
char buffer[4098]="",buff[128]="",buf[256]="",buf7[30]="",buf6[30]="",buf5[30]="";
char machineid[]="SUBANI";
char filename1[50]="";
int refno=0,paymode=0,taxtype=0;
FILE *fbc;
memset(filename1,0,sizeof(filename1));
sprintf(filename1,"/mnt/jffs2/Response_Details1.xml");
lk_dispclr();
lk_disptext(1,0,(unsigned char *)"Sending Request",0);
lk_disptext(2,0,(unsigned char *)"Please Wait",0);
memset(buffer,0,sizeof(buffer));
sprintf(buffer,"<?xml version=\"1.0\" encoding=\"utf-8\"?>\
<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:log=\"http://wsdlclassess.application.sims.test.com\">\
<soap:Header>\
</soap:Header>\
<soap:Body>\
<log:loginMethod>\
<log:loginid>%s</log:loginid>\
<log:password>%s</log:password>\
</log:loginMethod>\
</soap:Body>\
</soap:Envelope>","raja","test");
res=GET_FILE1(buffer,filename1);
return 0;
}
int GET_FILE1(char *buffer,char *filename)
{
CURL *curl;
CURLcode res;
struct curl_slist *headers = NULL;
FILE *out_fd = (FILE *) 0;
char errorbuf[300] = "",tmpbuff[128]="";
char errmsg[256];
int Timeout=120; //Default timeout is = 2 mins
int buffer_size = 0;
char urlbuff[256]="";
char mstr[10240];
memset(urlbuff,0,sizeof(urlbuff));
memset(tmpbuff,0,sizeof(tmpbuff));
buffer_size = strlen(buffer);
strcpy(tmpbuff,"http://10.10.1.111:8081/test_server/services/application?wsdl");
tmpbuff[strlen(tmpbuff)]='\0';
curl = curl_easy_init();
if(curl)
{
out_fd = fopen (filename, "w");
curl_easy_setopt(curl, CURLOPT_FILE, out_fd);
printf("%s:Sign-In Request\n", __func__);
headers = curl_slist_append(headers, "Content-type:application/soap+xml; charset=utf-8; action=\"http://wsdlclassess.application.sims.test.com/loginMethod\"");
curl_easy_setopt(curl, CURLOPT_URL, tmpbuff);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, mstr);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, buffer_size);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, buffer);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, Timeout);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER,errmsg);
printf("The Server%s:Performing Transaction.....\n",__func__);
res = curl_easy_perform(curl);
printf("res=after culreasey perform%d\n",res);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
printf("\nerrorbuf:%s\n",errmsg);
fclose(out_fd);
if(CURLE_OK != res)
{
puts("error occured is\n" );
//ppp_close();
return -1;
}
}
return 0;
}
The error is that you don't return the correct value from the function, in fact you don't return anything.
Also, the data provided to the function is actually the first ptr argument.
I agree that the documentation is not very clear, but it says:
The size of the data pointed to by ptr is size multiplied with nmemb, it will not be zero terminated.
The above line (emphasis mine) tells you that the data is in ptr which is the first argument in the function declaration provided in the documentation.
The documentation also states:
Return the number of bytes actually taken care of. If that amount differs from the amount passed to your function, it'll signal an error to the library. This will abort the transfer and return CURLE_WRITE_ERROR.
You don't return a value from the function, and so you have undefined behavior with a seemingly random value being returned causing the whole operation to fail. To fix this you should return size * count.
You also uses size to print the string, which is the size of the underlying type used (probably 1), your count variable is the number of characters read by CURL. To be fully working, without invoking more undefined behavior (since the data is not terminated) you should call printf like:
printf("%*.*s", size * count, size * count, ptr);
I am trying to read the content of a PHP / HTML file on a remote web server using C++, but haven't found a way to do it. I want to pass GET statements to it, so http://example.com/login.php?user=abc&password=def.
How would I do it?
Your best bet is to use an external library. libcurl is popular and fairly easy to use.
Here's a simple example, you need to add error checking though:
string data;
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, url_.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWrite);
curl_easy_perform(curl);
Your callback would look something like this:
size_t curlWrite(void *ptr, size_t size, size_t nmemb, void *usrPtr)
{
size_t bytes = size * nmemb;
string *data = static_cast<string *>(usrPtr);
data->append(static_cast<const char *>(ptr), bytes);
return bytes;
}
You can add your GET parameters on the end of the URL.
I would like to convert this code (using a scripting language with a Sockets extension) over to C++ using LibCurl. I have only used LibCurl once previously, so I am at a bit of a loss as to what else I am going to need. My main point of confusing is wether I should be able to just use curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); to send this, or if I am going to need to extract the socket, then send it over that.
Here is the relevant snippet from the script...
public OnSocketConnected(Handle:socket, any:friendId)
{
decl String:CommunityId[32];
FriendIDToCommunityId(friendId, CommunityId, sizeof(CommunityId));
decl String:query[2048];
decl String:cookieString[100];
decl String:inviterString[32];
decl String:groupString[32];
GetConVarString(cookie, cookieString, sizeof(cookieString));
GetConVarString(inviter, inviterString, sizeof(inviterString));
GetConVarString(group, groupString, sizeof(groupString));
Format(query, sizeof(query), "GET /actions/GroupInvite?type=groupInvite&inviter=%s&invitee=%s&group=%s HTTP/1.1\r\nHost: steamcommunity.com\r\nConnection: close\r\nCookie: steamLogin=%s\r\n\r\n", inviterString, CommunityId, groupString, cookieString);
SocketSend(socket, query);
LogMessage("%s", query);
}
and here is what I have in C++ so far. It is looking like I am going to need to extract the socket first, but I am not proficient with network coding, so I am not exactly sure where I need to go from here.
void InviteToGroup(const char *szUserSteamID, const char *szInviterSteamID, const char *steamUser, const char *steamPass)
{
CURL *curl;
CURLcode res, result;
//int sockfd; /* socket */
char errorBuffer[CURL_ERROR_SIZE];
const char *szUserID = GetCommunityID(szUserSteamID); // User's Steam Community ID
const char *szInviterID = GetCommunityID(szInviterSteamID); // Inviter's Steam Community ID
char *szGroupID = "";
GetGroupCommunityID(1254745, &szGroupID); // Group Steam Community ID
const char *szCookie = "76561198018111441%7C%7CC7D70E74A3F592F3E130CCF4CAACD4A7B9CAD993"; // Steam Community Login Cookie
char *buffer = new char[512];
// Create the GET request
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "GET /actions/GroupInvite?type=groupInvite&inviter=");
snprintf(buffer, sizeof(buffer), "%s&invitee=%s&group=%s ", szInviterID, szUserID, szGroupID);
headers = curl_slist_append(headers, buffer);
headers = curl_slist_append(headers, "HTTP/1.1\r\nHost: steamcommunity.com\r\nConnection: close\r\nCookie: ");
snprintf(buffer, sizeof(buffer), "steamLogin=%s\r\n\r\n", szCookie);
headers = curl_slist_append(headers, buffer);
delete buffer;
// Init CURL
curl = curl_easy_init();
if(curl)
{
curl_easy_setopt(curl, CURLOPT_URL, "https://www.steamcommunity.com");
curl_easy_setopt(curl, CURLOPT_PORT, 443); // Check this before using
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorBuffer);
//curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1); // No transfer, just extract the socket
// Find out if we need to use Proxy stuff here
char *userpass = new char[64];
snprintf(userpass, sizeof(userpass), "%s:%s", steamUser, steamPass);
curl_easy_setopt(curl, CURLOPT_USERPWD, userpass);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
// Attempt to Connect the Steam Community Server
res = curl_easy_perform(curl);
if (res == CURLE_OK)
Msg("Connected Successfully!\n");
else
Msg("Connection Failed! Error: %s\n", errorBuffer);
// Close the connection
curl_easy_cleanup(curl);
}
}
So corrections here as your C++ is incorrect which will mean your code won't work.
sizeof( char * ) is the size of a pointer, not the length of the string. So your snprintf will print up to 4 characters on a 32-bit system which will include the null terminator so you will actually get only a string of length 3.
If you are going to use a "should be big enough" buffer do not allocate it with new. You could use std::vector if you want to use snprintf, then check the return value of snprintf which is the length of the actual string that would be written and then increase the buffer size if necessary (to the return value + 1).
vector<char> buffer;
size_t required = 511;
do
{
buffer.resize( required + 1 );
required = snprintf( &buffer[0], buffer.size(), formatstr, ... );
}
while( required >= buffer.size() );
There is no need to "free" the buffer at the end (which incidentally you did incorrectly) as vector will do that automatically.
You now pass &buffer[0] to the function, it will point to a null-terminated string and will be writable if the API requires a char* rather than a const char*. (libcurl though takes ... as the 3rd parameter to curl_easy_setopt so you can pass in "anything". You therefore do have to be careful as you won't get warned by the compiler if you accidentally pass in a std::string or std::vector).