Не удалось загрузить файл с использованием данных формы многоуровневой формы NSURLSession в iOS
Я пытаюсь загрузить файл видео/изображения с помощью метода - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
, используя данные из нескольких частей. Но почему-то я не могу загрузить файл, и я получаю ошибку "stream ended unexpectedly
".
Требования
- Загрузите файл видео/изображения на сервер
- Приложение должно поддерживать фоновое скачивание (продолжить процесс загрузки даже после того, как приложение перейдет в фоновый режим).
- Сервер ожидает, что данные будут отправлены с использованием данных с несколькими частями.
Методы /API, используемые для достижения этого
-
API-интерфейс вспомогательного сеанса NSURLSession (полный код, указанный ниже)
2. - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL
Проблемы/проблемы, с которыми приходится сталкиваться
- сталкивается с ошибкой
stream ended unexpectedly
"каждый раз, когда я использую этот API для процесса загрузки
Точки, которые следует отметить
-
Загрузка выполняется с тем же кодом, если я использую NSURLConnection
вместо NSURLSession
.
-
NSURLSession
Процесс загрузки в фоновом режиме ожидает расположения файла (NSURL
) в качестве параметра, не принимает NSData. Это не позволяет нам преобразовать файл в NSData
перед загрузкой, т.е. Мы не можем добавить NSData в тело файла.
Нужна помощь по следующим пунктам
-
Есть ли какая-либо ошибка в формируемом множителе formdata (примечание - тот же код работает с NSURLConnection)
-
Где я ошибаюсь в своем подходе?
-
Нужно ли делать какие-либо изменения на уровне сервера для поддержки загрузки NSURLSession backgroundSession
? (в анализе данных или что-то еще?)
Вот код, который используется для загрузки файла
NSString * BoundaryConstant = @ "---------- V2ymHFg03ehbqgZCaKO6jy";
// string constant for the post parameter 'file'. My server uses this name: `file`. Your may differ
NSString* FileParamConstant = @"file";
// the server url to which the image (or video) is uploaded. Use your server url here
url=[NSURL URLWithString:[NSString stringWithFormat:@"%@%@%d",baseURL,@"posts/post/update/",createPostObject.PostID]];
// create request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
[request setHTTPShouldHandleCookies:NO];
[request setTimeoutInterval:120];
[request setHTTPMethod:@"POST"];
[request addValue:@"multipart/form-data" forHTTPHeaderField:@"Content-Type"];
[request setURL:url];
// set Content-Type in HTTP header
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", BoundaryConstant];
[request setValue:contentType forHTTPHeaderField: @"Content-Type"];
if([[NSUserDefaults standardUserDefaults] objectForKey:@"accessToken"]){
[request setValue:[[NSUserDefaults standardUserDefaults] objectForKey:@"accessToken"] forHTTPHeaderField:AccessTokenKey];
}
// post body
NSMutableData *body = [NSMutableData data];
// add params (all params are strings)
for (NSString *param in self.postParams) {
NSLog(@"param is %@",param);
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", BoundaryConstant] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", param] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"%@\r\n", [self.postParams objectForKey:param]] dataUsingEncoding:NSUTF8StringEncoding]];
}
// add video file name to body
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", BoundaryConstant] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"file.mp4\"\r\n", FileParamConstant] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithString:@"Content-Type: video/mp4\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
// [body appendData:self.dataToPost];
[body appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", BoundaryConstant] dataUsingEncoding:NSUTF8StringEncoding]];
// setting the body of the post to the request
[request setHTTPBody:body];
// set the content-length
NSString *postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[body length]];
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
NSLog(@"Request body %@", [[NSString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding]);
NSURLSessionConfiguration * backgroundConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"backgroundtask1"];
NSURLSession *backgroundSeesion = [NSURLSession sessionWithConfiguration: backgroundConfig delegate:self delegateQueue: [NSOperationQueue mainQueue]];
NSURLSessionUploadTask *uploadTask = [backgroundSeesion uploadTaskWithRequest:request fromFile:self.videoUrl];
[uploadTask resume];
Ответы
Ответ 1
Вы не загружаете то, что считаете себя. Ваше намерение заключается в том, чтобы данные тела были загружены как есть. Вместо этого, когда вы вызываете uploadTaskWithRequest:fromFile:
, этот метод эффективно исключает любые значения HTTPBody
или HTTPBodyStream
в запросе и заменяет их содержимым URL-адреса, переданного через параметр fromFile:
.
Поэтому, если вы не пишете этот блок данных тела, закодированных в форме, к этому файловому URL-адресу в другом месте, вы загружаете файл самостоятельно, а не в multipart form data.
Вам нужно настроить ваш код, чтобы записать данные формы в файл, а не хранить его в HTTPBody
, а затем передать URL-адрес этого файла параметру fromFile:
.
Ответ 2
Во избежание потери времени, связанного с этим.
Полный фрагмент, основанный на ответе @dgatwood
private func http(request: URLRequest){
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: .main)
/*Tweaking*/
let task = session.uploadTask(with: request, from: request.httpBody!)
task.resume()
}
И.. не забудьте добавить заголовок на объект запроса, например
request.setValue("multipart/form-data; boundary=\(yourboundary)", forHTTPHeaderField: "Content-Type")