Asked  7 Months ago    Answers:  5   Viewed   203 times

I've got a simple PHP script that sends an HTTP POST request via cURL, and expects a json string in response (would have loved to use an existing library like pecl_http/HTTPRequest for this, but can't). The call consistently fails with a 415 error - Unsupported Media Type. I think I'm not configuring cURL correctly, but after much searching, I can't find out what I'm doing wrong. Here's some code:

class URLRequest
{
    public $url;
    public $headers;
    public $params;
    public $body;
    public $expectedFormat;
    public $method;

    public function URLRequest($aUrl, array $aHeaders, array $aParams, $aFormat = "json", $isPost = false, $aBody = "+")
    {
        $this->url = $aUrl;
        $this->headers = $aHeaders;
        $this->params = $aParams;
        $this->expectedFormat = $aFormat;
        $this->method = ($isPost ? "POST" : "GET");
        $this->body = $aBody;

    }

    public function exec()
    {

        $queryStr = "?";
        foreach($this->params as $key=>$val)
            $queryStr .= $key . "=" . $val . "&";

        //trim the last '&'
        $queryStr = rtrim($queryStr, "&");

        $url = $this->url . $queryStr;

        $request = curl_init();
        curl_setopt($request, CURLOPT_URL, $url);
        curl_setopt($request, CURLOPT_HEADER, 1);
        curl_setopt($request, CURLOPT_HTTPHEADER, $this->headers);
        curl_setopt($request, CURLOPT_RETURNTRANSFER, 1);
        //curl_setopt($request, CURLOPT_SSL_VERIFYPEER, false);

        if($this->method == "POST")
        {
            curl_setopt($request, CURLOPT_POST, 1);
            curl_setopt($request, CURLOPT_POSTFIELDS, $this->body);

            //this prevents an additions code 100 from getting returned
            //found it in some forum - seems kind of hacky
            curl_setopt($request, CURLOPT_HTTPHEADER, array("Expect:"));
        }

        $response = curl_exec($request);
        curl_close($request);

        preg_match("%(?<=HTTP/[0-9].[0-9] )[0-9]+%", $response, $code);

        $resp = "";
        if($this->expectedFormat == "json")
        {
            //parse response
        }
        elseif($this->expectedFormat == "xml")
        {
            //parse response
        }

        return $resp;

    }
}


$url = "http://mydomain.com/myrestcall";

$query = array( "arg_1" =>      "test001",
                "arg_2" =>      "test002",
                "arg_3" =>      "test003");

$headers = array(    "Accept-Encoding" =>    "gzip",
                    "Content-Type" =>       "application/json",
                    "Accept" =>             "application/json",
                    "custom_header_1" =>    "test011",
                    "custom_header_2" =>    "test012",
                    "custom_header_3" =>    "test013");

$body = array(  "body_arg_1" =>      "test021",
                "body_arg_2" =>     array("test022", "test023"), 
                "body_arg_3" =>     "test024"); 


$request = new URLRequest($url, $headers, $query, "json", true, $body);

$response = $request->exec();

...and the response:

HTTP/1.1 415 Unsupported Media Type
Server: Apache-Coyote/1.1
X-Powered-By: Servlet 2.5; JBoss-5.0/JBossWeb-2.1
Content-Type: text/html;charset=utf-8
Content-Length: 1047
Date: Mon, 18 Jun 2012 16:30:44 GMT

<html><head><title>JBoss Web/2.1.3.GA - Error report</title></head><body><h1>HTTP Status 415 - </h1><p><b>type</b> Status report</p><p><b>message</b> <u></u></p><p><b>description</b> <u>The server refused this request because the request entity is in a format not supported by the requested resource for the requested method ().</u></p><h3>JBoss Web/2.1.3.GA</h3></body></html>

Any insights or ideas?

Thanks in advance!

 Answers

88

Problem solved! Here's the issue:

Sending an associative-array of headers DOES NOT WORK with cURL. There are several forums scattered around that show examples using an associative array for headers. DON'T DO IT!

The correct way (which is also scattered around the internets, but that I'm too dense to have noticed) is to construct your header key/value pairs as strings, and pass a standard array of these strings when setting the CURLOPT_HTTPHEADER option.

So in summary,

WRONG:

$headers = array(    "Accept-Encoding" =>    "gzip",
                     "Content-Type" =>       "application/json",
                     "custom_header_1" =>    "test011",
                     "custom_header_2" =>    "test012",
                     "custom_header_3" =>    "test013");

RIGHT:

$headers = array(    "Accept-Encoding: gzip",
                     "Content-Type: application/json",
                     "custom_header_1: test011",
                     "custom_header_2: test012",
                     "custom_header_3: test013");

I hope this comes in handy to some other noble doofus down the road before they waste as much time debugging as I did.

If I had to guess, I would assume that the same rule applies to the POST body key/value pairs as well, which is why @drew010 's comment about using http_build_query() or json_encode() to stringify your message body is a great idea as well.

Thanks to everyone for your very useful comments, and for you time and consideration. In the end, a side by side comparison of the http traffic (captured via Wireshark) revealed the issue.

Thanks!

Wednesday, March 31, 2021
 
Zigglzworth
answered 7 Months ago
50

PUT

$data = array('username'=>'dog','password'=>'tall');
$data_json = json_encode($data);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json','Content-Length: ' . strlen($data_json)));
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS,$data_json);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response  = curl_exec($ch);
curl_close($ch);

POST

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,$data_json);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response  = curl_exec($ch);
curl_close($ch);

GET See @Dan H answer

DELETE

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_POSTFIELDS,$data_json);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response  = curl_exec($ch);
curl_close($ch);
Wednesday, March 31, 2021
 
xenon
answered 7 Months ago
96

Use CURLOPT_CUSTOMREQUEST instead of CURLOPT_POST like;

curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");

And one more thhing, you need to add

curl_setopt($curl, CURLOPT_POSTREDIR, 3);

3 means follow redirect with the same type of request both for 301 and 302 redirects.

By doing this, second request will be as POST request as well. Note that, CURLOPT_POSTREDIR implemented in PHP 5.3.2 here

Wednesday, March 31, 2021
 
Octopus
answered 7 Months ago
42

I managed out how to make it works. Tell me in case I am wrong. I used only one way to serialize/deserialize: I removed all annotations regarding this (@JSONSerialize and @JSONDeserialize) and registered Serializers and Deserializers in CustomObjectMapper class. I didn't find an article explaining this behaviour but I resolved in this way. Hope it's useful.

Tuesday, June 1, 2021
 
williamcarswell
answered 5 Months ago
40

The Jersey distribution doesn't come with JSON/POJO support out the box. You need to add the dependencies/jars.

Add all these

  • jersey-media-json-jackson-2.17
  • jackson-jaxrs-json-provider-2.3.2
  • jackson-core-2.3.2
  • jackson-databind-2.3.2
  • jackson-annotations-2.3.2
  • jackson-jaxrs-base-2.3.2
  • jackson-module-jaxb-annotations-2.3.2
  • jersey-entity-filtering-2.17

With Maven, below will pull all the above in

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.17</version>
</dependency>

For any future readers not using Jersey 2.17 (and using jars directly instead of Maven), you can go here to find the Jersey version you are using, and see what transitive dependency versions you need. The current version of this Jersey dependency uses Jackson 2.3.2. That's the main thing you need to look out for.

Friday, June 4, 2021
 
julesj
answered 5 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :