HTTP Uploads with cURL and AppleScript

Here is a quick example in AppleScript of how to use the ‘curl’ program to upload a file to a website/cgi. As far as the web server is concerned a user is sending it a file via an html form.

Firstly here is the HTML form that we will be faking an upload from:


<form action="upload.php" method="post">
	<input name="myFile" type="file" />
	<input name="submit" type="submit" id="submit" value="Submit" />
</form>

Now, here’s the curl command we’re going to use to fake the upload:

curl -F "myFile=@/my/file.txt" http://ws.com/upload.php

Notice how the name of the form field that represents the file to be uploaded (’myFile’) is in the curl parameters!

And finally here is the AppleScript code to do the whole thing:


property uploadForm : "http://ws.com/upload.php"

on run
	set x to choose file
	tell me to open (x)
end run

on open theseFiles
	if class of theseFiles is not list then
		set theseFiles to {theseFiles}
	end if

	repeat with i from 1 to count of items of theseFiles
		set posixPathOfThisFile to POSIX path of
			(item i of theseFiles) as string
		my uploadFile(posixPathOfThisFile)
	end repeat
end open

on uploadFile(theFile)
	set theCMD to "curl -F \"myFile=@" & theFile & "\" "
		& uploadForm
	do shell script theCMD
end uploadFile

Getting complex with PHP and NuSOAP (Part 3)

Continued From Part 2

So far we have a working NuSOAP based web service, in this final instalment I will talk through some AppleScript to use it.

The guts of the script is in the callSoap function:


property soapURL : "http://localhost:8888/soapdemo/soap/"

on callSoap(m, p)
	using terms from application "http://www.apple.com/placebo"
		tell application soapURL
			return call soap {method name:m,
				method namespace uri:soapURL,
				SOAPAction:(soapURL & "#" & m),
				parameters:p
			}
		end tell
	end using terms from
end callSoap

It needs three things, firstly the ’soapURL’ property which tells it where to look for the server, secondly ‘m’ which tells it the name of the SOAP method (aka function) that is to be called and lastly ‘p’ which is an AppleScript record (aka hash) of the parameters that are being passed to the SOAP method.

The next two most important functions are these two:


on doSearch(myQuery)
	return callSoap("search", {query:myQuery})
end doSearch

on getPerson(MyID)
	return callSoap("getPerson", {id:MyID})
end getPerson

These two AppleScript functions map directly to the SOAP methods, notice how they take their parameters and turn them into records before passing them on. I write AppleScript this way as the language is very procedural (yeah, I know… but I don’t want to get into script objects here, maybe another time) so these two functions provide a layer of abstraction between the data layer (the web services) and the logic layer (the AppleScript)… this just means that if in 4 days time we want to rewrite this script to use FileMaker Pro’s AppleEvent API rather than SOAP, we’ll only have to change how those two functions work, not anything they get passed or anything they return!

The rest of code is just plain and simple AppleScript:


set theSearch to text returned of
	(display dialog "Enter name" default answer "donnie")

set people to my doSearch(theSearch)

set theNames to {} --every name found by search
set theIDs to {} --the IDs of names held above

repeat with person in people
	set end of theNames to
		(firstname of person & space & lastname of person)
	set end of theIDs to (|id| of person)
end repeat

set theName to choose from list theNames

--now loop through looking for the chosen name to find it's ID
repeat with i from 1 to count of items of theNames
	if (item i of theNames) as string is (theName as string) then
		set theID to item i of theIDs
	end if
end repeat

set thePersonWeWant to getPerson(theID)

display dialog "The phone number for "
	& (theName as string)
	& " is:" default answer phonenumber of thePersonWeWant

I’ve add a few comments and made the variable names pretty verbose, so you should be able to figure out what’s going on!

The source for the entire app can be downloaded here. If you look at the way the code is structured, you’ll notice that I’ve kept the PHP functions used by the web services in a different file that is included by the PHP script that deals with NuSOAP. This keeps things cleaner and the functions available to other PHP scripts that can be used for debugging and other things!

I hope someone somewhere finds this vaguely usefully!

Just don’t forget: I never said it’d work any way!

Dk0

Getting complex with PHP and NuSOAP (Part 2)

Continued From Part 1

When we left off in Part 1 we had one function to get the data for a ‘Person’ by their ID, it returned that data as a SOAP Complex Data type that we called ‘Person’. Now in Part 2 I’m going to show you how to send an array of Person objects… an array of People!

Firstly, let’s look at the function that returns the array:


function search($query) {
	$people = Person::search($query);

	$results = array();
	foreach ($people as $person) {
		$tempArray = array('id' => $person->getID(),
		 		'firstName' => $person->firstName,
		 		'lastName' => $person->lastName,
		 		'phoneNumber' => $person->phoneNumber
			);
		array_push($results, $tempArray);
	}

	return $results;
}

