multipart/form-data в Cocoa
написал Владимир Пузанов - December 13, 2008 – 21:23Не знаю, по какой причине в Cocoa нет встроенного пути для создания HTTP запроса multipart/form-data (применяется в POST-запросах с бинарными данными, в основном – для передачи данных.
В сети можно найти несколько реализаций. Все они не устраивали меня по тем или иным причинам, так что я решил написать еще одну. Я должен сказать, что RFC 2388 достаточно неочевиден и делать реализацию только по нему сложно. Так что основывался я на реализации “Mini-Mallows: A Multi-Part Form Wrapper for Cocoa & iPhone” © Sam Schroeder.
И так, давайте определим, что нам нужно от интерфейса. А нужно нам всего три вещи:
- (id)initWithURL:(NSURL *)url;
- (void)addValue:(id)v forField:(NSString *)f;
- (NSURLRequest *)urlRequest;
init для создания нового экземпляра с указанным URL, addValue:forField: для добавления нового поля и urlRequest для получения объекта запроса. В принципе можно было бы сделать класс более совместимым с KVC, но он и так достаточно гибок и прост в использовании.
Поля мы будем хранить не в очевидном для этой задачи NSMutableDictionary, а в NSMutableArray. Почему? Потому что есть такая презабавнейшая вещь как PHP, которая принимает агрументы вида f[]=a&f[]=b и собирает их в массив. А мне как раз надо было отправлять файл на PHP-обработчик по спеке заказчика, потому пришлось подумать о случае с неуникальными ключами.
Для создания ключа границы мы будем использовать /dev/urandom. Это дает высокую вероятность того, что в байтовом потоке такой ключ случайно не попадется (идею я взял из Сафари, изучая его Wireshark’ом). Данный код частично написан proger’ом, потому как я что-то начал подзабывать простые указатели и libc:
- (NSString *)getRandomBoundary
{
NSMutableString *s = [NSMutableString string];
[s appendString:kPrefix];
int i;
FILE *fp = fopen("/dev/urandom", "r");
assert(fp);
for(i=0; i
Второй интересный момент - как мы собираем поля в байтовый объект. Мы проверяем value на принадлежность классу NSString. Если это строка – то проверяем первый символ field (не value). Если ключ начинается на @, то мы работаем как curl, и принимаем value как путь к файлу.
Если значение не строка, то просто конвертируем его через -[description]. Еще бы неплохо добавить поддержку NSData для прямого ввода бинарних данных, но руки не дошли. Если есть желание – "Fix it and send patch" © Paul Sokolovsky. Еще бы неплохо все это завернуть в NSInputStream (если файл большой), но опять же руки не дошли.
DQMultipartForm.zip доступен из DropBox (AS-IS), замечания и предложения – в комментарии и на почту.