Using call/input-url

:: Racket, racket-cookbook

When I learned Racket, one of the first things I wanted to try was doing HTTP requests. And Racket’s net/url module is great.

Racket was the first real Lisp/Scheme family language I ever learned. As a result I was focused on building blocks like ports, and assuming I would need to open and close them directly all the time. At that early stage, I also didn’t really appreciate the value of higher-order functions. So I overlooked the value of call/input-url. I sometimes see other folks do the same, and wanted to write this short blog post.

Let’s say we want a function to take a url?, make an HTTP GET request, and return the response as a string?. For lack of a better name, we’ll call our function fetch.

First we need to require the net/url module:

1
(require net/url)

I won’t keep repeating that in the various examples that follow.

Our first cut at fetch uses all the basic, obvious functions:

1
2
3
4
5
(define (fetch url)
  (define in (get-pure-port url))
  (define out (open-output-string))
  (copy-port in out)
  (get-output-string out))

We try using it like this:

1
(fetch (string->url "http://www.racket-lang.org/"))

And it gives output like this:

1
2
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML "
; ... lots more omitted ...

Great. So this seems to work. But how can we improve it?

First, the business with open-output-string, copy-port, and get-output-string is a bit tedious. Really, we just want to change an input-port? into a string?. Turns out Racket supplies port->string, which does just that. So we can simplify:

1
2
3
(define (fetch url)
  (define in (get-pure-port url))
  (port->string in))

Much better. Unfortunately, there’s a problem. We don’t close the input-port? returned from get-pure-port. Let’s do that:

1
2
3
4
5
(define (fetch url)
  (define in (get-pure-port url))
  (begin0
      (port->string in)
    (close-input-port in)))

If you’re unfamiliar with begin0, it’s like begin except it returns the value of the first expression instead of the last one. begin0 fits the pattern where you need to return a value from a resource then close/release the resource.

OK, but, what if an exception were thrown sometime before we reached close-input-port?, for example some runtime error inside port->string? on line 4? The port would remain open.

Well, we could wrap this in an exception handler:

1
2
3
4
5
6
7
8

But this is really verbose. We’re writing a lot of boilerplate code to handle exceptions, compared to the task we care about.

Fortunately, Racket provides call/input-url. It takes three arguments: a url?, a connection function such as get-pure-port, and a “handler” function to do something with the opened input-port?. It guarantees that the input port will be closed, even if an exception is thrown.

Using that:

1
2
3
4
5
(define (fetch url)
  (call/input-url url
                  get-pure-port
                  (lambda (in)
                    (port->string in))))

The handler function takes an input-port? and returns any — whatever you need it to — which in turn is what call/input-url returns.

Hey wait a second. I notice that port->string is just such a function: (input-port? . -> . any). We don’t need to wrap that in a lambda. We can supply it directly:

1
2
3
4

And that’s the final version. This is a nice example of how using higher-order functions can result in elegant code with a clean separation of concerns.

tl;dr: When you need to do an HTTP GET in Racket, you probably want to use call/input-url. It ensures the HTTP input port will be closed, and it nudges you into using higher-order functions.