image upload to web service in JavaScript

10,310

Solution 1

For uploading images I use either Valum's ajax upload plugin or jQuery form plugin that allows to submit a normal form in an ajax way.

If you will use POST requests then don't forget to use MAX_FILE_SIZE hidden attribute:

<input type="hidden" name="MAX_FILE_SIZE" value="20000000">

Note that it must precede the file input field. It is in bytes, so this will limit the upload to 20MB. See PHP documentation for details.

Solution 2

Assuming that your Java code is using Apache HttpComponents (what you really should have said then), your code, when augmented with

URI aWebImageUrl2 = new URI("http://localhost:1337/");
File imgPath = new File("…/face.png");
final String aImgCaption = "face";
// …
HttpClient httpClient = new DefaultHttpClient();
httpClient.execute(post);

submits the following example HTTP request (as tested with nc -lp 1337, see GNU Netcat):

POST / HTTP/1.1
Content-Length: 990
Content-Type: multipart/form-data; boundary=oQ-4zTK_UL007ymPgBL2VYESjvFwy4cN8C-F
Host: localhost:1337
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.2 (java 1.5)

--oQ-4zTK_UL007ymPgBL2VYESjvFwy4cN8C-F
Content-Disposition: form-data; name="picture"; filename="face.png"
Content-Type: application/octet-stream

�PNG[…]

The simplest solution to do something like this in HTML is, of course, to use a FORM element and no or minimal client-side scripting:

<form action="http://service.example/" method="POST"
      enctype="multipart/form-data">
  <input type="file" name="picture">
  <input type="submit">
</form>

