1

In my ios 7 application I am trying to upload an Image with its properties to my server. I want to pass 4 values x,y,w,h where w and h are width and height and x and y are 0. I used the exact format for the image upload via multipart file upload. But my server shows "bad Input" error. I spend two days on same problem and tried many things.But they did n't work. I am following our working android code which is given below. Also I am attaching my ios 7 code.

Someone please point out what is wrong or what I am missing in my code. Thanks for the replies.

Android code:

public class OWBImageUpload implements Runnable{

    URL connectURL;
    String responseString;
    String Title;
    String Description;
    byte[ ] dataToServer;
    FileInputStream fileInputStream = null;
    String urlString = "https://stage.oneworkbook.com/owb/attachments/photos";
    String token = "";

    public OWBImageUpload(String t, String vTitle, String vDesc){
            try{
                token = t;
                connectURL = new URL(urlString);
                Title= vTitle;
                Description = vDesc;
            }catch(Exception ex){
                Log.i("HttpFileUpload","URL Malformatted");
            }
    }

    public void uploadPhoto(FileInputStream fStream){
            fileInputStream = fStream;
            upload();
    }

    public void upload(){
            String iFileName = Title;
            String lineEnd = "\r\n";
            String twoHyphens = "--";
            String boundary = "*****";
            String Tag="fSnd";

            try
            {
                    Log.e(Tag,"Starting Http File Sending to URL");

                    // Open a HTTP connection to the URL
                    HttpURLConnection conn = (HttpURLConnection)connectURL.openConnection();
                    conn.setRequestProperty(OWBConstants.OWB_TOKEN_AUTH, token);
                    conn.setRequestProperty(OWBConstants.CLIENT_ID, OWBConstants.ANDROID_CLIENT_ID);
                    conn.setRequestProperty(OWBConstants.CLIENT_SECRET, OWBConstants.ANDROID_CLIENT_SECRET);

                    // Allow Inputs
                    conn.setDoInput(true);

                    // Allow Outputs
                    conn.setDoOutput(true);

                    // Don't use a cached copy.
                    conn.setUseCaches(false);

                    // Use a post method.
                    conn.setRequestMethod("POST");

                    conn.setRequestProperty("Connection", "Keep-Alive");

                    conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="+boundary);

                    DataOutputStream dos = new DataOutputStream(conn.getOutputStream());


                    dos.writeBytes(twoHyphens + boundary + lineEnd);
                    dos.writeBytes("Content-Disposition: form-data; name=\"x\""+ lineEnd);
                    dos.writeBytes(lineEnd);
                    dos.writeBytes("0");
                    dos.writeBytes(lineEnd);
                    dos.writeBytes(twoHyphens + boundary + lineEnd);

                    dos.writeBytes(twoHyphens + boundary + lineEnd);
                    dos.writeBytes("Content-Disposition: form-data; name=\"y\""+ lineEnd);
                    dos.writeBytes(lineEnd);
                    dos.writeBytes("0");
                    dos.writeBytes(lineEnd);
                    dos.writeBytes(twoHyphens + boundary + lineEnd);

                    dos.writeBytes(twoHyphens + boundary + lineEnd);
                    dos.writeBytes("Content-Disposition: form-data; name=\"w\""+ lineEnd);
                    dos.writeBytes(lineEnd);
                    dos.writeBytes("1400");
                    dos.writeBytes(lineEnd);
                    dos.writeBytes(twoHyphens + boundary + lineEnd);

                    dos.writeBytes(twoHyphens + boundary + lineEnd);
                    dos.writeBytes("Content-Disposition: form-data; name=\"h\""+ lineEnd);
                    dos.writeBytes(lineEnd);
                    dos.writeBytes("1400");
                    dos.writeBytes(lineEnd);
                    dos.writeBytes(twoHyphens + boundary + lineEnd);

                    dos.writeBytes("Content-Disposition: form-data; name=\"file\";filename=\"" + iFileName +"\"" + lineEnd);
                    dos.writeBytes(lineEnd);

                    // create a buffer of maximum size
                    int bytesAvailable = fileInputStream.available();

                    int maxBufferSize = 1024;
                    int bufferSize = Math.min(bytesAvailable, maxBufferSize);
                    byte[ ] buffer = new byte[bufferSize];

                    // read file and write it into form...
                    int bytesRead = fileInputStream.read(buffer, 0, bufferSize);

                    while (bytesRead > 0)
                    {
                            dos.write(buffer, 0, bufferSize);
                            bytesAvailable = fileInputStream.available();
                            bufferSize = Math.min(bytesAvailable,maxBufferSize);
                            bytesRead = fileInputStream.read(buffer, 0,bufferSize);
                    }
                    dos.writeBytes(lineEnd);
                    dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

                    // close streams
                    fileInputStream.close();

                    dos.flush();

                    Log.e(Tag,"File Sent, Response: "+String.valueOf(conn.getResponseCode()));

                    InputStream is = conn.getInputStream();

                    // retrieve the response from server
                    int ch;

                    StringBuffer b =new StringBuffer();
                    while( ( ch = is.read() ) != -1 ){ b.append( (char)ch ); }
                    String s=b.toString();
                    Log.i("Response",s);
                    dos.close();
            }
            catch (MalformedURLException ex)
            {
                    Log.e(Tag, "URL error: " + ex.getMessage(), ex);
            }

            catch (IOException ioe)
            {
                    Log.e(Tag, "IO error: " + ioe.getMessage(), ioe);
            }
    }

