6

I am attempting to code an API call to a online testing company. They have provided a sample call in PHP and cURL that I need to implement in ColdFusion 11 using <CFHTTP>. So far my attempt has failed. The only response I get from their server / API is:

Statuscode = "Connection Failure. Status code unavailable. "

and

ErrorDetail = "I/O Exception: Remote host closed connection during handshake".

If it were working I would get a JSON string detailing computed scores. Note that in the code below I've changed a few values for security reasons, other than that it's the original code. Any suggestions or comments would be much appreciated, thanks.

Here is the ColdFusion/cfhttp code:

<cfoutput>
<cfset sdata = [
    {
        "customerid" = "ACompany",
        "studentid" = "test",
        "form" = "X",
        "age" = "18.10",
        "norms" = "grade",
        "grade" = "2"
    },
    {
        "scores" = [
        {"subtest"="math","score"="34"},
        {"score"="23","subtest"="lang"},
        {"score"="402","subtest"="rcomp"}
        ]
    }

]>
<!--- create JSON string for request --->
<cfset jsdata = serializeJSON(sdata)>
<!--- make the call --->
<cfhttp method="Get" url="https://www.APIwebsite.php" timeout="10" result="varx">
     <cfhttpparam type="header" name="Content-Type" value = "application/json; charset=utf-8"/>
     <cfhttpparam type="body" value = "#jsdata#"/>
     <cfhttpparam type="header" name="Authorization" value="AuthCode"/> 
     <cfhttpparam type="header" name="Content-Length" value = "#len(jsdata)#"/>
</cfhttp>

