• Stars
    star
    105
  • Rank 317,342 (Top 7 %)
  • Language
    Crystal
  • License
    GNU Lesser Genera...
  • Created almost 8 years ago
  • Updated almost 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Simple and flexible HTTP client for Crystal with middleware and test support.

Cossack crystal Cossack logo

Build Status docrystal.org

Simple and flexible HTTP client for Crystal with middleware and test support.

Installation

Add this to your application's shard.yml:

dependencies:
  cossack:
    github: crystal-community/cossack
    version: ~> 0.1

And install dependencies:

crystal deps

Usage

require "cossack"

# Send a single GET request
response = Cossack.get("https://www.w3.org/")
response.body  # => "Bla bla bla"

# Create an instance of a client with basic URL
cossack = Cossack::Client.new("http://example.org") do |client|
  # Set headers
  client.headers["Authorization"] = "Bearer SECRET-TOKEN"

  # Modify request options (by default connect_timeout is 30 sec)
  client.request_options.connect_timeout = 60.seconds
end

# Send GET request to http://example.org/info
response = cossack.get("/info") do |request|
  # modify a particular request
  request.headers["Accept-Language"] = "eo"
end

# Explore response
response.status                     # => 200
response.body                       # => "Info"
response.headers["Content-Length"]  # => 4

# Send POST request
cossack.post("/comments", "Request body")

The concept

Cossack is inspired by Faraday and Hurley libraries from the ruby world.

The main things are: Client, Request, Response, Connection, Middleware.

  • Client - provides a convenient API to build and perform HTTP requests. Keeps default request parameters(base url, headers, request options, etc.)
  • Request - HTTP request(method, uri, headers, body) with its options (e.g. connect_timeout).
  • Response - HTTP response(method, headers, body).
  • Connection - executes actual Request, used by Client and can be subsituted (e.g. for test purposes).
  • Middleware - can be injected between Client and Connection to execute some custom stuff(e.g. logging, caching, etc.)

The following time diagram shows how it works:

Crystal HTTP client Cossack time diagram

Using Middleware

Middleware are custom classes that are injected in between Client and Connection. They allow you to intercept request or response and modify them.

Middleware class should be inherited from Cossack::Middleware and implement #call(Cossack::Request) : Cossack::Response interface. It also should execute app.call(request) in order to forward a request.

Let's implement simple middleware that prints all requests:

class StdoutLogMiddleware < Cossack::Middleware
  def call(request)
    puts "#{request.method} #{request.uri}"
    app.call(request).tap do |response|
      puts "Response: #{response.status} #{response.body}"
    end
  end
end

Now let's apply it to a client:

cossack = Cossack::Client.new("http://example.org") do |client|
  client.use StdoutLogMiddleware
end

# Every request will be logged to STDOUT
response = cossack.get("/test")

Cossack has some preimplemented middleware, don't be afraid to take a look.

Connection Swapping

Connection is something, that receives Request and returns back Response. By default client as HTTPConnection, that 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 with you own. It must be a proc a subclass of Cossack::Connection:

client = Cossack::Client.new
client.connection = -> (request : Cossack::Request) do
  Cossack::Response.new(200, "Everything is fine")
end

response = client.put("http://example.org/no/matter/what")
puts response.body # => "Everything is fine"

Testing

There is more use of connection swapping, when it comes to testing. Cossack has TestConnection that allows you to stub HTTP requests in specs.

describe "TestConnection example" do
  it "stubs real requests" do
    connection = Cossack::TestConnection.new
    connection.stub_get("/hello/world", {200, "Hello developer!"})

    client = Cossack::Client.new("http://example.org")
    client.connection = connection

    response = client.get("/hello/world")
    response.status.should eq 200
    response.body.should eq "Hello developer!"
  end
end

You can find real examples in Glosbe and GoogleTranslate clients. Or in Cossack specs itself.

FAQ

How to follow redirections

