Using XMLHttpRequest

Esta traducción está incompleta. Por favor, ayuda a traducir este artículo del inglés.

XMLHttpRequest hace de mandar solicitudes HTTP algo muy facil.  Solo crea una instancia del objecto, abre una URL, y envia la solicitud.  El estatus HTTP y el contenido del resultado, estan disponibles en el objecto cuando se termina la transacción.

Solicitudes Síncronas y Asíncronas

XMLHttpRequest soporta tanto comunicaciones síncronas como asíncronas.

Nota: No deberias usar XMLHttpRequests síncronas porque, dada la naturaleza inerentemente asíncrona de las redes, hay multiples formas en que la memoria y eventos se puedan perder usando solicitudes síncronas.

En versiones de Firefox anteriores a Firefox 3, (solicitudes) XMLHttpRequest síncronas bloqueaban la interfaz de usuario.  Con tal de permitirle al usuario terminar solicitudes congeladas, Firefox 3 ya no lo hace.

Ejemplo: Solicitud síncrona

Este ejemplo demuestra como hacer una solicitud síncrona.

var req = new XMLHttpRequest();
req.open('GET', 'http://www.mozilla.org/', false); 
req.send(null);
if (req.status == 200)
  dump(req.responseText);

En la linea 1 se instancia un objeto XMLHttpRequest.  Despues en la linea 2 se abre una nueva solicitud, especificando que una solicitud GET se utilizará para extraer la pagina de inicio de Mozilla.org, y que la operación no debe ser asíncrona.

En la linea 3 se envía la solicitud.  El parametro null indica que la solicitud GET no necesita contenido en el cuerpo.

En la linea 4 se verifica el codigo de estatus despues de que la transaction se completa.  Si el resultado es 200 -- El código HTTP para resultado "OK"-- el contenido de texto del documento se escribe en la consola.

Ejemplo: Solicitudes síncronas no-HTTP

A pesar de su nombre, XMLHttpRequest se puede usar para hacer solicitudes que no sean de HTTP.  Este ejemplo muestra como usarlo para extraer un archivo del sistemas de archivos local.

var req = new XMLHttpRequest();
req.open('GET', 'file:///home/user/file.json', false); 
req.send(null);
if(req.status == 0)
  dump(req.responseText);

La clave aqui es notar que el estado del resultado se compara con 0 en lugar de 200.  Esto es porque los esquemas file y ftp no usan los codigos de resultado de HTTP.

Ejemplo: Solicitudes síncronas

Si usas XMLHttpRequest desde una extensión, deberias usarla asíncronamente.  En este caso, recibiras una llamada de regreso cuando se hallan recibido los datos, lo cual permite al navegador continuar trabajando con normalidad mientras se maneja tu solicitud.

var req = new XMLHttpRequest();
req.open('GET', 'http://www.mozilla.org/', true);
req.onreadystatechange = function (aEvt) {
  if (req.readyState == 4) {
     if(req.status == 200)
      dump(req.responseText);
     else
      dump("Error loading page\n");
  }
};
req.send(null); 

La linea 2 especifica true en su tercer parametro indicando que la solicitud debe manejarse asíncronamente.

Line 3 crea un objeto función para manejar eventos y lo asigna al atributo de la solicitud onreadystatechange.  Este manejador observa el readyState de la solicitud verificando si la transacción se ha completado en la linea 4, si así es, y el estatus HTTP es 200, imprime el contenido recibido.  Si ocurrió un error, se muestra un mensaje de error.

La linea 11 de hecho inicia la solicitud.  La función onreadystatechange es llamada siempre que el estado de una solicitud cambia.

Analizando y Manipulando el Texto de Respuesta HTML