    @Override
    public void run() {
            // TODO Auto-generated method stub
    }

}

My ios 7 code :

- (void)postUserImage:(NSString *)url postData:(NSMutableDictionary *)imageDetails token:(NSString *)token onSuccess:(HttpRequestSuccess)completion onFailure:(HttpRequestFailure)failure {

    self.httpURL = url;
    self.httpSuccess = completion;
    self.httpFailure = failure;

    NSString *filePath = [imageDetails valueForKey:@"filePath"];
    NSString *fileName = [imageDetails valueForKey:@"fileName"];
    NSData *imageData;
    if ([[NSFileManager defaultManager]fileExistsAtPath:filePath]) {
        NSLog(@"file path exists");
        imageData=[NSData dataWithContentsOfFile:filePath];
    }


    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
    [request setHTTPMethod:@"POST"];

    [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
    [request setHTTPShouldHandleCookies:NO];
    [request setTimeoutInterval:30];
    if(token != nil) {
        [request setValue:token forHTTPHeaderField:@"x-owb-token"];
    }

    [request setValue:IOS_CLIENT_ID forHTTPHeaderField:CLIENT_ID];
    [request setValue:IOS_CLIENT_SECRET forHTTPHeaderField:CLIENT_SECRET];
    //[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];

    NSString *boundary = @"**********";

    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
    [request setValue:contentType forHTTPHeaderField: @"Content-Type"];
    NSMutableData *body = [[NSMutableData alloc]init];

    // giving x,y,w,h

    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"x\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    NSString *x=@"0";
    [body appendData:[x dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"y\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    NSString *y=@"0";
    [body appendData:[y dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"w\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    NSString *w=@"500";
    [body appendData:[w dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"h\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    NSString *h=@"500";
    [body appendData:[h dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

    // adding Image content

    if (imageData) {

        //[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

        [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"file\"; filename=\"%@\"\r\n", fileName] dataUsingEncoding:NSUTF8StringEncoding]];

        //[body appendData:[@"Content-Type: image/jpeg\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

        [body appendData:imageData];

        [body appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
        [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    }

    [request setHTTPBody:body];


    //NSLog(@"http request body:%@",body);

    //[request setAllHTTPHeaderFields:[request allHTTPHeaderFields]];

    //NSString *postLength = [NSString stringWithFormat:@"%d", [body length]];
    //[request setValue:postLength forHTTPHeaderField:@"Content-Length"];

    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    if(connection == nil){
        NSLog(@"BAD CONNECTION");
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
Augustin Jose
  • 350
  • 3
  • 12

1 Answers1

0

A couple of observations:

  1. One problem is the presence of the --(boundary)-- strings within the request. It should only appear at the end of the request.

  2. The second problem is that you appear to be writing this boundary string at the start and end of every part. It should appear once at the start of every part. I.e., it should appear only once between two fields.

    Bottom line, the --(boundary) should appear at the start of every "part" of the request and the --(boundary)-- string should appear at the end

  3. You have commented out the Content-Type for the uploaded file. I'm not sure why you've done that (if it's not JPG, replace that content type with the appropriate content type), but in the process, you've removed the \r\n\r\n that should appear after the part's header and before the data. You're now missing not only the Content-Type, but also a \r\n.

I would suggest that you observe this request using a tool like Charles and compare it to a well-formed request (e.g. from your Android code) and compare them. You should verify that the two requests look the same. Blank lines, occurrences of boundaries, and -- strings are very important.

Frankly, you might consider abandoning this code which creates your own multipart request and use an established library, like AFNetworking, which does this properly. There's no need to re-invent the wheel.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks for the tip. I tried it. But now it shows "internal server error". – Augustin Jose Jan 29 '15 at 13:57
  • 1
    Your request is probably still malformed. I notice that you were writing your boundary string both at the end of one part and again at the start of the next (thus appearing twice between two fields). This boundary string should appear once at the start of each part, and that's it. Also, where you're uploading the file, you've commented out not only the `Content-Type`, but also a necessary `\r\n`. – Rob Jan 29 '15 at 14:41
  • Thanks that worked. It was difficult to find that. Also in my response I am getting a blank image . Any ideas? – Augustin Jose Jan 30 '15 at 05:23
  • @AugustinJose No, that comes down to what your web service API was supposed to return, the precise format of the request, etc. You don't show us how the Android code was parsing the response (much less that the response was supposed to be an image at all ... or are you saying its blank on server side?). Again, I'd suggest watching a successful transaction with something like Charles and then compare it against this transaction, and identify what precisely is different. But without more details, it's hard to say. – Rob Jan 30 '15 at 05:39
  • Thanks for your help. I found the problem on image content. Its because I am passing very less w and h (width and height) values. I want to pass the exact image width and height after common iPhone cropping. Any ideas for that? I am taking the image from both camera and gallery using image picker and saving into documents\ directory and converting to NSData. – Augustin Jose Jan 30 '15 at 05:57
  • If you have other questions, I'd suggest you post another question. I'm unclear what you mean by "common iPhone cropping", but if you want the actual image dimensions, load it into a `UIImage` and then look at the `size` property. But, again, these sorts of questions do not belong in this networking question... – Rob Jan 30 '15 at 05:59