This article is about Crest — HTTP and REST client for Crystal programming language.
A little about me
My name is Anton and I'm a self-taught full-stack Ruby on Rails developer.
Back in 2017, I had a desire to learn a new programming language.
Crystal caught my attention because of it's similarity to Ruby and because "Fast as C, Slick as Ruby ©".
Literally you can run the same code in Ruby and in Crystal, and it will work.
Unlike Ruby, Crystal is a typed language, but most of the time it's not required to specify types.
Crystal comes with a great standard library and tools. It has almost all the stuff you need to build applications.
CSV, XML, YAML, JSON, HTTP server/client, and even WebSocket are bundled with Crystal itself, making it super simple to start building something.
Project history
For practical reasons, I decided to re-implement Ruby's rest-client gem to dive into the Crystal.
I started the Crest project at April 2017
when Crystal was 0.21.1
.
And in a 7 months I has working implementation with next features:
- redirects support
- proxy support
- support nested hashes for headers, cookies, query params, and form data.
- multipart file uploads
- logging
The first stable version 0.9.2
was released on 1 Nov 2017
.
After more than 4 years of development and 48 releases, I'm happy to announce version Crest 1.0.0.
Example
The common case of HTTP clients is sending a form data to a server.
Let's implement curl command below:
curl -X POST https://httpbin.org/post\?author\=John+Doe\&offset\=20 -F 'user[name]=Tom' -F 'user[file]=@/path/to/file.png' -H 'Foo: bar' --cookie 'foo=bar' -A 'Mozilla/5.0'
Here is an example showing how to do this in pure Crystal:
require "http/client"
# Builds a multipart/form-data
channel = Channel(String).new(1)
io = IO::Memory.new
HTTP::FormData.build(io) do |formdata|
channel.send(formdata.content_type)
formdata.field("user[name]", "Tom")
File.open("/path/to/file.png") do |file|
mime = MIME.from_filename(file.path)
metadata = HTTP::FormData::FileMetadata.new(filename: file.path)
headers = HTTP::Headers{"Content-Type" => mime}
formdata.file("user[file]", file, metadata, headers)
end
end
content_type = channel.receive
# Builds headers
headers = HTTP::Headers{"User-Agent" => "Mozilla/5.0", "Content-Type" => content_type, "Foo" => "bar"}
# Builds cookies
cookies = HTTP::Cookies.new
cookie = HTTP::Cookie.new("foo", "bar")
cookies << cookie
# Adds Cookie headers for the cookies in this collection to the given `HTTP::Headers` instance
cookies.add_request_headers(headers)
# Build a URL encoded HTTP query
params = URI::Params.encode({"author" => "John Doe", "offset" => "20"})
# Make a request
response = HTTP::Client.post(
URI.new("https", "httpbin.org", path: "/post", query: params),
headers: headers,
body: io.to_s
)
puts response.body
Here is an example showing how to do the same with Crest:
require "crest"
response = Crest.post(
"https://httpbin.org/post",
form: {"user" => {"name" => "Tom", "file" => File.open("/path/to/file.png")}},
params: {"author" => "John Doe", "offset" => "20"},
headers: {"Foo" => "bar"},
cookies: {"foo" => "bar"},
user_agent: "Mozilla/5.0"
)
puts response.body
Bonus
Do you remember a curl command from the beginning of this article?
Crest have a .to_curl
method to convert Crest::Request
object to curl command.
puts response.to_curl
# => curl -X POST https://httpbin.org/post?author=John+Doe&offset=20 -F 'user[name]=Tom' -F 'user[file]=@/home/mama/ruby/crystal/crest/spec/support/fff.png' -H 'Foo: bar' -H 'User-Agent: Mozilla/5.0' -H 'Cookie: foo=bar' -H 'Content-Type: multipart/form-data'
If you have any questions, please let me know. I'd love to get your feedback!
Top comments (2)
Congrats on the release! 🤗🎉
Thanks!