Opera Unite developer's primer

By Hans S. Tømmerholt

Further content contributions by Arve, Chris, Zi Bin, and Lissy.

There is an updated version of this article available: Opera Unite developer’s primer — revisited

Introduction

Opera Unite features a Web server running inside the Opera browser, which allows you to do some amazing things. At the touch of a button, you can share images, documents, video, music, games, collaborative applications and all manner of other things with your friends and colleagues.

This article gets you started on the road to Opera Unite Service development — it describes how the Opera Unite Web Server in Opera works and how it can be used. Below I will briefly recap some of the basic concepts related to Opera Unite, show how you can enable the Web server in your browser, and give an example of how to write a simple Opera Unite blog service.

The contents of this article are as follows:

Basic concepts

What is Opera Unite?

Opera Unite is, in short, a Web server running inside the Opera Web browser. This Web server allows the user to install services and share these services with their friends and colleagues (or everyone, if they wish). The interaction is all done via a central Opera Unite server — Opera Unite uses a proxy between the server and its clients (found at operaunite.com) to avoid the need for any special firewall configuration.

Opera Unite proxy

Traditionally, when a user runs a Web server in a home network, the network has a device that acts as a firewall, which needs to be configured separately, as illustrated in Figure 1.

traditional web server setup

Figure 1: A traditional Web server setup

Typically, the user will need to open ports and enable port forwarding to a local computer in order for people outside the firewall to be able to access the server.

However, when the user is using Opera Unite, no configuration is needed, as seen in Figure 2.

server set up when using the Unite server in your browser

Figure 2: The set up when using the Opera Unite server in your browser

The Web server initiates a connection to the proxy, which uses this to pass information back about incoming requests.

Note that the proxy is really only a fallback mechanism, to ensure that data can be delivered in case NAT traversal fails. Opera Unite has support for "UPnP", meaning Universal Plug and Play, which allows you to share your data using direct connections to your computer, if available. This can make loading speeds for your services faster, as they will bypass the proxy server. However, as it is up to each service to load content using the direct connection, it may not always run services faster. UPnP has no authentication mechanism, and assumes that local systems and their users are completely trustworthy.

Opera Unite Services

An Opera Unite Service is a special kind of Opera Widget, which holds the logic for receiving requests and responding to them. A widget is an Opera Unite Service if its config.xml contains a feature element like this:

<feature name="http://xmlns.opera.com/webserver">
  <param name="type" value="service"/>
  <param name="servicepath" value="blog"/>
</feature>

In this case, a special JavaScript object, opera.io.webserver, becomes available to the service. See the Opera Unite Web Server JavaScript API for more information.

As the Opera Widgets technology is used, the Opera Unite Service can provide the person running the Opera Unite server with a simple way of controlling and configuring it, all using standard HTML, CSS and JavaScript. Opera Unite Services do however get access to functionality normally not present in widgets or Web pages, for example a sandboxed file system.

For those of you who are interested in learning more about Opera Widgets, you can find more Widgets articles on dev.opera.

Let’s move on, get Opera Unite enabled, and start building up a simple Opera Unite Service.

Enabling your Web server

For security and performance reasons, the Web server does not run by default when Opera is started. You enable the server by selecting Tools > Opera Unite Server > Enable Opera Unite, or by opening an Opera Unite service. When you do so, a dialog pops up asking you to specify a username and password. This is your My Opera username and password.

Note that only My Opera usernames containing valid URL characters will work with Opera Unite. Invalid characters include “/”, “.”, “_” and space.

In the next wizard screen, you need to define a device. You may select a device name from the drop-down list, or specify your own. The device name is used to identify your server via the proxy. It will be available via a URL like the following:

http://devicename.username.proxyaddress/servicename

So, to visit the service test on the server your_device on operaunite.com, the URL becomes:

http://your_device.your_username.operaunite.com/test

Creating the Opera Unite Service: A simple blog

Here is a short walk-through for creating a simple blogging service that allows the user to write blog entries. Once stored, the entries are immediately available to the world through the server.

The service has two parts: One is a configuration view for the service, where the owner can configure and control it. The other part is a series of Web pages generated or served by the service, which are visible to the user.

For those who just can’t wait, you can download Opera Unite blog source code. It is packaged with extension .us, the default extension for Unite Services. You can unzip the package to look at the source code or drag the package into Opera browser to fire up the Unite blog example.

Files and folders in the service

Our service will contain the files and folders shown in Figure 3:

The directory structure of the service

Figure 3: The directory structure of the service

  • config.xml: Configuration file for the service
  • index.html: Starting logic for the service, including scripts.
  • script/script.js: The actual Web server code.

Of these, only config.xml and index.html are required.

