Blog tagged httpd

Ceylon in the browser

Last week, we met the Ceylon HTTP server, which we can use to serve dynamic (or even static) content over HTTP. But what about the client side? Well, today we're going to write a little HTTP client that runs in the browser, mainly as a way of showing off Ceylon's new dynamic blocks.

First, let's see the server, in a module named hello.server:

import ceylon.net.httpd { createServer, startsWith, 
                          AsynchronousEndpoint, 
                          Request, Response }
import ceylon.net.httpd.endpoints { serveStaticFile }

void run() {
    createServer{ 
        AsynchronousEndpoint { 
            path = startsWith("/sayHello"); 
            void service(Request request, Response response, 
                    void complete()) {
                response.writeString("Hello, Client!");
                complete(); //async response complete
            }
        },
        AsynchronousEndpoint {
            path = startsWith(""); 
            service = serveStaticFile("../client");
        } 
    }
    .start();
}

The server has two asynchronous endpoints, one of which simply serves up static content out of the directory ../client, and the other of which just responds to requests with the text Hello, Client!.

The entrypoint to our application is a HTML page.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-type" 
              content="text/html;charset=UTF-8"/>
        <title>Hello</title>
        <script type="text/javascript"
                src="script/require/1.0.8/require.js">
        </script>
        <script type="text/javascript">
            require.config({
                // location of our project's compiled modules
                baseUrl: 'modules',
                // locations of external dependencies here
                paths: {
                    // path to the Ceylon language module 
                    'ceylon/language': 
                        '/script/ceylon/language'
                }
            });
            //alias our hello module to 'h'
            require(['hello/client/1/hello.client-1'], 
                    function (hello) { h = hello; });
        </script>
    </head>
    <body>
        <div>
            <input type="submit" 
                   value="Say hello" 
                   onclick="h.hello()"/>
        </div>
        <div>
            <span id="greeting"/>
        </div>
    </body>
</html>

The page has a button which calls the hello() function of the Ceylon module hello.client.

We're using require.js to load our Ceylon modules. Unfortunately require.js doesn't really have a concept of module repositories like Ceylon does, which means configuring it to find all our compiled Ceylon is a bit fiddly. Nor does it feature very good error reporting which turns "fiddly" into "time-wasting". (The implementation of require() in node.js is a much better fit,
but unfortunately we don't have that in the browser.)

Finally, the module named hello.client has the following function:

shared void hello() {
    dynamic {
        value req = XMLHttpRequest();       
        void handleResponse() {
            if (req.readyState==4) {
                document.getElementById("greeting")
                        .innerHTML = req.status==200 
                                then req.responseText 
                                else "error";
            }
        }
        req.onreadystatechange = handleResponse;
        req.open("GET", "/sayHello", true);
        req.send();
    }
}

This function makes use of the native JavaScript API XMLHttpRequest to send an asynchronous request to our server, and then interacts with the browser's DOM. But how on earth can a statically typed language like Ceylon call a weakly typed language like JavaScript!?

Well, code that appears inside a dynamic block is optionally typed. That is, Ceylon will make use of typing information when it is available, but when it is not, it will just shut up and let you write almost whatever you like, as long as it's syntactically well-formed Ceylon code. Ceylon can't be certain that there's really a function called XMLHttpRequest, and it certainly can't be sure that it has a member named onreadystatechange, but it will just assume you know what you're doing.

Inside a dynamic block, you can even instantiate a "dynamic" object:

dynamic {
    void printGreeting(value obj) {
        console.log(obj.greeting + " " + obj.language);
    }
    value obj = value { greeting="Hello"; language="Ceylon"; };
    printGreeting(obj);
} 

Of course, inside a dynamic block, all kinds of typing errors can occur at runtime, things that the Ceylon compiler would usually detect at compile time. But the compiler at least protects you from having dynamic typing errors "leak" out of the dynamic block and into your regular Ceylon code by performing runtime checks when you assign a dynamically typed expression (that is, an expression of unknown type) to something with a well-defined static type.

Well, that's essentially all there is to our application, except for a couple of boring module and package descriptors. Here we can see a server written in Ceylon running in the JVM being called by a client written in Ceylon running in a web browser and interacting with the browser's native JavaScript APIs. I think that's pretty cool.

A note of caution: we're still working through some wrinkles in the semantics of dynamic, and dynamic is still not supported on the JVM (it will be, eventually). Nevertheless, this will be available as an experimental feature in Ceylon M5.

Ceylon HTTP server

One of the exciting new features coming to the Ceylon SDK in M5 is the HTTP server contributed by Matej Lazar. The HTTP server forms part of the module ceylon.net and is based on Undertow, a fairly new project that will serve up HTTP in future versions of JBoss.

The API is still evolving, but I can give you a taste of it with a little hello world program.

import ceylon.net.httpd { createServer, Endpoint, Response, Request }

void runServer() {
    //create a HTTP server
    value server = createServer {
        //an endpoint, on the path /hello
        Endpoint { 
            path = "/hello";
            //handle requests to this path
            service(Request request, Response response) =>
                    response.writeString("hello world");
        }
    };

    //start the server on port 8080
    server.start(8080);
}

Or, if you're the kind of person who gets their kicks out of squeezing things into one line:

import ceylon.net.httpd { createServer, Endpoint, Response, Request }

void runServer() => 
        createServer { Endpoint("/hello", 
                (Request request, Response response) =>
                        response.writeString("hello world")) }
        .start(8080);

(No, FTR, I would never write it like that.)

Oh, I almost forgot, you'll also need to import ceylon.net in your module descriptor:

module hellohttp '1' {
    import ceylon.net '0.5';
}

Of course, what you don't need is any kind of XML metadata or build script to package stuff up into deployment archives or copy archives to a deployment directory.

And it starts up in an instant, right from the commandline or IDE.