3

I am currently writing a REST-like client that only is required to do PUT requests.

Problem:
Running the program is not giving me the correct results on the URL's API and I do not know why.

Using curl_easy_perform(curl) does not throw an error when called. But the expected result is not generated on the URL's API.

Using curl_easy_send(curl,..,..,..) throws a : unsupported protocol error

Assumption:
I am assuming the order in which I am using the curl_easy_opts is a problem? And I am even missing a couple of key lines?

I have been reading on here of how other people do PUT requests and have been using their methods.

Summary of Program:

My program prompts the user for some string/character data, and from that, I construct the strings myself such as the header and the payload. The header and payload are both in JSON format but the payload is simply a string ( in this case, a char *str = (char *)mallo.. etc). How the header is constructed is shown below.

My header is being constructed using

struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Accept: application/json");
//there is more content being appended to the header

The CURL function calls :

    //init winsock stuff
    curl_global_init(CURL_GLOBAL_ALL);

    //get a curl handle
    curl = curl_easy_init();

if(curl){
    //append the headers
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

    //specify the target URL
    curl_easy_setopt(curl, CURLOPT_URL, url);

    //connect ( //i added this here since curl_easy_send() says it requires it. )
    curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY,1L); 

    //specify the request (PUT in our case)
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");

    //append the payload
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);

    res = curl_easy_perform(curl);
    //res = curl_easy_send(curl, payload, strlen(payload),&iolen);

    //check for errors
    if(res != CURLE_OK)
        fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));

    curl_easy_cleanup(curl);
}
jtor
  • 133
  • 1
  • 4
  • 13

3 Answers3

3

You should not be using the CURLOPT_CONNECT_ONLY option or curl_easy_send() function, those are intended to be used for custom, non-HTTP protocols.

See this page for an example of how to do a PUT request with libcurl. Basically, you want to enable the CURLOPT_UPLOAD and CURLOPT_PUT options to say that you're doing a PUT request and to enable uploading a body with the request, and then you set the CURLOPT_READDATA and CURLOPT_INFILESIZE_LARGE options to tell libcurl how to read the data you're uploading and how big the data is.

In your case, if you already have the data in memory, then you don't need to read it out of a file, and you can just memcpy() it inside your read callback.

Example code copied below:

/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) 1998 - 2012, 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.
 *
 ***************************************************************************/ 
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <curl/curl.h>

/*
 * This example shows a HTTP PUT operation. PUTs a file given as a command
 * line argument to the URL also given on the command line.
 *
 * This example also uses its own read callback.
 *
 * Here's an article on how to setup a PUT handler for Apache:
 * http://www.apacheweek.com/features/put
 */ 

static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *stream)
{
  size_t retcode;
  curl_off_t nread;

  /* in real-world cases, this would probably get this data differently
     as this fread() stuff is exactly what the library already would do
     by default internally */ 
  retcode = fread(ptr, size, nmemb, stream);

  nread = (curl_off_t)retcode;

  fprintf(stderr, "*** We read %" CURL_FORMAT_CURL_OFF_T
          " bytes from file\n", nread);

  return retcode;
}