If you want a client to follow redirections, you can use Cossack::RedirectionMiddleware:

cossack = Cossack::Client.new do |client|
  # follow up to 10 redirections (by default 5)
  client.use Cossack::RedirectionMiddleware, limit: 10
end

cossack.get("http://example.org/redirect-me")

How to persist cookies between requests

If, for example, you're calling an API that relies on cookies, you'll need to use the CookieJarMiddleware like so:

cossack = Cossack::Client.new do |client|
  # Other middleware goes here
end
cossack.use Cossack::CookieJarMiddleware, cookie_jar: cossack.cookies

Note that cossack.use Cossack::CookieJarMiddleware needs to be outside of the do ... end block due to problems in Crystal (as of v0.18.7)

How to persist cookies past the life of the application

If, for example, you have a need to retain cookies you're already storing between requests, you have the option to write them out to a file using something like the following:

cossack = Cossack::Client.new do |client|
  # Other middleware goes here
end
cossack.use Cossack::CookieJarMiddleware, cookie_jar: cossack.cookies

# [code]

cossack.cookies.export_to_file("/path/to/writable/directory/cookies.txt")

You may also import the cookies like so:

cossack = Cossack::Client.new do |client|
  # Other middleware goes here
end
cossack.cookies.import_from_file("/path/to/writable/directory/cookies.txt")

# OR

cossack = Cossack::Client.new do |client|
  client.cookies = Cossack::CookieJar.from_file("/path/to/writable/directory/cookies.txt")
  # Other middleware goes here
end

Development

To run all tests:

make test

To run unit tests:

make test_unit

To run acceptance tests:

make test_acceptance

Roadmap

  • Implement before / after callbacks
  • Add context/env Hash(String, String) to Request and Response
  • Find a way to perform basic autentication

Afterword

If you like the concept and design of the library, then may be we can bring the idea to Crystal! There is no need to keep this library, if we can have the same things in standard library. And I guess crystal maintainers won't resist. But first we need to get positive feedback to ensure we're moving in the right direction =)

Contributors

More Repositories

1

icr

Interactive console for Crystal programming language
Crystal
500
star
2

crystal-patterns

πŸ“– Examples of GOF patterns written in Crystal
Crystal
291
star
3

jwt

JWT implementation in Crystal
Crystal
204
star
4

crystal-libraries-needed

A list of libraries that are needed or wanted for the Crystal-Language
141
star
5

msgpack-crystal

MessagePack implementation in Crystal msgpack.org[Crystal]
Crystal
133
star
6

hardware

Get CPU, Memory and Network informations of the running OS and its processes
Crystal
71
star
7

kiwi

A unified Crystal interface for key-value stores.
Crystal
61
star
8

toml.cr

TOML parser for Crystal
Crystal
58
star
9

zeromq-crystal

Crystal
46
star
10

crystal-ann

Web site to announce new Crystal projects, blog posts, updates and other work activities
CSS
45
star
11

leveldb

Crystal binding for LevelDB
Crystal
38
star
12

future.cr

Crystal
34
star
13

bloom_filter

Bloom filter implementation in Crystal lang
Crystal
34
star
14

timecop.cr

A testing library that allows "time travel," "freezing time," and "time acceleration". Inspired by the ruby-timecop library.
Crystal
19
star
15

crystal-kcov

Crystal
16
star
16

autolink.cr

πŸ”— Auto link for Crystal
Crystal
16
star
17

crystal-notifications

A library for notifications, this started as a port from ActiveSupport::Notifications
Crystal
16
star
18

bluetooth

Bluetooth Bluez binding in Crystal
Crystal
10
star
19

community

The crystal community
10
star
20

cr-config

An all-in-one configuration library to handle any possible configuration need
Crystal
9
star
21

snappy-crystal

Snappy bindings for Crystal
Crystal
6
star
22

cr-i18n

Crystal
3
star
23

kilt-components

Crystal
2
star