Быстрое добавление вложения для multipart / form-data POST - PullRequest
0 голосов
/ 05 июня 2019

Swift 5, Xcode Version 10.2.1 (10E1001)

Привет всем, я был бы признателен за любую помощь в этом.

Я создаю вызов, чтобы опубликовать вложение (PNG) в моем вызове POST. Я звоню в ServiceNow. Если я использую те же ключи в теле, что и PostMan, вызов в Postman работает нормально. Тем не менее, ниже кажется, что он испытывает трудности с приложением. Изображение в этом примере является активом PNG.

Для сравнения, если я опускаю вложение в Почтальоне, я получаю точно такое же сообщение об ошибке. Я считаю, что изображение не отформатировано должным образом ...

Заранее спасибо ...

Я получаю следующую ошибку от 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

1 Ответ

1 голос
/ 05 июня 2019

Пара наблюдений:

  1. Конечная граница неверна. Предполагая, что вы создали границу, которая начинается с --, вы должны добавить \(boundary)-- в качестве конечной границы. Прямо сейчас код создает новый UUID (и пропускает все те лишние черты, которые вы добавили в исходную границу), поэтому он не будет соответствовать остальным границам. Вам нужна последовательность newLine после этой последней границы.

    Отсутствие этой окончательной границы может помешать ей распознать эту часть тела, и, следовательно, сообщение «Файловая часть может отсутствовать».

  2. boundary не должно быть локальной переменной. При подготовке составных запросов вы должны указать границу в заголовке (и здесь должна быть та же самая граница, а не другой экземпляр UUID()).

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

    Как правило, я бы попросил вызывающую сторону создать границу, использовать ее при создании заголовка запроса, а затем передать границу в качестве параметра этому методу. См. Загрузка изображения с параметрами в 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 примере для /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'
    

Если после исправления вышеописанного все равно не работает, я бы посоветовал вам использовать Charles или Wireshark и сравнить успешный запрос с генерируемым вами. программным способом.

Само собой разумеется, вы можете рассмотреть возможность использования Alamofire , который избавит вас от сорняков при создании правильно сформированных многочастных запросов.

...