int main(int argc, char **argv)
{
  CURL *curl;
  CURLcode res;
  FILE * hd_src ;
  struct stat file_info;

  char *file;
  char *url;

  if(argc < 3)
    return 1;

  file= argv[1];
  url = argv[2];

  /* get the file size of the local file */ 
  stat(file, &file_info);

  /* get a FILE * of the same file, could also be made with
     fdopen() from the previous descriptor, but hey this is just
     an example! */ 
  hd_src = fopen(file, "rb");

  /* In windows, this will init the winsock stuff */ 
  curl_global_init(CURL_GLOBAL_ALL);

  /* get a curl handle */ 
  curl = curl_easy_init();
  if(curl) {
    /* we want to use our own read function */ 
    curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);

    /* enable uploading */ 
    curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);

    /* HTTP PUT please */ 
    curl_easy_setopt(curl, CURLOPT_PUT, 1L);

    /* specify target URL, and note that this URL should include a file
       name, not only a directory */ 
    curl_easy_setopt(curl, CURLOPT_URL, url);

    /* now specify which file to upload */ 
    curl_easy_setopt(curl, CURLOPT_READDATA, hd_src);

    /* provide the size of the upload, we specicially typecast the value
       to curl_off_t since we must be sure to use the correct data size */ 
    curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE,
                     (curl_off_t)file_info.st_size);

    /* Now run off and do what you've been told! */ 
    res = curl_easy_perform(curl);
    /* Check for errors */ 
    if(res != CURLE_OK)
      fprintf(stderr, "curl_easy_perform() failed: %s\n",
              curl_easy_strerror(res));

    /* always cleanup */ 
    curl_easy_cleanup(curl);
  }
  fclose(hd_src); /* close the local file */ 

  curl_global_cleanup();
  return 0;
}
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • The reason I did not use the example from the libcurl webpage was because I wasn't uploading a file, I was uploading raw data. What does the read_callback function exactly do? Does it simply just display what is being read by libcurl? Also when you say to "enable uploading a body with the request", is there an option available to do that? - Also, you say memcpy() inside read_callback, but what would I be copying the memory to since I already have a pointer to the memory? – jtor Apr 22 '14 at 00:48
  • @mrJTparadise: The `read_callback` is just a function that curl calls when it needs to know the data to upload, it doesn't need to come from a file. If you already have the data loaded into memory, then your `read_callback()` can be something really simple like `{ memcpy(ptr, my_payload, size*nmemb); return nmemb; }`. – Adam Rosenfield Apr 22 '14 at 01:56
  • Thank you so much for the reply and advice to help me understand these functions. I'm going to try this out tomorrow and let you know how it goes. Will also update post with a solution. – jtor Apr 22 '14 at 02:33
  • I am quite confused on this `read_callback` function. I somehow need to pass my payload to the `read_callback` function correct? In the example shown, they are calling the `read_callback` function inside `curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback)`. They are not passing the function any parameters so how can `read_callback()` see the file? In my case, I need to pass it my payload, correct? – jtor Apr 23 '14 at 16:18
  • Another thing I've been reading about `CURLOPT_READDATA Data pointer to pass to the file read function. If you use the CURLOPT_READFUNCTION option, this is the pointer you'll get as input. If you don't specify a read callback but instead rely on the default internal read function, this data must be a valid readable FILE * (cast to 'void *').` This says the parameter must be a pointer to a file. In my code, I have `curl_easy_setopt(curl, CURLOPT_READDATA, payload);` Will this suffice? - payload is just a char* – jtor Apr 23 '14 at 17:01
  • @mrJTparadise: The value passed to `curl_easy_setopt(CURLOPT_READDATA)` is just an arbitrary `void*` pointer that the library passes back to you in your read function, it can be anything you want. It only has to be a `FILE*` if you're using the *default* read function—if you're overriding the read function with `curl_easy_setopt(CURLOPT_READFUNCTION)`, then it does not need to be a `FILE*` and it can be whatever data you need. In your case, it could be a pointer to a structure containing your data buffer and the size of that buffer, for example. – Adam Rosenfield Apr 23 '14 at 21:21
  • @Adam_Rosenfield: I took your advice. I actually did override the read_callback function. The problem is, my code never enters the read_callback function. Even inside the debugger it never enters. I have even put a `printf("%s", "BLAHBLAHBLAH\n") and even fflush(stdout). – jtor Apr 25 '14 at 18:49
1

I agree, don't use CUSTOMREQUEST. One detail that is being missed on every related to PUT and CURL I've seen here is that you NEED to set the file size, otherwise you'll get HTTP error 411. Use CURLOPT_INFILESIZE or CURLOPT_INFILESIZE_LARGE for that. See more details here:

How do I send long PUT data in libcurl without using file pointers?

Community
  • 1
  • 1
eco
  • 1,254
  • 1
  • 12
  • 22
1

I know this is a very old question, but in case somebody want to use libcurl with GLib and json-glib to send a JSON with PUT request. Code below works for me:

    #include <curl/curl.h>
    #include <json-glib/json-glib.h>

    //this is callback function for CURLOPT_READFUNCTION: 

    static size_t
    curlPutJson ( void *ptr, size_t size, size_t nmemb, void *_putData )
    {
            GString *putData = ( GString * ) _putData;
            size_t realsize = ( size_t ) putData->len;
            memcpy ( ptr, putData->str, realsize );
            return realsize;
    }

   /*now inside main or other function*/

   //json_to_string ( jsonNode, FALSE ) is from json-glib to stringify JSON
   //created in jsonNode

   GString *putData = g_string_new ( json_to_string ( mainNode, FALSE ) );

   //now goes curl as usual: headers, url, other options and so on
   //and 4 most important lines

   curl_easy_setopt ( curl, CURLOPT_READFUNCTION, curlPutJson );
   curl_easy_setopt ( curl, CURLOPT_UPLOAD, 1L );

   curl_easy_setopt ( curl, CURLOPT_READDATA, putData );        //GString
   curl_easy_setopt ( curl, CURLOPT_INFILESIZE, putData->len ); //type long     
michal
  • 115
  • 6