HTTP and REST client for Crystal


Codacy Badge Build Status GitHub release Commits Since Last Release Docs License

HTTP and REST client for Crystal, inspired by the Ruby's RestClient gem.

Beloved features:

  • Redirects support.
  • HTTP(S) proxy support.
  • Elegant Key/Value headers, cookies, params, and payload.
  • Multipart file uploads.
  • Digest access authentication.
  • Logging.

Hopefully, someday I can remove this shard though. Ideally, Crystal's standard library would do all this already.


Add this to your application's shard.yml:

    github: mamantoha/crest


require "crest"

Basic usage:

  params: {:lang => "en"}
# curl -L ""
  form: {:age => 27, :name => {:first => "Kurt", :last => "Cobain"}}
# curl -L --data "age=27&name[first]=Kurt&name[last]=Cobain" -X POST ""
  form: {"file" =>"avatar.png"), "name" => "John"}
# curl -X POST -F 'file=@avatar.png' -F 'name=John' -H 'Content-Type: multipart/form-data'


Crest::Request accept next parameters:

Mandatory parameters:

  • :method - HTTP method (:get. :post, :put, :patch, :delete, :options, head)
  • :url - URL (e.g.:

Optional parameters:

  • :headers - a hash containing the request headers
  • :cookies - a hash containing the request cookies
  • :form - a hash containing form params
  • :params - a hash that represent query-string separated from the preceding part by a question mark (?) a sequence of attribute–value pairs separated by a delimiter (&)
  • auth - access authentication method basic or digest (default to basic)
  • :user and :password - for authentication
  • :tls - client certificates, you can pass in a custom OpenSSL::SSL::Context::Client (default to nil)
  • :p_addr, :p_port, :p_user, and :p_pass - specify a per-request proxy by passing these parameters
  • :max_redirects - maximum number of redirections (default to 10)
  • :logging - enable logging (default to false)
  • :logger - set logger (default to Crest::CommonLogger)
  • :handle_errors - error handling (default to true)
  • :http_client - instance of HTTP::Client

More detailed examples:

request =,
  headers: {"Content-Type" => "application/json"},
  form: {:width => 640, "height" => "480"}
# curl -L --data "width=640&height=480" --header "Content-Type: application/json" -X POST ""
  params: {:width => 640, "height" => "480"},
  headers: {"Content-Type" => "application/json"})
# curl -L --header "Content-Type: application/json" ""
  p_addr: "",
  p_port: 3128,
  p_user: "admin",
  p_pass: "1234"
# curl -L --proxy --proxy-user admin:1234 ""

A block can be passed to the Crest::Request initializer.

This block will then be called with the Crest::Request.

request =, "") do |request|
  request.headers.add("foo", "bar")

# curl -L --header "foo: bar"


A Crest::Resource class can be instantiated for access to a RESTful resource, including authentication, proxy and logging.

Additionally, you can set default params and headers separately. So can use Crest::Resource to share common headers and params.

The final headers and params consist of:

  • default headers from initializer
  • headers provided in call method (get, post, etc)

This is especially useful if you wish to define your site in one place and call it in multiple locations.

resource =
  params: {"key" => "value"},
  headers: {"Content-Type" => "application/json"}

  headers: {"Auth-Token" => "secret"}

  form: {:height => 100, "width" => "100"},
  params: {:secret => "secret"}

Use the [] syntax to allocate subresources:

site ="")

site["/post"].post(form: {:param1 => "value1", :param2 => "value2"})
# curl -L --data "param1=value1&param2=value2" -X POST

You can pass suburl through Request#http_verb methods:

site ="")"/post", form: {:param1 => "value1", :param2 => "value2"})
# curl -L --data "param1=value1&param2=value2" -X POST

site.get("/get", params: {:status => "active"})
# curl -L

A block can be passed to the Crest::Resource instance.

This block will then be called with the Crest::Resource.

resource ="") do |resource|
  resource.headers.merge!({"foo" => "bar"})


With HTTP basic authentication:

resource =
  user: "user",
  password: "passwd"

With Proxy authentication:

resource =
  p_host: "localhost",
  p_port: 3128

Result handling

The result of a Crest::Request and Crest::Resource is a Crest::Response object.

Response objects have several useful methods:

  • Response#body: The response body as a String
  • Response#body_io: The response body as a IO
  • Response#status: The response status as a HTTP::Status
  • Response#status_code: The HTTP response code
  • Response#headers: A hash of HTTP response headers
  • Response#cookies: A hash of HTTP cookies set by the server
  • Response#request: The Crest::Request object used to make the request
  • Response#http_client_res: The HTTP::Client::Response object
  • Response#history: A list of each response received in a redirection chain


  • for result codes between 200 and 207, a Crest::Response will be returned
  • for result codes 301, 302, 303 or 307, the redirection will be followed and the request transformed into a GET
  • for other cases, a Crest::RequestFailed holding the Response will be raised
  • call .response on the exception to get the server's response
# => HTTP status code 404: Not Found (Crest::NotFound)

rescue ex : Crest::NotFound
  puts ex.response