Now, the result of the function is exactly the same as the first function, only it is wrapped in an array… and there can be more than one object.

So how do you describe an array of Person objects to NuSOAP? Like this:

$server->wsdl->addComplexType('People',
				'complexType',
				'array',
				'',
				'SOAP-ENC:Array',
				array(),
				array(
					array('ref'=>'SOAP-ENC:arrayType',
					'wsdl:arrayType'=>'tns:Person[]')
				),
				'tns:Person'
			);

The only difference between this and the example in Part 1 worth discussing (everything else is best left as ‘it just is that way’ I think!) is the definition of the array type. It’s set to ‘tns:Person[]’, the ‘tns’ is a wsdl name space thing and ‘Person[]’ means… guess what… an array of Persons! No surprises there really… especially for any one who’s written any Java. Also notice that the third value passed to the function now says ‘array’ rather than ’structure’ which is what we used when we defined Person.

So all that’s left to do is register the function:


$server->register('search',
			array('query' => 'xsd:string'),
			array('return' => 'tns:People'),
			'soapdemo_wsdl',
			'soapdemo_wsdl#search',
			'rpc',
			'encoded',
			'Returns the data for every person'
		);

I’m not going to explain that, it’s just the same as Part 1 only with a People object and the new function name!

The source code is still available: soapdemo.zip (including a version of NuSOAP that I moded to fix a bug with PHP 5)!

Tune in next time for a guide to an AppleScript client and my thoughts on the best way to tie it all togeather!

Until then, just remember that I never said it’d work!

Dk0

Continued in Part 3

Getting complex with PHP and NuSOAP (Part 1)

I remember the first time I had to write some SOAP based web services in PHP that used complex data structures, it was back in the days before PHP 5 was considered production ready (by me at least…) so I didn’t have access to built in SOAP support, which lead me to discover the NuSOAP tool kit. These days I still use NuSOAP, partly out of laziness to get to grips with PHP 5’s SOAP support and because NuSOAP does some really cool stuff… like abstracting the process of making the WSDL so you don’t have to write out all that XML - after all these are web services, you shouldn’t have to see any XML right? And it even makes pretty html documentation for you!

As it’s cold out side, and in the absence of any kind of life, I thought I’d post a short example of how I structure NuSOAP stuff and how to use complex data structures. I’m going to spilt this into three posts: the first being a how to do a simple ‘complex type’, the next being an array of complex types and the third being a little AppleScript client to prove everything works. I’m doing it this way because writing about the code is actually really boring, not because I have any delusions that you’ll be sitting on the edge of you seat waiting for the next instalment!

The demo app is a phone list, all the database stuff is faked in the Person class, so the search will always return the same 5 results!
You can get all the source code for the entire app here: soapdemo.zip (including a version of NuSOAP that I moded to fix a bug with PHP 5)!

So, in a really patronising way, let’s look at the first function:


function getPerson($id) {
	$person = Person::getPerson($id);
	//load the data into an array with names and return it
	return array('id' => $person->getID(),
				'firstName' => $person->firstName,
				'lastName' => $person->lastName,
				'phoneNumber' => $person->phoneNumber
			);
}

If this function was called with a ‘1′ as $id then a var_dump() of the result would look like this:


array(4) {
  ["id"]=>
  int(1)
  ["firstName"]=>
  string(6) "Donnie"
  ["lastName"]=>
  string(5) "Darko"
  ["phoneNumber"]=>
  string(11) "111 111 111"
}

So the question on every body’s lips is: How do you describe this structure to NuSOAP? Well it’s easy:


$server = new soap_server();
$server->configureWSDL('soapdemo_wsdl', 'urn:soapdemo_wsdl');

$server->wsdl->addComplexType('Person',
	'complexType',
	'struct',
	'all',
	'',
	array(
		'id' => array('name' => 'id',
		 	'type' => 'xsd:int'),
		'firstName' => array('name' => 'firstName',
		 	'type' => 'xsd:string'),
		'lastName' => array('name' => 'lastName',
		 	'type' => 'xsd:string'),
		'phoneNumber' => array('name' => 'phoneNumber',
		 	'type' => 'xsd:string')
	)
);

So what’s this code doing exactly? Well:

Next we have to register the function with NuSOAP:


$server->register('getPerson',
			array('id' => 'xsd:int'),
			array('return' => 'tns:Person'),
			'soapdemo_wsdl',
			'soapdemo_wsdl#getPerson',
			'rpc',
			'encoded',
			'Returns the data for a person'
		);

This code:

Then all we have to do is pass the server the HTTP POST request from the client:


$request = isSet($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$server->service($request);

And there you go!

Stay tuned next time for arrays of complex types!

Until then, just remember that I never said it work anyway!

Dk0

Continued in Part 2