クラウド系のサービスを呼び出すためのAPIとして一般的なREST APIを呼び出すAppleScriptの最新版(のスケルトン)です。
# v4.2からv4.3にバージョンアップしました(2019/05/02)
macOS 10.10でほぼすべてのAppleScriptランタイムでCocoaの機能が利用できるようになったため、Cocoaの機能を利用してクラウド系サービスを呼び出すことを、割と早い時期から調査していました。
「ほぼすべて」と書いているのは、アプリケーション内蔵のScript Menu(iTunesとか)や、Folder Actionなどの独特なランタイム、あるいはMessages(旧称iChat)のメッセージ受信イベントから呼び出されるAppleScriptランタイムなど、それぞれのアプリケーション独自で実装している処理系についてはCocoaの機能が使えるかどうかは保証のかぎりではないからです。
明確に、当初からクラウドと機械学習をAppleScriptから利用するために、苦労してCocoaの機能を追いかけてきたわけです。その途中でいろいろサードパーティのFrameworkを呼び出せたりして、期待よりも大きな成果を手にしている昨今です。OSの内部機能を直接利用できることのメリットは、処理速度や詳細な情報取得など枚挙にいとまがありません(日本語の慣用句表現なので翻訳しにくいかも?)。
話をクラウドにもどしましょう。REST APIを呼び出すには、同期で実行されるNSURLConnectionを使うのが最も簡単で確実でした。
しかし、同APIは非推奨(近い将来に廃止される)という位置付けになったため、その代わりを探す必要に迫られました。すでにNSURLConnectionを用いて記述したREST API呼び出しのScriptが大量に存在していたからです。
代替APIとしてNSURLSessionが提示されましたが、こちらは非同期実行のみで同期実行モードはありません。いろいろ実験してみたものの、数回に1回ぐらい、サーバーからの応答をつかまえ損ねることがあり、処理時間もNSURLConnection版より長くかかります。
このため、「do shell script経由でcurlコマンドを使って回避するか?」といった回避策を検討していたところです。
そんな中、冗談半分でNSURLSessionを利用したバージョンをほんの少し修正してみたところ、いままでの不安定さが嘘のように安定して結果を得られるようになってきました。これはいい兆候です。
ただし、同時に問題点も見つかってきました。macOS標準装備のScript Editor上で実行する分には問題ないのですが、Script Debugger上で実行すると、macOS 10.12/10.13/10.14共通でサーバーからの応答を取得できず、タイムアウトエラーになります。
初回掲載時(v4.2)にはScript Debugger上でデータ受信を検出できないという問題がありました。データの受信用のプロパティがmissing valueでなければデータを受信した、という判定ロジックがうまく動いていない(どういう仕組みかはわからないものの、missing valueと判断され続けた? ラベル値とproperty名でコンフリクト起こしたかも?)現象が見られました。そこで、データの受信プロパティとデータ受信完了検出のプロパティを明確に分けて判定してみたところ、Script DebuggerやmacOS 10.14上でも問題なく動作することを確認しました。
AppleScript名:GET method REST API v4.3 |
— Created 2019-05-01 by Takaaki Naganoya — 2019 Piyomaru Software use AppleScript version "2.5" use scripting additions use framework "Foundation" use framework "AppKit" property |NSURL| : a reference to current application’s |NSURL| property NSString : a reference to current application’s NSString property NSURLSession : a reference to current application’s NSURLSession property NSJSONSerialization : a reference to current application’s NSJSONSerialization property NSMutableURLRequest : a reference to current application’s NSMutableURLRequest property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding property NSURLSessionConfiguration : a reference to current application’s NSURLSessionConfiguration property retData : missing value property retCode : 0 property retHeaders : 0 property drecF : false set reqURLStr to "http://jsonplaceholder.typicode.com/posts" set aRESTres to callRestGETAPIAndParseResults(reqURLStr, 10) of me –return retData of aRESTres set aRESTcode to retCode of aRESTres –return aRESTcode –> 200 return retHeaders as record –> {|content-type|:"application/json; charset=utf-8", Pragma:"no-cache", |x-powered-by|:"Express", |set-cookie|:"__cfduid=dd8b4572e0a59951cc7cf7a1120c368541554964103; expires=Fri, 10-Apr-20 06:28:23 GMT; path=/; domain=.typicode.com; HttpOnly", Server:"cloudflare", Via:"1.1 vegur", |content-encoding|:"gzip", Expires:"Wed, 01 May 2019 15:27:42 GMT", |cf-cache-status|:"HIT", |transfer-encoding|:"Identity", |cache-control|:"public, max-age=14400", |date|:"Wed, 01 May 2019 11:27:42 GMT", |access-control-allow-credentials|:"true", Connection:"keep-alive", |cf-ray|:"4d016861ac759413-NRT", Etag:"W/\"6b80-Ybsq/K6GwwqrYkAsFxqDXGC7DoM\"", Vary:"Origin, Accept-Encoding", |x-content-type-options|:"nosniff"} on callRestGETAPIAndParseResults(reqURLStr as string, timeoutSec as integer) set (my retData) to false set (my retCode) to 0 set (my retHeaders) to {} set (my drecF) to false set aURL to |NSURL|’s URLWithString:reqURLStr set aRequest to NSMutableURLRequest’s requestWithURL:aURL aRequest’s setHTTPMethod:"GET" aRequest’s setValue:"gzip" forHTTPHeaderField:"Content-Encoding" aRequest’s setValue:"application/json; charset=UTF-8" forHTTPHeaderField:"Content-Type" set aConfig to NSURLSessionConfiguration’s defaultSessionConfiguration() set aSession to NSURLSession’s sessionWithConfiguration:aConfig delegate:(me) delegateQueue:(missing value) set aTask to aSession’s dataTaskWithRequest:aRequest set hitF to false aTask’s resume() –Start URL Session repeat (1000 * timeoutSec) times if (my drecF) = true then set hitF to true exit repeat end if delay ("0.001" as real) end repeat if hitF = false then error "REST API Timeout Error" return {retData:retData, retCode:retCode, retHeaders:retHeaders} end callRestGETAPIAndParseResults on URLSession:tmpSession dataTask:tmpTask didReceiveData:tmpData parseSessionResults(tmpSession, tmpTask, tmpData) of me set (my drecF) to true end URLSession:dataTask:didReceiveData: –ないとエラーになるので足した。とくに何もしていない on URLSession:tmpSession dataTask:tmpTask willCacheResponse:cacheRes completionHandler:aHandler — end URLSession:dataTask:willCacheResponse:completionHandler: on parseSessionResults(aSession, aTask, tmpData) set aRes to aTask’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 (my retData) to aJsonDict as list of string or string –as anything end parseSessionResults |
URLにリソースが存在するかチェック v5(NSURLSession) – AppleScriptの穴 says:
[…] んでしまいますが、そこをあえてCocoaの機能を利用して、というよりはちょっと前に書いたREST API呼び出しのサブルーチンが絶好調で動いているので、これをそのまま流用してみました。 […]
NSURLSessionでREST API呼び出しv4.4 – AppleScriptの穴 says:
[…] GET method REST API v4.3 –> GET method REST API v4.1 –> GET method REST API […]
NSURLSessionでREST API呼び出しv4.4.2a – AppleScriptの穴 says:
[…] GET method REST API v4.4.1 –> GET method REST API v4.4 –> GET method REST API v4.3 –> GET method REST API v4.1 –> GET method REST API […]