To not raise exceptions but return the Crest::Response you can set :handle_errors => false.

response = Crest.get("", handle_errors: false) do |resp|
  case resp
  when .success?
    puts resp.body_io.gets_to_end
  when .client_error?
    puts "Client error"
  when .server_error?
    puts "Server error"
# => Client error

response.status_code # => 404

But note that it may be more straightforward to use exceptions to handle different HTTP error response cases:

response = begin
rescue ex : Crest::NotFound
  puts "Not found"
rescue ex : Crest::InternalServerError
  puts "Internal server error"
# => Not found

response.status_code # => 404

Streaming responses

Normally, when you use Crest, Crest::Request or Crest::Resource methods to retrieve data, the entire response is buffered in memory and returned as the response to the call.

However, if you are retrieving a large amount of data, for example an iso, or any other large file, you may want to stream the response directly to disk rather than loading it in memory. If you have a very large file, it may become impossible to load it into memory.

If you want to stream the data from the response to a file as it comes, rather than entirely in memory, you can pass a block to which you pass a additional logic, which you can use to stream directly to a file as each chunk is received.

With a block, an Crest::Response body is returned and the response's body is available as an IO by invoking Crest::Response#body_io.

The following is an example:

Crest.get("") do |resp|
  filename = resp.filename || "", "w") do |file|
    IO.copy(resp.body_io, file)

Advanced Usage

This section covers some of crest more advanced features.


Yeah, that's right! This does multipart sends for you!

file ="#{__DIR__}/example.png")"", form: {:image => file})
file ="#{__DIR__}/example.png")
resource ="")
response = resource["/post"].post(form: {:image => file})

JSON payload

crest does not speak JSON natively, so serialize your form to a string before passing it to crest.
  headers: {"Content-Type" => "application/json"},
  form: {:foo => "bar"}.to_json


Request headers can be set by passing a hash containing keys and values representing header names and values:

response = Crest.get(
  headers: {"Authorization" => "Bearer cT0febFoD5lxAlNAXHo6g"}
# => {"Authorization" => ["Bearer cT0febFoD5lxAlNAXHo6g"]}


Request and Response objects know about HTTP cookies, and will automatically extract and set headers for them as needed:

response = Crest.get(
  params: {"k1" => "v1", "k2" => "v2"}
# => {"k1" => "v1", "k2" => "v2"}
response = Crest.get(
  cookies: {"k1" => "v1"}
# => {"k1" => "v1"}

Basic access authentication

For basic access authentication for an HTTP user agent you should to provide a user name and password when making a request.

  user: "user",
  password: "passwd"
# curl -L --user user:passwd

Digest access authentication

For digest access authentication for an HTTP user agent you should to provide a user name and password when making a request.

  auth: "digest",
  user: "user",
  password: "passwd"
# curl -L --digest --user user:passwd

SSL/TLS support

If tls is given it will be used:

Crest.get("", tls: OpenSSL::SSL::Context::Client.insecure)


If you need to use a proxy, you can configure individual requests with the proxy host and port arguments to any request method:

  p_addr: "localhost",
  p_port: 3128

To use authentication with your proxy, use next syntax:

  p_addr: "localhost",
  p_port: 3128,
  p_user: "user",
  p_pass: "qwerty"


Logger class is completely taken from halite shard. Thanks icyleaf!

By default, the Crest does not enable logging. You can enable it per request by setting logging: true:

Crest.get("", logging: true)
Filter sensitive information from logs with a regex matcher
resource = Crest::Request.get("", params: {api_key => "secret"}, logging: true) do |request|
  request.logger.filter(/(api_key=)(\w+)/, "\\1[REMOVED]")

# => crest | 2018-07-04 14:49:49 | GET |[REMOVED]
Customize logger

You can create the custom logger by integration Crest::Logger abstract class. Here has two methods must be implement: Crest::Logger.request and Crest::Logger.response.

class MyLogger < Crest::Logger
  def request(request) ">> | %s | %s" % [request.method, request.url]

  def response(response) "<< | %s | %s" % [response.status_code, response.url]

Crest.get("", logging: true, logger:


By default, crest will follow HTTP 30x redirection requests.

To disable automatic redirection, set :max_redirects => 0.

Crest::Request.execute(method: :get, url: "", max_redirects: 0)
# => Crest::Found: 302 Found

Access HTTP::Client

You can access HTTP::Client via the http_client instance method.

This is usually used to set additional options (e.g. read timeout, authorization header etc.)

client ="")
client.read_timeout = 1.second

    http_client: client
rescue IO::Timeout
  puts "Timeout!"
client ="")
client.read_timeout = 1.second

  resource ="", http_client: client)
rescue IO::Timeout
  puts "Timeout!"

Convert Request object to cURL command

Use to_curl method on instance of Crest::Request to convert request to cURL command.

request =
  form: {"title" => "New Title", "author" => "admin"}
# => curl -X POST -d 'title=New+Title&author=admin' -H 'Content-Type: application/x-www-form-urlencoded'
request =
  user: "user",
  password: "passwd"
# => curl -X GET --user user:passwd

Also you can directly use Crest::Curlify which accept instance of Crest::Request

request =, "")
# => curl -X GET


