我们在进行网络开发的时候,难免要从网络服务器中获取数据,很多时候还需要给服务器提交数据,就现在来说,一般我们都会习惯使用 JSON 格式的数据,因为它方便好用,这次我们就一起来看看,如何用 Swift 发送 JSON-RPC 命令并获取回复。
JSON-RPC
JSON-RPC是一个无状态且轻量级的远程过程调用(RPC)协议。 本规范主要定义了一些数据结构及其相关的处理规则。它允许运行在基于 socket, http 等诸多不同消息传输环境的同一进程中。其使用JSON(RFC 4627)作为数据格式。
当我们需要从服务器获取数据的时候,要么通过 URL 附带信息,要么就需要使用一些特定的方法给服务器提交信息,比如常见的 HTTP POST 。
我们通过 HTTP 的 POST 来给服务器发送 JSON 请求对象以表示一次 RPC 调用,按照 JSON-RPC 规则,我们需要传入 4 个基本成员:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
jsonrpc //指定JSON-RPC协议版本的字符串,必须准确写为“2.0” method //包含所要调用方法名称的字符串,以rpc开头的方法名,用英文句号(U+002E or ASCII 46)连接的为预留给rpc内部的方法名及扩展名,且不能在其他地方使用。 params //调用方法所需要的结构化参数值,该成员参数可以被省略。 id //已建立客户端的唯一标识id,值必须包含一个字符串、数值或NULL空值。如果不包含该成员则被认定为是一个通知。该值一般不为NULL,若为数值则不应该包含小数。 |
好了,大概了解了这些信息后,我们就可以尝试写出一个 RPC 调用对象:
1 |
{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1} |
关于格式规则部分我们就介绍这么多,要了解更多关于 JSON-RPC 的信息请看文章末尾“延伸阅读”中的相关链接。
Swift 里的网络请求
在 Swift 里边,我们使用网络请求的话还是会使用 Cocoa 里的 API,一般我们常用的即 NSURLConnection ,使用它的 sendSynchronousRequest(_:returningResponse:) 方法来进行网络请求,但不幸的是这个方法将终止在 OS X 10.11 了,所以我们应当尽量不去使用它。
NSURLSession
它是彻底重构过的 NSURLConnection ,在 iOS 7 中引入,我们使用它来替代 NSURLConnection ,不过,既然重构了,那么使用方法也会有些许的不同,现在我们就来尝试看看使用 NSURLSession 获取数据。
首先来说说 NSURLSession 并不是一个类,而是一个工厂,它最终会返回一个 NSURLSessionDataTask ,而我们需要调用 task 的 .resume() 方法来启动链接,这里我们用到 NSURLSession 的 dataTaskWithRequest(_:) 方法来产生 NSURLSessionDataTask 实例。
1 2 3 |
let s = NSURLRequest(URL: NSURL(string: "https://www.logcg.com/")!) NSURLSession.sharedSession().dataTaskWithRequest(s).resume() |
这里我们创建了一个落格博客首页的 Request ,然后从 NSURLSession 的 sharedSession() 方法直接获取默认配置的 NSURLSession 实例,然后调用其中的 dataTaskWithRequest(_:) 方法,传入 Request ,由于这东西是并行的我们直接顺便调用 resume() 让它立即启动。
……等等,这么执行好像什么也没有?对,由于我们还要得到服务器返回的 JSON 对象,所以我们还要给 dataTaskWithRequest(_:) 方法追加一个闭包进去——如果你不知道什么是闭包,也没关系,其实就是多传入一个函数作为参数,这个函数用来处理获取到的数据:
1 2 3 4 5 6 |
let s = NSURLRequest(URL: NSURL(string: "https://www.logcg.com/")!) NSURLSession.sharedSession().dataTaskWithRequest(s, completionHandler: { (data, res, e) -> Void in let t = NSString(data: data!, encoding: NSUTF8StringEncoding) print(t) }).resume() |
按照 dataTaskWithRequest(_:) 方法的声明:
1 |
public func dataTaskWithRequest(request: NSURLRequest, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDataTask |
我们的函数需要接收三个实际参数,而且不能带有返回值,那么我们的函数实际上也可以单独声明为这样:
1 2 3 |
func handler (data:NSData?, response:NSURLResponse?, e:NSError?) { //Do something here. } |
总之,按照上边的代码执行,我们就可以在终端看到获取到的落格首页的 HTML 数据了。
对于要发送更多的数据进去,使用 NSURLRequest 必然是不够的,这里我们要使用 NSMutableURLRequest ,它是 NSURLRequest 的一个子类,拥有更多的功能,比如添加 HTTP 头,以及添加我们所需要提交的 HTTP Body 信息:
1 2 3 4 5 6 |
let request = NSMutableURLRequest(URL: NSURL(string: url)!) request.HTTPMethod = "POST" request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("application/json", forHTTPHeaderField: "Accept") request.HTTPBody = //我们的 JSON 数据 |
稍后我们来把它提交到服务器。
Swift 里的 JSON
说起来,Swift 对 JSON 的处理至今也不尽人意,不过好在我们只需要简单地将 RPC 调用封装,所以用起来也不会那么困难。
NSJSONSerialization
我们通过这个类将 Swift 里的对象转换为 JSON 对象,它能把 Swift 里的 NSString、NSNumber、NSArray、NSDictionary、NSNull 与 JSON 互转,这里我们只用到 NSDictionary 就可以了,因为这个最方便。
我们只需要写好一个对应的 NSDictionary 然后传给 dataWithJSONObject 方法即可:
1 2 3 |
let data = ["jsonrpc": "2.0", "method": "subtract", "params":"getinfo", "id": 1] request.HTTPBody = try! NSJSONSerialization.dataWithJSONObject(data, options: NSJSONWritingOptions.PrettyPrinted) |
这样,我们的 JSON-RPC 命令就写好了!
通过 HTTP 进行 JSON-RPC 远程调用
最后要做的就是把它发出去了!
不过,这个测试你可能需要在项目而不是 Playground 里做,因为在 iOS 9 以后苹果引入了新特性 App Transport Security (ATS),默认禁止了不安全的 HTTP 链接,如果你测试的地址是 HTTPS 则无所谓,如果你的地址是 HTTP,那你需要先禁用这个安全机制。
禁用 App Transport Security (ATS)
在你的项目当中找到项目的属性,然后进入 Info 选项卡,在里边添加一条 NSAppTransportSecurity 类型的 Dictionary ,然后再在 NSAppTransportSecurity 里边添加一条 NSAllowsArbitraryLoads 键,对应的值为 Boolean 类型的 YES 。
如果你尝试使用 CLI 项目测试,那估计你找不到 Info.plist 文件……所以还是不要尝试了,不然你不但得不到期望的结果,连错误提示都不会得到。
封装一个类型方法
好了,做足了前期的准备,我们来看看如何封装一个类来进行 RPC 调用:
首先我们写一个叫做 JSONRPC 的类,这里边写一个类型方法 post :
1 2 3 4 5 6 |
class JSONRPC { class func post (params : NSDictionary, url : String, postCompleted : (succeeded: Bool, msg: String, data:String) -> ()) {} } |
这个类型方法接收一个 NSDictionary 的实际参数,它就是我们要发送的数据;然后是服务器的地址,最后接收一个闭包,它用来产生回调,这个闭包包含:
一个 succeeded 用来给你判断是否成功, msg 返回 HTTP Response,如果成功无错,则 data 会包含返回的 JSON。
首先我们根据传入的信息来准备数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let request = NSMutableURLRequest(URL: NSURL(string: url)!) let session = NSURLSession.sharedSession() request.HTTPMethod = "POST" do { request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params, options: NSJSONWritingOptions.PrettyPrinted) } catch (let e) { print(e) return } request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("application/json", forHTTPHeaderField: "Accept") |
然后我们把数据传入,同时扔一个闭包进去根据结果来执行我们函数接收的那个函数(也就是回调函数):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in let json = try? NSJSONSerialization.JSONObjectWithData(data!, options: .MutableLeaves) as? NSDictionary guard json != nil else { let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding) print("Error could not parse JSON: \(jsonStr)") postCompleted(succeeded: false, msg:String(response),data: "Error") return } postCompleted(succeeded: true, msg:String(response),data: NSString(data: data!, encoding: NSUTF8StringEncoding)! as String) }).resume() |
总之,最终的类型方法是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import Foundation class JSONRPC { class func post (params : NSDictionary, url : String, postCompleted : (succeeded: Bool, msg: String, data:String) -> ()) { let request = NSMutableURLRequest(URL: NSURL(string: url)!) let session = NSURLSession.sharedSession() request.HTTPMethod = "POST" do { request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params, options: NSJSONWritingOptions.PrettyPrinted) } catch (let e) { print(e) return } request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("application/json", forHTTPHeaderField: "Accept") session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in let json = try? NSJSONSerialization.JSONObjectWithData(data!, options: .MutableLeaves) as? NSDictionary guard json != nil else { let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding) print("Error could not parse JSON: \(jsonStr)") postCompleted(succeeded: false, msg:String(response),data: "Error") return } postCompleted(succeeded: true, msg:String(response),data: NSString(data: data!, encoding: NSUTF8StringEncoding)! as String) }).resume() } } |
测试
好了,最终我们来测试一下,这里本地我运行了一个 Twisterd 客户端进程,如果你不知道它是什么……就去看看文章末尾的延伸阅读……总之,我们来给它发送 RPC 指令:
1 2 3 4 5 6 7 8 |
let cmd:NSDictionary = ["jsonrpc":"2.0","method":"getinfo","id":"1"] JSONRPC.post(cmd, url: "http://user:pwd@127.0.0.1:28332") { (succeeded, msg, data) -> () in print(succeeded) print(msg) print(data) } |
最终,我们会得到结果:
1 2 3 4 5 6 7 8 9 10 |
true Optional(<NSHTTPURLResponse: 0x600000027620> { URL: http://user:pwd@127.0.0.1:28332/ } { status code: 200, headers { Connection = "keep-alive"; "Content-Length" = 1004; "Content-Security-Policy" = "script-src 'self' 'unsafe-eval'"; "Content-Type" = "application/json"; Date = "Fri, 12 Feb 2016 09:17:17 +0000"; Server = "bitcoin-json-rpc/v0.9.34.0-unk-beta"; } }) {"result":{"version":93400,"protocolversion":70003,"walletversion":60000,"blocks":124132,"timeoffset":0,"connections":25,"addrman_total":13235,"addrman_get":2500,"dht_nodes":111,"dht_global_nodes":444,"proxy":"","ext_port1":28333,"ext_port2":29333,"difficulty":0.01040237,"testnet":false,"public_server_mode":false,"errors":"This is a pre-release test build - use at your own risk","ext_addr_net1":"110.253.91.44","ext_addr_net2":"110.253.91.44","dht_torrents":761,"num_peers":235,"peerlist_size":30526,"num_active_requests":5,"download_rate":8330,"upload_rate":18828,"dht_download_rate":5722,"dht_upload_rate":15055,"ip_overhead_download_rate":1724,"ip_overhead_upload_rate":1974,"payload_download_rate":0,"payload_upload_rate":0,"total_download":123761426,"total_upload":177836912,"total_dht_download":92672437,"total_dht_upload":131644914,"total_ip_overhead_download":19615000,"total_ip_overhead_upload":23216840,"total_payload_download":1263802,"total_payload_upload":791702},"error":null,"id":null} |
接下来就是你操作这些结果的时间了:)
延伸阅读
swift通过HttpRequest请求发送json格式的数据
本文由 落格博客 原创撰写:落格博客 » Swift 通过 http 发送 JSON-RPC 命令
转载请保留出处和原文链接:https://www.logcg.com/archives/1554.html