Cocoaの機能を使ってURLをGET/POSTするAppleScriptハンドラ


## URLをGETするAppleScriptハンドラ

* シェルでいうところの`curl 'http://www.example.com/'`みたいなもの


```AppleScript:GETRequestForURL.scpt
use scripting additions
use framework "Foundation"

set requestURL to current application's NSURL's URLWithString:"https://www.example.com/"

set response to my GETRequestForURL(requestURL)

set returnedString to current application's NSString's alloc()'s initWithData:(response's |data|) encoding:(current application's NSUTF8StringEncoding)
return returnedString as text

on GETRequestForURL(requestURL)
	--require framework: Foundation
	set session to my getValidURLSession()
	set task to session's dataTaskWithURL:requestURL
	task's resume()
	return my fetchURLSesstionTasksResult(current application's NSArray's arrayWithObject:task)'s firstObject()
end GETRequestForURL

on getValidURLSession()
	--require framework: Foundation
	global URLSession
	try
		if URLSession = missing value then
			error number -2753
		end if
	on error number -2753
		set configuration to current application's NSURLSessionConfiguration's ephemeralSessionConfiguration()
		set URLSession to current application's NSURLSession's sessionWithConfiguration:configuration delegate:(me) delegateQueue:(missing value)
	end try
	return URLSession
end getValidURLSession

on URLSession:session didBecomeInvalidWithError:anError
	global URLSession
	log "URLSession invalidated"
	set URLSession to missing value
end URLSession:didBecomeInvalidWithError:

on fetchURLSesstionTasksResult(tasks)
	my waitForCompletionOfURLSessionTasks(tasks)
	my errorURLSessionTasksIfNeeded(tasks)
	return my getResponsesToURLSessionTasks(tasks)
end fetchURLSesstionTasksResult

on waitForCompletionOfURLSessionTasks(tasks)
	--require framework: Foundation
	set tasksAreRunningPredicate to current application's NSPredicate's predicateWithFormat_("NONE state == %@", current application's NSURLSessionTaskStateRunning as integer)
	repeat until tasksAreRunningPredicate's evaluateWithObject:tasks
		delay 0.01
	end repeat
end waitForCompletionOfURLSessionTasks

on errorURLSessionTasksIfNeeded(tasks)
	--require framework: Foundation
	repeat with errorTask in (tasks's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"error != nil"))
		error errorTask's |error|'s localizedDescription as text number errorTask's |error|'s code as integer
	end repeat
	repeat with notCompletedTask in (tasks's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat_("state != %@", current application's NSURLSessionTaskStateCompleted as integer)))
		error "task has not completed"
	end repeat
end errorURLSessionTasksIfNeeded

on getResponsesToURLSessionTasks(tasks)
	--require framework: Foundation
	global responsesToURLSessionTask
	try
		responsesToURLSessionTask
	on error number -2753
		set responsesToURLSessionTask to current application's NSMutableArray's alloc()'s init()
	end try
	set newTasksFilterPredicate to current application's NSPredicate's predicateWithFormat_("NOT (taskIdentifier IN %@)", responsesToURLSessionTask's valueForKeyPath:"task.taskIdentifier")
	repeat with newTask in (tasks's filteredArrayUsingPredicate:newTasksFilterPredicate)
		(responsesToURLSessionTask's addObject:{task:newTask, |data|:current application's NSMutableData's alloc()'s init()})
	end repeat
	responsesToURLSessionTask's sortUsingDescriptors:{current application's NSSortDescriptor's sortDescriptorWithKey:"task.taskIdentifier" ascending:true}
	set responsesFilterPredicate to current application's NSPredicate's predicateWithFormat_("task.taskIdentifier IN %@", tasks's valueForKey:"taskIdentifier")
	return responsesToURLSessionTask's filteredArrayUsingPredicate:responsesFilterPredicate
end getResponsesToURLSessionTasks

on getResponseToURLSesstionTask(task)
	--require framework: Foundation
	return my getResponsesToURLSessionTasks(current application's NSArray's arrayWithObject:task)'s firstObject()
end getResponseToURLSesstionTask

on URLSession:session taskIsWaitingForConnectivity:task
	log "waiting for connectivity..."
end URLSession:taskIsWaitingForConnectivity:

on URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend
	log "sending..."
end URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:

on URLSession:session dataTask:task didReceiveData:aData
	log "receiving..."
	set receivedData to my getResponseToURLSesstionTask(task)'s |data|
	receivedData's appendData:aData
end URLSession:dataTask:didReceiveData:

on URLSession:session task:task didCompleteWithError:anError
	log "URLSessionTask completed"
end URLSession:task:didCompleteWithError:
```

## URLをPOSTするAppleScriptハンドラ

* シェルでいうところの`curl -F 'name=value' 'http://www.example.com/'`みたいなもの