You may include a public_html folder, which is a magic folder in Opera Unite Services. Normally, files and folders inside your service are not available to users requesting your service, so if you want to distribute a fancy stylesheet, static images and similar, these files go inside here. These files are mapped to the relative root of your service, so a file named cats.png inside the public_html folder of the helloOperaUnite service will be available at

http://your_device.your_username.operaunite.com/helloOperaUnite/cats.png.

Configuring the service: config.xml

This service will be packaged in the same way as an Opera Widget, so we’ll need to define a config.xml file. The file is just like a normal Opera Widgets config.xml file, with a few extra details. In order to identify your service as a Opera Unite Service, you need to add a feature element to the widget element in your config.xml file.

Please note that Opera Widgets are packaged as regular zip files and renamed to use the extension .wgt, whereas Opera Unite Services are packaged and renamed to use the extension .us to denote Opera Unite Service.

<widget>
  <widgetname>My blogging service</widgetname>
  <description>Blogging service example from the Opera Unite Services primer.</description>
  <author>
    <name>Hans S. Toemmerholt</name>
    <organisation>Opera Software ASA</organisation>
  </author>
  <feature name="http://xmlns.opera.com/webserver">
    <param name="type" value="service"/>
    <param name="servicepath" value="blog"/>
  </feature>
</widget>

The widgetname element of the service also acts as its service name. This is the name which will be shown to the user when installing and using the service.

You may also add a servicepath element to the config.xml file. The content of this element must be a valid part of a URI and will define what the name of your service will be in the URI of the service. If this element is not present, Opera will attempt to use the content widgetname element as the URI component. If this name is not valid as a URI component, the installation of the service will fail with an error message.

When the service is packaged and run, the above config.xml will make it respond to

http://your_device.your_username.operaunite.com/blog/

Tying it together: index.html

A service has no UI beyond the Web pages it produces. index.html is the starting point for the service, so that is in effect the UI. In our example, we’ll use a minimal HTML 5 file with a reference to the script we're using:

<!DOCTYPE html>
<script src="script/script.js"></script>

Creating the script: script.js

Note the link to the script file script.js is in the above code snippet. The Web server listens to requests made from clients (users browsing the URL of the service) and creates responses that are sent back. The response is typically a generated Web page containing information.

The functionality in Opera Unite is exposed to developers through a set of JavaScript APIs, including objects representing the Web server, connections, incoming requests and outgoing responses.

What follows is a walk-through of the script.

The request event listeners

A Web server handles requests from clients and sends reponses back to them. The Opera Unite Web server is event-based and will raise a DOM event in the service every time a Web browser makes a connection to the server asking for files related to the Opera Unite Service. In order to respond to such events, we need to set up event listeners. This is done in window.onload:

var webserver;
var entries = [];

window.onload = function () {

    webserver = opera.io.webserver

    if (webserver)
    {
        //Handle requests for various URLs
        webserver.addEventListener('_index', showEntryList, false);
        webserver.addEventListener('entry', showEntry, false);
        webserver.addEventListener('form', showForm, false);
        webserver.addEventListener('save', saveEntry, false);
    }
}

What is going on here?

We are checking if the service is actually a Web service, by checking for the webserver object. If it is present, we add four event listeners _index, entry, form and save.

When this listener is set up, the service will now call one of the functions each time a user visits one of the following URLs:

  • http://your_device.your_username.operaunite.com/blog/
  • http://your_device.your_username.operaunite.com/blog/entry
  • http://your_device.your_username.operaunite.com/blog/form

The _index request is special, and means a request to the root path of the service. As we shall see, the user will not visit “save” directly, only through the form.

Showing a list of blog entries

The code for the _index request, the showEntryList function, is quite simple. When receiving a request, it writes back a HTML page with a list of the saved entries.

function showEntryList(e)
{
    var response = e.connection.response;
    response.write( '<!DOCTYPE html>'
        + '<html><head><title>Entries</title></head>'
        + '<body><ul>'
    );

    for ( var i = 0, entry; entry = entries[i]; i++ )
    {
        response.write('<li>'+entry.date+': <a href="entry?id='+i+'">'+entry.title+'</a></li>');
    }

    response.write('</ul>'
      + '<p><a href="form">Add en entry</a>.</p>'
      + '</body></html>'
    );
    response.close();
}

Line-by-line, the script does the following:

It first gets a reference to the response object. This is the object that holds the methods necessary to send output back to the client:

var response = e.connection.response;

The write method then writes the content to the web browser that requested the page. First, we write a simple HTML shell:

response.write( '<!DOCTYPE html>'
    + '<html><head><title>Entries</title></head>'
    + '<body><ul>'
);

The existing blog entries are marked up as a list with links to the individual entries:

