Wednesday, November 17, 2021

[SOLVED] No XML response from cURL POST to REST API in PHP

Issue

I am sending a POST request in PHP via cURL to a REST API that uses XML. When I use Postman or Advanced REST Client, I get a XML response to my POST request. However, when I use PHP and cURL I do not seem able to see back the XML responses. What do I need to do to get these back? Eventually I need to retrieve a token that I can then use to process INSERT, UPDATES and GETS through this API via XML.

Here is the code that I am currently using:

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_HTTPHEADER => array(
    'xxxxxx-Username: xxx',
    'xxxxxx-Password: xxx',
    'content-type: application/xml'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

and currently I am getting a blank page. I have tried quite a few solutions, like the following

//header("Content-Type: text/xml");
//header('Content-type: application/xml');
//$decoded = iconv("UTF-8", "ISO-8859-1//TRANSLIT", $response);
//echo $decoded;

//echo $response;
//print_r($response);

// set up your xml result
$xml = new SimpleXMLElement($response, LIBXML_NOCDATA);

// loop through the results
$cnt = count($xml->Result);
for($i=0; $i<$cnt; $i++){
    echo 'XML : First Name: = ';
}

but nothing seems to give me back what I get from Postman or Advanced REST Client, which on this particular command is the following

<?xml version="1.0" encoding="UTF-8"?>
<AuthInfo>
    <token/>
    <AuthStatus>
        <Id>503</Id>
        <Description>There's no proapi manager running with the given company code: crmapp</Description>
    </AuthStatus>
</AuthInfo>

I understand that at this stage there is an issue with my url that I need to fix, but I still should be able to receive that error back via XML.

Can anyone please help me get this XML response back so that I can progress my interface?

Thank you in advance, Adri

Thanks again Professor, here is the full debug with the latest version of PHP and cUrl

Verbose debug info

*   Trying xxx.xx.xxx.xxx:443...
* Connected to xxxxx-xx-xx.xxxxxxxx.com.au (xxx.xx.xxx.xxx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: D:/Adri/PHP/MoW/famac/cacert.pem
*  CApath: D:/Adri/PHP/MoW/famac/cacert.pem
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=*.prontohosted.com.au
*  start date: Jun  2 00:00:00 2020 GMT
*  expire date: Sep  4 00:00:00 2022 GMT
*  subjectAltName: host "xxxxx-xx-xx.xxxxxxxx.com.au" matched cert's "*.xxxxxxxx.com.au"
*  issuer: C=GB; ST=Greater Manchester; L=Salford; O=Sectigo Limited; CN=Sectigo RSA Domain Validation Secure Server CA
*  SSL certificate verify ok.
> GET /xxxxx/rest/xxx.xxx/login HTTP/1.1
Host: xxxxx-xx-xx.xxxxxxxx.com.au
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.38 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.38
Accept: */*
Accept-Encoding: deflate, gzip
xxxxxx-Username: xxx
xxxxxx-Password: xxx
Content-Type: application/xml

* Mark bundle as not supporting multiuse
< HTTP/1.1 404 Not Found
< Date: Tue, 09 Nov 2021 11:34:57 GMT
< Server: Apache
< Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< Content-Security-Policy: img-src 'self' *.xxxxx.net *.xxxxx.com.au https://www.google.com https://*.googleapis.com/ www.google-analytics.com stats.g.doubleclick.net http://*.xxxxx-xxxxx.com *.twitter.com *.twimg.com data: blob: https://*.google.com https://*.gstatic.com https://*.googleapis.com; frame-src * blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.xxxxx.net *.xxxxx.com.au https://*.google.com www.google-analytics.com *.twitter.com *.twimg.com  https://*.googleapis.com https://jawj.github.io https://*.gstatic.com; connect-src 'self' wss: blob: *.twitter.com www.google-analytics.com stats.g.doubleclick.net; base-uri 'none'; style-src 'self' 'unsafe-inline' *.twitter.com *.twimg.com https://*.google.com *.googleapis.com https://*.gstatic.com; font-src 'self' data: https://*.googleapis.com https://fonts.gstatic.com; child-src * blob:; object-src 'none'; default-src 'self' blob:
< X-Permitted-Cross-Domain-Policies: master-only
< Content-Type: text/html; charset=UTF-8
< Content-Length: 994
* The requested URL returned error: 404
* Closing connection 0

Info

stdClass Object
(
    [url] => https://xxxxx-xx-xx.xxxxxxxx.com.au/xxxxx/rest/xxx.xxx/login
    [content_type] => text/html; charset=UTF-8
    [http_code] => 404
    [header_size] => 1271
    [request_size] => 350
    [filetime] => -1
    [ssl_verify_result] => 0
    [redirect_count] => 0
    [total_time] => 0.232624
    [namelookup_time] => 0.029367
    [connect_time] => 0.05058
    [pretransfer_time] => 0.162497
    [size_upload] => 0
    [size_download] => 0
    [speed_download] => 0
    [speed_upload] => 0
    [download_content_length] => 994
    [upload_content_length] => 0
    [starttransfer_time] => 0.232609
    [redirect_time] => 0
    [redirect_url] => 
    [primary_ip] => xxx.xx.xxx.xxx
    [certinfo] => Array
        (
        )

    [primary_port] => 443
    [local_ip] => xxx.xxx.x.xxx
    [local_port] => 52711
    [http_version] => 2
    [protocol] => 2
    [ssl_verifyresult] => 0
    [scheme] => HTTPS
    [appconnect_time_us] => 162464
    [connect_time_us] => 50580
    [namelookup_time_us] => 29367
    [pretransfer_time_us] => 162497
    [redirect_time_us] => 0
    [starttransfer_time_us] => 232609
    [total_time_us] => 232624
)

Can you please let me know what you think of this? While I am no longer getting the previous error, I still seem unable to receive the XML response back. :(

Thank you in advance, Adri


Solution

The curl function I use is as follows. It has extra debugging information in the output and the default settings can be easily overridden at runtime by supplying a different $options argument. I'm not suggesting this is the answer but with a better set of options configured and better debug info you should get closer.

function curl( $url=NULL, $options=NULL, $headers=false ){
    $cacert='c:/wwwroot/cacert.pem';
    $vbh = fopen('php://temp', 'w+');
    /*
        Download a copy of CACERT.pem from
        https://curl.haxx.se/docs/caextract.html
        
        save to webserver and modify the $cacert variable
        to suit - ensuring that the path you choose is
        readable.
    */
    
    $res=array(
        'response'  =>  NULL,
        'info'      =>  array( 'http_code' => 100 ),
        'headers'   =>  NULL,
        'errors'    =>  NULL
    );
    if( is_null( $url ) ) return (object)$res;

    session_write_close();

    /* Initialise curl request object - these should be OK as-is */
    $curl=curl_init();
    if( parse_url( $url,PHP_URL_SCHEME )=='https' ){
        curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, true );
        curl_setopt( $curl, CURLOPT_SSL_VERIFYHOST, 2 );
        curl_setopt( $curl, CURLOPT_CAINFO, $cacert );
        curl_setopt( $curl, CURLOPT_CAPATH, $cacert );
    }

    /* Define standard options */
    curl_setopt( $curl, CURLOPT_URL,trim( $url ) );
    curl_setopt( $curl, CURLOPT_AUTOREFERER, true );
    curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, true );
    curl_setopt( $curl, CURLOPT_FAILONERROR, true );
    curl_setopt( $curl, CURLOPT_HEADER, false );
    curl_setopt( $curl, CURLINFO_HEADER_OUT, false );
    curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
    curl_setopt( $curl, CURLOPT_BINARYTRANSFER, true );
    curl_setopt( $curl, CURLOPT_CONNECTTIMEOUT, 20 );
    curl_setopt( $curl, CURLOPT_TIMEOUT, 60 );
    curl_setopt( $curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.38 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.38' );
    curl_setopt( $curl, CURLOPT_MAXREDIRS, 10 );
    curl_setopt( $curl, CURLOPT_ENCODING, '' );
    
    /* enhanced debug */
    curl_setopt( $curl, CURLOPT_VERBOSE, true );
    curl_setopt( $curl, CURLOPT_NOPROGRESS, true );
    curl_setopt( $curl, CURLOPT_STDERR, $vbh );
    

    /* Assign runtime parameters as options to override defaults if needed. */
    if( isset( $options ) && is_array( $options ) ){
        foreach( $options as $param => $value ) curl_setopt( $curl, $param, $value );
    }
    /* send any headers with the request that are needed */
    if( $headers && is_array( $headers ) ){
        curl_setopt( $curl, CURLOPT_HTTPHEADER, $headers );
    }

    /* Execute the request and store responses */
    $res=(object)array(
        'response'  =>  curl_exec( $curl ),
        'info'      =>  (object)curl_getinfo( $curl ),
        'errors'    =>  curl_error( $curl )
    );
    rewind( $vbh );
    $res->verbose=stream_get_contents( $vbh );
    fclose( $vbh );
    curl_close( $curl );
    return $res;
}

Then, to use it:

$url='https://www.example.com/api/';
$args=array();
$headers=array(
    'xxxxxx-Username: xxx',
    'xxxxxx-Password: xxx',
    'Content-Type: application/xml'
);

$res=curl( $url, $args, $headers );
if( $res->info->http_code==200 ){
    #cool - use $res->response in further processing
    print_r($res->response,true);
}else{
    # useful information will be displayed here...
    printf('<h1>Verbose debug info</h1><pre>%s</pre>',print_r($res->verbose,true));
    printf('<h1>Info</h1><pre>%s</pre>',print_r($res->info,true));
}


Answered By - Professor Abronsius