在ios中解析图像下载的多部分响应

时间:2014-02-28 12:25:02

标签: ios json download multipartform-data

在我的应用程序中,我从服务器下载图像作为多部分内容。在我的响应数据中,我得到了两个部分:一个是json内容,另一个是下载文件。回复采用以下格式。

--poa89012-3212-1232-9201-fdsakjkj921
Content-Type: application/json; charset=utf-8
Content-Disposition: inline; name=info

{
  //json content
}

--poa89012-3212-1232-9201-fdsakjkj921
Content-Disposition: file; name=file; filename=photo.png
Content-Type: application/octet-stream

// File data
˘íë77íí77Í¥2008:02:11 11:32:512008:02:1
------

当我尝试在didReceiveResponse中获取标头时,我无法处理这个包含2个部分的响应:它为整个响应提供了标题,其内容类型是multipart / mixed。请告诉我如何通过通过拆分json内容和文件内容来处理此响应。

3 个答案:

答案 0 :(得分:5)

我也遇到了http-multipart response的问题。我为NSData写了一个类别。代码如下:

的NSData + MultipartResponses.h

#import <Foundation/Foundation.h>

@interface NSData (MultipartResponses)

- (NSArray *)multipartArray;
- (NSDictionary *)multipartDictionary;

@end

的NSData + MultipartResponses.m

#import "NSData+MultipartResponses.h"

@implementation NSData (MultipartResponses)

static NSMutableDictionary *parseHeaders(const char *headers)
{
   NSMutableDictionary *dict=[NSMutableDictionary dictionary];
   int max=strlen(headers);
   int start=0;
   int cursor=0;
   while(cursor<max)
   {
      while((headers[cursor]!=':')&&(headers[cursor]!='='))
      {
         cursor++;
      }
      NSString *key=[[NSString alloc] initWithBytes:(headers+start) length:(cursor-start) encoding:NSASCIIStringEncoding];
      cursor++;

      while(headers[cursor]==' ')
      {
         cursor++;
      }
      start=cursor;
      while(headers[cursor]&&(headers[cursor]!=';')&&((headers[cursor]!=13)||(headers[cursor+1]!=10)))
      {
         cursor++;
      }

      NSString *value;
      if((headers[start]=='"')&&(headers[cursor-1]=='"'))
      {
         value=[[NSString alloc] initWithBytes:(headers+start+1) length:(cursor-start-2) encoding:NSASCIIStringEncoding];
      }
      else
      {
         value=[[NSString alloc] initWithBytes:(headers+start) length:(cursor-start) encoding:NSASCIIStringEncoding];
      }
      [dict setObject:value forKey:key];

      if(headers[cursor]==';')
      {
         cursor++;
      }
      else
      {
         cursor+=2;
      }

      while(headers[cursor]==' ')
      {
         cursor++;
      }
      start=cursor;
   }
   return dict;
}

- (NSDictionary *)multipartDictionaryWithBoundary:(NSString *)boundary
{
   NSMutableDictionary *dict=[NSMutableDictionary dictionary];

   const char *bytes=(const char *)[self bytes];
   const char *pattern=[boundary cStringUsingEncoding:NSUTF8StringEncoding];

   int cursor=0;
   int start=0;
   int max=[self length];
   int keyNo=0;
   while(cursor<max)
   {
      if(bytes[cursor]==pattern[0])
      {
         int i;
         int patternLength=strlen(pattern);
         BOOL match=YES;
         for(i=0; i<patternLength; i++)
         {
            if(bytes[cursor+i]!=pattern[i])
            {
               match=NO;
               break;
            }
         }
         if(match)
         {
            if(start!=0)
            {
               int startOfHeaders=start+2;
               int cursor2=startOfHeaders;
               while((bytes[cursor2]!=(char)0x0d)||(bytes[cursor2+1]!=(char)0x0a)||(bytes[cursor2+2]!=(char)0x0d)||(bytes[cursor2+3]!=(char)0x0a))
               {
                  cursor2++;
                  if(cursor2+4==max)
                  {
                     break;
                  }
               }
               if(cursor2+4==max)
               {
                  break;
               }
               else
               {
                  int lengthOfHeaders=cursor2-startOfHeaders;
                  char *headers=(char *)malloc((lengthOfHeaders+1)*sizeof(char));
                  strncpy(headers, bytes+startOfHeaders, lengthOfHeaders);
                  headers[lengthOfHeaders]=0;

                  NSMutableDictionary *item=parseHeaders(headers);

                  int startOfData=cursor2+4;
                  int lengthOfData=cursor-startOfData-2;

                  if(([item valueForKey:@"Content-Type"]==nil)&&([item valueForKey:@"filename"]==nil))
                  {
                     NSString *string=[[NSString alloc] initWithBytes:(bytes+startOfData) length:lengthOfData encoding:NSUTF8StringEncoding];
                     keyNo++;
                     [dict setObject:string forKey:[NSString stringWithFormat:@"%d", keyNo]];
                  }
                  else
                  {
                     NSData *data=[NSData dataWithBytes:(bytes+startOfData) length:lengthOfData];
                     [item setObject:data forKey:@"data"];
                     keyNo++;
                     [dict setObject:item forKey:[NSString stringWithFormat:@"%d", keyNo]];
                  }
               }
            }
            cursor=cursor+patternLength-1;
            start=cursor+1;
         }
      }
      cursor++;
   }

   return dict;
}

