tags:

views:

105

answers:

2

Is it possible to upload files with PHP and SOAP?

Here is the PHP code being used:

$post = "a.txt";

$fp = fopen($post, "r");

$client = new SoapClient("http://api.4shared.com/servlet/services/DesktopApp?wsdl");

$id = $client->createUploadSessionKey("user","pass",-1);

$new_id = $client->uploadStartFile("user","pass",-1, "name", 500*1024);         
$dcId = $client->getNewFileDataCenter("user","pass");
$sessKey = $client->createUploadSessionKey("user","pass", -1);
$upload = $client->getUploadFormUrl($dcId, $sessKey);

$res = $client->uploadFinishFile("user","pass", $new_id, $fp);
+1  A: 

Yes, it's possible.

What have you tried so far? Are you using a soap library or did you roll your own? Have you experienced any problems getting file transfer to work? If so what did you try to do and what happened when you tried to do it?

edit - Inspecting the HTTP traffic can be a useful way to debug SOAP servers. Also if the client you are using is flat-out incompatible with a server, you may have to find another client or "roll your own." I wrote a very simple client which works well with the SOAP servers generated by MS frameworks, it may work well with Java servers too.

If you do end up needing to create your own client, this may help get you started:


SimpleSoapClient.php

Extend this with your own class. See below...

<?php

/**
 * Simple Soap Client
 *
 * Override this class to create a SOAP client.
 *
 * </pre>
 **/
class SimpleSoapClient
{
  protected $host;
  protected $port;
  protected $ns;
  protected $url;
  protected $act;
  protected $debug;

  protected function Post($method, $params)
  {
    return $this->_Post($method, $params);
  }

  protected function _Post($method, $params)
  {

    $namespaces = array();
    foreach($params as $p)
    {
      if (isset($p->ns))
      {
        if ($namespaces[$p->ns])
          $p->prefix = $namespaces[$p->ns];
        else
          $p->prefix = $namespaces[$p->ns] = 'ns'.count($namespaces);
      }
    }   

    if ($this->debug)
    {
      $cn = get_class($this);
      echo "\n   ======   Calling $cn::$method   ======   \n\nParams: ";
      print_r($params);
    }

    $host = $this->host;
    $port = $this->port;
    $ns   = $this->ns;
    $url  = $this->url;
    $act  = $this->act;

    $fp = fsockopen($host, $port, $errno, $errstr, 30);
    if (!$fp)
      die ("Oops: $errstr ($errno)<br />\n");

    $xml  = "<?xml version=\"1.0\" encoding=\"utf-8\"?><s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"";
    foreach($namespaces as $k=>$v)
      $xml .= " xmlns:$v=\"$k\"";
    $xml .= "><s:Body><$method xmlns=\"$ns\">";
    foreach($params as $k=>$v) 
      $xml .= "<$k>$v</$k>";
    $xml .= "</$method></s:Body></s:Envelope>";
    $head = "POST $url HTTP/1.1\r\n"
          . "Host: $host\r\n"
          . "Content-Type: text/xml; charset=utf-8\r\n"
          . "Content-Length: ".strlen($xml)."\r\n"
          . "SOAPAction: \"$act$method\"\r\n"
          . "Connection: Close\r\n\r\n";

    if ($this->debug)
      echo "\nRequest:\n\n$head$xml\n\n";

    $s;
    fwrite($fp, $head.$xml);
    while (!feof($fp)) 
      $s .= fgets($fp);
    fclose($fp);
    $s = trim(substr($s,strpos($s, "\r\n\r\n")));

    if ($this->debug)
      echo "Response:\n\n$s\n\n";

    if (strstr($s,'<error_message>'))
      die("\nError communicating with SOAP server.\n");

    return($this->xml2assoc($s));
  }

  private function xml2assoc($xmlstring) 
  {
    $xml;
    if (is_object($xmlstring))
      $xml = $xmlstring;
    else
    {
      $xml = new XMLReader();
      $xml->xml($xmlstring);
    }

    $tree = null;
    while($xml->read())
    {
      switch ($xml->nodeType) 
      {
        case XMLReader::END_ELEMENT: return $tree;
        case XMLReader::ELEMENT:
          $node = array('tag' => $xml->name, 
            'value' => $xml->isEmptyElement ? '' : $this->xml2assoc($xml));
          if($xml->hasAttributes)
            while($xml->moveToNextAttribute())
              $node['attributes'][$xml->name] = $xml->value;
          $tree[] = $node;
          break;
        case XMLReader::TEXT:
        case XMLReader::CDATA:
          $tree .= $xml->value;
      }
    }
    // if ($this->debug) { echo "\nTREE:\n"; print_r($tree); }
    return  $tree;
  }  

  public function DateFormat($date=null)
  {
    if (is_string($date))
      $date = new DateTime($date);
    return implode('-',array_slice(split('-',$date ? $date->format('c') : date('c')), 0, 3));
  }

}