which submits (either when submitted with the submit button or the form object's submit() method) the following example request:

POST / HTTP/1.1
Host: localhost:1337
Connection: keep-alive
Content-Length: 886
Cache-Control: max-age=0
Origin: http://localhost
User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryhC26St5JdG0WUaCi
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Referer: http://localhost/scripts/test/XMLHTTP/file.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: de-CH,de;q=0.8,en-US;q=0.6,en;q=0.4
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

------WebKitFormBoundaryhC26St5JdG0WUaCi
Content-Disposition: form-data; name="picture"; filename="face.png"
Content-Type: image/png

�PNG[…]

But since you have asked explicitly about a "javascript" solution (there really is no such programming language), I presume that you want to have more client-side control over the submit process. In that case, you can use the W3C File API and XMLHttpRequest or XMLHttpRequest2 APIs as provided by recent browsers (not the programming languages):

<script type="text/javascript">
  function isHostMethod(obj, property)
  {
    if (!obj)
    {
      return false;
    }

    var t = typeof obj[property];
    return (/\bunknown\b/i.test(t) || /\b(object|function)\b/i.test(t) && obj[property]);
  }

  var global = this;

  function handleSubmit(f)
  {
    if (isHostMethod(global, "XMLHttpRequest"))
    {
      try
      {
        var input = f.elements["myfile"];
        var file = input.files[0];
        var x = new XMLHttpRequest();
        x.open("POST", f.action, false);  // ¹

        try
        {
          var formData = new FormData();
          formData.append("picture", file);
          x.send(formData);
          return false;
        }
        catch (eFormData)
        {
          try
          {
            var reader = new FileReader();
            reader.onload = function (evt) {
              var boundary = "o" + Math.random();
              x.setRequestHeader(
                "Content-Type", "multipart/form-data; boundary=" + boundary);
              x.send(
                  "--" + boundary + "\r\n"
                + 'Content-Disposition: form-data; name="picture"; filename="' + file.name + '"\r\n'
                + 'Content-Type: application/octet-stream\r\n\r\n'
                + evt.target.result
                + '\r\n--' + boundary + '--\r\n');
            };
            reader.readAsBinaryString(file);
            return false;
          }
          catch (eFileReader)
          {
          }
        }
      }
      catch (eFileOrXHR)
      {
      }
    }

    return true;
  }
</script>
<form action="http://service.example/" method="POST"
      enctype="multipart/form-data"
      onsubmit="return handleSubmit(this)">
  <input type="file" name="myfile">
  <input type="submit">
</form>

This approach tries to use the XMLHttpRequest API. If that fails, the function returns true, so true is returned to the event handler (see the attribute value), and the form is submitted the usual way (the latter might not work with your Web service; test before use by disabling script support).

If XMLHttpRequest can be used, it is "tested"² if the file input has a files property and the object referred to by that has a 0 property (referring to the first selected File for that form control, if supported).

If yes, the XMLHttpRequest2 API is tried, which send() method can take a reference to a FormData and do all the multi-part magic by itself. If the XMLHttpRequest2 API is not supported (which should throw an exception), the File API's FileReader is tried, which can read the contents of a File as binary string (readAsBinaryString()); if that is successful (onload), the request is prepared and submitted. If one of those approaches seemingly worked, the form is not submitted (return false).

Example request submitted with this code using the FormData API:

POST / HTTP/1.1
Host: localhost:1337
Connection: keep-alive
Content-Length: 887
Origin: http://localhost
User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLIXsjWnCpVbD8FVA
Accept: */*
Referer: http://localhost/scripts/test/XMLHTTP/file.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: de-CH,de;q=0.8,en-US;q=0.6,en;q=0.4
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

------WebKitFormBoundaryLIXsjWnCpVbD8FVA
Content-Disposition: form-data; name="picture"; filename="face.png"
Content-Type: image/png

�PNG[…]

The example request looks slightly different when the FileReader API was used instead (just as proof of concept):

POST / HTTP/1.1
Host: localhost:1337
Connection: keep-alive
Content-Length: 1146
Origin: http://localhost
User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1
Content-Type: multipart/form-data; boundary=o0.9578036249149591
Accept: */*
Referer: http://localhost/scripts/test/XMLHTTP/file.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: de-CH,de;q=0.8,en-US;q=0.6,en;q=0.4
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

--o0.9578036249149591
Content-Disposition: form-data; name="picture"; filename="face.png"
Content-Type: application/octet-stream

PNG[…]

Notice that the XMLHttpRequest2, FormData and File API are having only Working Draft status and so are still in flux. Also, this approach works if the resource submitted from and the resource submitted to are using the same protocol, domain, and port number; you may have to deal with and work around the Same Origin Policy. Add feature tests and more exception handling as necessary.

Also notice that the request made using FileReader is larger with the same file and misses the leading character, as indicated in the question referred to by Frits van Campen. This may be due to a (WebKit) bug, and you may want to remove this alternative then; suffice it for me to say that the readAsBinaryString() method is deprecated already in the File API Working Draft in favor of readAsArrayBuffer() which should use Typed Arrays.

See also "Using files from web applications".

¹ Use true for asynchronous handling; this avoids UI blocking, but requires you to do processing in the event listener, and you will always have to cancel form submission (even if XHR was unsuccessful).

² If the property access is not possible, an exception will be thrown. If you prefer a real test, implement (additional) feature-testing (instead), and be aware that not everything can be safely feature-tested.

Share:
10,310
vissu
Author by

vissu

Passionate software developer Java, Groovy, Python, PySpark, DataBricks, AWS.

Updated on August 10, 2022

Comments

  • vissu
    vissu almost 2 years

    I need to upload an image to a webservice from javascript. I have to send a json string an a file(image). In java we have MultipartEntity. I have the followig code in java:

    HttpPost post = new HttpPost( aWebImageUrl2 );
    MultipartEntity entity  = new MultipartEntity( HttpMultipartMode.BROWSER_COMPATIBLE );
    // For File parameters
    entity.addPart( "picture", new FileBody((( File ) imgPath )));
    // For usual String parameters
    entity.addPart( "url", new StringBody( aImgCaption, "text/plain", Charset.forName( "UTF-8" )));
    post.setEntity( entity );  
    

    Now I need to do the same image upload in javascript.
    But in javaScript I didn't find any equivalent of MultipartEntity. Please Suggest any solutions.

  • vissu
    vissu over 12 years
    Anantha Sharma,I have a webservice which is written by some one in java and a Restfull webservice. I have to upload the image and json message from a html page using javascript directly to webservice (I cannot use any server side script). The uploaded image and json message I need to send as multiple parts (MultipartEntity) with javascript.
  • Christian
    Christian over 12 years
    this sends a base64 encoded png file which has some additional headers in front so it's probably useless, unless the webservice supports the dataURL format.
  • Tomas
    Tomas over 12 years
    @kommradHomer, I use this only for Opera, for other browsers I use Valum's plugin - the main advantage is that no form is needed - the image is just added with one click.
  • Choy
    Choy over 12 years
    that's true. You could strip those out by passing this to a custom php function, then using curl to send it to the web service.
  • omarojo
    omarojo about 9 years
    What about doing this: var dataURL = canvas.toDataURL("image/png"); var yourImageData = dataURL.replace(/^data:image\/(png|jpg);base64,/, "");