I'm trying to upload a torrent file to a server like this:
func add( _ torrents: [String: Data]) async -> Result<Void, API.Error> {
do {
let url = URL(string: "http://192.168.1.1:9080/api/v2/torrents/add")!
let boundary: String = UUID().uuidString
var body = Data()
for (torrentName, torrentData) in torrents {
body.addField("--\(boundary)")
body.addField("Content-Disposition: form-data; name=\"torrents\"; filename=\"\(torrentName)\"")
body.addField("Content-Type: application/x-bittorrent")
body.addField(torrentData)
}
body.addField("--\(boundary)--")
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "post".uppercased()
urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
urlRequest.setValue("\(body.count)", forHTTPHeaderField: "Content-Length")
urlRequest.httpBody = body
let apiResponse = try await urlSession.data(for: urlRequest)
print(apiResponse)
print(String(data: apiResponse.0, encoding: .utf8) ?? "noup")
guard (apiResponse.1 as? HTTPURLResponse)?.statusCode == 415 else { return .success(()) }
return .failure(Error(TorrentFileError.invalideFile))
} catch {
let apiError = handleError(error)
return .failure(apiError)
}
}
extension Data {
private mutating func append(_ string: String) {
guard let data = string.data(using: .utf8) else { return }
append(data)
}
mutating func addField(_ string: String) {
append(string)
append(.httpFieldDelimiter)
}
mutating func addField(_ data: Data) {
append(data)
append(.httpFieldDelimiter)
}
}
extension String {
static let httpFieldDelimiter = "\r\n"
}
Loading torrent file (SwiftUI):
.fileImporter(isPresented: $showTorrentFileImporter, allowedContentTypes: [.torrent], allowsMultipleSelection: true) { result in
switch result {
case .success(let urls):
let torrents: [String: Data] = urls.reduce(into: [:]) { partialResult, url in
let name = url.lastPathComponent
let data = try? Data(contentsOf: url)
partialResult[name] = data == nil ? Data() : data
}
appContext.add(torrents)
case .failure(let error):
print(error)
}
}
API description here.
Please help me understand what I'm doing wrong. Response from server is:
(0 bytes, <NSHTTPURLResponse: 0x600000cf8160> { URL: http://192.168.1.1:9080/api/v2/torrents/add } { Status Code: 400, Headers {
Connection = (
close
);
"Content-Length" = (
0
);
Date = (
"Mon, 26 Feb 2024 08:13:11 GMT"
);
} })
UPD: If i set the urlReguest Content Type as part of body (not header field):
body.addField("Content-Type: multipart/form-data; boundary=\(boundary)")
response from server is:
(6 bytes, <NSHTTPURLResponse: 0x600000681a80> { URL: http://192.168.1.1:9080/api/v2/torrents/add } { Status Code: 200, Headers {
Connection = (
"keep-alive"
);
"Content-Length" = (
6
);
"Content-Type" = (
"text/plain; charset=UTF-8"
);
Date = (
"Mon, 26 Feb 2024 13:19:21 GMT"
);
"content-security-policy" = (
"frame-ancestors 'self';"
);
"x-content-type-options" = (
nosniff
);
"x-frame-options" = (
SAMEORIGIN
);
"x-xss-protection" = (
"1; mode=block"
);
} })
Fails.
In general, some strange APIs. For some reason, query items also need to be sent in the body of the request and not as part of the URL.