class SimpleSoapType
{
  public $prefix;
  public $type;
  public $value;
  public $ns;
  function __construct($value)
  {
    $this->value  = $value;
  }
  function __toString()
  {
    $t = (isset($this->prefix) ? $this->prefix.':' : '').$this->type; 
    $st = "<$t>"; $et = "</$t>";
    if (is_array($this->value))
      foreach ($this->value as $v)
        $r .= $st.$v.$et;
    else
      $r = $st.$this->value.$et;
    return $r;
  }
  protected function init() { throw('init is abstract'); }
}

?>

ExampleSoapClient.php

This is actually a production[1] soap client renamed to 'Example'.

<?php

require_once 'SimpleSoapClient.php';

/**
 * Example Soap Client
 **/
class ExampleSoapClient extends SimpleSoapClient
{

  function __construct()
  {
    $this->host = 'connect.example.com';
    $this->port = 80;
    $this->ns   = "https://{$this-&gt;host}/connect";
    $this->url  = "http://{$this-&gt;host}/svc/connect.svc";
    $this->act  = "{$this->ns}/IConnect/";
    $this->debug = true;
  }

  protected function Post ($method, $params) {
    $params['apiKey'] = 'abcdef1234567890';
    return $this->_Post($method, $params);
  }

  private function returnMulti($d)
  {
    foreach($d[0]['value'][0]['value'][0]['value'][0]['value'] as $v)
      $r[] = $v['value'];
    return $r;
  }

  private function returnSingle($d)
  {
    $r =  $d[0]['value'][0]['value'][0]['value'][0]['value'];
    return $r;
  }

  private function returnMultiPairs($d)
  {
    $d = $this->returnMulti($d);
    foreach ($d as $v)
      $r[$v[0]['value']] = $v[1]['value'];
    return $r;
  }

 /**
  * Get Property Categories
  *
  *
  **/
  public function GetPropertyCategories()
  {
    $d = $this->Post(__FUNCTION__, get_defined_vars());
    return $this->returnMulti($d);
  }

 /**
  * Get Property IDs (undocumented)
  *
  * @param  dateTime  $lastMod    Last modified date
  *
  **/
  public function GetPropertyIDs($lastMod)
  {
    $lastMod = $this->DateFormat($lastMod);
    $d = $this->Post(__FUNCTION__, get_defined_vars());
    return $this->returnMulti($d);
  }

 /**
  * Get Property (undocumented)
  *
  * @param  string  $propertyID     Property ID
  *
  **/
  public function GetProperty($propertyID)
  {
    $d = $this->Post(__FUNCTION__, get_defined_vars());
    return $this->returnSingle($d);
  }

 /**
  * Get Property IDs by Category
  *
  * @param  int     $propertyCategory     Property category to get IDs for
  *
  **/
  public function GetPropertyIDsByCategory($propertyCategory)
  {
    $d = $this->Post(__FUNCTION__, get_defined_vars());
    return $this->returnMulti($d);
  }

 /**
  * Get Rates
  *
  * @param  int     $propertyID     Property ID to get rates for
  * @param  string  $rateType       Currently unused
  * @param  int     $los            Length of stay - 1 (daily), 7 (weekly), or 30 (monthly) 
  * @param  string  $startDate      Beginning of period to retrieve data for
  * @param  string  $endDate        End of period to retrieve data for
  * @param  string  $currency       Currently 'USD' only
  *
  **/
  public function GetRates($propertyID, $rateType, $los, $startDate, $endDate, $currency)
  {
    $startDate = $this->DateFormat($startDate);
    $endDate = $this->DateFormat($endDate);
    $d = $this->Post(__FUNCTION__, get_defined_vars());
    return $this->returnMultiPairs($d);
  }

 /**
  * Get Availability
  *
  * @param  int     $propertyID     Property ID to get availability for
  * @param  string  $rateType       Currently unused
  * @param  string  $startDate      Beginning of period to retrieve data for
  * @param  string  $endDate        End of period to retrieve data for
  *
  **/
  public function GetAvailability($propertyID, $rateType, $startDate, $endDate)
  {
    $startDate = $this->DateFormat($startDate);
    $endDate = $this->DateFormat($endDate);
    $d = $this->Post(__FUNCTION__, get_defined_vars());
    return $this->returnMultiPairs($d);
  }

 /**
  * Set Rates
  *
  * @param  int               $propertyID     Property ID to set rates for
  * @param  string            $rateType       Currently unused
  * @param  int               $los            Length of stay - 1 (daily), 7 (weekly), or 30 (monthly) 
  * @param  array             $effDates       Effective dates
  * @param  array             $rates          Rate for each date
  * @param  string            $currency       Currently 'USD' only
  *
  **/
  public function SetRates($propertyID, $rateType, $los, $effDates, $rates, $currency)
  {
    if (!get_class($effDates) == 'msDateTime')
      $effDates = new msDateTime($effDates);
    if (!get_class($rates) == 'msDecimal')
      $rates = new msDecimal($rates);
    $d = $this->Post(__FUNCTION__, get_defined_vars());
    return $d;
  }

