【问题标题】:Swift adding attachment for multipart/form-data POST为多部分/表单数据 POST 快速添加附件
【发布时间】:2019-06-05 16:46:10
【问题描述】:

Swift 5,Xcode 版本 10.2.1 (10E1001)

大家好,如果能提供任何帮助,我将不胜感激。

我正在创建一个调用以将附件 (PNG) 发布到我的 POST 调用。我正在打电话给 ServiceNow。如果我在正文中使用与 PostMan 相同的键,则 Postman 中的调用可以正常工作。但是,下面的附件似乎很难处理。此示例中的图像是 PNG 资源。

为了比较,如果我在 Postman 中省略了附件,我会得到完全相同的错误消息。我认为图像的格式不正确...

提前谢谢...

我从 ServiceNow 收到以下错误:

{
    error =     {
        detail = "<null>";
        message = "Failed to create the attachment. File part might be missing in the request.";
    };
    status = failure;
}

这是我的代码:

func createDataBody() -> Data {

    let newLine = "\r\n"
    let twoNewLines = newLine + newLine

    let boundary = "----------------------------\(UUID().uuidString)" + newLine

    var body = Data()

    let stringEncoding = String.Encoding.utf16

    body.append(boundary.data(using: stringEncoding)!)

    let table_name = "Content-Disposition: form-data; name=\"table_name\"" + twoNewLines
    body.append(table_name.data(using: stringEncoding)!)

    //incident
    body.append("incident".data(using: stringEncoding)!)

    //new line
    body.append(newLine.data(using: stringEncoding)!)

    //boundary
    body.append(boundary.data(using: stringEncoding)!)

    let table_sys_id = "Content-Disposition: form-data; name=\"table_sys_id\"" + twoNewLines
    body.append(table_sys_id.data(using: stringEncoding)!)

    //ba931ddadbf93b00f7bbdd0b5e96193c
    body.append("ba931ddadbf93b00f7bbdd0b5e96193c".data(using: stringEncoding)!)

    //new line
    body.append(newLine.data(using: stringEncoding)!)

    //boundary
    body.append(boundary.data(using: stringEncoding)!)

    let file = "Content-Disposition: form-data; name=\"file\"; filename=\"Artboard@1x.png\"" + newLine
    body.append(file.data(using: stringEncoding)!)

    let type = "Content-Type: image/png" + twoNewLines
    body.append(type.data(using: stringEncoding)!)

    //new line
    body.append(newLine.data(using: stringEncoding)!)

    let img = #imageLiteral(resourceName: "Artboard@1x")

    if let fileContent = img.pngData() {

        body.append(fileContent)

    }

    //new line
    body.append(newLine.data(using: stringEncoding)!)
    body.append("--\(UUID().uuidString)--".data(using: stringEncoding)!)

    print(String(data: body, encoding: .utf16)!)

    return body
}

这是身体的样子,省略了图像数据:

----------------------------F2152BF1-CE54-4E86-B8D0-931FA36F7C36
Content-Disposition: form-data; name="table_name"

incident
----------------------------F2152BF1-CE54-4E86-B8D0-931FA36F7C36
Content-Disposition: form-data; name="table_sys_id"

ba931ddadbf93b00f7bbdd0b5e96193c
----------------------------F2152BF1-CE54-4E86-B8D0-931FA36F7C36
Content-Disposition: form-data; name="file"; filename="Artboard@1x.png"
Content-Type: image/png

.....

----------------------------F2152BF1-CE54-4E86-B8D0-931FA36F7C36

这是标题调用

func addAttachmentToIncident() {

let passwordString = "\(userNameTextField.text!):\(passwordTextField.text!)"
let passwordData = passwordString.data(using: String.Encoding.utf8)
let base64EncodedCredential = passwordData?.base64EncodedString(options: Data.Base64EncodingOptions.lineLength76Characters)

let boundary = generateBoundaryString()

let headers = [
    "authorization": "Basic " + base64EncodedCredential!,
    "cache-control": "no-cache",
    "Accept": "application/json",
    "content-type": "multipart/form-data; boundary=--\(boundary)"
]

guard let url = URL(string: "https://xxx.service-now.com/api/now/attachment/upload") else {
    return

}

var request = URLRequest(url: url)

request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let dataBody = createDataBody(boundary: boundary)
request.httpBody = dataBody

let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
    if let response = response {
        print(response)
    }

    if let data = data {
        do {
            let json = try JSONSerialization.jsonObject(with: data, options: [])
            print(json)
        } catch {
            print(error)
        }
    }
    }.resume()

} //addAttachmentToIncident

