Chapter 13. Let's Create a Web Server!

In Chapter 6, you learned how to interact with a user by sending text to and from the REPL. However, when people talk about “interacting with a user” these days, they’re usually referring to a user on the Web. In this chapter, you’re going to learn how to interact with users on the Web by building a web server from scratch. Since communications over a network are error prone by their nature, you’ll first learn how errors are handled in Lisp.

Error Handling in Common Lisp

Any time you’re interacting with the outside world, as our web server will, unexpected things can happen. No matter how smart a modern computer network may be, it can never anticipate every possible exceptional situation. After all, even the smartest network can’t recover from some fool tripping over the wrong cable.

Common Lisp has a very extensive set of features for dealing with unexpected exceptional situations in your code. This exception handling system is very flexible, and it can be used to do things that are impossible with exception systems in most other languages.

Signaling a Condition

If you’re writing a function and something goes horribly wrong, a Lisp function can notify the Lisp environment that a problem has been encountered. This is done by signaling a condition. What sort of things could go wrong? Maybe a function tried to divide by zero. Or maybe a library function received a parameter of the wrong type. Or maybe a socket communication was interrupted because you tripped over your network cable.

If you want to signal a condition directly, you can do so with the error command. You would do this if a function you wrote detected a problem on its own—a problem so serious the program just could not continue normally. Using the error command will interrupt your running Lisp program, unless you intercept the error elsewhere to prevent an interruption. Let’s signal a condition and print the message “foo” to describe the error:

> (error "foo")

*** - foo
The following restarts are available:
ABORT          :R1      Abort main loop
>

As you can see, signaling this condition causes Lisp to interrupt our program, print the message “foo,” and show an error prompt at the REPL. (In CLISP, you can type :a at this point to abort the program and return to the normal REPL.)

Most of the time your program signals a condition, it will probably not be because you called error yourself. Instead, it will be because your program has a bug, or because you called a library function, and that function signals a condition. However, any time something prevents normal execution in your program, leading to a condition, your program will stop and show an error prompt such as in the preceding example.

Creating Custom Conditions

In our first example, we passed a string describing the condition to the error command. However, this text string just customizes the error message and doesn’t lead to a different “type” of condition. Common Lisp also allows you to have various types of conditions that can be handled in different ways.

A more sophisticated way to signal conditions is to first define a custom condition using define-condition, as in the following example:

 (define-condition foo () ()
    (:report (lambda (condition stream)
                (princ "Stop FOOing around, numbskull!" stream))))

This is a typical example of creating a new type of condition, which we’ve named foo . When this condition is signaled, we can supply a custom function that will be called to report the error. Here, we declare a lambda function for this purpose . Within the lambda function, we print a custom message to report the error .

Let’s see what happens when we trigger this new condition:

> (error 'foo)

*** - Stop FOOing around, numbskull!
The following restarts are available:
ABORT          :R1      Abort main loop
>

As you can see, our custom message was printed. This technique allows the programmer to get a more meaningful error report, customized for the specific condition that was triggered.

Intercepting Conditions

When we create a condition with define-condition, it’s given a name (such as foo). This name can be used by the higher-level parts of our program to intercept and handle that condition, so it won’t stop the program’s execution. We can do this with the handler-case command, as follows:

> (defun bad-function ()
       (error 'foo))
  BAD-FUNCTION
 > (handler-case (bad-function)
      (foo () "somebody signaled foo!")
       (bar () "somebody signaled bar!"))
 "somebody signaled foo!"

The first thing we put inside a handler-case command is the piece of code that may signal conditions that we want to handle .

In this example, the code we’re watching is a call to bad-function. The rest of handler-case lets us specify actions to perform if a particular condition occurs . When this code is run, bad-function signals the foo condition by calling (error 'foo). Usually, this would cause our program to be interrupted and lead to a error prompt at the REPL. However, our handler-case command intercepts the foo condition . This means that the program can keep running without interruption, with the handler-case evaluating as “somebody signaled foo!” .

Protecting Resources Against Unexpected Conditions

When an unexpected exception happens in a program, there is always a risk that it could bring down your program, or even cause damage to resources outside your program. Exceptions interrupt the regular flow of your code, and they may stop your code dead in its tracks, even while it’s in the middle of a sensitive operation.

For instance, your program may be writing to a file or to a socket stream when an unexpected exception happens. In this case, it is critically important that your program has an opportunity to close the file/socket stream and free the file handle or socket; otherwise, that resource may become locked indefinitely. If such resources aren’t cleaned up properly, the users may need to reboot their computer first before the resource becomes available again.

The unwind-protect command can help us to avoid these problems. With this command, we can tell the Lisp compiler, “This piece of code must run no matter what happens.” Consider the following example:

 > (unwind-protect (/ 1 0)
                   (princ "I need to say 'flubyduby' matter what"))

  *** - /: division by zero
  The following restarts are available:
  ABORT          :R1      Abort main loop
  > :r1
  I need to say 'flubyduby' matter what
  >

Within the unwind-protect, we divide by 0, which signals a condition . But even after we tell CLISP to abort, the program still prints its crucial message .

We can usually avoid calling unwind-protect directly by relying on Common Lisp’s “with-” macros; many of these call unwind-protect themselves, under the hood. In Chapter 16, we’ll create our own macros to see how this is possible.

Note

In the comic book epilogue at the end of the book, you’ll learn about an additional feature of the Common Lisp signaling system called restarts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.235.144