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)
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)
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
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:
- In lines 1 and 2, we’re making a new instance of a NuSOAP server and saying what its WSDL will be called, that’s the XML file that tells our clients what our web services consist of.
- Add a complex data type called ‘Person’ to our SOAP server. The first four values get passed through to the WSDL, I’m not going to get into explaining what they do here… the array() that follows them tells NuSOAP what makes up our ‘Person’ structure:
- A thing called ‘id’, that will be refereed to as ‘id’ and is an integer
- A thing called ‘firstName’, that will be refereed to as ‘firstName’ and is a string
- and so on…
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:
- Says there’s an available function called ‘getPerson’.
- That it accepts one integer referred to as ‘id’.
- It returns an object that is of type Person.
- The name of the wsdl
- That its a Remote Procedure Call
- That its encoded
- and a brief description
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