Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Serving HTML

There are three different ways in which you can serve HTML content from a Ktor application. We explore two of them below. The third approach, using templates, will be covered later.

Serving Static Files

You can serve static content from three different places:

  • The filesystem
  • A Zip archive
  • The application classpath

The first two of these are configured using the staticFiles() and staticZip() functions, respectively. The third option is configured using the staticResources() function.

These functions are all called as part of setting up the routing of the application.

Let’s experiment with the third option.

  1. Download ktor-static.zip from the Topic 2 folder in Minerva, then unzip this archive in your SoCS Linux filestore or your own PC.

  2. Examine the contents of the directory that is created. This is an Amper project for a very basic Ktor application. Take a look at Routing.kt and you can see that staticResources() has been used:

    fun Application.configureRouting() {
        routing {
            staticResources("/", "static")
        }
    }
    

    This declares that Ktor should try to satisfy GET requests by serving content from a directory named static, located in the application classpath, alongside the code of the application.

    Because there is no other routing declared here using get() or other request handling functions, all requests will be treated as if they are for static resources; in other words, this Ktor server will function like a classic web server, serving HTML, CSS, images and other files.

  3. Run the application with

    ./amper run
    

    (Note: this is for Linux & macOS; adjust this and subsequent commands as necessary if you are using Windows.)

    If you try visiting the application home page you should get a 404 Not Found error, because there are as yet no HTML files.

    Stop the server with Ctrl+C.

  4. Create a file named index.html, in the directory resources/static. Any files that we want our server to use should be placed here. They are not served from here at runtime, but this is where they have go so that they are bundled with the application code whenever it is compiled.

    In index.html, create a web page. Use the Pico CSS framework to style it nicely, and include an image or two. Remember that you will also need to put the image files in the static subdirectory.

  5. Rebuild the application with

    ./amper build
    

    Examine the contents of build/tasks/_ktor-static_compileJvm. You should see the .class files of the application here, along with a static subdirectory containing index.html and any image files that you added.

  6. Run the application with

    ./amper run
    

    Visit http://0.0.0.0:8080 again and you should now see a nicely styled web page.

Constructing HTML Dynamically

Another approach is to create a response dynamically using Kotlin code. The kotlinx.html library provides a neat way of doing this. It defines a domain-specific language (DSL) for building HTML documents. Ktor integrates this approach and allows you to use it, in conjunction with the respondHtml() function, to issue HTML documents as responses to a request.

Let’s examine a demo application.

  1. Download ktor-html.zip from the Topic 2 folder in Minerva, then unzip this archive in your SoCS Linux filestore or your own PC.

  2. Examine the contents of the directory that is created. This is another Amper-based Ktor project, a small web application that simulates die rolls.

    If you look at Routing.kt you’ll see that it defines a single route for GET requests with URLs that match /roll/d followed by the number of die sides.

    routing {
        get("/roll/d{sides}") {
            val sides: Int by call.parameters
            val number = dieRoll(sides)
            call.respondHtml(HttpStatusCode.OK) {
                head {
                    title { +"Dice Roller" }
                }
                body {
                    h1 { +"d${sides} Dice Roll" }
                    p { +"Result = $number" }
                }
            }
        }
    }
    

    The first thing done to handle the request is link the parameters of the call (the data encoded into the URL or request body) with variables. In this case, there is just the one variable, sides. The by keyword indicates that this is done using Kotlin’s delegated properties feature.

    After this, a function is called to roll the die (see Dice.kt for its implementation). Then an HTML page giving the result of the die roll is built, using the DSL mentioned earlier. Notice how Kotlin syntax is used to build a nested structure of HTML elements here.

  3. Run the application with

    ./amper run
    

    Visit http://0.0.0.0:8080/roll/d20 in your browser to see the response created by the application. Refresh the page to roll a d20 again.

    Use your browser’s ‘View Page Source’ feature to examine the raw HTML.

  4. Try changing the number of die sides to one of the other allowed values (4, 6, 8, 10, 12). Then investigate what happens if you specify an invalid number, or provide something that isn’t a number at all. Why do you think the server doesn’t crash when you do this?

    Hint

    Look at the code in Application.kt for a clue.

  5. Experiment with modifying the HTML of the die roll results page or the error page, so that you can get a feel for this DSL-based approach.