for ( var i = 0, entry; entry = entries[i]; i++ )
{
    response.write('<li>'+entry.date+': <a href="entry?id='+i+'">'+entry.title+'</a></li>');
}

Finally, we close the connection.

response.close();

Showing a single entry

Next, we need to output something when the user clicks a link to an entry:

function showEntry(e)
{
    var index = e.connection.request.queryItems['id'][0];
    var entry = entries[index];
    //ToDo Should have error handling here
    var response = e.connection.response;
    response.write('<!DOCTYPE html>'
        + '<html><head><title>'+entry.title+'</title></head>'
        + '<body><h1>'+entry.title+'</h1>'
        + '<p>'+entry.date+'</p>'
        + '<div>'+entry.text+'</div>'
        + '</body></html>'
    );
    response.close();
}

Line-by-line, the script does the following:

It first gets a reference to the request object, which contains information about the incomming request:

var request = e.connection.request;

CGI GET arguments are stored in the queryItems property of the request. We get the id of the entry to display. Note that the the same CGI argument may have multiple values:

var index = request.queryItems['id'][0];

Next we get the corresponding blog entry:

var entry = entries[index];

The write method then writes the content to the web browser that requested the page. The title, date and text of the blog entry are wrapped in suitable markup:

response.write('<!DOCTYPE html>'
    + '<html><head><title>'+entry.title+'</title></head>'
    + '<body><h1>'+entry.title+'</h1>'
    + '<p>'+entry.date+'</p>'
    + '<div>'+entry.text+'</div>'
    + '</body></html>'
);

Showing the form for adding an entry

When you click the “Add an entry” link, a classic Web form is displayed:

function showForm(e)
{
    var response = e.connection.response;
    response.write('<!DOCTYPE html>'
        + '<html><head><title>Add entry</title></head>'
        + '<body><h1>Add entry</h1>'
        + '<form method="post" action="save">'
        + '<p><label for="namefield">Title</label> <input id="nameField" type="text" name="title"></p>'
        + '<p><label for="textArea">Text</label> <textarea id="textArea" name="text"></textarea></p>'
        + '<p><input type="submit" name="Add entry"></p>'
        + '</form>'
        + '</body></html>'
    );
    response.close();
}

This could be a lot more complicated, handling error messages, adding already filled-in values and so on. You should also offer some authentication scheme for potentially destructive data operations, but we keep it simple as an example.

Saving an entry

Finally, when you submit the form, a new entry should be saved. For now, entries are stored in a simple array, so will be lost when the service is restarted, but it wouldn’t be so hard to extend the example to provide a means of retaining the blog entries.

function saveEntry(e)
{
    var request = e.connection.request
    var response = e.connection.response;

    //Get POST data
    var title = request.bodyItems['title'][0];
    var text = request.bodyItems['text'][0];

    entries.push({
        'title' : title, 
        'text'  : text,
        'date'  : new Date()
    });


    //Redirect back to the index of the service
    response.setStatusCode(302);
    response.setResponseHeader( 'Location', webserver.currentServicePath );
    response.close();
}

Instead of request.queryItems, we use the bodyItems property to access data sent by POST, in this case the title and the content of the new entry.

var title = request.bodyItems['title'][0];
var text = request.bodyItems['text'][0];

Submitting the form saves the entry, storing it in an array:

entries.push({
 	     'title' : title,
 	     'text'  : text,
 	     'date'  : new Date()
 	 });

Finally, when the entry is saved, we want to redirect back to the list of entries:

response.setStatusCode(302);
response.setResponseHeader( 'Location', webserver.currentServicePath );
response.close();

Here we create a standard HTTP temporary redirect back to the root of the service, represented by the webserver.currentServicePath property. This will fire an _index request, and the list of entries will then be shown.

Again, you should add error handling and status messages to this.

Using your Opera Unite Service

In order to get your Opera Unite Service running, you simply need to load the service. Click and drag its config.xml or a zipped version of the service into your browser window. Or open it from a file dialog. If you have not previously started any Opera Unite Services, the Opera Unite configuration dialog will now appear.

Double click the My blogging service service in the Unite Services pane, and you should get a page appearing in the browser window, as seen in Figure 4:

the blog service main screen

Figure 4: The blog service main screen.

Clicking the Add an entry link will take you to a form that allows you to add a blog entry, as seen in Figure 5.

a form for entering a new blog entry

Figure 5: The form for entering a new blog post.

When you enter some text and press submit, you are taken back to the blog main screen, and your blog entry is available to view. You can click on the blog entry title to view the post. Add a few blog entries, have a play around. You should end up with something like Figure 6.

a few blog entries entered into the blog service an expanded full blog entry

Figure 6: Our blog is now nicely populated.

Viewing your Opera Unite Service

