tags:

views:

42

answers:

3

I am sending a POST request to a URL. It works correctly in python but in php-curl, I always get a bad request error (i.e. my POST data is not as expected by the server)

Python code: (works correctly. 200 OK)

import httplib, urllib, json

def SendRequest(param1):
    url = "api.xyz.com"
    body = {"version": "1.0", "data":[{"param1":param1, "age":35}]}
    jsonBody = json.dumps(body)

    headers = {"Content-type": "application/json",
               "Accept": "application/json; charset=utf8"}
    conn = httplib.HTTPConnection(url)
    conn.request("POST", "/api/?client_id=xx-xx-xx", jsonBody, headers)
    response = conn.getresponse()

php-cURL code (does not work. 406 Bad request error)

function sendCurlRequest($param1)
{
    $payload = preparePayload($param1); 
    $url = "http://api.xyz.com/api/?client_id=xx-xx-xx"; 
    $jsonResponse = executePOSTRequest($url, $payload);
    $response = json_decode($jsonResponse);
}

function preparePayload($param1)
{
    $payload = array("version" = "1.0",
                     "data" => array(array("param1" => $param1,
                                           "age" => 35
                                          )
                                    ),
                    );

    $jsonPayload = json_encode($payload);
    return $jsonPayload;
}

function executePOSTRequest($url, $payload)
{
    $newlines = array("\r", "\n", " ");
    $url = str_replace($newlines, '', $url);
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt ($ch, CURLOPT_HTTPHEADERS,array('Content-Type:application/json; Accept: application/json; charset=utf8'));

    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $result = curl_exec($ch);
}

I know there is some mistake in my use of cURL. Please suggest.

Update: Use of double array in php is because server expects the JSON string (POST payload) in this format

{
"version": "1.0",
"data":
[{"param1":name, "age":35},{"param1":name,"age":60}]
}

In python, I am using list of dict; in php I think it is array of arrays only.

+1  A: 

In a case like this, I find that it's always better to look at what goes over the wire instead of trying to puzzle out what might be wrong with the code.

My advice:

  • Create a little test server using the python BaseHTTPServer and derive a class from BaseHTTPRequestHandler that doesn't do anything but provide an override of the do_POST method

  • Your do_POST should just dump the headers and POST body it receives to file

  • Point your code samples at this little test server and then diff the files that it creates. I'm guessing that if the answer isn't immediately obvious, you'll at the very least get one or more useful clues to stomp this problem out.

bgporter
Thanks for the sound advice. It will be of great help in future problems as well. Finally, I did a POST to local server through php and python and compared the differences in headers and data. I was making some mistakes in using curl. Thanks.
vivek.m
Excellent! Glad to be able to help.
bgporter
+1  A: 

I'm not sure if it causes your problem, but I do see a difference between the two: In the python code you send two header lines: Content-Type and Accept. While in the PHP code, they are a single line.

Michielvv
no, that does not cause a problem. I have wrestled with curl for quite a long time. I have tried sending Content-Type and Accept as two separate lines. No difference.
vivek.m
Ok, are you also aware that the option should be named: CURLOPT_HTTPHEADER without the S at the end? So: curl_setopt ($ch, CURLOPT_HTTPHEADER,array('Content-Type:application/json;','Accept: application/json; charset=utf8'));
Michielvv
Hey, Michielvv, nice catch. I did not notice the extra 'S'. When I did a POST to local server, only then I saw it in apache error log. Thanks. :-)Also, as you said, Content-Type and Accept should be different elements in array. Merging them as a single line is WRONG. Thanks.
vivek.m
+1  A: 

You are missing the parameter index - cURL POST fields need to be sent as a query string or as an array.

Either:

curl_setopt($ch, CURLOPT_POSTFIELDS, 'payload=' . urlencode($payload));

Or

curl_setopt($ch, CURLOPT_POSTFIELDS, array('payload' => $payload));

Replace payload with the name of the parameter expected by the API to hold the JSON string.

If the endpoint API is not expecting a parameter, then it should work - it just won't appear in the POST array in your test script. You can fetch the body of the POST contents using

$data = file_get_contents("php://input");
var_dump($data);
Eran Galperin
yes, I have got your point. But in python, I am not using any parameter to hold JSON string. Also, the API docs do not mention any such parameter. All it mentions is "format the request as JSON string and submit it as POST data to given URL". Is this cURL limitation?
vivek.m
In that case, it should've worked as you've written it before. Did you try it with the actual API or just with your testing script? I added a way for you to test direct POST content with the $_POST array
Eran Galperin
yes, Eran. thanks. I was following the advice of bgporter to post on local server through php and python and check the difference between the two. That's how I got into empty _POST problem. http://stackoverflow.com/questions/4060345/post-is-empty-in-php
vivek.m
My API is working fine now. I was checking response header through get_header($url, 1) php function. And it was always returning 406 error. The correct way is to use "curl_getinfo($ch)". It gives 200 ok. Another mistake was as pointed by Michielvv. (using CURLOPT_HTTPHEADER instead of HEADERS :) )
vivek.m