Examples
This page shows a longer example of a Delight (version 0.2) program.
The example is a mini web-server called "quickshare", which shares the current
directory on the port of your choosing. It is used like this:
$ quickshare 5000
2008-10-12 11:15:57,505 Info quickshare.main.Main - Server starting on padzu
2008-10-12 11:15:57,506 Info quickshare.server.Server - Waiting for connections on 0.0.0.0:5000
2008-10-12 11:35:23,842 Info quickshare.handler.DirRequestHandler - Handling client request: GET / HTTP/1.1
2008-10-12 11:35:27,826 Info quickshare.handler.DirRequestHandler - Handling client request: GET /Makefile HTTP/1.1
Then anyone can point their browser at http://padzu:5000/ and download any files in the
directory the server was run from.
quickshare.main
The Main class is usually the best place to start examining a new program. Because
there is no global state, the main class acts as a configuration file than wires the
rest of the program together and connects it to the outside world.
module quickshare.main
import dlt.net
import dlt.io
import dlt.log
import std.getopt: getopt
import quickshare.server: Server
import quickshare.handler: DirRequestHandler
class Main:
in Network network
in FileSystem fs
in LogManager logManager
void main(string[] args):
const options = new Options(args)
logManager.level = Level.Trace if options.verbose else Level.Info
log_info("Server starting on {}", network.hostname)
auto handler = new DirRequestHandler(fs.cwd)
auto serverSocket = network.server.create(options.address, 32, true)
auto server = new Server(serverSocket, handler)
server.runServer()
From the in fields in Main and the arguments to Main.main, we can see
that this program:
- makes use of the network,
- makes use of the file-system.
- makes use of the command-line arguments,
- reconfigures the logging system.
Looking inside the main method, we see that it creates four objects:
- options
- stores the parsed command-line arguments
- handler
- handles an individual request from a web browser by providing access to a directory
(here, the current working directory)
- serverSocket
- a listening socket (which can queue up to 32 requests and which allows the
address to be reused as soon as the program finishes)
- server
- A generic server that listens for incoming connections on serverSocket and
passes each request to handler
We can also see which of these objects have access to each other. For example, we
can see that options cannot read any configuration file (since we didn't pass
fs to its constructor).
After the Main class, we have the simple Options class:
class Options:
bool verbose
Address address
this(string[] args):
getopt(args,
"verbose|v", &verbose)
if args.length != 2:
throw new SystemExit("Usage: {} port".format(args[0]).idup)
address = new Address(":" ~ args[1])
This accepts an option --verbose (or -v) flag and a required port number, from which
it builds a local Address.
(note that args[0] is the name of the program)
quickshare.server
The server module defines the RequestHandler interface and a generic single-threaded
server:
module quickshare.server
import dlt.net
interface RequestHandler:
void handle(ClientSocket client)
# A generic single-threaded server
class Server:
in ServerSocket ss
in RequestHandler handler
void runServer():
log_info("Waiting for connections on {}", ss)
while true:
ClientSocket sock = ss.accept()
log_trace("Got connection from {}", sock.remoteAddress)
try:
handler.handle(sock)
catch (Exception ex):
log_error("Error processing request: {}", ex)
sock.send("Error: " ~ str(ex))
finally:
log_trace("Closing client socket")
sock.close()
A Server takes a listening socket and a handler. Each time it gets a new
connection on the socket it passes it to the request handler. When the handler returns
(or throws an exception) the socket is closed.
This also shows a rare example of an appropriate use of log_error, since the process
should not exit on error and this is the top-level event loop.
quickshare.handler
This provides a very primitive implementation of the HTTP protocol. It reads the
request from the browser until it gets an empty line. If the request was for "/" then
it sends a directory listing. Otherwise, it sends the requested file:
module quickshare.handler
import dlt.net
import dlt.io
import std.algorithm: sort
import quickshare.server: RequestHandler
# Replace HTML special characters with entities
string escape(string raw):
return raw.replace("&", "&").replace("<", "<")
class DirRequestHandler implements RequestHandler:
in FilePath dir
void handle(ClientSocket client):
char[] request
while true:
char[256] buf
int got = client.receive(buf)
assert got >= 0
if not got:
throw new Exception("Client closed connection!")
request ~= buf[0 .. got]
if request.endswith("\r\n\r\n"):
break
string[] requestLines = request.idup.splitlines()
log_info("Handling client request: {}", requestLines[0])
string[] parts = requestLines[0].split()
assert parts[0] == "GET"
assert parts[1].startswith("/")
string resource = parts[1][1 .. $]
# TODO: should unescape special characters
if resource:
sendResource(client, dir.child(resource))
else:
sendListing(client)
void sendResource(ClientSocket client, FilePath item):
client.send("HTTP/1.1 200 OK\r\n")
client.send("Context-Type: text/plain\r\n\r\n")
ubyte[4096] buf
auto src = item.read()
while true:
int got = src.read(buf)
if not got:
break
log_trace("Sending {} bytes of {}", got, item)
client.send(buf[0 .. got])
void sendListing(ClientSocket client):
string[] items
for item in dir.children:
string name = item.name
if not name.startswith("."):
items ~= [name]
items.sort()
client.send("HTTP/1.1 200 OK\r\n")
client.send("Context-Type: text/html\r\n\r\n")
client.send("<html><body><ul>\n")
for item in items:
client.send(format("<li><a href='{0}'>{0}</a></li>\n", escape(item)))
client.send("</ul></body></html>\n")