 /**
  * Set Availability
  *
  * @param  int               $propertyID     Property ID to set availability for
  * @param  array             $effDates       Effective dates
  * @param  array             $numAvailabile  Available units for each date [sic]
  *
  **/
  public function SetAvailability($propertyID, $effDates, $numAvailabile) // notice spelling: numAvailabile
  {
    if (!get_class($effDates) == 'msDateTime')
      $effDates = new msDateTime($effDates);
    if (!get_class($numAvailabile) == 'msInt')
      $numAvailabile = new msInt($numAvailabile);
    $d = $this->Post(__FUNCTION__, get_defined_vars());
    return $d;
  }

 /**
  * Set Rates and Availability
  *
  * @param  int               $propertyID     Property ID to set rates and availability for
  * @param  string            $rateType       Currently unused
  * @param  int               $los            Length of stay - 1 (daily), 7 (weekly), or 30 (monthly) 
  * @param  array             $effDates       Effective dates
  * @param  array             $rates          Rate for each date
  * @param  string            $currency       Currently 'USD' only
  * @param  array             $numAvailabile  Available units for each date [sic]
  *
  **/
  public function SetRatesAndAvailability($propertyID, $rateType, $los, $effDates, $rates, $currency, $numAvailabile)
  {
    if (!get_class($effDates) == 'msDateTime')
      $effDates = new msDateTime($effDates);
    if (!get_class($rates) == 'msDecimal')
      $rates = new msDecimal($rates);
    if (!get_class($numAvailabile) == 'msInt')
      $numAvailabile = new msInt($numAvailabile);
    $d = $this->Post(__FUNCTION__, get_defined_vars());
    return $d;
  }

 /**
  * Get Booking
  *
  * @param  int       $bookingID      ID of Booking to retrieve
  *
  **/
  public function GetBooking($bookingID)
  {
    $d = $this->Post(__FUNCTION__, get_defined_vars());
    return $this->returnSingle($d);
  }

 /**
  * Make Booking
  *
  * @param  bcBooking   $booking        Booking object
  * @param  bool        $infoOnly       If true, simulate booking without actually booking anything
  *
  **/
  public function MakeBooking($booking, $infoOnly)
  {
    $d = $this->Post(__FUNCTION__, get_defined_vars());
    return $d; // $this->returnMulti($d);
  }

}


/**
 * base soap type - MS array serialization 
 **/
class msSoapType extends SimpleSoapType
{
  function __construct($value)
  {
    $this->ns = 'http://schemas.microsoft.com/2003/10/Serialization/Arrays';
    parent::__construct($value);
  }
}

/**
 * dateTime soap type - MS array serialization 
 **/
class msDateTime extends msSoapType
{
  function __construct($value)
  {
    $this->type   = 'dateTime';
    parent::__construct($value);
    if (is_array($value))
      foreach ($value as $k=>$v)
        $this->value[$k] = SimpleSoapClient::DateFormat($v);
    else
      $this->value = SimpleSoapClient::DateFormat($value);
  }
}

/**
 * decimal soap type - MS array serialization 
 **/
class msDecimal extends msSoapType
{
  function __construct($value)
  {
    $this->type   = 'decimal';
    parent::__construct($value);
  }
}

/**
 * int soap type - MS array serialization 
 **/
class msInt extends msSoapType
{
  function __construct($value)
  {
    $this->type   = 'int';
    parent::__construct($value);
  }
}

?>

[1] - May not look production quality, but I'm using this and others like it on some cron jobs and PHP sites and it's working well :)

no
http://help.4shared.com/index.php/SOAP_API Here is an example of java, but I could not add files to the port to the php. java code: http://4shared-api.googlecode.com/svn/trunk/java/demo/src/com/pmstation/api/demo/UploadDemo.java
Hajimba
Can you post the code for your PHP port? Also have you looked at this: http://php.net/manual/en/book.soap.php
no
ok. there my php code but How will you add files, java examples in a way not defined in the wsdl are doing it.http://pastebin.com/H1R78AZY
Hajimba
Hmm. Take a look here, it looks like PHP SoapClient and java SOAP server don't get along when uploading files: http://www.php.net/manual/en/book.soap.php#84977
no
I don't actually use the built-in PHP SOAP client because I've had similar problems with it not recognizing the response as XML because it has some whitespace before it or whatever. I've written my own simple SOAP client in PHP; I can post it here if you like. I hear NuSoap is also good, but I haven't tried it.
no
ok. thanks for all.
Hajimba
+1  A: 

Yes it is.

You can transfer any data you like. You only need to base64 encode the binary data. It will transform it to ascii characters. Then you can transfer it like ordinary string variable. The only think you need to be careful is the server limits.

Regards

darko petreski