```AppleScript:POSTRequestForURL.scpt
use scripting additions
use framework "Foundation"

set requestURL to current application's NSURL's URLWithString:"https://www.example.com/"
set postString to current application's NSString's stringWithString:"name=value"
set allowedCharacters to current application's NSCharacterSet's URLQueryAllowedCharacterSet
set postData to (postString's stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters)'s dataUsingEncoding:(current application's NSUTF8StringEncoding)

set response to my POSTRequestForURL(requestURL, postData)

set returnedString to current application's NSString's alloc()'s initWithData:(response's |data|) encoding:(current application's NSUTF8StringEncoding)
return returnedString as text

on POSTRequestForURL(requestURL, dataToPost)
	--require framework: Foundation
	set request to current application's NSMutableURLRequest's requestWithURL:requestURL
	set request's HTTPMethod to "POST"
	request's setValue:"application/x-www-form-urlencoded" forHTTPHeaderField:"Content-Type"
	set session to my getValidURLSession()
	set task to session's uploadTaskWithRequest:request fromData:dataToPost
	task's resume()
	return my fetchURLSesstionTasksResult(current application's NSArray's arrayWithObject:task)'s firstObject()
end POSTRequestForURL

on getValidURLSession()
	--require framework: Foundation
	global URLSession
	try
		if URLSession = missing value then
			error number -2753
		end if
	on error number -2753
		set configuration to current application's NSURLSessionConfiguration's ephemeralSessionConfiguration()
		set URLSession to current application's NSURLSession's sessionWithConfiguration:configuration delegate:(me) delegateQueue:(missing value)
	end try
	return URLSession
end getValidURLSession

on URLSession:session didBecomeInvalidWithError:anError
	global URLSession
	log "URLSession invalidated"
	set URLSession to missing value
end URLSession:didBecomeInvalidWithError:

on fetchURLSesstionTasksResult(tasks)
	my waitForCompletionOfURLSessionTasks(tasks)
	my errorURLSessionTasksIfNeeded(tasks)
	return my getResponsesToURLSessionTasks(tasks)
end fetchURLSesstionTasksResult

on waitForCompletionOfURLSessionTasks(tasks)
	--require framework: Foundation
	set tasksAreRunningPredicate to current application's NSPredicate's predicateWithFormat_("NONE state == %@", current application's NSURLSessionTaskStateRunning as integer)
	repeat until tasksAreRunningPredicate's evaluateWithObject:tasks
		delay 0.01
	end repeat
end waitForCompletionOfURLSessionTasks

on errorURLSessionTasksIfNeeded(tasks)
	--require framework: Foundation
	repeat with errorTask in (tasks's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"error != nil"))
		error errorTask's |error|'s localizedDescription as text number errorTask's |error|'s code as integer
	end repeat
	repeat with notCompletedTask in (tasks's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat_("state != %@", current application's NSURLSessionTaskStateCompleted as integer)))
		error "task has not completed"
	end repeat
end errorURLSessionTasksIfNeeded

on getResponsesToURLSessionTasks(tasks)
	--require framework: Foundation
	global responsesToURLSessionTask
	try
		responsesToURLSessionTask
	on error number -2753
		set responsesToURLSessionTask to current application's NSMutableArray's alloc()'s init()
	end try
	set newTasksFilterPredicate to current application's NSPredicate's predicateWithFormat_("NOT (taskIdentifier IN %@)", responsesToURLSessionTask's valueForKeyPath:"task.taskIdentifier")
	repeat with newTask in (tasks's filteredArrayUsingPredicate:newTasksFilterPredicate)
		(responsesToURLSessionTask's addObject:{task:newTask, |data|:current application's NSMutableData's alloc()'s init()})
	end repeat
	responsesToURLSessionTask's sortUsingDescriptors:{current application's NSSortDescriptor's sortDescriptorWithKey:"task.taskIdentifier" ascending:true}
	set responsesFilterPredicate to current application's NSPredicate's predicateWithFormat_("task.taskIdentifier IN %@", tasks's valueForKey:"taskIdentifier")
	return responsesToURLSessionTask's filteredArrayUsingPredicate:responsesFilterPredicate
end getResponsesToURLSessionTasks

on getResponseToURLSesstionTask(task)
	--require framework: Foundation
	return my getResponsesToURLSessionTasks(current application's NSArray's arrayWithObject:task)'s firstObject()
end getResponseToURLSesstionTask

on URLSession:session taskIsWaitingForConnectivity:task
	log "waiting for connectivity..."
end URLSession:taskIsWaitingForConnectivity:

on URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend
	log "sending..."
end URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:

on URLSession:session dataTask:task didReceiveData:aData
	log "receiving..."
	set receivedData to my getResponseToURLSesstionTask(task)'s |data|
	receivedData's appendData:aData
end URLSession:dataTask:didReceiveData:

on URLSession:session task:task didCompleteWithError:anError
	log "URLSessionTask completed"
end URLSession:task:didCompleteWithError:
```

## 複数のURLを並列でGETするAppleScriptハンドラ