Si usas XMLHttpRequest para obtener el contenido de una página HTML remota, el responseText (texto de la respuesta) sera una cadena que contenga una "sopa" de etiquetas HTML, lo que puede ser dificil de analizar y manipular. Existen tres maneras principales de analizar estas cadenas HTML

  1. Analizar con nsIScriptableUnescapeHTML repidamente convertira la cadena HTML en DOM, al mismo tiempo que tira javascript y otros elementos avanzados, incluyendo la etiqueta <head> de la página.
  2. RegExp se puede usar si de antemano conoces el HTML que vendra en el responseText. Quizas quieras remover los saltos de linea, si usas RegExp para escanear considerandolos. Sin embargo, este metodo es un "ultimo recurso" ya que si el HTML cambia ligeramente, posiblemente fallara.
  3. Usar un hidden chrome o un content-level iframe para cargar toda la pagina también se puede hacer para manipularla luego como DOM, sin embargo existen riesgos de seguridad al dar a código remoto este nivel de acceso privilegiado, que puede causar problemas en la revisión de tu addon. Por ejemplo, si una pagina ejecuta el comando común "document.location = redirecttothispage.html" para cargar, esto se interpretara como cambiar la locación del navegador (document.location en una extensión) en contraposición a la locación de una página web (content.document.location en una extensión), y en consecuecia destruir todos los componentes del navegador. Alternativamente, y de algun modo mas seguro, una cadena responseText adquirida mediante XMLHttpRequest se puede analizar con RegExp para remover problemas de javascript, luego cargada en un iframe oculto previamente  establecido:
document.getElementById('hiddenXULiframe').contentWindow.document.body.innerHTML = req.responseText

Using FormData objects

The FormData object lets you compile a set of key/value pairs to send using XMLHttpRequest. It's primarily intended for use in sending form data, but can be used independently from forms in order to transmit keyed data. The transmitted data is in the same format that the form's submit() method would use to send the data if the form's encoding type were set to "multipart/form-data".

Creating a FormData object from scratch

You can build a FormData object yourself, instantiating it then appending fields to it by calling its append() method, like this:

var formData = new FormData();

formData.append("username", "Groucho");
formData.append("accountnum", 123456);
formData.append("afile", fileInputElement.files[0]);

var xhr = new XMLHttpRequest();
xhr.open("POST", "http://foo.com/submitform.php");
xhr.send(formData);

This example builds a FormData object containing values for fields named "username" and "accountnum", then uses the XMLHttpRequest method send() to send the form's data.

Retrieving a FormData object from an HTML form

To construct a FormData object that contains the data from an existing <form>, specify that form element when creating the FormData object:

newFormData = new FormData(someFormElement);

For example:

var formElement = document.getElementById("myFormElement");
var xhr = new XMLHttpRequest();
xhr.open("POST", "submitform.php");
xhr.send(new FormData(formElement));

You can also add data to the FormData object between retrieving it from a form and sending it, like this:

var formElement = document.getElementById("myFormElement");
formData = new FormData(formElement);
formData.append("serialnumber", serialNumber++);
xhr.send(formData); 

This lets you augment the form's data before sending it along, to include additional information that's not necessarily user editable on the form.

Sending files using a FormData object

You can also send files using FormData. Simply include an <input> element of type "file":

<form enctype="multipart/form-data" method="post" name="fileinfo" id="fileinfo">
  <label>Your email address:</label>
  <input type="email" autocomplete="on" autofocus name="userid" placeholder="email" required size="32" maxlength="64"><br />
  <label>Custom file ID:</label>
  <input type="text" name="fileid" size="12" maxlength="32"><br />
  <label>File to stash:</label>
  <input type="file" name="file" required>
</form>
<div id="output"></div>
<a href="javascript:sendForm()">Stash the file!</a>

Then you can send it using code like the following:

function sendForm() {
  var output = document.getElementById("output");
  var data = new FormData(document.getElementById("fileinfo"));
  
  data.append("CustomField", "This is some extra data");
  
  var xhr = new XMLHttpRequest();
  xhr.open("POST", "stash.pl", false)
  xhr.send(data);
  if (xhr.status == 200) {
    output.innerHTML += "Uploaded!<br />";
  } else {
    output.innerHTML += "Error " + xhr.status + " occurred uploading your file.<br />";
  }
}

Note that this example is directing the output to a Perl CGI script running on the server, and handles HTTP errors, although not prettily.

Handling binary data

Although XMLHttpRequest is most commonly used to send and receive textual data, it can be used to send and receive binary content.

Receiving binary data

The load_binary_resource() function shown below loads binary data from the specified URL, returning it to the caller.

function load_binary_resource(url) {
  var req = new XMLHttpRequest();
  req.open('GET', url, false);
  //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
  req.overrideMimeType('text/plain; charset=x-user-defined');
  req.send(null);
  if (req.status != 200) return '';
  return req.responseText;
}

The magic happens in line 5, which overrides the MIME type, forcing Firefox to treat it as plain text, using a user-defined character set.  This tells Firefox not to parse it, and to let the bytes pass through unprocessed.

var filestream = load_binary_resource(url);
var abyte = filestream.charCodeAt(x) & 0xff; // throw away high-order byte (f7)