<!--- show results --->
cfhttp return status code: [#varx.statusCode#]<br> 
cfhttp return fileContent: [#varx.fileContent#]<br>
</cfoutput>

Here is the PHP/cURL code:

<?php
    $data = array
    (
    "customerid" => "ACompany",
    "studentid" => "test",
    "scoringtype" => 2,
    "form" => "X",
    "age" => "18.10",
    "norms" => 'grade',
    "grade" => '2',
    "scores" => array(
        array("subtest" => "math", "score" => "34"),
        array("subtest" => "lang", "score" => "23"),
        array("subtest" => "rcomp", "score" => "402")
    ));

    $url = 'https://www.APIwebsite.php';
    $json_string = json_encode($data);

   $headers = array (
        "Content-Type: application/json; charset=utf-8",
        "Content-Length: " .strlen($json_string),
        "Authorization: AuthCode"
    );

    $channel = curl_init($url);
    curl_setopt($channel, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($channel, CURLOPT_CUSTOMREQUEST, "GET");
    curl_setopt($channel, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($channel, CURLOPT_POSTFIELDS, $json_string);
    curl_setopt($channel, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($channel, CURLOPT_CONNECTTIMEOUT, 10);

    $response = curl_exec($channel); // execute the request
    $statusCode = curl_getInfo($channel, CURLINFO_HTTP_CODE);
    $error = curl_error($channel);
    curl_close($channel);

    http_response_code($statusCode);
    if ( $statusCode != 200 ){
        echo "Status code: {$statusCode} \n".$error;
    } else {
        $data = json_decode($response,true);
        foreach ($data as $key => $value) {
            echo nl2br($key . ': ' . $value . "\n");
        }
    }
?>
Miguel-F
  • 13,450
  • 6
  • 38
  • 63
LocoRolly
  • 61
  • 1
  • It may or may not be relavent to the problem, but the CF Code does not include a scoringtype value. – Dan Bracuk Nov 25 '19 at 19:32
  • Thanks for the suggestion, Dan. I'm not sure what you mean by scoringtype. Does something like that appear in the cURL code or do you think it needs adding to the cf code to be equivalent? – LocoRolly Nov 25 '19 at 19:58
  • Also your method is wrong, either send as post in the body or as get in the url, right now you are sending as get in the body which doesn't make sense If the api supports post, I'd suggest using post else you might end up with issue about the url being too long – Tofandel Nov 25 '19 at 20:14
  • @LocoRolly He just meant that in the example you posted, in curl you have scoringtype in your payload but not in cfhttp – Tofandel Nov 25 '19 at 20:16
  • 2
    What url are you using? It is likely the issue for this particular error.. Is the ssl certificate valid? I see you have ssl verifypeer set to false in curl but cfhttp does not have such feature – Tofandel Nov 25 '19 at 20:20
  • Thanks Tofandel for all your comments. In the curl code I have a curlopt_customrequest, "Get" but the payload in curlopt_postfields, $json_string. They seem to me contradictory, but the person I'm liaising with at the company says it's a GET. So I'm confused. I know I don't have an equivalent cfhttpparam to the verifypeer, do you know what it would be? – LocoRolly Nov 25 '19 at 22:11
  • I also believe it might be a problem at their server since my call doesn't seem to make it to the API. When I simply put the url into the browser with no data or headers I do get a response from the API. It's an error response, of course, but at least the server didn't block the call – LocoRolly Nov 25 '19 at 22:13
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/203094/discussion-between-tofandel-and-locorolly). – Tofandel Nov 26 '19 at 10:27

1 Answers1

2

First make sure the url you provided is correct (I know it's for the example but .php is not a valid domain name extension), and that the SSL certificate is valid

If both are correct, you should change the request method to POST for sending json data through the body

According to the http semantics

A client should not generate a body in a GET request. A payload received in a GET request has no defined semantics, cannot alter the meaning or target of the request, and might lead some implementations to reject the request and close the connection because of its potential as a request smuggling attack

There is a charset parameter in cfhttp so you don't need to send it in the header

Here is a code that should work

<cfset sdata = [
    {
        "customerid" = "ACompany",
        "studentid" = "test",
        "form" = "X",
        "age" = "18.10",
        "scoringtype" = 2,
        "norms" = "grade",
        "grade" = "2"
    },
    {
        "scores" = [
            {"subtest"="math","score"="34"},
            {"score"="23","subtest"="lang"},
            {"score"="402","subtest"="rcomp"}

        ]
    }

]>
<!--- create JSON string for request --->
<cfset jsdata = serializeJSON(sdata)>
<!--- make the call --->
<cfhttp method="post" charset="utf-8" url="https://api.website.com/" timeout="10" result="varx">
     <cfhttpparam type="header" name="Content-Type" value="application/json"/>
     <cfhttpparam type="header" name="Authorization" value="AuthCode"/> 
     <cfhttpparam type="header" name="Content-Length" value="#len(jsdata)#"/>
     <cfhttpparam type="body" value="#jsdata#"/>
</cfhttp>

<!--- show results --->
<cfoutput>
cfhttp return status code: [#varx.statusCode#]<br>
cfhttp return fileContent: [#varx.fileContent#]<br>
</cfoutput>
Tofandel
  • 3,006
  • 1
  • 29
  • 48
  • Thanks again for all your effort, much appreciated. I tried the code you suggested, with the same result. I confess however to being pretty ignorant re. SSL certificates. How might I know that's a problem? Maybe I should just educate myself some more about them so a pointer or link? Thanks – LocoRolly Nov 25 '19 at 22:30
  • If you can access the url with HTTPS without getting a warning about security, then there likely isn't an issue and the ssl verifypeer is not needed – Tofandel Nov 26 '19 at 11:33
  • Thanks. I did some more testing and found that although the API responds directly to a hyperlink in a template or the browser url window (with an error of course), I get a connection failure when attempting the same simple link via cfhttp. Whatever else is wrong with my code, this seems a problem in itself. I've seen posts about this that revolve around security certifications. Any comments or suggestions welcome. Also I want to say this has been very helpful. Even though I've not yet resolved the problem I've received confirmation that my code is at least syntactically correct. Thanks – LocoRolly Nov 26 '19 at 21:49
  • Did you try with an url like google.com? Just to make sure it's not a problem on your server – Tofandel Nov 27 '19 at 12:15
  • Thanks, I'm back. Actually I have a server on my own machine for development purposes, non ssl. I then have two remote sites on the same server where the app is used, one for testing, one for production. So I have a test bed to craft an api call and get a response from an ssl server, which I've done. At this point I'm pretty convinced there's some setting or protocol at the target company side that is rejecting my call before it gets processed by their API. There may be other problems but that needs addressing first and I need input from them to proceed. – LocoRolly Nov 30 '19 at 23:51