{"id":13483388,"url":"https://github.com/mamantoha/crest","last_synced_at":"2026-02-12T11:22:46.826Z","repository":{"id":19785691,"uuid":"86991509","full_name":"mamantoha/crest","owner":"mamantoha","description":"HTTP and REST client for Crystal","archived":false,"fork":false,"pushed_at":"2025-03-26T16:01:27.000Z","size":893,"stargazers_count":239,"open_issues_count":0,"forks_count":14,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-03T20:12:23.500Z","etag":null,"topics":["crystal","curl","hacktoberfest","http","http-client","https","rest-client"],"latest_commit_sha":null,"homepage":"https://mamantoha.github.io/crest/","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mamantoha.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-04-02T14:27:30.000Z","updated_at":"2025-03-26T16:01:04.000Z","dependencies_parsed_at":"2023-02-14T02:02:10.013Z","dependency_job_id":"2ee890d7-01e1-45fa-9914-5dc91fa45da7","html_url":"https://github.com/mamantoha/crest","commit_stats":{"total_commits":627,"total_committers":16,"mean_commits":39.1875,"dds":0.1291866028708134,"last_synced_commit":"78afa54c05d40a6e319676089496969dd85a988f"},"previous_names":[],"tags_count":71,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mamantoha%2Fcrest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mamantoha%2Fcrest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mamantoha%2Fcrest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mamantoha%2Fcrest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mamantoha","download_url":"https://codeload.github.com/mamantoha/crest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248609644,"owners_count":21132916,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crystal","curl","hacktoberfest","http","http-client","https","rest-client"],"created_at":"2024-07-31T17:01:10.682Z","updated_at":"2026-02-12T11:22:46.819Z","avatar_url":"https://github.com/mamantoha.png","language":"Crystal","readme":"# \u003cimg src=\"https://stars.medv.io/mamantoha/crest.svg\" align=\"right\"/\u003e\n\u003cp align=\"left\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/mamantoha/crest/master/logo/logotype_horizontal_dark.png\" alt=\"crest\" height=\"150px\"\u003e\u003c/p\u003e\n\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/16e439ef2706472988306ef13da91a51)](https://app.codacy.com/app/mamantoha/crest?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=mamantoha/crest\u0026utm_campaign=Badge_Grade_Dashboard)\n![Crystal CI](https://github.com/mamantoha/crest/workflows/Crystal%20CI/badge.svg)\n[![GitHub release](https://img.shields.io/github/release/mamantoha/crest.svg)](https://github.com/mamantoha/crest/releases)\n[![Commits Since Last Release](https://img.shields.io/github/commits-since/mamantoha/crest/latest.svg)](https://github.com/mamantoha/crest/pulse)\n[![Docs](https://img.shields.io/badge/docs-available-brightgreen.svg)](https://mamantoha.github.io/crest/)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/mamantoha/crest)\n[![License](https://img.shields.io/github/license/mamantoha/crest.svg)](https://github.com/mamantoha/crest/blob/master/LICENSE)\n\u003c!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --\u003e\n[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-)\n\u003c!-- ALL-CONTRIBUTORS-BADGE:END --\u003e\n\n[![Visitors](https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fgithub.com%2Fmamantoha%2Fcrest\u0026countColor=%23263759\u0026style=plastic)](https://visitorbadge.io/status?path=https%3A%2F%2Fgithub.com%2Fmamantoha%2Fcrest)\n\nHTTP and REST client for Crystal, inspired by the Ruby's RestClient gem.\n\nBeloved features:\n\n- Redirects support.\n- HTTP(S) proxy support.\n- Elegant Key/Value headers, cookies, query params, and form data.\n- Multipart file uploads.\n- JSON request with the appropriate HTTP headers.\n- Streaming requests.\n- International Domain Names.\n- Digest access authentication.\n- Logging.\n\nHopefully, someday I can remove this shard though. Ideally, Crystal's standard library would do all this already.\n\n## Installation\n\nAdd this to your application's `shard.yml`:\n\n```yaml\ndependencies:\n  crest:\n    github: mamantoha/crest\n```\n\n## Usage\n\n```crystal\nrequire \"crest\"\n```\n\nBasic usage:\n\n```crystal\nCrest.get(\n  \"http://httpbin.org/get\",\n  params: {:lang =\u003e \"en\"},\n  user_agent: \"Mozilla/5.0\"\n)\n# curl -L http://httpbin.org/get?lang=en -H 'User-Agent: Mozilla/5.0'\n\nCrest.post(\n  \"http://httpbin.org/post\",\n  {:age =\u003e 27, :name =\u003e {:first =\u003e \"Kurt\", :last =\u003e \"Cobain\"}}\n)\n# curl -L --data \"age=27\u0026name[first]=Kurt\u0026name[last]=Cobain\" -X POST \"http://httpbin.org/post\"\n\nCrest.post(\n  \"http://httpbin.org/post\",\n  {\"file\" =\u003e File.open(\"avatar.png\"), \"name\" =\u003e \"John\"}\n)\n# curl -X POST http://httpbin.org/post -F 'file=@/path/to/avatar.png' -F 'name=John' -H 'Content-Type: multipart/form-data'\n\nresponse = Crest.post(\n  \"http://httpbin.org/post\",\n  {:age =\u003e 27, :name =\u003e {:first =\u003e \"Kurt\", :last =\u003e \"Cobain\"}},\n  json: true\n)\n# curl -X POST http://httpbin.org/post -d '{\"age\":27,\"name\":{\"first\":\"Kurt\",\"last\":\"Cobain\"}}' -H 'Content-Type: application/json'\n```\n\n### Request\n\n`Crest::Request` accept next parameters:\n\nMandatory parameters:\n\n- `:method` - HTTP method (`:get`. `:post`, `:put`, `:patch`, `:delete`, `:options`, `head`)\n- `:url` - URL (e.g.: `http://httpbin.org/ip`)\n\nOptional parameters:\n\n- `:form` - a hash containing form data (or a raw string or IO or Bytes)\n- `:headers` - a hash containing the request headers\n- `:cookies` - a hash containing the request cookies\n- `:params` - a hash that represent query params (or a raw string) - a string separated from the preceding part by a question mark (`?`) and a sequence of attribute–value pairs separated by a delimiter (`\u0026`)\n- `:params_encoder` params encoder (default to `Crest::FlatParamsEncoder`)\n- `:auth` - access authentication method `basic` or `digest` (default to `basic`)\n- `:user` and `:password` - for authentication\n- `:tls` - client certificates, you can pass in a custom `OpenSSL::SSL::Context::Client` (default to `nil`)\n- `:p_addr`, `:p_port`, `:p_user`, and `:p_pass` - specify a per-request proxy by passing these parameters\n- `:json` - make a JSON request with the appropriate HTTP headers (default to `false`)\n- `:multipart` make a multipart request with the appropriate HTTP headers even if not sending a file (default to `false`)\n- `:user_agent` - set \"User-Agent\" HTTP header (default to `Crest::USER_AGENT`)\n- `:max_redirects` - maximum number of redirects (default to 10)\n- `:logging` - enable logging (default to `false`)\n- `:logger` - set logger (default to `Crest::CommonLogger`)\n- `:handle_errors` - error handling (default to `true`)\n- `:close_connection` - close the connection after request is completed (default to `true`)\n- `:http_client` - instance of `HTTP::Client`\n- `:read_timeout` - read timeout (default to `nil`)\n- `:write_timeout` - write timeout (default to `nil`)\n- `:connect_timeout` - connect timeout (default to `nil`)\n\nMore detailed examples:\n\n```crystal\nrequest = Crest::Request.new(:post,\n  \"http://httpbin.org/post\",\n  headers: {\"Content-Type\" =\u003e \"application/json\"},\n  form: {:width =\u003e 640, \"height\" =\u003e \"480\"}\n)\nrequest.execute\n# curl -L --data \"width=640\u0026height=480\" --header \"Content-Type: application/json\" -X POST \"http://httpbin.org/post\"\n```\n\n```crystal\nCrest::Request.execute(:get,\n  \"http://httpbin.org/get\",\n  params: {:width =\u003e 640, \"height\" =\u003e \"480\"},\n  headers: {\"Content-Type\" =\u003e \"application/json\"}\n)\n# curl -L --header \"Content-Type: application/json\" \"http://httpbin.org/get?width=640\u0026height=480\"\n```\n\n```crystal\nCrest::Request.new(:post, \"http://httpbin.org/post\", {:foo =\u003e \"bar\"}, json: true)\n\n# curl -X POST http://httpbin.org/post -d '{\\\"foo\\\":\\\"bar\\\"}' -H 'Content-Type: application/json'\"\n```\n\n```crystal\nCrest::Request.get(\n  \"http://httpbin.org/get\",\n  p_addr: \"127.0.0.1\",\n  p_port: 3128,\n  p_user: \"admin\",\n  p_pass: \"1234\"\n)\n# curl -L --proxy admin:1234@127.0.0.1:3128 \"http://httpbin.org/get\"\n```\n\nA block can be passed to the `Crest::Request` initializer.\n\nThis block will then be called with the `Crest::Request`.\n\n```crystal\nrequest = Crest::Request.new(:get, \"http://httpbin.org/headers\") do |request|\n  request.headers.add(\"foo\", \"bar\")\nend\n\nrequest.execute\n# curl -L --header \"foo: bar\" http://httpbin.org/headers\n```\n\n### Resource\n\nA `Crest::Resource` class can be instantiated for access to a RESTful resource,\nincluding authentication, proxy and logging.\n\nAdditionally, you can set default `params`, `headers`, and `cookies` separately.\nSo you can use `Crest::Resource` to share common `params`, `headers`, and `cookies`.\n\nThe final parameters consist of:\n\n- default parameters from initializer\n- parameters provided in call method (`get`, `post`, etc)\n\nThis is especially useful if you wish to define your site in one place and\ncall it in multiple locations.\n\n```crystal\nresource = Crest::Resource.new(\n  \"http://httpbin.org\",\n  params: {\"key\" =\u003e \"value\"},\n  headers: {\"Content-Type\" =\u003e \"application/json\"},\n  cookies: {\"lang\" =\u003e \"uk\"}\n)\n\nresource[\"/get\"].get(\n  headers: {\"Auth-Token\" =\u003e \"secret\"}\n)\n\nresource[\"/post\"].post(\n  {:height =\u003e 100, \"width\" =\u003e \"100\"},\n  params: {:secret =\u003e \"secret\"}\n)\n```\n\nUse the `[]` syntax to allocate subresources:\n\n```crystal\nsite = Crest::Resource.new(\"http://httpbin.org\")\n\nsite[\"/post\"].post({:param1 =\u003e \"value1\", :param2 =\u003e \"value2\"})\n# curl -L --data \"param1=value1\u0026param2=value2\" -X POST http://httpbin.org/post\n```\n\nYou can pass `suburl` through `Request#http_verb` methods:\n\n```crystal\nsite = Crest::Resource.new(\"http://httpbin.org\")\n\nsite.post(\"/post\", {:param1 =\u003e \"value1\", :param2 =\u003e \"value2\"})\n# curl -L --data \"param1=value1\u0026param2=value2\" -X POST http://httpbin.org/post\n\nsite.get(\"/get\", params: {:status =\u003e \"active\"})\n# curl -L http://httpbin.org/get?status=active\n```\n\nA block can be passed to the `Crest::Resource` instance.\n\nThis block will then be called with the `Crest::Resource`.\n\n```crystal\nresource = Crest::Resource.new(\"http://httpbin.org\") do |resource|\n  resource.headers.merge!({\"foo\" =\u003e \"bar\"})\nend\n\nresource[\"/headers\"].get\n```\n\nWith HTTP basic authentication:\n\n```crystal\nresource = Crest::Resource.new(\n  \"http://httpbin.org/basic-auth/user/passwd\",\n  user: \"user\",\n  password: \"passwd\"\n)\n```\n\nWith Proxy:\n\n```crystal\nresource = Crest::Resource.new(\n  \"http://httpbin.org/get\",\n  p_addr: \"localhost\",\n  p_port: 3128\n)\n```\n\n### Result handling\n\nThe result of a `Crest::Request` and `Crest::Resource` is a `Crest::Response` object.\n\nResponse objects have several useful methods:\n\n- `Response#body`: The response body as a `String`\n- `Response#body_io`: The response body as a `IO`\n- `Response#status`: The response status as a `HTTP::Status`\n- `Response#status_code`: The HTTP response code\n- `Response#headers`: A hash of HTTP response headers\n- `Response#cookies`: A hash of HTTP cookies set by the server\n- `Response#request`: The `Crest::Request` object used to make the request\n- `Response#http_client_res`: The `HTTP::Client::Response` object\n- `Response#history`: A list of each response received in a redirection chain\n\n### Exceptions\n\n- for status codes between `200` and `207`, a `Crest::Response` will be returned\n- for status codes `301`, `302`, `303` or `307`, the redirection will be followed and the request transformed into a `GET`\n- for other cases, a `Crest::RequestFailed` holding the `Crest::Response` will be raised\n- call `.response` on the exception to get the server's response\n\n```crystal\nCrest.get(\"http://httpbin.org/status/404\")\n# =\u003e HTTP status code 404: Not Found (Crest::NotFound)\n\nbegin\n  Crest.get(\"http://httpbin.org/status/404\")\nrescue ex : Crest::NotFound\n  puts ex.response\nend\n```\n\nTo not raise exceptions but return the `Crest::Response` you can set `handle_errors` to `false`.\n\n```crystal\nresponse = Crest.get(\"http://httpbin.org/status/404\", handle_errors: false) do |resp|\n  case resp\n  when .success?\n    puts resp.body_io.gets_to_end\n  when .client_error?\n    puts \"Client error\"\n  when .server_error?\n    puts \"Server error\"\n  end\nend\n# =\u003e Client error\n\nresponse.status_code # =\u003e 404\n```\n\nBut note that it may be more straightforward to use exceptions to handle different HTTP error response cases:\n\n```crystal\nresponse = begin\n  Crest.get(\"http://httpbin.org/status/404\")\nrescue ex : Crest::NotFound\n  puts \"Not found\"\n  ex.response\nrescue ex : Crest::InternalServerError\n  puts \"Internal server error\"\n  ex.response\nend\n# =\u003e Not found\n\nresponse.status_code # =\u003e 404\n```\n\n### Parameters serializer\n\n`Crest::ParamsEncoder` class is used to encode parameters.\n\nThe encoder affect both how `crest` processes query strings and how it serializes POST bodies.\n\nThe default encoder is `Crest::FlatParamsEncoder`.\n\nIt provides `#encode` method, which converts the given params into a URI query string:\n\n```crystal\nCrest::FlatParamsEncoder.encode({\"a\" =\u003e [\"one\", \"two\", \"three\"], \"b\" =\u003e true, \"c\" =\u003e \"C\", \"d\" =\u003e 1})\n# =\u003e 'a[]=one\u0026a[]=two\u0026a[]=three\u0026b=true\u0026c=C\u0026d=1'\n```\n\n### Custom parameters serializers\n\nYou can build a custom params encoder.\n\nThe value of Crest `params_encoder` can be any subclass of `Crest::ParamsEncoder` that implement `#encode(Hash) #=\u003e String`\n\nAlso Crest include other encoders.\n\n#### `Crest::NestedParamsEncoder`\n\n```crystal\nresponse = Crest.post(\n  \"http://httpbin.org/post\",\n  {\"size\" =\u003e \"small\", \"topping\" =\u003e [\"bacon\", \"onion\"]},\n  params_encoder: Crest::NestedParamsEncoder\n)\n\n# =\u003e curl -X POST http://httpbin.org/post -d 'size=small\u0026topping=bacon\u0026topping=onion' -H 'Content-Type: application/x-www-form-urlencoded'\n```\n\n#### `Crest::EnumeratedFlatParamsEncoder`\n\n```crystal\nresponse = Crest.post(\n  \"http://httpbin.org/post\",\n  {\"size\" =\u003e \"small\", \"topping\" =\u003e [\"bacon\", \"onion\"]},\n  params_encoder: Crest::EnumeratedFlatParamsEncoder\n)\n\n# =\u003e curl -X POST http://httpbin.org/post -d 'size=small\u0026topping[1]=bacon\u0026topping[2]=onion' -H 'Content-Type: application/x-www-form-urlencoded'\n```\n\n#### `Crest::ZeroEnumeratedFlatParamsEncoder`\n\n```crystal\nresponse = Crest.post(\n  \"http://httpbin.org/post\",\n  {\"size\" =\u003e \"small\", \"topping\" =\u003e [\"bacon\", \"onion\"]},\n  params_encoder: Crest::ZeroEnumeratedFlatParamsEncoder\n)\n\n# =\u003e curl -X POST http://httpbin.org/post -d 'size=small\u0026topping[0]=bacon\u0026topping[1]=onion' -H 'Content-Type: application/x-www-form-urlencoded'\n```\n\n### Streaming responses\n\nNormally, 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.\n\nHowever, 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 into memory. If you have a very large file, it may become impossible to load it into memory.\n\nIf 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.\n\nWith a block, an `Crest::Response` body is returned and the response's body is available as an `IO` by invoking `Crest::Response#body_io`.\n\nThe following is an example:\n\n```crystal\nCrest.get(\"https://github.com/crystal-lang/crystal/archive/1.0.0.zip\") do |resp|\n  filename = resp.filename || \"crystal.zip\"\n\n  File.open(filename, \"w\") do |file|\n    IO.copy(resp.body_io, file)\n  end\nend\n```\n\n### Advanced Usage\n\nThis section covers some of `crest` more advanced features.\n\n#### Multipart\n\nYeah, that's right! This does multipart sends for you!\n\n```crystal\nfile = File.open(\"#{__DIR__}/example.png\")\nCrest.post(\"http://httpbin.org/post\", {:image =\u003e file})\n```\n\n```crystal\nfile_content = \"id,name\\n1,test\"\nfile = IO::Memory.new(file_content)\nCrest.post(\"http://httpbin.org/post\", {\"data.csv\" =\u003e file})\n```\n\n```crystal\nfile = File.open(\"#{__DIR__}/example.png\")\nresource = Crest::Resource.new(\"https://httpbin.org\")\nresponse = resource[\"/post\"].post({:image =\u003e file})\n```\n\n#### JSON payload\n\n`crest` speaks JSON natively by passing `json: true` argument to `crest`.\n\n```crystal\nCrest.post(\"http://httpbin.org/post\", {:foo =\u003e \"bar\"}, json: true)\n```\n\nAs well you can serialize your _form_ to a string by itself before passing it to `crest`.\n\n```crystal\nCrest.post(\n  \"http://httpbin.org/post\",\n  {:foo =\u003e \"bar\"}.to_json\n  headers: {\"Accept\" =\u003e \"application/json\", \"Content-Type\" =\u003e \"application/json\"},\n)\n```\n\n#### Headers\n\nRequest headers can be set by passing a hash containing keys and values representing header names and values:\n\n```crystal\nresponse = Crest.get(\n  \"http://httpbin.org/bearer\",\n  headers: {\"Authorization\" =\u003e \"Bearer cT0febFoD5lxAlNAXHo6g\"}\n)\nresponse.headers\n# =\u003e {\"Authorization\" =\u003e [\"Bearer cT0febFoD5lxAlNAXHo6g\"]}\n```\n\n#### Cookies\n\n`Request` and `Response` objects know about HTTP cookies, and will automatically extract and set headers for them as needed:\n\n```crystal\nresponse = Crest.get(\n  \"http://httpbin.org/cookies/set\",\n  params: {\"k1\" =\u003e \"v1\", \"k2\" =\u003e \"v2\"}\n)\nresponse.cookies\n# =\u003e {\"k1\" =\u003e \"v1\", \"k2\" =\u003e \"v2\"}\n```\n\n```crystal\nresponse = Crest.get(\n  \"http://httpbin.org/cookies\",\n  cookies: {\"k1\" =\u003e \"v1\", \"k2\" =\u003e {\"kk2\" =\u003e \"vv2\"}}\n)\nresponse.cookies\n# =\u003e {\"k1\" =\u003e \"v1\", \"k2[kk2]\" =\u003e \"vv2\"}\n```\n\n#### Basic access authentication\n\nFor basic access authentication for an HTTP user agent you should to provide a `user` name and `password` when making a request.\n\n```crystal\nCrest.get(\n  \"http://httpbin.org/basic-auth/user/passwd\",\n  user: \"user\",\n  password: \"passwd\"\n)\n# curl -L --user user:passwd http://httpbin.org/basic-auth/user/passwd\n```\n\n#### Digest access authentication\n\nFor digest access authentication for an HTTP user agent you should to provide a `user` name and `password` when making a request.\n\n```crystal\nCrest.get(\n  \"https://httpbin.org/digest-auth/auth/user/passwd/MD5\",\n  auth: \"digest\",\n  user: \"user\",\n  password: \"passwd\"\n)\n# curl -L --digest --user user:passwd https://httpbin.org/digest-auth/auth/user/passwd/MD5\n```\n\n#### SSL/TLS support\n\nIf `tls` is given it will be used:\n\n```crystal\nCrest.get(\"https://expired.badssl.com\", tls: OpenSSL::SSL::Context::Client.insecure)\n```\n\n#### Proxy\n\nIf you need to use a proxy, you can configure individual requests with the proxy host and port arguments to any request method:\n\n```crystal\nCrest.get(\n  \"http://httpbin.org/ip\",\n  p_addr: \"localhost\",\n  p_port: 3128\n)\n```\n\nTo use authentication with your proxy, use next syntax:\n\n```crystal\nCrest.get(\n  \"http://httpbin.org/ip\",\n  p_addr: \"localhost\",\n  p_port: 3128,\n  p_user: \"user\",\n  p_pass: \"qwerty\"\n)\n```\n\n#### Logging\n\n\u003e `Logger` class is completely taken from [halite](https://github.com/icyleaf/halite) shard.\n\u003e Thanks [icyleaf](https://github.com/icyleaf)!\n\nBy default, the `Crest` does not enable logging. You can enable it per request by setting `logging: true`:\n\n```crystal\nCrest.get(\"http://httpbin.org/get\", logging: true)\n```\n\n##### Filter sensitive information from logs with a regex matcher\n\n```crystal\nresource = Crest::Request.get(\"http://httpbin.org/get\", params: {api_key =\u003e \"secret\"}, logging: true) do |request|\n  request.logger.filter(/(api_key=)(\\w+)/, \"\\\\1[REMOVED]\")\nend\n\n# =\u003e crest | 2018-07-04 14:49:49 | GET | http://httpbin.org/get?api_key=[REMOVED]\n```\n\n##### Customize logger\n\nYou can create the custom logger by integration `Crest::Logger` abstract class.\nHere has two methods must be implement: `Crest::Logger.request` and `Crest::Logger.response`.\n\n```crystal\nclass MyLogger \u003c Crest::Logger\n  def request(request)\n    @logger.info { \"\u003e\u003e | %s | %s\" % [request.method, request.url] }\n  end\n\n  def response(response)\n    @logger.info { \"\u003c\u003c | %s | %s\" % [response.status_code, response.url] }\n  end\nend\n\nCrest.get(\"http://httpbin.org/get\", logging: true, logger: MyLogger.new)\n```\n\n#### Redirection\n\nBy default, `crest` will follow HTTP 30x redirection requests.\n\nTo disable automatic redirection, set `:max_redirects =\u003e 0`.\n\n```crystal\nCrest::Request.execute(method: :get, url: \"http://httpbin.org/redirect/1\", max_redirects: 0)\n# =\u003e Crest::Found: 302 Found\n```\n\n#### Access HTTP::Client\n\nYou can access `HTTP::Client` via the `http_client` instance method.\n\nThis is usually used to set additional options (e.g. read timeout, authorization header etc.)\n\n```crystal\nclient = HTTP::Client.new(\"httpbin.org\")\nclient.read_timeout = 1.second\n\nbegin\n  Crest::Request.new(:get,\n    \"http://httpbin.org/delay/10\",\n    http_client: client\n  )\nrescue IO::TimeoutError\n  puts \"Timeout!\"\nend\n```\n\n```crystal\nclient = HTTP::Client.new(\"httpbin.org\")\nclient.read_timeout = 1.second\n\nbegin\n  resource = Crest::Resource.new(\"http://httpbin.org\", http_client: client)\n  resource.get(\"/delay/10\")\nrescue IO::TimeoutError\n  puts \"Timeout!\"\nend\n```\n\n#### Convert Request object to cURL command\n\nUse `to_curl` method on instance of `Crest::Request` to convert request to cURL command.\n\n```crystal\nrequest = Crest::Request.new(\n  :post,\n  \"http://httpbin.org/post\",\n  {\"title\" =\u003e \"New Title\", \"author\" =\u003e \"admin\"}\n)\nrequest.to_curl\n# =\u003e curl -X POST http://httpbin.org/post -d 'title=New+Title\u0026author=admin' -H 'Content-Type: application/x-www-form-urlencoded'\n```\n\n```crystal\nrequest = Crest::Request.new(\n  :get,\n  \"http://httpbin.org/basic-auth/user/passwd\",\n  user: \"user\",\n  password: \"passwd\"\n)\nrequest.to_curl\n# =\u003e curl -X GET http://httpbin.org/basic-auth/user/passwd --user user:passwd\n```\n\nAlso you can directly use `Crest::Curlify` which accept instance of `Crest::Request`\n\n```crystal\nrequest = Crest::Request.new(:get, \"http://httpbin.org\")\nCrest::Curlify.new(request).to_curl\n# =\u003e curl -X GET http://httpbin.org\n```\n\n#### Params decoder\n\n`Crest::ParamsDecoder` is a module for decoding query-string into parameters.\n\n```crystal\nquery = \"size=small\u0026topping[1]=bacon\u0026topping[2]=onion\"\nCrest::ParamsDecoder.decode(query)\n# =\u003e {\"size\" =\u003e \"small\", \"topping\" =\u003e [\"bacon\", \"onion\"]}\n```\n\n## Development\n\nInstall dependencies:\n\n```console\nshards\n```\n\nTo run test:\n\n```console\ncrystal spec\n```\n\n### Workbook\n\n```console\ncrystal play\nopen http://localhost:8080\n```\n\nThen select the Workbook -\u003e Requests from the menu.\n\n## Contributing\n\n1. Fork it (\u003chttps://github.com/mamantoha/crest/fork\u003e)\n2. Create your feature branch (git checkout -b my-new-feature)\n3. Commit your changes (git commit -am 'Add some feature')\n4. Push to the branch (git push origin my-new-feature)\n5. Create a new Pull Request\n\n## Contributors\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\u003ctable\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003ctd align=\"center\" valign=\"top\" width=\"14.28%\"\u003e\u003ca href=\"https://github.com/mamantoha\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/61285?v=4?s=100\" width=\"100px;\" alt=\"Anton Maminov\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eAnton Maminov\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/mamantoha/crest/commits?author=mamantoha\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n      \u003ctd align=\"center\" valign=\"top\" width=\"14.28%\"\u003e\u003ca href=\"https://www.linkedin.com/in/chao-yang-r/\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/15083254?v=4?s=100\" width=\"100px;\" alt=\"Chao Yang\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eChao Yang\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/mamantoha/crest/commits?author=cyangle\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n      \u003ctd align=\"center\" valign=\"top\" width=\"14.28%\"\u003e\u003ca href=\"https://github.com/psikoz\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/40601249?v=4?s=100\" width=\"100px;\" alt=\"psikoz\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003epsikoz\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"#design-psikoz\" title=\"Design\"\u003e🎨\u003c/a\u003e\u003c/td\u003e\n      \u003ctd align=\"center\" valign=\"top\" width=\"14.28%\"\u003e\u003ca href=\"https://github.com/jphaward\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/52081790?v=4?s=100\" width=\"100px;\" alt=\"jphaward\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003ejphaward\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/mamantoha/crest/commits?author=jphaward\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n      \u003ctd align=\"center\" valign=\"top\" width=\"14.28%\"\u003e\u003ca href=\"https://miry.codeberg.page/\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/21104?v=4?s=100\" width=\"100px;\" alt=\"Michael Nikitochkin\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eMichael Nikitochkin\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/mamantoha/crest/commits?author=miry\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003c!-- markdownlint-restore --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\n\u003c!-- markdownlint-restore --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\n## License\n\nCopyright: 2017-2026 Anton Maminov (anton.maminov@gmail.com)\n\nThis library is distributed under the MIT license. Please see the LICENSE file.\n","funding_links":[],"categories":["Crystal","HTTP","\u003ca name=\"Crystal\"\u003e\u003c/a\u003eCrystal"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmamantoha%2Fcrest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmamantoha%2Fcrest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmamantoha%2Fcrest/lists"}