【问题讨论】:

  • 我猜你想POST/now/attachment/upload 对吗?
  • 是的。 https://xxx.service-now.com/api/now/attachment/upload"
  • 好的,然后仔细检查文件字段名,因为their example 使用uploadFile,而不是file 像您的Swift 代码。
  • 当然我会仔细检查,但我在 PostMan 中使用 file 并且它有效...
  • 好的。我在那里没有帐户,所以我无法测试它。我刚刚离开他们的curl 示例...

标签: swift post postman multipartform-data


【解决方案1】:

几个观察:

  1. 最终边界不正确。假设您创建了一个以-- 开头的边界,您应该附加\(boundary)-- 作为最终边界。现在代码正在创建一个新的 UUID(并省略您在原始边界中添加的所有额外破折号),因此它不会匹配其余边界。在最终边界之后,您还需要一个 newLine 序列。

    没有这个最终边界可能会阻止它识别正文的这一部分,因此“文件部分可能丢失”消息。

  2. boundary 不应是局部变量。在准备多部分请求时,您必须在标头中指定边界(此处必须是相同的边界,而不是另一个 UUID() 实例)。

    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    

    通常,我会让调用者创建边界,在创建请求标头时使用它,然后将边界作为参数传递给此方法。见Upload image with parameters in Swift

    标题和正文中缺少相同的边界值会阻止它识别正文的任何​​这些部分。

  3. 您已定义本地边界以包括newLine。显然,它根本不应该是本地变量,但它不能在末尾包含换行符,否则尝试附加/(boundary)--的最后一个边界将失败。

    显然,如果您将其移出边界,请确保在构建主体时在需要的地方插入适当的换行符。最重要的是,确保你的身体看起来像下面这样(最后是--):

    ----------------------------F2152BF1-CE54-4E86-B8D0-931FA36F7C36
    Content-Disposition: form-data; name="table_name"
    
    incident
    ----------------------------F2152BF1-CE54-4E86-B8D0-931FA36F7C36
    Content-Disposition: form-data; name="table_sys_id"
    
    ba931ddadbf93b00f7bbdd0b5e96193c
    ----------------------------F2152BF1-CE54-4E86-B8D0-931FA36F7C36
    Content-Disposition: form-data; name="file"; filename="Artboard@1x.png"
    Content-Type: image/png
    
    .....
    
    ----------------------------F2152BF1-CE54-4E86-B8D0-931FA36F7C36--
    
  4. 在他们的curl example for /now/attachment/upload 中,他们使用的字段名称为uploadFile,但您使用的是file。您可能需要仔细检查您的字段名称并匹配 curl 和邮递员示例。

    curl "https://instance.service-now.com/api/now/attachment/upload" \
    --request POST \
    --header "Accept:application/json" \
    --user "'admin':'admin'" \
    --header "Content-Type:multipart/form-data" \
    -F 'table_name=incident' \
    -F 'table_sys_id=d71f7935c0a8016700802b64c67c11c6' \
    -F 'uploadFile=@image.png'
    

如果在修复上述问题后仍然无法正常工作,我建议您使用 CharlesWireshark 并将成功的请求与您以编程方式生成的请求进行比较。

不用说,您可能需要考虑使用 Alamofire,它可以让您摆脱创建格式良好的多部分请求的麻烦。

【讨论】:

  • 抱歉,如果我不清楚 PostMan 的工作原理很完美。但是,如果我省略了实际的附件,我会得到与 Xcode 相同的错误...
猜你喜欢
  • 1970-01-01
  • 2014-03-29
  • 1970-01-01
  • 1970-01-01
  • 2015-07-07
  • 2017-07-02
  • 2021-12-12
  • 2022-11-22
  • 2016-02-29
相关资源
最近更新 更多