The example above fetches the byte at offset x within the loaded binary data.  The valid range for x is from 0 to filestream.length-1.

See downloading binary streams with XMLHttpRequest for a detailed explanation. See also downloading files.

Receiving binary data using JavaScript typed arrays

Gecko 2.0 (Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1) adds a Gecko-specific mozResponseArrayBuffer property to the XMLHttpRequest object, which contains a JavaScript typed array representing the raw binary contents of the response from the server. This lets you read the binary data without taking any special steps.

var xhr = new XMLHttpRequest();
xhr.open("GET", "binary_file", false);
xhr.send(null);

buffer = xhr.mozResponseArrayBuffer;
if (buffer) {
  var byteArray = new UInt8Array(buffer);
  for (var i=0; i<byteArray.byteLength; i++) {
    // do something with each byte in the array
  }
}

This example reads a binary file and interprets it as 8-bit unsigned integers.

Sending binary data

This example transmits binary content asynchronously, using the POST method.

var req = new XMLHttpRequest();
req.open("POST", url, true);
// set headers and mime-type appropriately
req.setRequestHeader("Content-Length", 741);
req.sendAsBinary(aBody);

Line 4 sets the Content-Length header to 741, indicating that the data is 741 bytes long.  Obviously you need to change this value based on the actual size of the data being sent.

Line 5 uses the sendAsBinary() method to initiate the request.

You can also send binary content by passing an instance of the nsIFileInputStream to send().  In that case, you don't have to set the Content-Length header yourself, as the information is fetched from the stream automatically:

// Make a stream from a file.
var stream = Components.classes["@mozilla.org/network/file-input-stream;1"]
                       .createInstance(Components.interfaces.nsIFileInputStream);
stream.init(file, 0x04 | 0x08, 0644, 0x04); // file is an nsIFile instance   

// Try to determine the MIME type of the file
var mimeType = "text/plain";
try {
  var mimeService = Components.classes["@mozilla.org/mime;1"]
          .getService(Components.interfaces.nsIMIMEService);
  mimeType = mimeService.getTypeFromFile(file); // file is an nsIFile instance
}
catch(e) { /* eat it; just use text/plain */ }

// Send    
var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                    .createInstance(Components.interfaces.nsIXMLHttpRequest);
req.open('PUT', url, false); /* synchronous! */
req.setRequestHeader('Content-Type', mimeType);
req.send(stream);

Monitoring progress

XMLHttpRequest provides the ability to listen to various events that can occur while the request is being processed. This includes periodic progress notifications, error notifications, and so forth.

In Firefox 3.5 and later

Firefox 3.5 adds support for DOM progress event monitoring of XMLHttpRequest transfers; this follows the Web API specification for progress events.

var req = new XMLHttpRequest();

req.addEventListener("progress", updateProgress, false);
req.addEventListener("load", transferComplete, false);
req.addEventListener("error", transferFailed, false);
req.addEventListener("abort", transferCanceled, false);

req.open();

...

// progress on transfers from the server to the client (downloads)
function updateProgress(evt) {
  if (evt.lengthComputable) {
    var percentComplete = evt.loaded / evt.total;
    ...
  } else {
    // Unable to compute progress information since the total size is unknown
  }
}

function transferComplete(evt) {
  alert("The transfer is complete.");
}

function transferFailed(evt) {
  alert("An error occurred while transferring the file.");
}

function transferCanceled(evt) {
  alert("The transfer has been canceled by the user.");
}

Lines 3-6 add event listeners for the various events that are sent while performing a data transfer using XMLHttpRequest.  See nsIDOMProgressEvent and nsIXMLHttpRequestEventTarget for details on these events.

Note: You need to add the event listeners before calling open() on the request.  Otherwise the progress events will not fire.

The progress event handler, specified by the updateProgress() function in this example, receives the total number of bytes to transfer as well as the number of bytes transferred so far in the event's total and loaded fields.  However, if the lengthComputable field is false, the total length is not known and will be zero.

Progress events exist for both download and upload transfers. The download events are fired on the XMLHttpRequest object itself, as shown in the above sample. The upload events are fired on the XMLHttpRequest.upload object, as shown below:

var req = new XMLHttpRequest();

req.upload.addEventListener("progress", updateProgress, false);
req.upload.addEventListener("load", transferComplete, false);
req.upload.addEventListener("error", transferFailed, false);
req.upload.addEventListener("abort", transferCanceled, false);

