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
First we need to
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
We try using it like this:
(fetch (string->url "http://www.racket-lang.org/"))
And it gives output like this:
"<?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
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:
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
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.
The handler function takes an
input-port? and returns
any — whatever you need it to — which in turn is what
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:
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.