Songkick::Transport

This is a transport layer abstraction for talking to our service APIs. It
provides an abstract HTTP-like interface while hiding the underlying
transport and serialization details. It transparently deals with parameter
serialization, including the following:

The client supports the delete, get, head,
patch, post, put and options methods,
which all take a path and an optional Hash of parameters, for
example:

client.post('/users',:username=>'bob',:password=>'foo')

If the response is successful, meaning there are no errors caused by the
server- or client-side software or the network between them, then a
response object is returned. If the response contains data, the
object's data method exposes it as a parsed data structure.

The response's headers are exposed through the headers method,
which is an immutable hash-like object that normalizes various header
conventions.

response=client.get('/users')# These all return 'application/json'
response.headers['Content-Type']response.headers['content-type']response.headers['HTTP_CONTENT_TYPE']

If there is an error caused by our software, the request returns
nil and an error is logged. If there is an error caused by user
input, a UserError response is returned with data and
errors attributes.

Response conventions

This library was primarily developed to talk to Songkick's backend
services, and as such adopts some conventions that put it at a higher level
of abstraction than a vanilla HTTP client.

A response object has the following properties:

body – the raw response body

data – the result of parsing the body according to its
content-type

headers – a read-only hash-like object containing response headers

status – the response's status code

Only responses with status codes, 200 (OK), 201 (Created), 204 (No
Content), and 409 (Conflict) yield response objects. All other status codes
cause an exception to be raised. We use 409 to indicate user error, i.e.
input validation errors as opposed to software/infrastructure errors. The
response object is typed for the status code; the possible types are:

200: Songkick::Transport::Response::OK

201: Songkick::Transport::Response::Created

204: Songkick::Transport::Response::NoContent

409: Songkick::Transport::Response::UserError

If the request raises an exception, it will be of one of the following
types:

Songkick::Transport::UpstreamError – generic base error type

Songkick::Transport::HostResolutionError – the hostname could be
resolved using DNS

Songkick::Transport::ConnectionFailedError – a TCP connection
could not be made to the host

Songkick::Transport::TimeoutError – the request timed out before a
response could be received

Songkick::Transport::HttpError – we received a response with a
non-successful status code, e.g. 404 or 500

It is possible to customise the status codes which are treated as UserError
when initializing the client. Requests responding with any of the provided
status codes will then yield a response object with error details, rather
than raising an exception;

Note that with_headers will normalize Rack-style headers for easy
forwarding of input from the front end. For example,
HTTP_USER_AGENT is converted to User-Agent in the
outgoing request.

Similarly, the request timeout can be adjusted per-request:

client.with_timeout(10).get('/slow_resource')

Likewise basic auth credentials:

client.with_basic_auth({:username=>"foo",:password=>"bar"}).get('/')

File uploads

File uploads are handled transparently for you by the post and
put methods. If the value of any parameter (including parameters
nested inside hashes) is of type Songkick::Transport::IO, the
whole request will be treated as multipart/form-data and all the
data will be serialized for you.

Songkick::Transport::IO must be instantiated with an IO object, a
mime type, and a filename, for example:

Logging, instrumentation and reporting

The default setting (before you set Songkick::Transport.verbose =
true is that Transport will warn you about all errors, i.e. any
request that raises an exception. With verbose = true, it also
logs the details of every request made; it logs the requests using a format
you can paste into a curl command, and logs the status code, data
and duration of every response.

There may be params you don't want in your logs, and you can specify
those:

There is also a more advanced reporting system that lets you aggregate
request statistics. During a request to a web application, many requests to
backend services may be involved. The reporting system lets you collect
information about all the backend requests that happened while executing a
block. For example you can use it to create a logging middleware:

The report object is an array-like object that contains data for
all the requests made during the block's execution. Each request
responds to the following API:

endpoint – The origin the request was sent to

verb – The HTTP method of the request, e.g.
"get"

path – The requested path

params – The hash of parameters used to make the request

response – The response object the request returned

error – The exception the request raised, if any

duration – The request's duration in milliseconds

The report object itself also responds to total_duration,
which gives you the total time spent calling backend services during the
block.

To instrument transports using the `ActiveSupport::Notifications` API, pass
`=> ActiveSupport::Notifications` in the options. You
can also override the default event label of `http.songkick_transport` by
passing `:instrumentation_label`.

Writing Service classes

`Songkick::Transport::Service` is a class to make writing service clients
more convenient.

classBlahService<Songkick::Transport::Serviceendpoint"blah-service"# these global configs can also be set at the class level, in which case they
# override the global config
user_agent"myproject mainservice class"timeout10defget_datahttp.get("/stuff",:search=>"name")endend

The default transport layer for clients inheriting from
`Songkick::Transport::Service` is Curb, if you want to use something else
you can override it globally or in a class with:

License

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the “Software”),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.