```AppleScript:parallelGETRequestForURLs.scpt
use scripting additions
use framework "Foundation"

set requestURLs to {current application's NSURL's URLWithString:"https://www.example.com/", current application's NSURL's URLWithString:"https://www.apple.com", current application's NSURL's URLWithString:"https://www.yahoo.co.jp"}

set responses to my parallelGETRequestForURLs(requestURLs)

repeat with response in responses
	set returnedString to (current application's NSString's alloc()'s initWithData:(response's |data|) encoding:(current application's NSUTF8StringEncoding))
	log returnedString as text
end repeat

on parallelGETRequestForURLs(requestURLs)
	--require framework: Foundation
	set session to my getValidURLSession()
	set tasks to current application's NSMutableArray's alloc()'s init()
	repeat with requestURL in requestURLs
		set task to (session's dataTaskWithURL:requestURL)
		task's resume()
		(tasks's addObject:task)
	end repeat
	return my fetchURLSesstionTasksResult(tasks)
end parallelGETRequestForURLs

on getValidURLSession()
	--require framework: Foundation
	global URLSession
	try
		if URLSession = missing value then
			error number -2753
		end if
	on error number -2753
		set configuration to current application's NSURLSessionConfiguration's ephemeralSessionConfiguration()
		set URLSession to current application's NSURLSession's sessionWithConfiguration:configuration delegate:(me) delegateQueue:(missing value)
	end try
	return URLSession
end getValidURLSession

on URLSession:session didBecomeInvalidWithError:anError
	global URLSession
	log "URLSession invalidated"
	set URLSession to missing value
end URLSession:didBecomeInvalidWithError:

on fetchURLSesstionTasksResult(tasks)
	my waitForCompletionOfURLSessionTasks(tasks)
	my errorURLSessionTasksIfNeeded(tasks)
	return my getResponsesToURLSessionTasks(tasks)
end fetchURLSesstionTasksResult

on waitForCompletionOfURLSessionTasks(tasks)
	--require framework: Foundation
	set tasksAreRunningPredicate to current application's NSPredicate's predicateWithFormat_("NONE state == %@", current application's NSURLSessionTaskStateRunning as integer)
	repeat until tasksAreRunningPredicate's evaluateWithObject:tasks
		delay 0.01
	end repeat
end waitForCompletionOfURLSessionTasks

on errorURLSessionTasksIfNeeded(tasks)
	--require framework: Foundation
	repeat with errorTask in (tasks's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"error != nil"))
		error errorTask's |error|'s localizedDescription as text number errorTask's |error|'s code as integer
	end repeat
	repeat with notCompletedTask in (tasks's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat_("state != %@", current application's NSURLSessionTaskStateCompleted as integer)))
		error "task has not completed"
	end repeat
end errorURLSessionTasksIfNeeded

on getResponsesToURLSessionTasks(tasks)
	--require framework: Foundation
	global responsesToURLSessionTask
	try
		responsesToURLSessionTask
	on error number -2753
		set responsesToURLSessionTask to current application's NSMutableArray's alloc()'s init()
	end try
	set newTasksFilterPredicate to current application's NSPredicate's predicateWithFormat_("NOT (taskIdentifier IN %@)", responsesToURLSessionTask's valueForKeyPath:"task.taskIdentifier")
	repeat with newTask in (tasks's filteredArrayUsingPredicate:newTasksFilterPredicate)
		(responsesToURLSessionTask's addObject:{task:newTask, |data|:current application's NSMutableData's alloc()'s init()})
	end repeat
	responsesToURLSessionTask's sortUsingDescriptors:{current application's NSSortDescriptor's sortDescriptorWithKey:"task.taskIdentifier" ascending:true}
	set responsesFilterPredicate to current application's NSPredicate's predicateWithFormat_("task.taskIdentifier IN %@", tasks's valueForKey:"taskIdentifier")
	return responsesToURLSessionTask's filteredArrayUsingPredicate:responsesFilterPredicate
end getResponsesToURLSessionTasks

on getResponseToURLSesstionTask(task)
	--require framework: Foundation
	return my getResponsesToURLSessionTasks(current application's NSArray's arrayWithObject:task)'s firstObject()
end getResponseToURLSesstionTask

on URLSession:session taskIsWaitingForConnectivity:task
	log "waiting for connectivity..."
end URLSession:taskIsWaitingForConnectivity:

on URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend
	log "sending..."
end URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:

on URLSession:session dataTask:task didReceiveData:aData
	log "receiving..."
	set receivedData to my getResponseToURLSesstionTask(task)'s |data|
	receivedData's appendData:aData
end URLSession:dataTask:didReceiveData:

on URLSession:session task:task didCompleteWithError:anError
	log "URLSessionTask completed"
end URLSession:task:didCompleteWithError:
```


## 更新履歴

* 2018-07-20: FoundationフレームワークのNSURLSessionクラスを使って作成
* 2018-07-21: エラー対応機能を追加
* 2018-08-17: `POSTRequestForURL`ハンドラを作成
* 2018-08-18: NSURLSessionConfigurationを、キャッシュやcookieを使用しない`ephemeralSessionConfiguration`に変更
* 2018-09-05: invalidateされたURLSessionを避けるために`getValidURLSession`ハンドラを作成
* 2018-09-06: `getResponsesToURLSessionTasks`ハンドラを作成して並列実行に対応
* 2018-09-10: 並列にGETリクエストを実行する`parallelGETRequestForURLs`ハンドラを作成