- (NSArray *)multipartArray
{
   NSDictionary *dict=[self multipartDictionary];
   NSArray *keys=[[dict allKeys] sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
   NSMutableArray *array=[NSMutableArray array];
   for(NSString *key in keys)
   {
      [array addObject:dict[key]];
   }
   return array;
}

- (NSDictionary *)multipartDictionary
{
   const char *bytes=(const char *)[self bytes];
   int cursor=0;
   int max=[self length];
   while(cursor<max)
   {
      if(bytes[cursor]==0x0d)
      {
         break;
      }
      else
      {
         cursor++;
      }
   }
   char *pattern=(char *)malloc((cursor+1)*sizeof(char));
   strncpy(pattern, bytes, cursor);
   pattern[cursor]=0x00;
   NSString *boundary=[[NSString alloc] initWithCString:pattern encoding:NSUTF8StringEncoding];
   free(pattern);
   return [self multipartDictionaryWithBoundary:boundary];
}

@end

答案 1 :(得分:1)

对我来说,你的代码没有用。相反,我用纯粹的objective-c重写了那段代码: 注意这个代码中的(我的)边界总是有额外的 - 在下一个边界和最后边界之前 - 被剥离。 NSArray返回每个部分的NSDictionary,包含键&#34;标题&#34;作为NSDictionary和&#34; body&#34;作为NSData

- (NSArray *)multipartArrayWithBoundary:(NSString *)boundary
{
    NSString *data = [[[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding] autorelease];

    NSArray *multiparts = [data componentsSeparatedByString:[@"--" stringByAppendingString:boundary]]; // remove boundaries
    multiparts = [multiparts subarrayWithRange:NSMakeRange(1, [multiparts count]-2)]; // continued removing of boundaries

    NSMutableArray *toReturn = [NSMutableArray arrayWithCapacity:2];
    for(NSString *part in multiparts)
    {
        part = [part stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
        NSArray *separated = [part componentsSeparatedByString:@"\n\n"];

        NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithCapacity:3];
        for(NSString *headerLine in [[separated objectAtIndex:0] componentsSeparatedByString:@"\n"])
        {
            NSArray *keyVal = [headerLine componentsSeparatedByString:@":"];

            [headers setObject:[[keyVal objectAtIndex:1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] forKey:[[keyVal objectAtIndex:0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
        }

        [toReturn addObject:[NSDictionary dictionaryWithObjectsAndKeys:[[separated objectAtIndex:1] dataUsingEncoding:NSUTF8StringEncoding], @"body", headers, @"headers", nil]];
    }

    return toReturn;
}

答案 2 :(得分:1)

这是一个老话题,但同意 @possen(“如果您的部分由 UTF-8 和二进制数据组成,则效果不佳,因为它试图将其视为 UTF-8,二进制数据将导致 initWithData 函数失败。相反,您需要将数据流式传输并根据内容类型为每种编码分别处理每种类型。").

如果这有帮助,这是一个处理二进制图像的 Swift 实现。

if let multiparts = responseData?.multipartArray(withBoundary: boundary) {
    for part in multiparts {
        if part.contentType == "application/json" {
            let a = try? JSONDecoder().decode(YOUR_DECODABLE_STRUCT.self, from: part.body)
        } else if part.contentType == "image/jpg" {
            let imageData = part.body
        }
    }                    
}
extension Data {
    
    func multipartArray(withBoundary boundary: String, key: String = "Content-Type:") -> [(contentType: String, body: Data)]? {
        func extractBody(_ data: Data) -> Data? {
            guard let startOfLine = key.data(using: .utf8) else { return nil }
            guard let endOfLine = "\r\n".data(using: .utf8) else { return nil }
            var result: Data? = nil
            var pos = data.startIndex
            while let r1 = data[pos...].range(of: startOfLine)
            {
                if let r2 = data[r1.upperBound...].range(of: endOfLine) {
                    pos = r2.upperBound
                }
            }
            
            if pos < data.endIndex {
                result = data[(pos+2)...]
            }
            return result
        }
        
        let multiparts = components(separatedBy: ("--" + boundary))
        var result: [(String, Data)]? = nil
        for part in multiparts
            .enumerated()
            .map({ index, data -> Data in
                if index == multiparts.count-1 {
                    return data.dropLast(2)
                } else {
                    return data
                }
            })
        {
            for contentTypeData in part.slices(between: key, and: "\r") {
                if let contentType = String(data: contentTypeData, encoding: .utf8),
                   let body = extractBody(part)
                {
                    if result == nil {
                        result = [(String, Data)]()
                    }
                    result?.append(
                        (contentType.trimmingCharacters(in: .whitespacesAndNewlines), body)
                    )
                } else {
                    continue
                }
            }
        }
        return result
    }
    
    func slices(between from: String, and to: String) -> [Data] {
        guard let from = from.data(using: .utf8) else { return [] }
        guard let to = to.data(using: .utf8) else { return [] }
        return slices(between: from, and: to)
    }
    
    func slices(between from: Data, and to: Data) -> [Data] {
        var chunks: [Data] = []
        var pos = startIndex
        while let r1 = self[pos...].range(of: from),
              let r2 = self[r1.upperBound...].range(of: to)
        {
            chunks.append(self[r1.upperBound..<r2.lowerBound])
            pos = r1.upperBound
        }
        return chunks
    }
    
    func components(separatedBy separator: String) -> [Data] {
        guard let separator = separator.data(using: .utf8)  else { return [] }
        return components(separatedBy: separator)
    }
    
    func components(separatedBy separator: Data) -> [Data] {
        var chunks: [Data] = []
        var pos = startIndex
        while let r = self[pos...].range(of: separator) {
            if r.lowerBound > pos {
                chunks.append(self[pos..<r.lowerBound])
            }
            pos = r.upperBound
        }
        if pos < endIndex {
            chunks.append(self[pos..<endIndex])
        }
        return chunks
    }
}