Http Communication in ECMAScript with XMLHttpRequest
Keywords:
script,
ECMAScript,
http,
xml,
XMLHttpRequest,
xhr,
REST
Author(s): Manuel Olbrich
Date: 2010-06-24
Summary: This tutorial shows you how to communicate with Http servers in script nodes using ECMAScript.
Introduction
Getting information into X3D/VRML-Scene usually requires converting the data into the language's syntax. Also, runtime changes, such as painting on a PaintTexture are lost when the application is closed. A common way to get around this problem involves java based script nodes, which can be allowed to bypass security and write data to disk. The downsides of this method are relatively complex code in an additional programming language and modifications to the java settings. A XMLHttpRequest ECMAScript object allows accessing dynamic data outside of the application, while having a relatively simple setup.Warning: To prevent foreign scenes from sending personal data to third parties, a XMLHttpRequest object will only communicate with whitelisted servers. If you try to use a non-whitelisted server, a ECMAScript exception will inform you about this problem. You can whitelist a host by adding --ecmaScriptXHRTrustedHosts HOSTNAME to your commandline or by changing this parameter via the SceneGraph web interface (Setup section). HOSTNAME can be a single host name, a comma separated list of host names, or the keyword "any" which would disable this security check.
Connecting to an Http Server
The Http protocol is used in many applications. In this tutorial, we will connect to a simple webserver, to a WebDAV server, and to apaches CouchDB.- WebDAV is very similar to a usual webserver, but it also allows writing and deleting documents. All common desktop operating systems can handle WebDAV like a remote file system. Webservers, such as apache and lighttp, support this service, but it is disabled by default, since it can be used to write in a server's file system.
- CouchDB is a simple and open DBMS that communicates over HTTP. The database entries (called documents in couchdb) are stored in JSON (JavaScript Object Notation) and can therefore be easily used in JavaScript applications.
Code: The following Script node creates an XMLHttpRequest object.
Script{ url["JavaScript: var xhr; function initialize() { xhr=new XMLHttpRequest(); } "] }
GETting Data from a Webserver
Code: The following code fetches a file from an Http server.
xhr.open('GET','http://localhost/test.txt'); xhr.send(); print(xhr.responseText);
PUTting Files on a WebDAV Server
Putting files is nearly as simple as getting them. You should check your WebDAV setup with a native client to make sure that your permissions are set correctly and that you can write files.Code: The following code creates a file on a WebDAV server.
xhr.open('PUT','http://localhost/newfile.txt'); xhr.send('The file content goes here.')
PUTting SFImages as JPEG- or PNG-Files on a WebDAV Server
If you like to save an image that was generated in your application (like an image from a PaintTexture or a frame from a Sensor), you would most likely want to save it in a readable image format rather than using SFImage encoding. XMLHttpRequest allows an second parameter to send() which sets a convertion before the data will be send. Possible values for this parameter are SFImage2jpg and SFImage2png.Code: The following code creates a several image files on a WebDAV server. img is an SFImage.
xhr.open('PUT','http://localhost/testimage.sfimage'); xhr.send(img); //new file will be the string representation of an SFImage xhr.open('PUT','http://localhost/testimage.jpg'); xhr.send(img,'SFImage2jpg'); // convertion to JPEG xhr.open('PUT','http://localhost/testimage.png'); xhr.send(img,'SFImage2png'); // convertion to PNG
DELETE Files from a WebDAV Server
Deleting files works like getting. The following Example shows the deletion of a file.Code: The following code deletes a file on a WebDAV server.
xhr.open('DELETE','http://localhost/test.txt'); xhr.send();
Asynchronous requests
XMLHttpRequest allows asynchronous requests, which is important since the rendering will be blocked by an synchronous request. The open() method has an third parameter, which determines if the request is async or not. The default Value for this parameter is false. You can set an callback function for XMLHttpRequest with the onreadystatechange attribute. This callback function gets called on every state change. Usually the state we are most interested in is DONE, indicated by the number 4.Code: Asynchronous version of the GET example.
xhr.open('GET','http://localhost/test.txt',true); xhr.onreadystatechange = function(){ if(xhr.readyState==4) //4==DONE print(xhr.responseText); } xhr.send();
Information about your last Request
An XMLHttpRequest object stores the last reply's header and statusline. The attribute status returns the response status code. It will be 200 if everything went right. The corresponding status phrase is available in the statusText attribute.Code: The following code fetches a file from an Http server and reads the response status information and headers.
xhr.open('GET','http://localhost/test.txt'); xhr.send(); print('status code: ' + xhr.status + '\n'); print('status phrase: ' + xhr.statusText + '\n'); print('response headers are:\n:' + xhr.getAllResponseHeaders());
status code: 200 status phrase: OK response headers are: Accept-Ranges: bytes Content-Length: 14 Content-Type: text/plain Date: Tue, 05 Oct 2010 09:46:14 GMT ETag: "111fc-e-491db7dd4e565" Last-Modified: Tue, 05 Oct 2010 09:43:21 GMT Server: Apache/2.2.14 (Ubuntu) Vary: Accept-Encoding
Working with CouchDB
apache's CouchDB is quite simple and easy to set up and use. However, you should understand the basic concepts before you play with these examples. The CouchDB Wiki is a good starting point. Once you do, here is a neat API-Cheatsheet. You should have understood the following:- Every document (database entry) is accessed via its id, which is usually a random generated UUID
- You have to provide an id when generating a document. You can get a new UUID from couchdb
- if you want to change or delete an object, you have to provide the current revision (this prevents conflicts when multiple users are working on the same document)
- Documents can have attachments, just like an email. Adding an attachment is a change, so you need to provide the current revision. Attachments can be directly accessed, which means that you can point a web browser to an attached Image and simply get it. This is quite useful since you can simply provide this url to an ImageTexture
- A view is a special kind of document, called a design document (with "_design/namehere" as id)
- All documents (including views) are in one container. use views to separate them
- CouchDB comes with a web frontend, called Futon, to access the DB. (http://localhost:5984/_utils/)
Parsing JSON
The neat thing about the combination of CouchDB and JavaScript is that the documents (and nearly everything that CouchDB) returns are encoded in JSON (JavaScript Object Notation). That means, you can simply evaluate what CouchDB returns to you have a JavaScript object. The following example shows how to connect to the couchdbms and get its welcome message as a JavaScript object.Code: Connect to CouchDB, read and eval the welcome msg.
Script{ url["javascript: var xhr; function initialize() { xhr=new XMLHttpRequest(); xhr.open('GET','http://localhost:5984/'); xhr.send(); print(xhr.responseText); var myobj = eval('('+xhr.responseText+')'); print(myobj.couchdb); print(myobj.version); } "] }
{"couchdb":"Welcome","version":"0.10.0"} Welcome 0.10.0
List All Documents in Your DB
The db in this example is called demodb. You can create yours either with the apicalls or using Futon. If you want to list all DBs in your DBMS, get /_all_dbs from you db.Code: Listing all documents in your db.
xhr.open('GET','http://localhost:5984/demodb/_all_docs'); xhr.send(); print(xhr.responseText);
{"total_rows":0,"offset":0,"rows":[]}
{"id":"e42c296c5201a7506768b60c3074affe","value":{"rev":"1-dfc98c905e3b4899ae2114a63fbdd2c4"}}
Writing a New Document
Writing a document is like PUTting files to WebDAV, mentioned previously. The document's content in JSON encoding is transferred as the body. The target URI is /databasename/id. As mentioned before, you should use UUIDs as IDs. If you have no convenient way of generating these yourself, simply callCode: Get UUIDs from CouchDB.
xhr.open('GET','http://localhost:5984/_uuids');
{"uuids":["7f06ce4956f68193f66b1f172ef5badf"]}
Code: Writing a document to CouchDB
var newDocument = '{\"documentname\":\"myNewDocument\",\"rating\":\"totally awesome\",\"someNumbers\":12345}'; xhr.open('PUT','http://localhost:5984/demodb/7f06ce4956f68193f66b1f172ef5badf') xhr.send(newDocument);
{"ok":true,"id":"7f06ce4956f68193f66b1f172ef5badf","rev":"1-9bfc66754ea484c25293510de7b6ed9f"}
Reading Documents
Reading documents is quite simple. We assume you already have the document's id (either from a view or from a _all_docs request).Code: Getting a document from CouchDB
xhr.open('GET','http://localhost:5984/demodb/7f06ce4956f68193f66b1f172ef5badf'); xhr.send();
{"_id":"7f06ce4956f68193f66b1f172ef5badf","_rev":"1-9bfc66754ea484c25293510de7b6ed9f","documentname":"myNewDocument","rating":"totally awesome","someNumbers":12345}
Updating a Document
Updating documents is similar to writing. The only difference is that you have to supply the document's current revision to make sure nobody else changed the document since you've last seen it.Code: Updating a document in CouchDB
var updatedDocument = '{\"_rev\":\"1-9bfc66754ea484c25293510de7b6ed9f\",\"documentname\":\"myUpdatedDocument\",\"rating\":\"still awesome\",\"someNumbers\":42}'; xhr.open('PUT','http://localhost:5984/demodb/7f06ce4956f68193f66b1f172ef5badf'); xhr.send(updatedDocument);
{"ok":true,"id":"7f06ce4956f68193f66b1f172ef5badf","rev":"2-23892d3ee7cf3b99899dadd3c3ffabe2"}
{"error":"conflict","reason":"Document update conflict."}
Adding an Attachment
Adding an attachment to a document is like writing a file to webdav. The documents URI acts as path for the new file. The main difference is that you need to supply the current document revision because you are updating the document.Code: Adding an attachment to a document.
xhr.open('PUT','http://localhost:5984/demodb/7f06ce4956f68193f66b1f172ef5badf/newpic.jpg?rev=2-23892d3ee7cf3b99899dadd3c3ffabe2'); xhr.send(someSFImageField,'SFImage2jpg');
{"ok":true,"id":"7f06ce4956f68193f66b1f172ef5badf","rev":"3-d00474de6495109df7cdfe4e194ccd9f"}
Deleting a New Document
If you plan to delete a document, you need its id and its current revision string. A revision string is necessary to assure that nobody has changed the document since you have last seen it. Let's delete our updated document:Code: Deleting a document in CouchDB
xhr.open('DELETE','http://localhost:5984/demodb/7f06ce4956f68193f66b1f172ef5badf?rev=3-d00474de6495109df7cdfe4e194ccd9f'); xhr.send();
{"ok":true,"id":"7f06ce4956f68193f66b1f172ef5badf","rev":"4-e02f8207d71b9ac2c93f11a25be9a770"}
{"error":"not_found","reason":"deleted"}
{"error":"conflict","reason":"Document update conflict."}