NSData Multi-part response from NSURLConnection
This looks like very plausible multipart response, I see what appears to be a valid JPEG (or at least the start of one) in the second part. This is extremely unusual to see multipart responses: We construct multipart requests in our iOS code all the time, but we don't handle multipart responses, generally.
Your Objective-C code, though is trying to load this as an image, and it's not a simple image. You actually have to parse the multipart response.
Before we drag you through the weeds of the ugly process of parsing this multipart response, are you stuck with this sort of response? I'd much rather see a simple JSON response with the image base-64 encoded. Or a JSON response with a URL for the image which can be downloaded separately.
It's going to take a lot of ugly code to parse this non-standard response, so the question is whether there's any chance to change the response.
It sounds like you're stuck with this format. In that case, to parse this response, you first have to get the boundary
out of the Content-Type
in the NSURLResponse
headers. Then you can scan the NSData
response payload for --
followed by that boundary string. Then you're going to have a couple of headers for each part, followed by a blank line, followed by the payload. The first payload is the JSON. The second is the image. You simple read in the bytes until you encounter the next \r\n
followed by --
, followed by the boundary. The whole thing is terminated with a --
followed by a boundary, followed immediately by another --
.
For discussions on how multipart responses are constructed see the discussion of multipart responses in RFC 1341 (the MIME spec). Or see RFC 2388 (the multipart request created by HTML forms). Or see this example of how to create a multipart request in Objective-C. Or just google "create multipart request". Once you get your arms around how multipart responses are created, then the parsing of the multipart response will be fairly intuitive. But definitely make sure you grok multipart payloads before going too much further.
See Parse multipart response for image download in ios for some promising Objective-C code for parsing these sorts of responses.
Parse a multipart SOAP response in Swift
Parsing a multi-part SOAP response can be easily done by using MultipartKit
I created an extension on Alamofire
's DataResponse
to handle the response
import Alamofire
import MultipartKit
import Swime
extension DataResponse where Success == Data {
var boundary: String? {
let header = response?.allHeaderFields.first(where: {
$0.key as? String == "Content-Type"
})
let scanner = Scanner(string: header?.value as? String ?? "")
_ = scanner.scanUpToString("boundary=\"")
_ = scanner.scanString("boundary=\"")
let boundary = scanner.scanUpToString("\";")
return boundary
}
func multipartParts() -> [MultipartPart] {
guard let boundary = boundary else { return [] }
guard let data = try? result.get() else { return [] }
let parser = MultipartParser(boundary: boundary)
var parts: [MultipartPart] = []
var headers: HTTPHeaders = [:]
var body: Data = Data()
parser.onHeader = { (field, value) in
headers.replaceOrAdd(name: field, value: value)
}
parser.onBody = { new in
body.append(contentsOf: new.readableBytesView)
}
parser.onPartComplete = {
let part = MultipartPart(headers: headers, body: body)
headers = [:]
body = Data()
parts.append(part)
}
do {
try parser.execute(data)
} catch {
print(error.localizedDescription)
}
return parts
}
func parseMultipart() -> (message: String, files: [Data])? {
let parts = multipartParts()
let message = parts.first(where: { $0.id == "<root.message@cxf.apache.org>" })?.body.string ?? ""
let dataPart = parts.filter { $0.id != "<root.message@cxf.apache.org>" }
let files = dataPart.compactMap { Data(multipart: $0) }
return (message, files)
}
}
extension ByteBuffer {
var string: String {
return String(decoding: self.readableBytesView, as: UTF8.self)
}
}
extension MultipartPart {
/// Gets or sets the `name` attribute from the part's `"Content-ID"` header.
public var id: String? {
get { self.headers.first(name: "Content-ID") }
}
}
which then can be used on a data response like this:
let request = SessionManager.request(urlRequest).responseData(completionHandler: { response in
switch response.result {
case .failure(let error):
self.alert(error)
case .success:
guard let parts = response.parseMultipart() else { return }
DispatchQueue.main.async {
self.loadResponse(message: parts.message, files: parts.files)
}
}
})
the message id can be different so I would maybe suggest checking the type too
Hope this will help someone.
Related Topics
How to Create a Development Framework in iOS Including Swift
Dismissing Both Uinavigation Views and Modal Views at Once Programmatically
How to Save List of Object in User Default
Error: Cuicatalog: Invalid Asset Name Supplied: (Null), or Invalid Scale Factor:2.000000
Arkit Hide Objects Behind Walls
Google Maps iOS Sdk, Getting Directions Between 2 Locations
Set Uitableview's Height to the Height of Its Content with Auto Layout
Decrease the Width of the Last Line in Multiline Uilabel
Converting Nsdictionary Object to Nsdata Object and Vice-Versa
Find Attributes from Attributed String That User Typed
How to Get Word Wrap Information with the New iOS 7 APIs
Xcode 8.1 Push Notifications in Swift 2.3 with Firebase Integration Not Getting
Xcode UI Testing Error Keyboard
Back Button Callback in Navigationcontroller in iOS
How to Add a Button to Uinavigationbar