If you followed this guide and started the service in Opera, you should now have a functioning web service. Anyone can visit it by going to the URL

http://devicename.username.proxyaddress/servicename

In this case, if the device is called your_device and it’s running the blog service, the URL becomes:

http://your_device.username.operaunite.com/blog

As you saw when running the example above, you can visit the root address for the device to see the installed services on a system, for example:

http://your_device.username.operaunite.com/

This page will contain information on which services are installed on the system, and if the information is found in config.xml, it will also list information on each service and its author.

Uploading your Opera Unite Service onto unite.opera.com

So now you've put together a cool Opera Unite Service, you not only want to let people use it via your Opera Unite server — you also want to make it available for others to download and install on their Opera Unite servers, right? So how do we do this? The answer is to upload them to unite.opera.com — this is the site where Unite Services are distributed. This section will show you how.

Before publishing

Before publishing you should ideally test your service to find bugs. Test on different platforms, devices and Opera browser versions if you can. Also remember that people consuming your services can do so from any browser, not just Opera, so test your service pages in other browsers (Firefox, Safari, etc.) on other computers.

If you are having trouble getting your service to work and you are convinced that the actual code of the service is right, check your config.xml file for errors. It needs to work for the service to be accepted. Opening the file in a different browser will check it for well-formedness. Check also that your config.xml contains enough information. We will use this file to supply information about your service to unite.opera.com, and to the Opera Unite Service page on computers where the service is installed.

Also consider translating the service, if this is appropriate and you can do so.

Finally, take a screenshot of the service in action, as described below.

Publishing your service

To do this, you need to visit Opera’s Upload page. Select your service archive file (.zip) in the archive file chooser dialog and upload it. Read through and verify the information taken from your config.xml. Feel free to add more text if you wish.

Next up, select your screenshot in the screenshot file dialog box, so that others will be able to see what your service looks like before they try it out.

You also need to select the target devices that your service is designed to be used with. Make sure you have tested it on those devices. Select a relevant group for your service. The last step is to select the target languages for your service. Make sure you have supplied translations for all the languages you select.

How can I get people to try my service?

When you have spent a lot of time making a service, you naturally want people to try it out. To increase the number of views, you need to tell potential users what to expect when running the service.

There are two effective ways of achieving this: Writing an effective service description and making useful screenshots (see below for both).

How can I write an effective service description?

There are two fields that you can use to communicate with your users:

  • The short description is taken from your config.xml file and shown on all lists the service appears on
  • The long description is shown on your service page

Use the short description to catch the user’s eye, stating what your service does and what value users can get out of it. It may be a tagline, but it should be informative. You should avoid phrases like “Download me” or “This is a super cool service”.

The following are examples of effective descriptions:

  • “Stay updated on the weather in location X, Y, Z.”
  • “Relax with this classic game of XYZ.”
  • “Get quick access to the XYZ specification."”
  • “Measure your Web page elements with this expandable ruler.”
  • “Read news from Slashdot.”

Use the long description to tell people what features your service has, how it was implemented, about changes in different versions, rules for games, and so on.

Bear in mind that your description will be read by people from different backgrounds, countries, cultures, and age groups, with different platforms, devices and browser versions. Not everyone understands things the way you do. You can often identify issues that need clarification by seeking feedback from friends and family.

How can I make effective screenshots of my service?

For screenshots to be effective, follow the tips listed below:

  • show the core functionality of your service
  • Show the most important service interaction pages, not the login page or the preferences page if your service has them
  • Show your service in action. If it is a game, show the game running. If it can have data, show it with data
  • Crop the screenshot to just show the most important parts of the service

Note that you should make your screenshots 445 x 230 pixels ideally — this is the size we have been using on the Unite web site. If you use different-sized screenshots, they will be resized, which may lead to undesirable results.

Approval of Opera Unite Services

All services need to be approved by Opera Software staff. We check for errors to ensure that our users have a good online experience, but we do not take responsibility for the content of the services or make any guarantees about the functionality. See our disclaimer.

What are the guidelines for approval of an Opera Unite Service?

These are some of the guidelines that apply to services:

  • The service must have a sensible name and description
  • The service must not have obvious bugs, so ensure that you test it before uploading
  • The service must not contain malicious or destructive code
  • The service must not contain or use copyrighted information for which you do not hold the rights
  • The service must not contain or point to adult or hateful content
  • The service should serve standards-compliant HTML pages that are viewable in all modern browsers on a variety of devices.

Further reading

Now that you are familiar with the basics of creating and uploading Opera Unite Services, you might want delve a bit deeper:

This article is licensed under a Creative Commons Attribution, Non Commercial - Share Alike 2.5 license.

Comments

The forum archive of this article is still available on My Opera.

No new comments accepted.