28 February 2022

Racket HTTP Server 2 - Reading the Path

Previously, I wrote a simple HTTP server in Racket. The next step I will take with this project is to figure out how to extract the HTTP method and path from the request.

The handle method's first argument, in, is an input port, from which the request can be read. We can read the first line from the request using the read-line function:

(define (handle in out)
    (display "HTTP/1.0 200 Okay\r\n\r\n" out)
    (display (read-line in) out)
    (display "\r\nHello World!\r\n" out))

We can keep calling read-line to read more of the message:

(define (handle in out)
    (display "HTTP/1.0 200 Okay\r\n\r\n" out)
    (display (read-line in) out)
    (display "\r\n" out)
    (display (read-line in) out)
    (display "\r\nHello World!\r\n" out))

Now, we could use read-line to read in the request, and then process it, but I am going to use the regexp-match function to extract the method and path, and this function will also accept an input port directly.

One thing to note here, is that that order in which data is read from the port matters: in is not an immutable object, and reading data from it can only be done once.

To make things easier to read, I have defined a couple of helper functions, read-method and read-path. Note that the in port gives us bytes, not utf-8 strings, so the results from the regexp will also be bytes. As these are bytes, they cannot be appended to strings, hence why I am using the bytes-append with bytes literals (e.g. #"Method:") - rather than string-append with string literals (e.g. "Method:").

(define (read-method in)
  (last(regexp-match #rx"^([A-Z]*)" in)))

(define (read-path in)
  (last(regexp-match #rx"(/[^? ]*)" in)))

(define (handle in out)
  (let ([method (read-method in)]
        [path (read-path in)])
    (display "HTTP/1.0 200 Okay\r\n\r\n" out)
    (display (bytes-append #"Method:" method #"\r\n") out)
    (display (bytes-append #"Path:" path #"\r\n") out)
    (display "\r\nHello World!\r\n" out)))

If we now make a GET request:

 curl 'localhost:8083/foo/bar'

The response lists the method and path:

Method:GET
Path:/foo/bar

Hello World!

And with a POST request:

curl 'localhost:8083/foo/bar' -d 'raw data'
Method:POST
Path:/foo/bar

Hello World!

And opening it in the browser:

Screenshot of a web browser, showing the same response as curl received.

Tags: Scheme Racket