I’m trying to do XMLRPC request using ASObjC, but I have always problem with completionHandler and most of the time I never use it. The code is based on the same
example I have in code-exchange. To get a copy of xmlrpc server python script its in code-exchange.
use framework "Foundation"
use scripting additions
set post to current application's NSString's stringWithFormat:"metodName=add¶ms={2,3}"
set postData to post's dataUsingEncoding:(current application's NSASCIIStringEncoding) allowLossyConversion:true
set postLength to current application's NSString's stringWithFormat_("%d", postData's |length|())
set theURL to current application's |NSURL|'s URLWithString:"http://localhost:8000/RPC2"
set request to current application's NSMutableURLRequest's alloc()'s init()
request's setURL:theURL
request's setHTTPMethod:"POST"
request's setValue:postLength forHTTPHeaderField:"Content-Length"
request's setHTTPBody:postData
set theConfiguration to current application's NSURLSessionConfiguration's defaultSessionConfiguration()
set session to current application's NSURLSession's sessionWithConfiguration:theConfiguration
session's dataTaskWithRequest:request completionHandler:(missing value)
-- set requestReply to current application's NSString's alloc()'s initWithData:object() encoding:(current application's NSASCIIStringEncoding)
The completionHandler argument takes a block, which ASObjC doesn’t support – it’s essentially asking for a block of Objective-C code to run when the action is completed. NSURLSession fortunately offers versions of these methods without the need for blocks, so just use them.
I could see a message on the server but it say: code 501, message Unsupported method (‘GET’)
And that is wrong. The correct message should be: “POST /RPC2 HTTP/1.1” 200 -
So postData should be wrong, to be honest I did a guess from xmlrpc specification.
XMLRPC include both XML and HTTP data.
Maybe I could save everything to a string to see what it send.
Some examples I have seen use dataTask’s resume()
use framework "Foundation"
use scripting additions
set post to current application's NSString's stringWithFormat:"metodname=add¶m=2¶m=3}"
set postData to post's dataUsingEncoding:(current application's NSASCIIStringEncoding) allowLossyConversion:true
set postLength to current application's NSString's stringWithFormat_("%d", postData's |length|())
set theURL to current application's |NSURL|'s URLWithString:"http://localhost:8000/RPC2"
set request to current application's NSMutableURLRequest's alloc()'s initWithURL:theURL
request's setHTTPMethod:"POST"
request's setValue:postLength forHTTPHeaderField:"Content-Length"
request's setHTTPBody:postData
set theConfiguration to current application's NSURLSessionConfiguration's defaultSessionConfiguration()
set session to current application's NSURLSession's sessionWithConfiguration:theConfiguration
set dataTask to session's dataTaskWithURL:theURL
if (dataTask's state()) is 1 then
log "Session Task State Suspended"
end if
Since can’t use block need to use the delegate methods of the NSURLSession.
Also need to use the request not the URL when setting up the task
set session to current application's NSURLSession's sessionWithConfiguration:theConfiguration delegate:(me) delegateQueue:(missing value)
set dataTask to session's dataTaskWithRequest:request
dataTask's resume() --Start URL Session
and then include in your script the required delegate method:
on URLSession:tmpSession dataTask:tmpTask didReceiveData:tmpData
set aStat to (tmpTask's state()) as list of string or string
set aRes to tmpTask's response()
set (my retCode) to aRes's statusCode()
set (my retHeaders) to aRes's allHeaderFields()
set resStr to NSString's alloc()'s initWithData:tmpData encoding:(NSUTF8StringEncoding)
set jsonString to NSString's stringWithString:(resStr)
set jsonData to jsonString's dataUsingEncoding:(NSUTF8StringEncoding)
set aJsonDict to NSJSONSerialization's JSONObjectWithData:jsonData options:**0** |error|:(*missing value*)
set retData to aJsonDict as list of string or string
end URLSession:dataTask:didReceiveData:
After include this I got the right respond on the server “POST /RPC2 HTTP/1.1” 200 -
Your handler confuse me… in other words I did.
dataTask’s status() → 0 → NSURLSessionTaskStateRunning
The response return missing value so statusCode() do not work.
Its same for allHeaderFields()
So there is something wrong with the respond.
From:XMLRPC specification
It say a User-Adgent and Host need to be included so maybe the default configuration is
the wrong approach. When Apple’s documentation a user could make that configuration in
the session NSURLSessionConfiguration
I’m checking this code to see if I get any clue how it works. XMLRPC framework
I’m not sure if the stringWithFormat is correct. I have try many variants and its not so easy to
find useful information about it. The only reference I have is this.
I will try to make XML to NSData and see if that works.
And of course it didn’t work
set theHTTPBody to "<?xml version=\"1.0\"?>
<methodCall>
<methodName>add</methodName>
<params>
<param>
<value><i4>2</i4></value>
</param>
<param>
<value><i4>3</i4></value>
</param>
</params>
</methodCall>
"
set theString to current application's NSString's stringWithString:theHTTPBody
set theData to theString's dataUsingEncoding:(current application's NSASCIIStringEncoding)
-- set post to current application's NSString's stringWithFormat:"methodName=add¶m=2¶m:3"
-- set postData to post's dataUsingEncoding:(current application's NSASCIIStringEncoding) allowLossyConversion:true
set postLength to current application's NSString's stringWithFormat_("%d", theData's |length|())
set theURL to current application's |NSURL|'s URLWithString:"http://localhost:8000/RPC2"
set request to current application's NSMutableURLRequest's alloc()'s initWithURL:theURL
request's setHTTPMethod:"POST"
-- request's setValue:"localhost" forHTTPHeaderField:"Host"
request's setValue:postLength forHTTPHeaderField:"Content-Length"
request's setHTTPBody:theData
set theConfiguration to current application's NSURLSessionConfiguration's defaultSessionConfiguration()
set session to current application's NSURLSession's sessionWithConfiguration:theConfiguration delegate:(me) delegateQueue:(missing value)
set dataTask to session's dataTaskWithRequest:request
dataTask's resume()
Here is an edited version of the function with global properties and added in the correct current application’s where needed
-- classes, constants, and enums used
property retData : missing value
property retStatusCode : 0
property retStatusCodeInfo : ""
property retHeaders : missing value
on URLSession:tmpSession dataTask:tmpTask didReceiveData:tmpData
set aState to tmpTask's state()
set aResponse to tmpTask's response()
set (my retStatusCode) to aResponse's statusCode()
set (my retStatusCodeInfo) to current application's NSHTTPURLResponse's localizedStringForStatusCode:(my retStatusCode)
set (my retHeaders) to aResponse's allHeaderFields()
set resStr to current application's NSString's alloc()'s initWithData:tmpData encoding:(current application's NSUTF8StringEncoding)
set jsonString to current application's NSString's stringWithString:resStr
set jsonData to jsonString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
set aJsonDict to current application's NSJSONSerialization's JSONObjectWithData:jsonData options:0 |error|:(missing value)
if aJsonDict's isKindOfClass:(current application's NSDictionary's |class|()) then
set my retData to aJsonDict as record
else
set my retData to aJsonDict as list
end if
end URLSession:dataTask:didReceiveData:
Here is the code I try to run, and it include XMLRPC server in python code.
The respond of the POST to the server should return the number 5
Try it and maybe you get it to work.
use framework "Foundation"
use scripting additions
property retData : missing value
property retStatusCode : 0
property retStatusCodeInfo : ""
property retHeaders : missing value
set post to current application's NSString's stringWithFormat:"methodName=add¶m=2¶m=3"
set postData to post's dataUsingEncoding:(current application's NSASCIIStringEncoding) allowLossyConversion:true
set postLength to current application's NSString's stringWithFormat_("%d", postData's |length|())
set theURL to current application's |NSURL|'s URLWithString:"http://localhost:8000/RPC2"
set request to current application's NSMutableURLRequest's alloc()'s initWithURL:theURL
request's setHTTPMethod:"POST"
request's setValue:postLength forHTTPHeaderField:"Content-Length"
request's setHTTPBody:postData
set theConfiguration to current application's NSURLSessionConfiguration's defaultSessionConfiguration()
set session to current application's NSURLSession's sessionWithConfiguration:theConfiguration delegate:(me) delegateQueue:(missing value)
set dataTask to session's dataTaskWithRequest:request
dataTask's resume()
its URLSession:session dataTask:dataTask didReceiveData:tmpData
on URLSession:tmpSession dataTask:tmpTask didReceiveData:tmpData
set aState to tmpTask's state()
set aResponse to tmpTask's response()
set (my retStatusCode) to aResponse's statusCode()
set (my retStatusCodeInfo) to current application's NSHTTPURLResponse's localizedStringForStatusCode:(my retStatusCode)
set (my retHeaders) to aResponse's allHeaderFields()
set resStr to current application's NSString's alloc()'s initWithData:tmpData encoding:(current application's NSUTF8StringEncoding)
set jsonString to current application's NSString's stringWithString:resStr
set jsonData to jsonString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
set aJsonDict to current application's NSJSONSerialization's JSONObjectWithData:jsonData options:0 |error|:(missing value)
if aJsonDict's isKindOfClass:(current application's NSDictionary's |class|()) then
set my retData to aJsonDict as record
else
set my retData to aJsonDict as list
end if
end URLSession:dataTask:didReceiveData:
(** XMLRPC Server, copy to textfile.py and run it. (Python3)
from xmlrpc.server import SimpleXMLRPCServer
from xmlrpc.server import SimpleXMLRPCRequestHandler
# Restrict to a particular path.
class RequestHandler(SimpleXMLRPCRequestHandler):
rpc_paths = ('/RPC2',)
# Create server
with SimpleXMLRPCServer(('localhost', 8000),
requestHandler=RequestHandler) as server:
server.register_introspection_functions()
# Register pow() function; this will use the value of
# pow.__name__ as the name, which is just 'pow'.
server.register_function(pow)
# Register a function under a different name
def adder_function(x, y):
return x + y
server.register_function(adder_function, 'add')
# Register an instance; all the methods of the instance are
# published as XML-RPC methods (in this case, just 'mul').
class MyFuncs:
def mul(self, x, y):
return x * y
server.register_instance(MyFuncs())
# Run the server's main loop
server.serve_forever()
*)
I tested other approach to see some errors. I was able to return XML from the response
and it complain about what I believe is the postData. To edit the postData I could see
other type of error…
use framework "Foundation"
use scripting additions
set {methodString, paramNumber1, paramNumber2} to {"add", 2, 3}
set thePost to current application's NSString's ¬
stringWithFormat_("methodName=%@param=%@param=%@", methodString, paramNumber1, paramNumber2)
set postData to (thePost's dataUsingEncoding:(current application's NSASCIIStringEncoding))
set postLength to current application's NSString's stringWithFormat_("%d", postData's |length|())
set theURL to current application's |NSURL|'s URLWithString:"http://localhost:8000/RPC2"
set request to current application's NSMutableURLRequest's alloc()'s initWithURL:theURL
request's setHTTPMethod:"POST"
-- request's setValue:"text/xml" forHTTPHeaderField:"content-type"
request's setValue:postLength forHTTPHeaderField:"Content-Length"
request's setHTTPBody:postData
set response to current application's NSHTTPURLResponse's alloc()'s init()
set theConnectionData to (current application's NSURLConnection's sendSynchronousRequest:request returningResponse:response |error|:(missing value))
set responseBody to current application's NSString's alloc()'s initWithData:theConnectionData encoding:(current application's NSASCIIStringEncoding)
Maybe I'm missing something, or thought it was to simple. Many times the difficult part isn't to
convert Objective-C code to ASObjC its to understand how things are working when documentation is so poorly in many ways or how to get it with examples.
responseBody as string
Updated Code:::: Does not included the full request and session set up
-- classes, constants, and enums used
property retData : missing value
property retStatusCode : 0
property retStatusCodeInfo : ""
property retHeaders : missing value
property requestDone : false
property parsedData : missing value
on setUpAndSendRequest()
-- TRIMMED ALL OTHER request and theConiguration setUps
set session to current application's NSURLSession's sessionWithConfiguration:theConfiguration delegate:(me) delegateQueue:(missing value)
set dataTask to session's dataTaskWithRequest:request
my resetRecievedData()
dataTask's resume()
-- SET UP AND RUN A LOOP THAT CHECKS IF REQUEST IS DONE
repeat 10000 times
if (requestDone) then exit repeat
current application's NSThread's sleepForTimeInterval:("0.001" as real) --delay 0.001
end repeat
-- REQUEST IS DONE NOW CHECKS IF retData IS OK
if retData is not equal to missing value then
set parsedData to my parseRecievedData:retData
end if
my logAllVariables()
end setUpAndSendRequest
-- RESET ALL BASE RECIEVED DATA VARIABLES
on resetRecievedData()
set retData to current application's NSMutableData's alloc()'s init()
set retStatusCode to 0
set retStatusCodeInfo to ""
set retHeaders to missing value
set requestDone to false
set parsedData to missing value
end resetRecievedData
on URLSession:aSession dataTask:aTask didReceiveData:someData
(retData's appendData:someData)
end URLSession:dataTask:didReceiveData:
on URLSession:aSession didBecomeInvalidWithError:aError
my logError:aError
set requestDone to true
end URLSession:didBecomeInvalidWithError:
on URLSession:aSession task:aTask didCompleteWithError:aError
if (aError ≠ missing value) then
my logError:aError
set requestDone to true
return
end if
set aState to aTask's state()
log {"aTask's state is", aState}
set aResponse to aTask's response()
set (my retStatusCode) to aResponse's statusCode()
set (my retStatusCodeInfo) to current application's NSHTTPURLResponse's localizedStringForStatusCode:(my retStatusCode)
set (my retHeaders) to aResponse's allHeaderFields()
set requestDone to true
end URLSession:task:didCompleteWithError:
on parseRecievedData:someData
set parsedInfo to missing value
set resStr to current application's NSString's alloc()'s initWithData:someData encoding:(current application's NSUTF8StringEncoding)
set jsonString to current application's NSString's stringWithString:resStr
set jsonData to jsonString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
set jsonDict to current application's NSJSONSerialization's JSONObjectWithData:jsonData options:0 |error|:(missing value)
if jsonDict's isKindOfClass:(current application's NSDictionary's |class|()) then
set parsedInfo to jsonDict as record
else
set parsedInfo to jsonDict as list
end if
return parsedInfo
end parseRecievedData:
on logError:aError
error aError's localizedDescription() as text
end logError:
on logAllVariables()
log {"requestDone is", requestDone}
log {"retData is", retData}
log {"retStatusCode is", retStatusCode}
log {"retStatusCodeInfo is", retStatusCodeInfo}
log {"retHeaders is", retHeaders}
log {"parsedData is", parsedData}
end logAllVariables
@technomorph
I’m very thanksful that you take the time to teach me new thing.
I took your code and change little in the log handler to give me more useful info.
It looks like the code do POSTand the return is: faultCode:8002
From the Twisted API I could see its wrong arguments to XMLRPC
Reference: Error handler XMLRPC
The server I have test on is Twisted (python module)
So text step is to get the method name, arguments correct.
use framework "Foundation"
use scripting additions
-- classes, constants, and enums used
property retData : missing value
property retStatusCode : 0
property retStatusCodeInfo : ""
property retHeaders : missing value
property requestDone : false
property parsedData : missing value
set {methodString, paramNumber1} to {"type", {a:1, b:5}}
set thePost to current application's NSString's ¬
stringWithFormat_("methodName=%@param=%@param=%@", methodString, paramNumber1)
set postData to (thePost's dataUsingEncoding:(current application's NSASCIIStringEncoding))
set postLength to current application's NSString's stringWithFormat_("%d", postData's |length|())
set theURL to current application's |NSURL|'s URLWithString:"http://localhost:7080/"
set request to current application's NSMutableURLRequest's alloc()'s initWithURL:theURL
request's setHTTPMethod:"POST"
request's setValue:"text/xml" forHTTPHeaderField:"content-type"
request's setValue:postLength forHTTPHeaderField:"Content-Length"
request's setHTTPBody:postData
set theConfiguration to current application's NSURLSessionConfiguration's defaultSessionConfiguration()
its setUpAndSendRequest()
on setUpAndSendRequest()
-- TRIMMED ALL OTHER request and theConiguration setUps
set session to current application's NSURLSession's sessionWithConfiguration:(its theConfiguration) delegate:(me) delegateQueue:(missing value)
set dataTask to session's dataTaskWithRequest:(its request)
my resetRecievedData()
dataTask's resume()
-- SET UP AND RUN A LOOP THAT CHECKS IF REQUEST IS DONE
repeat 10000 times
if (requestDone) then exit repeat
current application's NSThread's sleepForTimeInterval:("0.001" as real) --delay 0.001
end repeat
-- REQUEST IS DONE NOW CHECKS IF retData IS OK
if retData is not equal to missing value then
set parsedData to my parseRecievedData:retData
end if
my logAllVariables()
end setUpAndSendRequest
-- RESET ALL BASE RECIEVED DATA VARIABLES
on resetRecievedData()
set retData to current application's NSMutableData's alloc()'s init()
set retStatusCode to 0
set retStatusCodeInfo to ""
set retHeaders to missing value
set requestDone to false
set parsedData to missing value
end resetRecievedData
on URLSession:aSession dataTask:aTask didReceiveData:someData
(retData's appendData:someData)
end URLSession:dataTask:didReceiveData:
on URLSession:aSession didBecomeInvalidWithError:aError
my logError:aError
set requestDone to true
end URLSession:didBecomeInvalidWithError:
on URLSession:aSession task:aTask didCompleteWithError:aError
if (aError ≠ missing value) then
my logError:aError
set requestDone to true
return
end if
set aState to aTask's state()
log {"aTask's state is", aState}
set aResponse to aTask's response()
set (my retStatusCode) to aResponse's statusCode()
set (my retStatusCodeInfo) to current application's NSHTTPURLResponse's localizedStringForStatusCode:(my retStatusCode)
set (my retHeaders) to aResponse's allHeaderFields()
set requestDone to true
end URLSession:task:didCompleteWithError:
on parseRecievedData:someData
set parsedInfo to missing value
set resStr to current application's NSString's alloc()'s initWithData:someData encoding:(current application's NSUTF8StringEncoding)
set jsonString to current application's NSString's stringWithString:resStr
set jsonData to jsonString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
set jsonDict to current application's NSJSONSerialization's JSONObjectWithData:jsonData options:0 |error|:(missing value)
if jsonDict is missing value then return "JSON Dictionary is missing value"
if jsonDict's isKindOfClass:(current application's NSDictionary's |class|()) then
set parsedInfo to jsonDict as record
else
set parsedInfo to jsonDict as list
end if
return parsedInfo
end parseRecievedData:
on logError:aError
error aError's localizedDescription() as text
end logError:
on logAllVariables()
set theString to current application's NSString's alloc()'s initWithData:retData encoding:(current application's NSASCIIStringEncoding)
log {"retData string is", theString as string}
log {"requestDone is", requestDone}
log {"retData is", retData's |description|() as string}
log {"retStatusCode is", retStatusCode}
log {"retStatusCodeInfo is", retStatusCodeInfo's |description|() as string}
log {"retHeaders is", retHeaders's |description|() as string}
log {"parsedData is", parsedData}
end logAllVariables
I made a new script with json also I made e new function (echo) return the input
use framework "Foundation"
use scripting additions
-- classes, constants, and enums used
property retData : missing value
property retStatusCode : 0
property retStatusCodeInfo : ""
property retHeaders : missing value
property requestDone : false
property parsedData : missing value
set jsonString to "{
\"methodCall\": {
\"methodName\": \"echo\",
\"params\": [
1
]
}
}"
set thePost to current application's NSString's stringWithString:jsonString
set postData to (thePost's dataUsingEncoding:(current application's NSUTF8StringEncoding))
set jsonData to current application's NSJSONSerialization's JSONObjectWithData:postData options:0 |error|:(missing value)
log (current application's NSJSONSerialization's isValidJSONObject:jsonData)
set postLength to current application's NSString's stringWithFormat_("%d", postData's |length|())
set theURL to current application's |NSURL|'s URLWithString:"http://localhost:7080"
set request to current application's NSMutableURLRequest's alloc()'s initWithURL:theURL
request's setHTTPMethod:"POST"
-- request's setValue:"text/xml" forHTTPHeaderField:"content-type"
-- request's setValue:postLength forHTTPHeaderField:"Content-Length"
request's setHTTPBody:postData
set theConfiguration to current application's NSURLSessionConfiguration's defaultSessionConfiguration()
its setUpAndSendRequest()
on setUpAndSendRequest()
-- TRIMMED ALL OTHER request and theConiguration setUps
set session to current application's NSURLSession's sessionWithConfiguration:(its theConfiguration) delegate:(me) delegateQueue:(missing value)
set dataTask to session's dataTaskWithRequest:(its request)
my resetRecievedData()
dataTask's resume()
-- SET UP AND RUN A LOOP THAT CHECKS IF REQUEST IS DONE
repeat 10000 times
if (requestDone) then exit repeat
current application's NSThread's sleepForTimeInterval:("0.001" as real) --delay 0.001
end repeat
-- REQUEST IS DONE NOW CHECKS IF retData IS OK
if retData is not equal to missing value then
set parsedData to my parseRecievedData:retData
end if
my logAllVariables()
end setUpAndSendRequest
-- RESET ALL BASE RECIEVED DATA VARIABLES
on resetRecievedData()
set retData to current application's NSMutableData's alloc()'s init()
set retStatusCode to 0
set retStatusCodeInfo to ""
set retHeaders to missing value
set requestDone to false
set parsedData to missing value
end resetRecievedData
on URLSession:aSession dataTask:aTask didReceiveData:someData
(retData's appendData:someData)
end URLSession:dataTask:didReceiveData:
on URLSession:aSession didBecomeInvalidWithError:aError
my logError:aError
set requestDone to true
end URLSession:didBecomeInvalidWithError:
on URLSession:aSession task:aTask didCompleteWithError:aError
if (aError ≠ missing value) then
my logError:aError
set requestDone to true
return
end if
set aState to aTask's state()
log {"aTask's state is", aState}
set aResponse to aTask's response()
set (my retStatusCode) to aResponse's statusCode()
set (my retStatusCodeInfo) to current application's NSHTTPURLResponse's localizedStringForStatusCode:(my retStatusCode)
set (my retHeaders) to aResponse's allHeaderFields()
set requestDone to true
end URLSession:task:didCompleteWithError:
on parseRecievedData:someData
set parsedInfo to missing value
set resStr to current application's NSString's alloc()'s initWithData:someData encoding:(current application's NSUTF8StringEncoding)
set jsonString to current application's NSString's stringWithString:resStr
set jsonData to jsonString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
set jsonDict to current application's NSJSONSerialization's JSONObjectWithData:jsonData options:0 |error|:(missing value)
if jsonDict is missing value then return "JSON Dictionary is missing value"
if jsonDict's isKindOfClass:(current application's NSDictionary's |class|()) then
set parsedInfo to jsonDict as record
else
set parsedInfo to jsonDict as list
end if
return parsedInfo
end parseRecievedData:
on logError:aError
error aError's localizedDescription() as text
end logError:
on logAllVariables()
set theString to current application's NSString's alloc()'s initWithData:retData encoding:(current application's NSUTF8StringEncoding)
log {"retData string is", theString as string}
log {"requestDone is", requestDone}
log {"retData is", retData's |description|() as string}
log {"retStatusCode is", retStatusCode}
log {"retStatusCodeInfo is", retStatusCodeInfo's |description|() as string}
log {"retHeaders is", retHeaders's |description|() as string}
log {"parsedData is", parsedData}
end logAllVariables