Install dependencies:


To run test:

crystal spec


crystal play
open http://localhost:8080

Then select the Workbook -> Requests from the menu.


  1. Fork it (
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request



Copyright: 2017-2019 Anton Maminov (

This library is distributed under the MIT license. Please see the LICENSE file.



0.22.0 (2019-09-17)

  • Support Crystal 0.31.0
  • Digest access authentication support (#127)
  • Add proxy to to_curl method

0.21.1 (2019-08-13)

  • (breaking-change) Require Crystal 0.30.1

0.21.0 (2019-08-02)

  • (breaking-change) Require Crystal 0.30.0
  • (breaking-change) Rename Crest::Response#successful? to Crest::Response#success?
  • Add method Crest::Response#status as HTTP::Status

0.20.0 (2019-06-14)

  • Tested with Crystal 0.29.0
  • Improve testing process (#120)

0.19.1 (2019-05-09)

  • Delegate method to_curl to Crest::Response instance
  • Fix and issue in Resource when base url ends with /

0.19.0 (2019-04-18)

  • Add method head (#116)
  • Tested with Crystal 0.28.0

0.18.3 (2019-02-06)

  • Tested with Crystal 0.27.2

0.18.2 (2019-02-03)

  • Tested with Crystal 0.27.1

0.18.1 (2019-01-16)

  • Fix extracting filename from Content-Disposition header

0.18.0 (2019-01-06)

  • (breaking-change) Streaming support. Crest, Crest::Request and Crest::Resource verb methods(get, post, etc.) yields the Crest::Response as stream to the block (#110)
  • (breaking-change) Needs to specify form, headers and params arguments for Crest::Resource methods (#112)
  • Add Crest::Response#filename method (#111)
  • Add response helper methods (successful?, redirection?, etc) (#107)
  • Extract redirection logic into Crest::Redirector class (#109)

0.17.0 (2018-11-17)

  • (breaking-change) Crest and Crest::Request verb methods(get, post, etc.) yields the Crest::Response to the block
  • Refactor proxy client

0.16.1 (2018-11-05)

  • Update to Kemal 0.25.1

0.16.0 (2018-11-03)

  • Tested with Crystal 0.27.0

0.15.0 (2018-10-12)

  • SSL/TLS support (#100)
  • Tested with Crystal 0.26.1

0.14.0 (2018-08-14)

  • Tested with Crystal 0.26.0

0.13.0 (2018-08-13)

  • Add Crest::Request#to_curl to convert request to cURL command (#95)
  • Bug fixes and other improvements

0.12.0 (2018-07-17)

  • (breaking-change) Rename Request#payload to Request#form
  • Use application/x-www-form-urlencoded for forms by default. And multipart/form-data when a form includes any <input type="file"> elements.
  • Fix serialize query to string representation as http url-encoded

0.11.0 (2018-07-14)

  • Add Logger#filter method to filter sensitive information from logs with a regex matcher
  • Allow to do request with suburl through Request#http_verb(suburl) method
  • Bug fixes and other improvements

0.10.2 (2018-06-15)

  • Tested with Crystal 0.25.0

0.10.1 (2018-05-14)

  • Fix Crest::Utils.flatten_params method (#85)
  • Reduce the false positiveness in code as much as possible (#83, thanks @veelenga)

0.10.0 (2018-04-24)

  • Add HTTP verb methods (get, post, etc) to Crest::Request
  • Crest and Crest::Request verb methods(get, post, etc.) can yields the Crest::Request to the block
  • Crest::Request and Crest::Resource initializer can accept block
  • Access instance of HTTP::Client via Crest::Request#http_client
  • Access instance of HTTP::Client via Crest::Resource#http_client
  • Crest::Request and Crest::Resource initializer can accept HTTP::Client as http_client
  • Add method options to HTTP::Resource

0.9.10 (2018-04-08)

  • Add option :handle_errors to don't raise exceptions but return the Response
  • Add custom exceptions for each status code

0.9.9 (2018-04-03)

  • Add method OPTIONS
  • Fix Crest::Response#headers method to return response headers

0.9.8 (2018-03-18)

  • Tested with Crystal 0.24.2
  • Fix Basic Authentication

0.9.7 (2018-03-05)

  • Allow Crest::Resource to accept default params and headers
  • Allow Crest::Resource to accept more parameters(proxy authentication credentials, logging setup)
  • Refactor exceptions class
  • Setup GitHub Pages branch to host docs

0.9.6 (2018-01-05)

  • Proxy on redirects
  • Logger in redirects

0.9.5 (2017-12-30)

  • Bug fixes and performance improvements

0.9.4 (2017-12-25)

  • Tested with Crystal 0.24.1

0.9.3 (2017-12-19)

  • Add logging

0.9.2 (2017-11-01)

  • First release 🎉
Github statistic:
  • 132
  • 37
  • 7
  • 3
  • 3
  • 26 days ago


MIT License