XMLHttpRequest
POST Method
ArrayBufferView
Data types allowed for uploads with the XHR POST method are ArrayBufferView, Blob, Document, DOMString and FormData. In the example below, a "snapshot" of the canvas data is taken as a Uint8ClampedArray instance. Then the array buffer view is sent to a Java server-side code that performs necessary bytes conversion and creates a BufferedImage based on the uploaded data. The image reflecting the canvas is saved on the server. Various graphics operations not implemented in the Canvas 2D API can be performed on the server side: e.g., the image data is converted into a grayscale image and saved under a new name. The transformed image can be downloaded or exposed as a source for a dynamically generated image tag:
var canvas=document.getElementById("canvas-id");
var context=canvas.getContext("2d");
. . . creating canvas graphics . . .
var imageData=context.getImageData(0, 0, 379, 600); // snapshot of the whole canvas
var samplesData=imageData.data; // Uint8ClampedArray instance
var xhr=new XMLHttpRequest();
xhr.open("POST", "image-converter.jsp");
xhr.send(samplesData);
. . . server-side code . . .
BufferedInputStream canvasDataStream=new BufferedInputStream(request.getInputStream());
ByteArrayOutputStream rawDataStream=new ByteArrayOutputStream();
int i; byte[] buffer=new byte[8192];
while((i=canvasDataStream.read(buffer))!=-1) {
rawDataStream.write(buffer, 0, i);
}
byte[] raw=rawDataStream.toByteArray();
. . .
byte[] rgba=new byte[raw.length]; // array will hold red, green, blue and alpha values
for(i=0; i<raw.length; i++) { // Uint8ClampedArray data elements will be converted to Java bytes
Byte sampleByte=new Byte(raw[i]);
String binaryString=Integer.toBinaryString(sampleByte.intValue());
if(binaryString.length()>8) {
binaryString=binaryString.substring(binaryString.length()-8);
}
int sampleValue=Integer.parseInt(binaryString, 2);
Array.setByte(rgba,i,(byte)sampleValue);
}
DataBufferByte dataBufferByte=new DataBufferByte(rgba, (int)909600); // byte-based DataBuffer with a single bank
int[] offsets={0, 1, 2, 3};
PixelInterleavedSampleModel pixelInterleavedSampleModel=new PixelInterleavedSampleModel(
DataBuffer.TYPE_BYTE, // data type for storing samples
379, width of the region of image data in pixels
600, height of the region of image data in pixels
4, // pixel stride: the number of data array elements between two samples for the same band on the same scanline
1516, // scanline stride: 379 * 4
offsets // band offsets
);
Raster raster=Raster.createRaster(pixelInterleavedSampleModel, dataBufferByte, new Point(0,0));
BufferedImage bufferedImage=new BufferedImage(379, 600, BufferedImage.TYPE_INT_ARGB_PRE);
bufferedImage.setData(raster);
ImageIO.write(bufferedImage, "png", new File(targetPath+File.separator+"canvas.png"));
ColorConvertOp colorConvertOp=new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
BufferedImage convertedImage=new BufferedImage(379, 600, BufferedImage.TYPE_BYTE_GRAY);
colorConvertOp.filter(bufferedImage, convertedImage);
ImageIO.write(convertedImage, "png", new File(targetPath+File.separator+"grayscale-canvas.png"));
original canvas: width=379, height=600 canvas image data: 909600 elements | converted canvas data saved as a grayscale image |
Blob
The HTTP request generated in the example above will have entity headers (including the Content-Length header field with the 909600
value) and a payload represented as a raw binary data. Blobs uploading will create the similar request structure, but the Content-Length and Content-Type headers will depend on the values defined in the BlobPropertyBag object during client-side Blob construction:
var stringArray=new Array();
stringArray.push("browser: "+navigator.userAgent+"\n");
stringArray.push("browser vendor: "+navigator.vendor+"\n");
stringArray.push("platform: "+navigator.platform+"\n");
stringArray.push("browser language: "+navigator.language+"\n");
var blobProperties={type: "text/plain; charset=UTF-8", endings: "native"}; // BlobPropertyBag instance
var blob=new Blob(stringArray, blobProperties);
var xhr=new XMLHttpRequest();
xhr.open("POST", "collect-browser-stats.jsp");
xhr.send(blob);
. . . entity headers . . .
Content-Length: 136
Content-Type: text/plain; charset=UTF-8
. . .server-side code . . .
BufferedInputStream blob=new BufferedInputStream(request.getInputStream());
BufferedOutputStream stream=new BufferedOutputStream(new FileOutputStream("user-agent.txt"));
int i; byte[] buffer=new byte[8192];
while((i=blob.read(buffer))!=-1) {
stream.write(buffer, 0, i);
}
. . .
Documents and Strings
A document that will act as the request payload can be built dynamically by using the DOM API. The current document can be also uploaded to the server:
var xhr=new XMLHttpRequest();
xhr.open("POST", "document-handler.php");
xhr.send(document);
. . . entity headers . . .
Content-Length: 13573
Content-Type: application/xml; charset=UTF-8
String data uploads are the most straightforward way to deal with the POST method:
var xhr=new XMLHttpRequest();
xhr.onreadystatechange=listenForEcho;
xhr.open("POST", "http://example.com/php/echo.php");
xhr.responseType="text";
xhr.send("message"); // Content-Type: text/plain; charset=UTF-8
. . .
function listenForEcho(event) {
if(event.target.readyState==4) {
if(event.target.status==200) {
console.log(event.target.responseText);
}
}
}
. . . server-side code . . .
<?php
echo($HTTP_RAW_POST_DATA);
?>
FormData
FormData instances are created programmatically or derived form an existing <form> element:
var xhr=new XMLHttpRequest();
var formData=new FormData();
formData.append("text", "text data"); // name of the form control and its value; the control type is text
xhr.open("POST", "http://example.com/php/form-data-handler.php");
xhr.send(formData);
This time the generated request carries the payload as the multipart/form-data:
POST http://example.com/php/form-data-handler.php HTTP/1.1 request line
Accept: */* request header
Referer: http://example.com/js/xhr-examples.html request header
Accept-Language: en-US request header
Accept-Encoding: gzip, deflate request header
User-Agent: Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko request header
Host: example.com request header
Connection: Keep-Alive general header
Pragma: no-cache general header
Content-Length: 154 entity header
Content-Type: multipart/form-data; boundary=---------------------------7de2e3ab0488 entity header
-----------------------------7de2e3ab0488
Content-Disposition: form-data; name="text-control" entity part header
text data entity part body
-----------------------------7de2e3ab0488--
The same structure will be created when the FormData is built from the markup elements:
var xhr=new XMLHttpRequest();
var form=document.getElementById("form-id");
var formData=new FormData(form);
xhr.open("POST", "http://example.com/php/form-data-handler.php");
xhr.send(formData);
Sending a complex form data with files will require additional manipulations:
<form action="javascript:formDataHandler()" method="POST" enctype="multipart/form-data">
<input type="file" name="file-control" multiple>
<input type="submit">
</form>
. . .
<script>
function formDataHandler() {
var form=document.getElementsByTagName("form")[0];
var fileControl=form["file-control"];
if(fileControl.files.length>0) {
var i; var formData=new FormData();
for(i=0; i<fileControl.files.length; i++) {
formData.append("file"+i.toString(), fileControl.files[i], fileControl.files[i].name);
}
var xhr=new XMLHttpRequest();
xhr.open("POST", "http://example.com/php/form-data-handler.php");
xhr.send(formData);
}
}
</script>
Let's assume three files with various MIME types were selected. Then the HTTP request will carry additional information about file names as well as the files contents:
POST http://example.com/php/form-data-handler.php HTTP/1.1 request line
Host: example.com request header
User-Agent: Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko request header
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 request header
Accept-Language: en-US,en;q=0.5 request header
Accept-Encoding: gzip, deflate request header
Referer: http://example.com/js/xhr-examples.html request header
Connection: keep-alive general header
Pragma: no-cache general header
Cache-Control: no-cache general header
Content-Length: 14792 entity header
Content-Type: multipart/form-data; boundary=---------------------------4895325576053 entity header
-----------------------------4895325576053
Content-Disposition: form-data; name="file0"; filename="image.jpeg" entity part header
Content-Type: image/jpeg entity part header
. . .image file data . . . entity part body
-----------------------------4895325576053
Content-Disposition: form-data; name="file1"; filename="text.txt" entity part header
Content-Type: text/plain entity part header
. . .text data . . . entity part body
-----------------------------4895325576053
Content-Disposition: form-data; name="file2"; filename="xml.xml" entity part header
Content-Type: text/xml entity part header
. . .XML markup . . . entity part body
-----------------------------4895325576053--
Progress Events
The same events model that is used for monitoring the load of the response payload can be employed to reflect the progress of data uploads:
var formData=new FormData(document.getElementById("form-id"));
var xhr=new XMLHttpRequest();
xhr.upload.onprogress=monitorUpload;
xhr.open("POST", "http://example.com/php/form-data-handler.php");
xhr.send(formData);
. . .
function monitorUpload(event) {
document.getElementById("log").innerHTML=event.loaded;
}