{"id":13483380,"url":"https://github.com/crystal-community/cossack","last_synced_at":"2025-03-17T09:30:43.138Z","repository":{"id":90863106,"uuid":"59944101","full_name":"crystal-community/cossack","owner":"crystal-community","description":"Simple and flexible HTTP client for Crystal with middleware and test support.","archived":false,"fork":false,"pushed_at":"2020-07-08T19:58:39.000Z","size":416,"stargazers_count":106,"open_issues_count":9,"forks_count":11,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-02-27T21:50:33.984Z","etag":null,"topics":["crystal","middleware"],"latest_commit_sha":null,"homepage":"","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/crystal-community.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-05-29T13:21:45.000Z","updated_at":"2024-05-31T11:53:32.000Z","dependencies_parsed_at":"2024-01-27T09:19:53.372Z","dependency_job_id":null,"html_url":"https://github.com/crystal-community/cossack","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crystal-community%2Fcossack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crystal-community%2Fcossack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crystal-community%2Fcossack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crystal-community%2Fcossack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/crystal-community","download_url":"https://codeload.github.com/crystal-community/cossack/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243858892,"owners_count":20359257,"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","middleware"],"created_at":"2024-07-31T17:01:10.614Z","updated_at":"2025-03-17T09:30:42.768Z","avatar_url":"https://github.com/crystal-community.png","language":"Crystal","funding_links":[],"categories":["Crystal","HTTP"],"sub_categories":[],"readme":"# Cossack \u003cimg src=\"https://cloud.githubusercontent.com/assets/113512/15764341/65d90c06-292a-11e6-8f91-44ed93e024f8.png\" alt=\"crystal Cossack logo\" width=\"48\"\u003e\n\n[![Build Status](https://travis-ci.org/crystal-community/cossack.svg?branch=master)](https://travis-ci.org/crystal-community/cossack)\n[![docrystal.org](http://docrystal.org/badge.svg?style=round)](http://docrystal.org/github.com/crystal-community/cossack)\n\nSimple and flexible HTTP client for Crystal with middleware and test support.\n\n* [Installation](#installation)\n* [Usage](#usage)\n* [The concept](#the-concept)\n* [Using Middleware](#using-middleware)\n* [Connection swapping](#connection-swapping)\n* [Testing](#testing)\n* [FAQ](#faq)\n  * [How to follow redirections](#how-to-follow-redirections)\n  * [How to persist cookies between requests](#how-to-persist-cookies-between-requests)\n  * [How to persist cookies past the life of the application](#how-to-persist-cookies-past-the-life-of-the-application)\n* [Development](#development)\n* [Roadmap](#roadmap)\n* [Afterword](#afterword)\n* [Contributors](#contributors)\n\n## Installation\n\nAdd this to your application's `shard.yml`:\n\n```yaml\ndependencies:\n  cossack:\n    github: crystal-community/cossack\n    version: ~\u003e 0.1\n```\n\nAnd install dependencies:\n\n```\ncrystal deps\n```\n\n## Usage\n\n```crystal\nrequire \"cossack\"\n\n# Send a single GET request\nresponse = Cossack.get(\"https://www.w3.org/\")\nresponse.body  # =\u003e \"Bla bla bla\"\n\n# Create an instance of a client with basic URL\ncossack = Cossack::Client.new(\"http://example.org\") do |client|\n  # Set headers\n  client.headers[\"Authorization\"] = \"Bearer SECRET-TOKEN\"\n\n  # Modify request options (by default connect_timeout is 30 sec)\n  client.request_options.connect_timeout = 60.seconds\nend\n\n# Send GET request to http://example.org/info\nresponse = cossack.get(\"/info\") do |request|\n  # modify a particular request\n  request.headers[\"Accept-Language\"] = \"eo\"\nend\n\n# Explore response\nresponse.status                     # =\u003e 200\nresponse.body                       # =\u003e \"Info\"\nresponse.headers[\"Content-Length\"]  # =\u003e 4\n\n# Send POST request\ncossack.post(\"/comments\", \"Request body\")\n```\n\n## The concept\n\nCossack is inspired by [Faraday](https://github.com/lostisland/faraday) and [Hurley](https://github.com/lostisland/hurley) libraries from the ruby world.\n\nThe main things are: Client, Request, Response, Connection, Middleware.\n* **Client** - provides a convenient API to build and perform HTTP requests. Keeps default request parameters(base url, headers, request options, etc.)\n* **Request** - HTTP request(method, uri, headers, body) with its options (e.g. `connect_timeout`).\n* **Response** - HTTP response(method, headers, body).\n* **Connection** - executes actual Request, used by Client and can be subsituted (e.g. for test purposes).\n* **Middleware** - can be injected between Client and Connection to execute some custom stuff(e.g. logging, caching, etc.)\n\nThe following time diagram shows how it works:\n\n![Crystal HTTP client Cossack time diagram](https://raw.githubusercontent.com/crystal-community/cossack/master/images/cossack_diagram.png)\n\n## Using Middleware\n\nMiddleware are custom classes that are injected in between Client and Connection. They allow you to intercept request or response and modify them.\n\nMiddleware class should be inherited from `Cossack::Middleware` and implement `#call(Cossack::Request) : Cossack::Response` interface.\nIt also should execute `app.call(request)` in order to forward a request.\n\nLet's implement simple middleware that prints all requests:\n\n```crystal\nclass StdoutLogMiddleware \u003c Cossack::Middleware\n  def call(request)\n    puts \"#{request.method} #{request.uri}\"\n    app.call(request).tap do |response|\n      puts \"Response: #{response.status} #{response.body}\"\n    end\n  end\nend\n```\n\nNow let's apply it to a client:\n\n```crystal\ncossack = Cossack::Client.new(\"http://example.org\") do |client|\n  client.use StdoutLogMiddleware\nend\n\n# Every request will be logged to STDOUT\nresponse = cossack.get(\"/test\")\n```\n\nCossack has some preimplemented middleware, don't be afraid to [take a look](https://github.com/crystal-community/cossack/tree/master/src/cossack/middleware).\n\n## Connection Swapping\n\nConnection is something, that receives Request and returns back Response. By default client as [HTTPConnection](https://github.com/crystal-community/cossack/blob/master/src/cossack/connection/http_connection.cr),\nthat performs real HTTP requests. But if you don't like it by some reason, or you want to modify its behaviour, you can replace it\nwith you own. It must be a proc a subclass of `Cossack::Connection`:\n\n```crystal\nclient = Cossack::Client.new\nclient.connection = -\u003e (request : Cossack::Request) do\n  Cossack::Response.new(200, \"Everything is fine\")\nend\n\nresponse = client.put(\"http://example.org/no/matter/what\")\nputs response.body # =\u003e \"Everything is fine\"\n```\n\n## Testing\n\nThere is more use of connection swapping, when it comes to testing. Cossack has `TestConnection` that allows you to\nstub HTTP requests in specs.\n\n```crystal\ndescribe \"TestConnection example\" do\n  it \"stubs real requests\" do\n    connection = Cossack::TestConnection.new\n    connection.stub_get(\"/hello/world\", {200, \"Hello developer!\"})\n\n    client = Cossack::Client.new(\"http://example.org\")\n    client.connection = connection\n\n    response = client.get(\"/hello/world\")\n    response.status.should eq 200\n    response.body.should eq \"Hello developer!\"\n  end\nend\n```\n\nYou can find real examples in [Glosbe](https://github.com/greyblake/crystal-glosbe/blob/master/spec/glosbe/client_spec.cr) and\n[GoogleTranslate](https://github.com/greyblake/crystal-google_translate/blob/master/spec/google_translate/client_spec.cr) clients.\nOr in [Cossack specs](https://github.com/crystal-community/cossack/blob/master/spec/unit/connection/test_connection_spec.cr) itself.\n\n## FAQ\n\n### How to follow redirections\n\nIf you want a client to follow redirections, you can use `Cossack::RedirectionMiddleware`:\n\n```crystal\ncossack = Cossack::Client.new do |client|\n  # follow up to 10 redirections (by default 5)\n  client.use Cossack::RedirectionMiddleware, limit: 10\nend\n\ncossack.get(\"http://example.org/redirect-me\")\n```\n\n### How to persist cookies between requests\n\nIf, for example, you're calling an API that relies on cookies, you'll need to\nuse the `CookieJarMiddleware` like so:\n\n```crystal\ncossack = Cossack::Client.new do |client|\n  # Other middleware goes here\nend\ncossack.use Cossack::CookieJarMiddleware, cookie_jar: cossack.cookies\n```\n\nNote that `cossack.use Cossack::CookieJarMiddleware` needs to be outside of the\n`do ... end` block due to problems in Crystal (as of v0.18.7)\n\n### How to persist cookies past the life of the application\n\nIf, for example, you have a need to retain cookies you're already storing\nbetween requests, you have the option to write them out to a file using\nsomething like the following:\n\n```crystal\ncossack = Cossack::Client.new do |client|\n  # Other middleware goes here\nend\ncossack.use Cossack::CookieJarMiddleware, cookie_jar: cossack.cookies\n\n# [code]\n\ncossack.cookies.export_to_file(\"/path/to/writable/directory/cookies.txt\")\n```\n\nYou may also import the cookies like so:\n```crystal\ncossack = Cossack::Client.new do |client|\n  # Other middleware goes here\nend\ncossack.cookies.import_from_file(\"/path/to/writable/directory/cookies.txt\")\n\n# OR\n\ncossack = Cossack::Client.new do |client|\n  client.cookies = Cossack::CookieJar.from_file(\"/path/to/writable/directory/cookies.txt\")\n  # Other middleware goes here\nend\n```\n\n## Development\n\nTo run all tests:\n\n```\nmake test\n```\n\nTo run unit tests:\n\n```\nmake test_unit\n```\n\nTo run acceptance tests:\n\n```\nmake test_acceptance\n```\n\n## Roadmap\n* [ ] Implement before / after callbacks\n* [ ] Add context/env Hash(String, String) to Request and Response\n* [ ] Find a way to perform basic autentication\n\n## Afterword\n\nIf you like the concept and design of the library, then may be we can bring the idea to Crystal!\nThere is no need to keep this library, if we can have the same things in standard library.\nAnd I guess [crystal maintainers won't resist](https://github.com/crystal-lang/crystal/issues/2721#issuecomment-223399683).\nBut first we need to get positive feedback to ensure we're moving in the right direction =)\n\n## Contributors\n\n- [greyblake](https://github.com/greyblake) Sergey Potapov - creator, maintainer\n- [thelonelyghost](https://github.com/thelonelyghost) David Alexander, cookie middleware support\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrystal-community%2Fcossack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcrystal-community%2Fcossack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrystal-community%2Fcossack/lists"}