This tutorial teaches you on developing a web server application in Sling programming language using the Sympathy framework.

Introduction

This tutorial is intended for individuals who have little or no knowledge about web server application development but wanted to learn. In this tutorial, we will be developing a web server application that will serve static HTML pages.

At the end of this tutorial, you will learn how to develop a web server application in Sling using the Sympathy framework.

What is a web server?

A web server is a program that uses the HTTP (Hypertext Transfer Protocol) to serve data that forms into web pages to users' browsers in response to their requests.

Development tools

In this tutorial, we will be using the following development tools and technologies:

Eqela Runtime

The QX Scripting Language

The Sling Programming Language

The Jkop Framework

Sympathy web server and application platform

Creating a Sympathy web server project

First, let's create our project directory and name it 'wsy' which stands for 'Web Server Sympathy'. Under this directory, create the following files with their respective contents:

WsyApp.sling

class is WebServer:

import sympathy
import sympathy.app

func main(args as array<string>) static as int #main:
	return new this().executeMain(args)

this.pling

title = "Sympathy Web Server"
preFilters += "@cape"
preFilters += "@sympathy"

Next, create the QX build script and name it 'build.qx' which we will use to build and run our web server program. Place it in the directory where our project directory 'wsy' resides (not under it):

use eqela:slingbuild:r19
use eqela:jsh:r4
use eqela:dotnet:2.1.301
set version v1.0.0

build : src {
	requireValue src
	set id ${=getIdNameForPath(${src})}
	eqela:jsh delete build/${id}
	eqela:slingbuild buildNetCoreStatic src=${src}
}

run : id {
	requireValue id
	eqela:dotnet build/${id}/netcore/${id}.dll
}

Build and run the project

Now that we have a working web server project, let's build it. Open your terminal and execute the "eqela" command like this (this assumes that your present working directory is where the 'build.qx' file resides):

eqela build.qx build src=wsy

Then run it by executing the same command like this:

eqela build.qx run id=srapiy

Then open your browser and open the link http://localhost:8080. If everything is working, you will see exactly this on the page:

Not found

By default, the server responds with a 'Not found' response if there are no request handlers implemented.

Implementing request handlers

To implement request handlers, we need to override the virtual method initializeServer(). Let's update our WsyApp class and add the following code snippet:

func initializeServer(server as HTTPServer) override as bool
{
	assert base.initializeServer(server)
	server.pushRequestHandler(func(req as HTTPServerRequest, next as function) {
		req.sendTextString("You have requested for: '" .. req.getURLPath() .. "'")
	})
	return true
}

Then rebuild your project. Afterwards, run your web server program then reload your browser. If everything is good, you will see this on the page:

You have requested for: '/'

From the code snippet above, the HTTPServerRequest object 'req' represents a single request from the connected client. This provides information such as the HTTP method, URL path, the HTTP headers, the data from its HTTP body if there is, etc., which will help to determine what the client is requesting for. We will also use this to send a response to the requesting client. With our code above, we are sending a text string response.

Let's update our request handler with the code below so we will send an HTML page as a response instead:

func initializeServer(server as HTTPServer) override as bool
{
	assert base.initializeServer(server)
	server.pushRequestHandler(func(req as HTTPServerRequest, next as function) {
		req.sendHTMLString([[
			<!DOCTYPE html>
			<html>
				<head>
					<title>Sample Title</title>
				</head>
				<body>
					<h1>Hello world</h1>
					<p><a href="about">Click me</a></p>
				</body>
			</html>
		]])
	})
	return true
}

Now when you rebuild and run your web server then reload your browser, you will see a 'Hello world' header and a link that says 'Click me'. Of course if you click the link, you will be redirected to the 'about' page, but it seems the page didn't really change, right? This is because we only serve the same HTML string to any request that our server receives. Let's update the code to check which page the client is requesting before sending our response:

func initializeServer(server as HTTPServer) override as bool
{
	assert base.initializeServer(server)
	server.pushRequestHandler(func(req as HTTPServerRequest, next as function) {
		var path = req.getURLPath()
		if String.equals("/about", path) {
			req.sendHTMLString([[
				<!DOCTYPE html>
				<html>
					<head>
						<title>Sample Title</title>
					</head>
					<body>
						<h1>This is the about page</h1>
					</body>
				</html>
			]])
		}
		else if String.equals("/", path) {
			req.sendHTMLString([[
				<!DOCTYPE html>
				<html>
					<head>
						<title>Sample Title</title>
					</head>
					<body>
						<h1>Hello world</h1>
						<p><a href="about">Click me</a></p>
					</body>
				</html>
			]])
		}
		else {
			next()
		}
	})
	return true
}

With the code above, when you try to click the link, it will now show you the about page. Try to specify a different path and you should receive the 'Not found' response.

Using the HTTPServerDirectoryHandler

The current implementation would be painful considering if you would serve several web pages which would have big contents like blog articles. It would be efficient if we create separate HTML files and let our web server load it and send it as a response. In this way, it would be easier to maintain the web pages. To do this, we will be using the HTTPServerDirectoryHandler class. First, let's create a directory called 'public' and place it along our project directory. Inside this directory, create the following HTML files with the respective contents:

index.html

<!DOCTYPE html>
<html>
	<head>
		<title>Sample Title</title>
	</head>
	<body>
		<h1>Hello world</h1>
		<p><a href="about.html">Click me</a></p>
	</body>
</html>

about.html

<!DOCTYPE html>
<html>
	<head>
		<title>Sample Title</title>
	</head>
	<body>
		<h1>This is the about page</h1>
	</body>
</html>

Now that we have our HTML files in place, we need to update our web server implementation in such a way that it will read a configuration file upon running it then it will load the configured public directory to get the appropriate HTML file to serve. To do this, let's override the virtual method onConfigure(). Insert the code below to our WsyApp class:

var contentDirectory as File

func configure(key as string, value as string, relativeTo as File, error as Error) override as bool
{
	if base.configure(key, value, relativeTo, error):
		return true
	if key == "contentDirectory" {
		contentDirectory = File.forRelativePath(value, relativeTo)
		return true
	}
	return false
}

Then replace our initializeServer() method with the code below:

func initializeServer(server as HTTPServer) override as bool
{
	assert base.initializeServer(server)
	assert contentDirectory:
		Log.error(ctx, "No 'contentDirectory' specified. Either use a config file or specify -OcontentDirectory=<directory>")
	assert contentDirectory.isDirectory():
		Log.error(ctx, "Not a directory: '" .. contentDirectory.getPath() .. "'")
	var files = HTTPServerDirectoryHandler.forDirectory(contentDirectory)
	files.setIndexFiles([
		"index.html"
	])
	server.pushRequestHandler(files)
	return true
}

Next create the actual configuration file that we will use along the project directory 'wsy' and name it 'wsy.config' with the contents below:

contentDirectory: public
listenPort: 8080

As you can see, the format for the configuration file is key value pair which is separated by the ':' character. The key 'contentDirectory' has the value 'public' which is the path to our public directory where our HTML files reside. The key 'listenPort' with the value 8080 means that our web server will be listening on port 8080, this is how we configure on which port our server will listen. There are many existing configuration keys that you can use and you can always add your own by overiding the onConfigure() method of the WebServer class.

Now let's update our build.qx and add the config parameter for us to be able to specify the config file that we will use when running our web server.

use eqela:slingbuild:r19
use eqela:jsh:r4
use eqela:dotnet:2.1.301
set version v1.0.0

build : src {
	requireValue src
	set id ${=getIdNameForPath(${src})}
	eqela:jsh delete build/${id}
	eqela:slingbuild buildNetCoreStatic src=${src}
}

run : id config {
	requireValue id
	requireValue config
	eqela:dotnet build/${id}/netcore/${id}.dll -config=${config}
}

Now rebuild the project then run it by executing the "eqela" command adding the config parameter like this:

eqela build.qx run id=srapiy config=wsy.config

Congratulations!

You have completed this tutorial. You now know how to develop a web server in Sling programming language using the Sympathy framework.

Where to go from here?

Web server development is fun and serving static HTML pages is just the beginning, you can do so much more with the Sympathy framework. Choose to learn more, there are other tutorials here that you can go next such as Sympathy Tutorial: Using Text Templates and Secure REST API server development in Sling with Sympathy.

Web Development Resources


Twitter Facebook LinkedIn Youtube Slideshare Github