C41e122d3973b5845ea4b0140f0d1e66
Alamofire(6)— 多表单上传

😊😊😊Alamofire专题目录,欢迎及时反馈交流 😊😊😊


Alamofire 目录直通车 --- 和谐学习,不急不躁!


实际开发过程中,多表单上传是非常重要的一种请求!服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。 所以说到 POST 提交数据方案,包含了 Content-Type 和消息主体编码方式两部分。 这个篇章我们来探索一下 多表单上传文件 ~

一、多表单格式

下面我通过 Charles 抓包上传图片的接口

  • --alamofire.boundary.4e076f46186e231d: 是分隔符,为了方便读取数据
  • Content-Disposition: form-data; name="name": 其中 Content-dispositionMIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。Content-disposition 其实可以控制用户请求所得的内容存为一个文件的时候提供一个默认的文件名,这里就是添加了一个 key = name
  • 接在后面就是 \r\n 换行符
  • 然后就是 key 对应的 value = LGCooci
  • 最下面的乱码是图片data数据

Multipart 格式显示整个数据就类似字典的 key-value

二、我们通过URLSeesion去请求多表单

1️⃣:分隔符初始化

init() {
 self.boundary = NSUUID().uuidString
}
  • 利用 NSUUID().uuidString 设定为分隔符

2️⃣:换行符号

extension CharacterSet {
    static func MIMECharacterSet() -> CharacterSet {
        let characterSet = CharacterSet(charactersIn: "\"\n\r")
        return characterSet.inverted
    }
}

3️⃣: 数据格式处理&拼接

public func appendFormData(_ name: String, content: Data, fileName: String, contentType: String) {

    let contentDisposition = "Content-Disposition: form-data; name=\"\(self.encode(name))\"; filename=\"\(self.encode(fileName))\""
    let contentTypeHeader = "Content-Type: \(contentType)"
    let data = self.merge([
        self.toData(contentDisposition),
        MutlipartFormCRLFData,
        self.toData(contentTypeHeader),
        MutlipartFormCRLFData,
        MutlipartFormCRLFData,
        content,
        MutlipartFormCRLFData
        ])
    self.fields.append(data)
}

4️⃣:数据处理完毕,然后设置httpBody

public extension URLRequest {
    mutating func setMultipartBody(_ data: Data, boundary: String) {
        self.httpMethod = "POST"
        self.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        self.httpBody = data
        self.setValue(String( data.count ), forHTTPHeaderField: "Content-Length")
        self.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    }
}

5️⃣:多表单格式封装,以及使用

public extension URLRequest {
    mutating func setMultipartBody(_ data: Data, boundary: String) {
        self.httpMethod = "POST"
        self.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        self.httpBody = data
        self.setValue(String( data.count ), forHTTPHeaderField: "Content-Length")
        self.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    }
}

// 换行符处理
extension CharacterSet {
    static func MIMECharacterSet() -> CharacterSet {
        let characterSet = CharacterSet(charactersIn: "\"\n\r")
        return characterSet.inverted
    }
}
// 多表单工厂器
struct LGMultipartDataBuilder{
    var fields: [Data] = []
    public let boundary: String
    // 初始化 - 分隔符创建
    init() {
        self.boundary = NSUUID().uuidString
    }
    // 所有数据格式处理
    func build() -> Data? {
        let data = NSMutableData()

        for field in self.fields {
            data.append(self.toData("--\(self.boundary)"))
            data.append(MutlipartFormCRLFData)
            data.append(field)
        }
        data.append(self.toData("--\(self.boundary)--"))
        data.append(MutlipartFormCRLFData)

        return (data.copy() as! Data)
    }
    // 数据格式key value拼接
    mutating public func appendFormData(_ key: String, value: String) {
        let content = "Content-Disposition: form-data; name=\"\(encode(key))\""
        let data = self.merge([
            self.toData(content),
            MutlipartFormCRLFData,
            MutlipartFormCRLFData,
            self.toData(value),
            MutlipartFormCRLFData
            ])
        self.fields.append(data)
    }

     // 格式拼接
    mutating public func appendFormData(_ name: String, content: Data, fileName: String, contentType: String) {

        let contentDisposition = "Content-Disposition: form-data; name=\"\(self.encode(name))\"; filename=\"\(self.encode(fileName))\""
        let contentTypeHeader = "Content-Type: \(contentType)"
        let data = self.merge([
            self.toData(contentDisposition),
            MutlipartFormCRLFData,
            self.toData(contentTypeHeader),
            MutlipartFormCRLFData,
            MutlipartFormCRLFData,
            content,
            MutlipartFormCRLFData
            ])
        self.fields.append(data)
    }
    // 数据编码
    fileprivate func encode(_ string: String) -> String {
        let characterSet = CharacterSet.MIMECharacterSet()
        return string.addingPercentEncoding(withAllowedCharacters: characterSet)!
    }
    // 转成data 方便拼接 处理
    fileprivate func toData(_ string: String) -> Data {
        return string.data(using: .utf8)!
    }
    // 合并单个数据
    fileprivate func merge(_ chunks: [Data]) -> Data {
        let data = NSMutableData()
        for chunk in chunks {
            data.append(chunk)
        }
        return data.copy() as! Data
    }
}

// 整个数据的调用使用
fileprivate func dealwithRequest(urlStr:String) -> URLRequest{
    var request = URLRequest(url: URL(string: urlStr)!)
    var builder = LGMultipartDataBuilder()
    let data = self.readLocalData(fileNameStr: "Cooci", type: "jpg")
    builder.appendFormData("filedata",content:data as! Data , fileName: "fileName", contentType: "image/jpeg")
    request.setMultipartBody(builder.build()!, boundary: builder.boundary)
    return request
}

小结

很显然,如果每一次我们上传文件,都这么处理那是非常恶心的!所以封装对于开发来说是多么的重要!这里我们可以自定义封装,根据自己公司需求包装格式!但是有很多公司是不需要关系太多的,直接默认操作就OK,只要字段匹配,那么 Alamofire 这个时候就很明显感受到了舒服 👍👍👍

Alamofire 表单数据上传

Alamofire 处理多表单的方式有三种,根据 URLSession 的三个方法封装而来

// 1:上传data格式
session.uploadTask(with: urlRequest, from: data)
// 2: 上传文件地址
session.uploadTask(with: urlRequest, fromFile: url)
// 3:上传stream流数据
session.uploadTask(withStreamedRequest: urlRequest)
top Created with Sketch.