req.open();
Note: Progress events are not available for the file: protocol.

In Firefox 3 and earlier

If, for example, you wish to provide progress information to the user while the document is being received, you can use code like this:

function onProgress(e) {
  var percentComplete = (e.position / e.totalSize)*100;
  // ...
}

function onError(e) {
  alert("Error " + e.target.status + " occurred while receiving the document.");
}

function onLoad(e) {
  // ...
}
// ...
var req = new XMLHttpRequest();
req.onprogress = onProgress; // or req.addEventListener("progress", onProgress, false);
req.open("GET", url, true);
req.onload = onLoad; // or req.addEventListener("load", onLoad, false);
req.onerror = onError; // or req.addEventListener("error", onError, false);
req.send(null);

The onprogress event's attributes, position and totalSize, indicate the current number of bytes received and the total number of bytes expected, respectively.

All of these events have their target attribute set to the XMLHttpRequest they correspond to.

Note: Firefox 3 properly ensures that the values of the target, currentTarget, and this fields of the event object are set to reference the correct objects when calling event handlers for XML documents represented by XMLDocument. See bug 198595 for details.

Cross-site XMLHttpRequest

Este artículo cubre características introducidas en Firefox 3.5

Firefox 3.5 supports cross-site requests by implementing the web applications working group's Access Control for Cross-Site Requests standard.  As long as the server is configured to allow requests from your web application's origin, XMLHttpRequest will work.  Otherwise, an INVALID_ACCESS_ERR exception is thrown.

Bypassing the cache

Normally, XMLHttpRequest tries to retrieve content from the cache, if it's available.  To bypass this, do the following:

var req = new XMLHttpRequest();
req.open('GET', url, false);
req.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
req.send(null);
Note: This approach will only work in Gecko-based software, as the channel attribute is Gecko-specific.

An alternate, cross-browser compatible approach is to append a timestamp to the URL, being sure to include a "?" or "&" as appropriate.  For example:

http://foo.com/bar.html

becomes

http://foo.com/bar.html?12345

and

http://foo.com/bar.html?foobar=baz

becomes

http://foo.com/bar.html?foobar=baz&12345

Since the local cache is indexed by URL, this causes every request to be unique, thereby bypassing the cache.

You can automatically adjust URLs using the following code:

var req = new XMLHttpRequest();
req.open("GET", url += (url.match(/\?/) == null ? "?" : "&") + (new Date()).getTime(), false);
req.send(null); 

Security

Nota sobre Firefox 3

Versions of Firefox prior to Firefox 3 allowed you to set the preference capability.policy..XMLHttpRequest.open to allAccess to give specific sites cross-site access.  This is no longer supported.

Downloading JSON and JavaScript from extensions

For security reasons, extensions should never use eval() to parse JSON or JavaScript code downloaded from the web.  See Downloading JSON and JavaScript in extensions for details.

Using XMLHttpRequest from JavaScript modules / XPCOM components

Instantiating XMLHttpRequest from a JavaScript module or an XPCOM component works a little differently; it can't be instantiated using the XMLHttpRequest() constructor. The constructor is not defined inside components and the code results in an error. You'll need to create and use it using a different syntax.

Instead of this:

var req = new XMLHttpRequest();
req.onprogress = onProgress;
req.onload = onLoad;
req.onerror = onError;
req.open("GET", url, true);
req.send(null);

Do this:

var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                    .createInstance(Components.interfaces.nsIXMLHttpRequest);
req.onprogress = onProgress;
req.onload = onLoad;
req.onerror = onError;
req.open("GET", url, true);
req.send(null);

For C++ code you would need to QueryInterface the component to an nsIEventTarget in order to add event listeners, but chances are in C++ using a channel directly would be better.

See also

  1. MDC AJAX introduction
  2. HTTP access control
  3. How to check the security state of an XMLHTTPRequest over SSL
  4. XMLHttpRequest - REST and the Rich User Experience
  5. Microsoft documentation
  6. Apple developers' reference
  7. "Using the XMLHttpRequest Object" (jibbering.com)
  8. The XMLHttpRequest Object: W3C Working Draft
  9. Web Progress Events specification
  10. Reading Ogg files with JavaScript (Chris Double)

Etiquetas y colaboradores del documento

 Colaboradores en esta página: iiegor, javierdp, bardackx, teoli, inma_610
 Última actualización por: iiegor,