Question

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);
}
Was it helpful?

Solution

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;
}

OTHER TIPS

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?

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     
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top