测试已测试 | ✗ |
Lang语言 | SwiftSwift |
许可证 | MIT |
Released最后发布 | 2016年4月 |
SPM支持 SPM | ✗ |
由 Franco Castellano 维护。
依赖 | |
Alamofire | >= 0 |
MagicalRecord | >= 0 |
Alamofire 是一个用 Swift 编写的 HTTP 网络库。
为了使 Alamofire 专注于核心网络实现,Alamofire 软件基金会创建了额外的组件库,以将更多功能引入 Alamofire 生态系统。
UIImage
和UIImageView
扩展、自定义图像过滤器、自动清除内存缓存的图像和基于优先级的图像下载系统。NSURLSession
实例。嵌入式框架要求最低部署目标为 iOS 8 或 OS X Mavericks(10.9)。
由于不支持框架,Alamofire不再支持iOS 7。没有框架,在iOS 7上运行Travis-CI将需要一个第二个复制的测试目标。单独的测试套件需要导入所有Swift文件,测试需要重复书写。这种拆分难以维护,无法确保Alamofire生态系统的最高质量。
如果您不想使用上述任何依赖管理器,您可以手动将Alamofire集成到项目中。
cd
命令进入顶级项目目录。如果您的项目没有初始化为git仓库,请运行以下命令:$ git init
$ git submodule add https://github.com/Alamofire/Alamofire.git
打开新的Alamofire
文件夹,将Alamofire.xcodeproj
拖入应用程序Xcode项目的项目导航器。
它应该出现在应用程序蓝色项目图标下面。它是否位于所有其他Xcode组之上或之下无关紧要。
在项目导航器中选择Alamofire.xcodeproj
,并确保部署目标与您的应用程序目标相匹配。
+
按钮。您将看到两个不同的Alamofire.xcodeproj
文件夹,每个文件夹中都有一个嵌套在Products
文件夹中的不同版本的Alamofire.framework
。
您可以选择哪个
Products
文件夹无关紧要,但您必须选择顶部的或底部的Alamofire.framework
。
对于iOS选择顶部的Alamofire.framework
,对于OS X选择底部的。
您可以通过查看项目的构建日志来验证您选择了哪个。Alamofire的构建目标会被列为
Alamofire iOS
或Alamofire OSX
。
这就完成了!
Alamofire.framework
在自动添加为目标依赖项,链接框架和嵌入式框架的复制文件构建阶段后被添加,这是在模拟器和设备上构建所需的所有内容。
import Alamofire
Alamofire.request(.GET, "https://httpbin.org/get")
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.responseJSON { response in
print(response.request) // original URL request
print(response.response) // URL response
print(response.data) // server data
print(response.result) // result of response serialization
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
}
Alamofire中的网络操作是异步进行的。对于不熟悉异步编程概念的程序员来说,异步编程可能是一个令人沮丧的原因,但这样做有很多很好的原因。
而不是阻塞执行等待来自服务器的响应,指定一个回调以处理接收到的响应。请求的结果仅在响应处理程序的作用域内可用。任何依赖于响应或从服务器接收到的数据执行的任何操作必须在处理程序内完成。
内置响应方法
response()
responseData()
responseString(encoding: NSStringEncoding)
responseJSON(options: NSJSONReadingOptions)
responsePropertyList(options: NSPropertyListReadOptions)
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.response { request, response, data, error in
print(request)
print(response)
print(data)
print(error)
}
response
序列化器不会评估任何响应数据。它仅将来自 URL 会话代表的全部信息直接转发。我们强烈建议您利用其他响应序列化器,利用Response
和Result
类型。
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.responseData { response in
print(response.request)
print(response.response)
print(response.result)
}
Alamofire.request(.GET, "https://httpbin.org/get")
.responseString { response in
print("Success: \(response.result.isSuccess)")
print("Response String: \(response.result.value)")
}
Alamofire.request(.GET, "https://httpbin.org/get")
.responseJSON { response in
debugPrint(response)
}
响应处理程序甚至可以串联使用
Alamofire.request(.GET, "https://httpbin.org/get")
.responseString { response in
print("Response String: \(response.result.value)")
}
.responseJSON { response in
print("Response JSON: \(response.result.value)")
}
Alamofire.Method
列出了在 RFC 7231 §4.3 中定义的 HTTP 方法
public enum Method: String {
case OPTIONS, GET, HEAD, POST, PUT, PATCH, DELETE, TRACE, CONNECT
}
这些值可以作为 Alamofire.request
方法的第一个参数传递
Alamofire.request(.POST, "https://httpbin.org/post")
Alamofire.request(.PUT, "https://httpbin.org/put")
Alamofire.request(.DELETE, "https://httpbin.org/delete")
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
// https://httpbin.org/get?foo=bar
let parameters = [
"foo": "bar",
"baz": ["a", 1],
"qux": [
"x": 1,
"y": 2,
"z": 3
]
]
Alamofire.request(.POST, "https://httpbin.org/post", parameters: parameters)
// HTTP body: foo=bar&baz[]=a&baz[]=1&qux[x]=1&qux[y]=2&qux[z]=3
您还可以使用 ParameterEncoding
枚举将参数编码为 JSON、属性列表或任何自定义格式
enum ParameterEncoding {
case URL
case URLEncodedInURL
case JSON
case PropertyList(format: NSPropertyListFormat, options: NSPropertyListWriteOptions)
case Custom((URLRequestConvertible, [String: AnyObject]?) -> (NSMutableURLRequest, NSError?))
func encode(request: NSURLRequest, parameters: [String: AnyObject]?) -> (NSURLRequest, NSError?)
{ ... }
}
URL
:要设置或附加到任何现有 URL 查询的查询字符串,用于 GET
、HEAD
和 DELETE
请求,或设置其他任何 HTTP 方法的主体。对于带有 HTTP 体的编码请求,其 Content-Type
HTTP 报头字段设置为 application/x-www-form-urlencoded
。由于没有关于如何编码集合类型的发布规范,Alamofire 遵循在数组值键后附加 []
的惯例(例如 foo[]=1&foo[]=2
),以及将键括在方括号中附加到嵌套字典值中(例如 foo[bar]=baz
)。URLEncodedInURL
:创建用于设置或附加到任何现有 URL 查询的查询字符串。使用与 .URL
情况相同的实现,但始终将编码后的结果应用于 URL。JSON
:使用 NSJSONSerialization
创建参数对象的 JSON 表示形式,并将其设置为请求的主体。编码请求的 Content-Type
HTTP 报头字段设置为 application/json
。PropertyList
:使用 NSPropertyListSerialization
创建参数对象的 plist 表示形式,根据关联的格式和写入选项值,并将其设置为请求的主体。编码请求的 Content-Type
HTTP 报头字段设置为 application/x-plist
。Custom
:使用关联的闭包值根据现有请求和参数构造新的请求。let URL = NSURL(string: "https://httpbin.org/get")!
var request = NSMutableURLRequest(URL: URL)
let parameters = ["foo": "bar"]
let encoding = Alamofire.ParameterEncoding.URL
(request, _) = encoding.encode(request, parameters: parameters)
let parameters = [
"foo": [1,2,3],
"bar": [
"baz": "qux"
]
]
Alamofire.request(.POST, "https://httpbin.org/post", parameters: parameters, encoding: .JSON)
// HTTP body: {"foo": [1, 2, 3], "bar": {"baz": "qux"}}
可以在全局的request
方法中直接添加自定义的HTTP头部到Request
中,这使得很容易为新更改的HTTP头部附加到Request
上。
对于不经常更改的HTTP头部,建议在
NSURLSessionConfiguration
中设置它们,以确保它们能够自动应用到由底层的NSURLSession
创建的任何NSURLSessionTask
上。
let headers = [
"Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
"Content-Type": "application/x-www-form-urlencoded"
]
Alamofire.request(.GET, "https://httpbin.org/get", headers: headers)
.responseJSON { response in
debugPrint(response)
}
缓存在系统框架级别由NSURLCache
处理。
支持的上传类型
let fileURL = NSBundle.mainBundle().URLForResource("Default", withExtension: "png")
Alamofire.upload(.POST, "https://httpbin.org/post", file: fileURL)
Alamofire.upload(.POST, "https://httpbin.org/post", file: fileURL)
.progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
print(totalBytesWritten)
// This closure is NOT called on the main queue for performance
// reasons. To update your ui, dispatch to the main queue.
dispatch_async(dispatch_get_main_queue()) {
print("Total bytes written on main queue: \(totalBytesWritten)")
}
}
.responseJSON { response in
debugPrint(response)
}
Alamofire.upload(
.POST,
"https://httpbin.org/post",
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(fileURL: unicornImageURL, name: "unicorn")
multipartFormData.appendBodyPart(fileURL: rainbowImageURL, name: "rainbow")
},
encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.responseJSON { response in
debugPrint(response)
}
case .Failure(let encodingError):
print(encodingError)
}
}
)
支持的下载类型
Alamofire.download(.GET, "https://httpbin.org/stream/100") { temporaryURL, response in
let fileManager = NSFileManager.defaultManager()
let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let pathComponent = response.suggestedFilename
return directoryURL.URLByAppendingPathComponent(pathComponent!)
}
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
Alamofire.download(.GET, "https://httpbin.org/stream/100", destination: destination)
Alamofire.download(.GET, "https://httpbin.org/stream/100", destination: destination)
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
print(totalBytesRead)
// This closure is NOT called on the main queue for performance
// reasons. To update your ui, dispatch to the main queue.
dispatch_async(dispatch_get_main_queue()) {
print("Total bytes read on main queue: \(totalBytesRead)")
}
}
.response { _, _, _, error in
if let error = error {
print("Failed with error: \(error)")
} else {
print("Downloaded file successfully")
}
}
Alamofire.download(.GET, "https://httpbin.org/stream/100", destination: destination)
.response { _, _, data, _ in
if let
data = data,
resumeDataString = NSString(data: data, encoding: NSUTF8StringEncoding)
{
print("Resume Data: \(resumeDataString)")
} else {
print("Resume Data was empty")
}
}
data
参数会在有可用resumeData
的情况下自动填充。
let download = Alamofire.download(.GET, "https://httpbin.org/stream/100", destination: destination)
download.response { _, _, _, _ in
if let
resumeData = download.resumeData,
resumeDataString = NSString(data: resumeData, encoding: NSUTF8StringEncoding)
{
print("Resume Data: \(resumeDataString)")
} else {
print("Resume Data was empty")
}
}
认证在系统框架级别由NSURLCredential
和NSURLAuthenticationChallenge
处理。
支持的认证方案
在Request
上的authenticate
方法会自动在适合的情况下提供一个NSURLCredential
到NSURLAuthenticationChallenge
。
let user = "user"
let password = "password"
Alamofire.request(.GET, "https://httpbin.org/basic-auth/\(user)/\(password)")
.authenticate(user: user, password: password)
.responseJSON { response in
debugPrint(response)
}
根据您的服务器实现,可能还需要设置一个Authorization
头部。
let user = "user"
let password = "password"
let credentialData = "\(user):\(password)".dataUsingEncoding(NSUTF8StringEncoding)!
let base64Credentials = credentialData.base64EncodedStringWithOptions([])
let headers = ["Authorization": "Basic \(base64Credentials)"]
Alamofire.request(.GET, "https://httpbin.org/basic-auth/user/password", headers: headers)
.responseJSON { response in
debugPrint(response)
}
let user = "user"
let password = "password"
let credential = NSURLCredential(user: user, password: password, persistence: .ForSession)
Alamofire.request(.GET, "https://httpbin.org/basic-auth/\(user)/\(password)")
.authenticate(usingCredential: credential)
.responseJSON { response in
debugPrint(response)
}
默认情况下,Alamofire将完成的任何请求视为成功的,无论响应的内容如何。在响应处理程序之前调用validate
将在响应有不可接受的响应状态码或MIME类型时生成错误。
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.response { response in
print(response)
}
自动验证 200...299
范围内的状态码,且响应的 Content-Type
响应头与请求的 Accept
请求头相匹配,如果提供了的话。
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.validate()
.responseJSON { response in
switch response.result {
case .Success:
print("Validation Successful")
case .Failure(let error):
print(error)
}
}
Alamofire 在请求的生命周期中收集时间信息,并创建一个暴露为 Response
属性的 Timeline
对象。
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.validate()
.responseJSON { response in
print(response.timeline)
}
上述报告了以下 Timeline
信息
延迟
:0.428秒请求持续时间
:0.428秒序列化持续时间
:0.001秒总持续时间
:0.429秒let request = Alamofire.request(.GET, "https://httpbin.org/ip")
print(request)
// GET https://httpbin.org/ip (200)
let request = Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
debugPrint(request)
$ curl -i \
-H "User-Agent: Alamofire" \
-H "Accept-Encoding: Accept-Encoding: gzip;q=1.0,compress;q=0.5" \
-H "Accept-Language: en;q=1.0,fr;q=0.9,de;q=0.8,zh-Hans;q=0.7,zh-Hant;q=0.6,ja;q=0.5" \
"https://httpbin.org/get?foo=bar"
Alamofire 构建于
NSURLSession
和 Foundation URL 加载系统之上。为了充分利用此框架,建议您熟悉底层网络堆栈的概念和能力。
推荐阅读
顶级便利方法像 Alamofire.request
使用 Alamofire.Manager
的共享实例,并使用默认的 NSURLSessionConfiguration
进行配置。
因此,以下两个语句是等价的
Alamofire.request(.GET, "https://httpbin.org/get")
let manager = Alamofire.Manager.sharedInstance
manager.request(NSURLRequest(URL: NSURL(string: "https://httpbin.org/get")!))
应用程序可以创建用于后台和短暂会话的管理器,以及自定义默认会话配置的新管理器,例如默认标题(HTTPAdditionalHeaders
)或超时间隔(timeoutIntervalForRequest
)。
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let manager = Alamofire.Manager(configuration: configuration)
let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("com.example.app.background")
let manager = Alamofire.Manager(configuration: configuration)
let configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration()
let manager = Alamofire.Manager(configuration: configuration)
var defaultHeaders = Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders ?? [:]
defaultHeaders["DNT"] = "1 (Do Not Track Enabled)"
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = defaultHeaders
let manager = Alamofire.Manager(configuration: configuration)
这不推荐用于
Authorization
或Content-Type
响应头。相反,请分别使用URLRequestConvertible
和ParameterEncoding
。
请求、上传或下载数据的结果是 Alamofire.Request
的一个实例。请求始终使用拥有管理器的构造方法创建,并且永远不会直接初始化。
如 authenticate
、validate
和 responseData
之类的函数返回调用者,以方便链式调用。
请求可以被挂起、恢复和取消
suspend()
:挂起底层任务和调度队列resume()
:恢复底层任务和调度队列。如果拥有管理器的 startRequestsImmediately
设置为 true
,则请求必须调用 resume()
才能开始。cancel()
: 取消基础任务,产生一个错误,该错误传递给所有已注册的响应处理器。Alamofire 为字符串、JSON 和属性列表提供了内置的响应序列化,但可以在 Alamofire.Request
的扩展中添加其他序列化。
例如,以下是使用 Ono 实现的响应处理器的一个示例
extension Request {
public static func XMLResponseSerializer() -> ResponseSerializer<ONOXMLDocument, NSError> {
return ResponseSerializer { request, response, data, error in
guard error == nil else { return .Failure(error!) }
guard let validData = data else {
let failureReason = "Data could not be serialized. Input data was nil."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
do {
let XML = try ONOXMLDocument(data: validData)
return .Success(XML)
} catch {
return .Failure(error as NSError)
}
}
}
public func responseXMLDocument(completionHandler: Response<ONOXMLDocument, NSError> -> Void) -> Self {
return response(responseSerializer: Request.XMLResponseSerializer(), completionHandler: completionHandler)
}
}
可以使用泛型来实现自动、类型安全的响应对象序列化。
public protocol ResponseObjectSerializable {
init?(response: NSHTTPURLResponse, representation: AnyObject)
}
extension Request {
public func responseObject<T: ResponseObjectSerializable>(completionHandler: Response<T, NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<T, NSError> { request, response, data, error in
guard error == nil else { return .Failure(error!) }
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, data, error)
switch result {
case .Success(let value):
if let
response = response,
responseObject = T(response: response, representation: value)
{
return .Success(responseObject)
} else {
let failureReason = "JSON could not be serialized into response object: \(value)"
let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
final class User: ResponseObjectSerializable {
let username: String
let name: String
init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.username = response.URL!.lastPathComponent!
self.name = representation.valueForKeyPath("name") as! String
}
}
Alamofire.request(.GET, "https://example.com/users/mattt")
.responseObject { (response: Response<User, NSError>) in
debugPrint(response)
}
同样的方法也可以用来处理返回对象集合表示的端点。
public protocol ResponseCollectionSerializable {
static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [Self]
}
extension Alamofire.Request {
public func responseCollection<T: ResponseCollectionSerializable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<[T], NSError> { request, response, data, error in
guard error == nil else { return .Failure(error!) }
let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONSerializer.serializeResponse(request, response, data, error)
switch result {
case .Success(let value):
if let response = response {
return .Success(T.collection(response: response, representation: value))
} else {
let failureReason = "Response collection could not be serialized due to nil response"
let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
final class User: ResponseObjectSerializable, ResponseCollectionSerializable {
let username: String
let name: String
init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.username = response.URL!.lastPathComponent!
self.name = representation.valueForKeyPath("name") as! String
}
static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [User] {
var users: [User] = []
if let representation = representation as? [[String: AnyObject]] {
for userRepresentation in representation {
if let user = User(response: response, representation: userRepresentation) {
users.append(user)
}
}
}
return users
}
}
Alamofire.request(.GET, "http://example.com/users")
.responseCollection { (response: Response<[User], NSError>) in
debugPrint(response)
}
采用 URLStringConvertible
协议的类型可用于构建 URL 字符串,这些字符串随后用于构建 URL 请求。NSString
、NSURL
、NSURLComponents
和 NSURLRequest
默认符合 URLStringConvertible
,允许将它们中的任何一种作为 URLString
参数传递给 request
、upload
和 download
方法。
let string = NSString(string: "https://httpbin.org/post")
Alamofire.request(.POST, string)
let URL = NSURL(string: string)!
Alamofire.request(.POST, URL)
let URLRequest = NSURLRequest(URL: URL)
Alamofire.request(.POST, URLRequest) // overrides `HTTPMethod` of `URLRequest`
let URLComponents = NSURLComponents(URL: URL, resolvingAgainstBaseURL: true)
Alamofire.request(.POST, URLComponents)
建议与网络应用程序进行重大交互的应用程序使自定义类型符合 URLStringConvertible
,以方便将特定领域模型映射到服务器资源。
extension User: URLStringConvertible {
static let baseURLString = "http://example.com"
var URLString: String {
return User.baseURLString + "/users/\(username)/"
}
}
let user = User(username: "mattt")
Alamofire.request(.GET, user) // http://example.com/users/mattt
采用 URLRequestConvertible
协议的类型可用于构建 URL 请求。NSURLRequest
默认符合 URLRequestConvertible
,可以直接将其传递到 request
、upload
和 download
方法中(这是指定单个请求的自定义 HTTP 体的推荐方法)。
let URL = NSURL(string: "https://httpbin.org/post")!
let mutableURLRequest = NSMutableURLRequest(URL: URL)
mutableURLRequest.HTTPMethod = "POST"
let parameters = ["foo": "bar"]
do {
mutableURLRequest.HTTPBody = try NSJSONSerialization.dataWithJSONObject(parameters, options: NSJSONWritingOptions())
} catch {
// No-op
}
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
Alamofire.request(mutableURLRequest)
建议与网络应用程序进行重大交互的应用程序使自定义类型符合 URLRequestConvertible
,以确保请求端点的持续性。此方法可以用来抽象服务器端的不一致性,提供类型安全路由,以及管理身份验证凭据和其他状态。
enum Router: URLRequestConvertible {
static let baseURLString = "http://example.com"
static let perPage = 50
case Search(query: String, page: Int)
// MARK: URLRequestConvertible
var URLRequest: NSMutableURLRequest {
let result: (path: String, parameters: [String: AnyObject]) = {
switch self {
case .Search(let query, let page) where page > 1:
return ("/search", ["q": query, "offset": Router.perPage * page])
case .Search(let query, _):
return ("/search", ["q": query])
}
}()
let URL = NSURL(string: Router.baseURLString)!
let URLRequest = NSURLRequest(URL: URL.URLByAppendingPathComponent(result.path))
let encoding = Alamofire.ParameterEncoding.URL
return encoding.encode(URLRequest, parameters: result.parameters).0
}
}
Alamofire.request(Router.Search(query: "foo bar", page: 1)) // ?q=foo%20bar&offset=50
enum Router: URLRequestConvertible {
static let baseURLString = "http://example.com"
static var OAuthToken: String?
case CreateUser([String: AnyObject])
case ReadUser(String)
case UpdateUser(String, [String: AnyObject])
case DestroyUser(String)
var method: Alamofire.Method {
switch self {
case .CreateUser:
return .POST
case .ReadUser:
return .GET
case .UpdateUser:
return .PUT
case .DestroyUser:
return .DELETE
}
}
var path: String {
switch self {
case .CreateUser:
return "/users"
case .ReadUser(let username):
return "/users/\(username)"
case .UpdateUser(let username, _):
return "/users/\(username)"
case .DestroyUser(let username):
return "/users/\(username)"
}
}
// MARK: URLRequestConvertible
var URLRequest: NSMutableURLRequest {
let URL = NSURL(string: Router.baseURLString)!
let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path))
mutableURLRequest.HTTPMethod = method.rawValue
if let token = Router.OAuthToken {
mutableURLRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
switch self {
case .CreateUser(let parameters):
return Alamofire.ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0
case .UpdateUser(_, let parameters):
return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0
default:
return mutableURLRequest
}
}
}
Alamofire.request(Router.ReadUser("mattt")) // GET /users/mattt
在与服务器和 Web 服务通信时使用安全的 HTTPS 连接是确保敏感数据的步骤之一。默认情况下,Alamofire 将使用 Apple Security 框架提供的内置验证评估服务器提供的证书链。虽然这保证了证书链的有效性,但并不能防止中间人 (MITM) 攻击或其他潜在漏洞。为了减轻 MITM 攻击,处理敏感客户数据或财务信息的应用程序应使用 ServerTrustPolicy
提供的证书或公钥锚定。
ServerTrustPolicy
枚举用于评估在安全 HTTPS 连接下连接到服务器时由 NSURLAuthenticationChallenge
提供的服务器信任程度。
let serverTrustPolicy = ServerTrustPolicy.PinCertificates(
certificates: ServerTrustPolicy.certificatesInBundle(),
validateCertificateChain: true,
validateHost: true
)
有许多不同类型的服务器信任评估情况,允许您完全控制验证过程。
PerformDefaultEvaluation
:使用默认的服务器信任评估,同时允许您控制是否验证挑战提供的主机。PinCertificates
:使用固定证书来验证服务器信任。如果其中一个固定证书与服务器证书之一相匹配,则认为服务器信任是有效的。PinPublicKeys
:使用固定公钥来验证服务器信任。如果有其中一个固定公钥与服务器证书的公钥之一相匹配,则认为服务器信任是有效的。DisableEvaluation
:禁用所有评估,从而始终将任何服务器信任视为有效。CustomEvaluation
:使用关联的闭包来评估服务器信任的有效性,从而完全控制验证过程。请谨慎使用。ServerTrustPolicyManager
负责将服务器信任策略的内部分配映射到特定主机。这允许 Alamofire 对每个主机进行不同的服务器信任策略评估。
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"test.example.com": .PinCertificates(
certificates: ServerTrustPolicy.certificatesInBundle(),
validateCertificateChain: true,
validateHost: true
),
"insecure.expired-apis.com": .DisableEvaluation
]
let manager = Manager(
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
请确保保留对新
Manager
实例的引用,否则当您的manager
被释放时,所有请求都将被取消。
这些服务器信任策略会产生以下行为
test.example.com
将始终使用带有证书链和启用主机验证的证书固定,因此需要满足以下标准以允许 TLS 握手成功:insecure.expired-apis.com
从不评估证书链,并且始终允许 TLS 握手成功。如果您发现自己需要更灵活的服务器信任策略匹配行为(例如,通配符域名),则可以子类化 ServerTrustPolicyManager
并用自己的自定义实现覆盖 serverTrustPolicyForHost
方法。
class CustomServerTrustPolicyManager: ServerTrustPolicyManager {
override func serverTrustPolicyForHost(host: String) -> ServerTrustPolicy? {
var policy: ServerTrustPolicy?
// Implement your custom domain matching behavior...
return policy
}
}
.PerformDefaultEvaluation
、.PinCertificates
和 .PinPublicKeys
服务器信任策略都接受一个 validateHost
参数。将值设置为 true
将导致服务器信任评估验证证书中的主机名是否与挑战中的主机名匹配。如果不匹配,则评估失败。一个 validateHost
的值为 false
仍将评估完整的证书链,但不会验证叶证书的主机名。
建议在生产环境中始终将
validateHost
设置为true
。
将证书和公钥进行固定时,都可通过使用 validateCertificateChain
参数来验证证书链。将该值设置为 true
,将评估完整的证书链,除了对固定的证书或公钥进行字节数据的相等性检查。设置为 false
将跳过证书链验证,但仍然执行字节数据的相等性检查。
在某些情况下,禁用证书链的验证可能是有意义的。禁用验证最常见的用例是自签名证书和过期证书。在这两种情况下,评估总是会失败,但字节数据的相等性检查将确保您从服务器接收到的确实是您预期的证书。
建议在生产环境中始终将
validateCertificateChain
设置为true
。
自 iOS 9 添加了应用传输安全(ATS)功能以来,使用带有几个 ServerTrustPolicy
对象的自定义 ServerTrustPolicyManager
可能不会有任何效果。如果您持续看到 CFNetwork SSLHandshake failed (-9806)
错误,您可能遇到了这个问题。苹果的 ATS 系统将在您未在应用的 plist 中配置 ATS 设置以禁用足够的功能以便应用评估服务器信任之前,覆盖整个挑战系统。
如果您遇到了这个问题(使用自签名证书时可能性很高),您可以通过将以下内容添加到您的 Info.plist
文件中来解决这个问题。
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>example.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
<!-- Optional: Specify minimum TLS version -->
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
</dict>
</dict>
</dict>
</dict>
您是否需要将 NSExceptionRequiresForwardSecrecy
设置为 NO
取决于您的 TLS 连接是否使用了一个允许的加密套件。在某些情况下,它必须设置为 NO
。为了允许 SessionDelegate
接收到挑战回调,NSExceptionAllowsInsecureHTTPLoads
必须设置为 YES
。一旦启动挑战回调,ServerTrustPolicyManager
将接管服务器信任评估。如果您尝试连接到一个只支持小于 1.2
版本的 TLS 的主机,您可能还需要指定 NSTemporaryExceptionMinimumTLSVersion
。
建议在生产环境中始终使用有效的证书。
NetworkReachabilityManager
监听 WWAN 和 WiFi 网络接口上主机和地址的可达性变化。
let manager = NetworkReachabilityManager(host: "www.apple.com")
manager?.listener = { status in
print("Network Status Changed: \(status)")
}
manager?.startListening()
请确保记住在上面的示例中保留
manager
,否则不会报告任何状态变化。
在使用网络可达性来确定下一步行动时,有一些重要的事情需要注意。
建议查看 WWDC 2012 演讲 706,“网络最佳实践” 以获取更多信息。
以下雷达信息对 Alamofire 当前的实现有些影响。
Alamofire 的名字来源于 阿拉莫之花,它是德克萨斯州州花蓝bonnet 的杂交变种。
Alamofire 由 Alamofire 软件基金会 拥有并维护。您可以在 Twitter 上关注他们 @AlamofireSF,获取项目更新和发布信息。
如果您认为您已经识别出与 Alamofire 相关的安全漏洞,请尽快通过电子邮件报告给 [email protected]。请不要在公开的问题跟踪器中发布。
Alamofire 在 MIT 许可协议下发布。详细信息请参阅 LICENSE 文件。