{"id":13879176,"url":"https://github.com/local-ch/lhc","last_synced_at":"2025-04-04T21:09:11.533Z","repository":{"id":21560552,"uuid":"24880318","full_name":"local-ch/lhc","owner":"local-ch","description":"🚀 Advanced HTTP Client for Ruby. Fueled with interceptors.","archived":false,"fork":false,"pushed_at":"2025-03-19T10:19:34.000Z","size":706,"stargazers_count":42,"open_issues_count":9,"forks_count":1,"subscribers_count":46,"default_branch":"master","last_synced_at":"2025-03-28T20:08:57.681Z","etag":null,"topics":["http","http-client","interceptors","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/local-ch.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2014-10-07T07:09:50.000Z","updated_at":"2025-03-19T10:19:01.000Z","dependencies_parsed_at":"2025-02-28T13:14:47.018Z","dependency_job_id":"d6c01678-5acd-4d8f-b9ee-f59eeb2026ea","html_url":"https://github.com/local-ch/lhc","commit_stats":null,"previous_names":[],"tags_count":157,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/local-ch%2Flhc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/local-ch%2Flhc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/local-ch%2Flhc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/local-ch%2Flhc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/local-ch","download_url":"https://codeload.github.com/local-ch/lhc/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247249528,"owners_count":20908212,"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":["http","http-client","interceptors","ruby"],"created_at":"2024-08-06T08:02:12.300Z","updated_at":"2025-04-04T21:09:11.518Z","avatar_url":"https://github.com/local-ch.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"LHC is an extended/advanced HTTP client. Implementing basic http-communication enhancements like interceptors, exception handling, format handling, accessing response data, configuring endpoints and placeholders and fully compatible, RFC-compliant URL-template support.\n\nLHC uses [typhoeus](https://github.com/typhoeus/typhoeus) for low level http communication.\n\nSee [LHS](https://github.com/local-ch/LHS), if you are searching for something more **high level** that can query webservices easily and provides an ActiveRecord like interface.\n\n## Quick start guide\n\n```ruby\n  gem install lhc\n```\n\nor add it to your Gemfile:\n\n```ruby\n  gem 'lhc'\n```\n\nuse it like:\n\n```ruby\n  response = LHC.get('http://datastore/v2/feedbacks')\n  response.data.items[0]\n  response.data.items[0].recommended\n  response.body\n  response.headers\n```\n\n## Table of contents\n  * [Quick start guide](#quick-start-guide)\n  * [Basic methods](#basic-methods)\n  * [Request](#request)\n     * [Formats](#formats)\n        * [Default format](#default-format)\n        * [Unformatted requests](#unformatted-requests)\n           * [Upload with LHC](#upload-with-lhc)\n     * [Parallel requests](#parallel-requests)\n     * [Follow redirects](#follow-redirects)\n     * [Transfer data through the request body](#transfer-data-through-the-request-body)\n     * [Request parameters](#request-parameters)\n        * [Array Parameter Encoding](#array-parameter-encoding)\n     * [Request URL encoding](#request-url-encoding)\n     * [Request URL-Templates](#request-url-templates)\n     * [Request timeout](#request-timeout)\n     * [Request Agent](#request-agent)\n  * [Response](#response)\n     * [Accessing response data](#accessing-response-data)\n  * [Exceptions](#exceptions)\n     * [Custom error handling (rescue)](#custom-error-handling-rescue)\n     * [Ignore certain errors](#ignore-certain-errors)\n  * [Configuration](#configuration)\n     * [Configuring endpoints](#configuring-endpoints)\n     * [Configuring placeholders](#configuring-placeholders)\n     * [Configuring scrubs](#configuring-scrubs)\n  * [Interceptors](#interceptors)\n     * [Quick start: Configure/Enable Interceptors](#quick-start-configureenable-interceptors)\n     * [Interceptors on local request level](#interceptors-on-local-request-level)\n     * [Core Interceptors](#core-interceptors)\n        * [Authentication Interceptor](#authentication-interceptor)\n           * [Bearer Authentication](#bearer-authentication)\n           * [Basic Authentication](#basic-authentication)\n           * [Body Authentication](#body-authentication)\n           * [Reauthenticate](#reauthenticate)\n           * [Bearer Authentication with client access token](#bearer-authentication-with-client-access-token)\n        * [Caching Interceptor](#caching-interceptor)\n           * [Options](#options)\n        * [Default Timeout Interceptor](#default-timeout-interceptor)\n           * [Overwrite defaults](#overwrite-defaults)\n        * [Logging Interceptor](#logging-interceptor)\n           * [Installation](#installation)\n           * [What and how it logs](#what-and-how-it-logs)\n           * [Configure](#configure)\n        * [Monitoring Interceptor](#monitoring-interceptor)\n           * [Installation](#installation-1)\n           * [Environment](#environment)\n           * [What it tracks](#what-it-tracks)\n              * [Before and after request tracking](#before-and-after-request-tracking)\n              * [Response tracking](#response-tracking)\n              * [Timeout tracking](#timeout-tracking)\n              * [Caching tracking](#caching-tracking)\n           * [Configure](#configure-1)\n        * [Prometheus Interceptor](#prometheus-interceptor)\n        * [Retry Interceptor](#retry-interceptor)\n           * [Limit the amount of retries while making the request](#limit-the-amount-of-retries-while-making-the-request)\n           * [Change the default maximum of retries of the retry interceptor](#change-the-default-maximum-of-retries-of-the-retry-interceptor)\n           * [Retry all requests](#retry-all-requests)\n           * [Do not retry certain response codes](#do-not-retry-certain-response-codes)\n        * [Rollbar Interceptor](#rollbar-interceptor)\n           * [Forward additional parameters](#forward-additional-parameters)\n        * [Throttle](#throttle)\n        * [Zipkin](#zipkin)\n     * [Create an interceptor from scratch](#create-an-interceptor-from-scratch)\n        * [Interceptor callbacks](#interceptor-callbacks)\n        * [Interceptor request/response](#interceptor-requestresponse)\n        * [Provide a response replacement through an interceptor](#provide-a-response-replacement-through-an-interceptor)\n  * [Testing](#testing)\n  * [License](#license)\n\n\n\n\n\n\n## Basic methods\n\nAvailable are `get`, `post`, `put` \u0026 `delete`.\n\nOther methods are available using `LHC.request(options)`.\n\n## Request\n\nThe request class handles the http request, implements the interceptor pattern, loads configured endpoints, generates urls from url-templates and raises [exceptions](#exceptions) for any response code that is not indicating success (2xx).\n\n```ruby\n  response = LHC.request(url: 'http://local.ch', method: :options)\n\n  response.request.response #\u003cLHC::Response\u003e the associated response.\n\n  response.request.options #\u003cHash\u003e the options used for creating the request.\n\n  response.request.params # access request params\n\n  response.request.headers # access request headers\n\n  response.request.url #\u003cString\u003e URL that is used for doing the request\n\n  response.request.method #\u003cSymbol\u003e provides the used http-method\n```\n\n### Formats\n\nYou can use any of the basic methods in combination with a format like `json`:\n\n```ruby\nLHC.json.get(options)\n```\n\nCurrently supported formats: `json`, `multipart`, `plain` (for no formatting)\n\nIf formats are used, headers for `Content-Type` and `Accept` are enforced by LHC, but also http bodies are translated by LHC, so you can pass bodies as ruby objects:\n\n```ruby\nLHC.json.post('http://slack', body: { text: 'Hi there' })\n# Content-Type: application/json\n# Accept: application/json\n# Translates body to \"{\\\"text\\\":\\\"Hi there\\\"}\" before sending\n```\n\n#### Default format\n\nIf you use LHC's basic methods `LHC.get`, `LHC.post` etc. without any explicit format, `JSON` will be chosen as the default format.\n\n#### Unformatted requests\n\nIn case you need to send requests without LHC formatting headers or the body, use `plain`:\n\n```ruby\nLHC.plain.post('http://endpoint', body: { weird: 'format%s2xX' })\n```\n\n##### Upload with LHC\n\nIf you want to upload data with LHC, it's recommended to use the `multipart` format:\n\n```ruby\nresponse = LHC.multipart.post('http://upload', body: { file })\nresponse.headers['Location']\n# Content-Type: multipart/form-data\n# Leaves body unformatted\n```\n\n### Parallel requests\n\nIf you pass an array of requests to `LHC.request`, it will perform those requests in parallel.\nYou will get back an array of LHC::Response objects in the same order of the passed requests.\n\n```ruby\n  options = []\n  options \u003c\u003c { url: 'http://datastore/v2/feedbacks' }\n  options \u003c\u003c { url: 'http://datastore/v2/content-ads/123/feedbacks' }\n  responses = LHC.request(options)\n```\n\n```ruby\nLHC.request([request1, request2, request3])\n# returns [response1, response2, response3]\n```\n\n### Follow redirects\n\n```ruby\nLHC.get('http://local.ch', followlocation: true)\n```\n\n### Transfer data through the request body\n\nData that is transfered using the HTTP request body is transfered using the selected format, or the default `json`, so you need to provide it as a ruby object.\n\nAlso consider setting the http header for content-type or use one of the provided [formats](#formats), like `LHC.json`.\n\n```ruby\n  LHC.post('http://datastore/v2/feedbacks',\n    body: feedback,\n    headers: { 'Content-Type' =\u003e 'application/json' }\n  )\n```\n\n### Request parameters\n\nWhen using LHC, try to pass params via `params` option. It's not recommended to build a url and attach the parameters yourself:\n\nDO\n```ruby\nLHC.get('http://local.ch', params: { q: 'Restaurant' })\n```\n\nDON'T\n```ruby\nLHC.get('http://local.ch?q=Restaurant')\n```\n\n#### Array Parameter Encoding\n\nLHC can encode array parameters in URLs in two ways. The default is `:rack` which generates URL parameters compatible with Rack and Rails.\n\n```ruby\nLHC.get('http://local.ch', params: { q: [1, 2] })\n# http://local.ch?q[]=1\u0026q[]=2\n```\n\nSome Java-based apps expect their arrays in the `:multi` format:\n\n```ruby\nLHC.get('http://local.ch', params: { q: [1, 2] }, params_encoding: :multi)\n# http://local.ch?q=1\u0026q=2\n```\n\n### Request URL encoding\n\nLHC, by default, encodes urls:\n\n```ruby\nLHC.get('http://local.ch?q=some space')\n# http://local.ch?q=some%20space\n\nLHC.get('http://local.ch', params: { q: 'some space' })\n# http://local.ch?q=some%20space\n```\n\nwhich can be disabled:\n\n```ruby\nLHC.get('http://local.ch?q=some space', url_encoding: false)\n# http://local.ch?q=some space\n```\n\n### Request URL-Templates\n\nInstead of using concrete urls you can also use url-templates that contain placeholders.\nThis is especially handy for configuring an endpoint once and generate the url from the params when doing the request.\nSince version `7.0` url templates follow the [RFC 6750](https://tools.ietf.org/html/rfc6570).\n\n```ruby\n  LHC.get('http://datastore/v2/feedbacks/{id}', params:{ id: 123 })\n  # GET http://datastore/v2/feedbacks/123\n```\n\nYou can also use URL templates, when [configuring endpoints](#configuring-endpoints):\n```ruby\n  LHC.configure do |c|\n    c.endpoint(:find_feedback, 'http://datastore/v2/feedbacks/{id}')\n  end\n\n  LHC.get(:find_feedback, params:{ id: 123 }) # GET http://datastore/v2/feedbacks/123\n```\n\nIf you miss to provide a parameter that is part of the url-template, it will raise an exception.\n\n### Request timeout\n\nWorking and configuring timeouts is important, to ensure your app stays alive when services you depend on start to get really slow...\n\nLHC forwards two timeout options directly to typhoeus:\n\n`timeout` (in seconds) - The maximum time in seconds that you allow the libcurl transfer operation to take. Normally, name lookups can take a considerable time and limiting operations to less than a few seconds risk aborting perfectly normal operations. This option may cause libcurl to use the SIGALRM signal to timeout system calls.\n`connecttimeout` (in seconds) - It should contain the maximum time in seconds that you allow the connection phase to the server to take. This only limits the connection phase, it has no impact once it has connected. Set to zero to switch to the default built-in connection timeout - 300 seconds.\n\n```ruby\nLHC.get('http://local.ch', timeout: 5, connecttimeout: 1)\n```\n\nLHC provides a [timeout interceptor](#default-timeout-interceptor) that lets you apply default timeout values to all the requests that you are performig in your application.\n\n### Request Agent\n\nLHC identifies itself towards outher services, using the `User-Agent` header.\n\n```\nUser-Agent LHC (9.4.2) [https://github.com/local-ch/lhc]\n```\n\nIf LHC is used in an Rails Application context, also the application name is added to the `User-Agent` header.\n\n```\nUser-Agent LHC (9.4.2; MyRailsApplicationName) [https://github.com/local-ch/lhc]\n```\n\n## Response\n\n```ruby\n  response.request #\u003cLHC::Request\u003e the associated request.\n\n  response.data #\u003cOpenStruct\u003e in case response body contains parsable JSON.\n  response.data.something.nested\n\n  response.body #\u003cString\u003e\n\n  response.code #\u003cFixnum\u003e\n\n  response.headers #\u003cHash\u003e\n\n  response.time #\u003cFixnum\u003e Provides response time in ms.\n\n  response.timeout? #true|false\n```\n\n### Accessing response data\n\nThe response data can be access with dot-notation and square-bracket notation. You can convert response data to open structs or json (if the response format is json).\n\n```ruby\n  response = LHC.request(url: 'http://datastore/entry/1')\n  response.data.as_open_struct #\u003cOpenStruct name='local.ch'\u003e\n  response.data.as_json # { name: 'local.ch' }\n  response.data.name # 'local.ch'\n  response.data[:name] # 'local.ch'\n```\n\nYou can also access response data directly through the response object (with square bracket notation only):\n\n```ruby\n  LHC.json.get(url: 'http://datastore/entry/1')[:name]\n```\n\n## Exceptions\n\nAnything but a response code indicating success (2xx) raises an exception.\n\n```ruby\n\n  LHC.get('localhost') # UnknownError: 0\n  LHC.get('http://localhost:3000') # LHC::Timeout: 0\n\n```\n\nYou can access the response object that was causing the error.\n\n```ruby\nLHC.get('local.ch')\nrescue =\u003e e\n  e.response #\u003cLHC:Response\u003e\n  e.response.code # 403\n  e.response.timeout? # false\n  Rails.logger.error e\n  # LHC::UnknownError: get http://local.cac\n  # Params: {:url=\u003e\"http://local.cac\", :method=\u003e:get}\n  # Response Code: 0\n  # \u003cResponse Body\u003e\n```\n\nAll errors that are raise by LHC inherit from `LHC::Error`.\nThey are divided into `LHC::ClientError`, `LHC::ServerError`, `LHC::Timeout` and `LHC::UnkownError` and mapped according to the following status code.\n\n```ruby\n400 =\u003e LHC::BadRequest\n401 =\u003e LHC::Unauthorized\n402 =\u003e LHC::PaymentRequired\n403 =\u003e LHC::Forbidden\n403 =\u003e LHC::Forbidden\n404 =\u003e LHC::NotFound\n405 =\u003e LHC::MethodNotAllowed\n406 =\u003e LHC::NotAcceptable\n407 =\u003e LHC::ProxyAuthenticationRequired\n408 =\u003e LHC::RequestTimeout\n409 =\u003e LHC::Conflict\n410 =\u003e LHC::Gone\n411 =\u003e LHC::LengthRequired\n412 =\u003e LHC::PreconditionFailed\n413 =\u003e LHC::RequestEntityTooLarge\n414 =\u003e LHC::RequestUriToLong\n415 =\u003e LHC::UnsupportedMediaType\n416 =\u003e LHC::RequestedRangeNotSatisfiable\n417 =\u003e LHC::ExpectationFailed\n422 =\u003e LHC::UnprocessableEntity\n423 =\u003e LHC::Locked\n424 =\u003e LHC::FailedDependency\n426 =\u003e LHC::UpgradeRequired\n\n500 =\u003e LHC::InternalServerError\n501 =\u003e LHC::NotImplemented\n502 =\u003e LHC::BadGateway\n503 =\u003e LHC::ServiceUnavailable\n504 =\u003e LHC::GatewayTimeout\n505 =\u003e LHC::HttpVersionNotSupported\n507 =\u003e LHC::InsufficientStorage\n510 =\u003e LHC::NotExtended\n\ntimeout? =\u003e LHC::Timeout\n\nanything_else =\u003e LHC::UnknownError\n```\n\n### Custom error handling (rescue)\n\nYou can provide custom error handlers to handle errors happening during the request.\n\nIf a error handler is provided nothing is raised.\n\nIf your error handler returns anything else but `nil` it replaces the response body.\n\n```ruby\nhandler = -\u003e(response){ do_something_with_response; return {name: 'unknown'} }\nresponse = LHC.get('http://something', rescue: handler)\nresponse.data.name # 'unknown'\n```\n\n### Ignore certain errors\n\nAs it's discouraged to rescue errors and then don't handle them (ruby styleguide)[https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions],\nbut you often want to continue working with `nil`, LHC provides the `ignore` option.\n\nErrors listed in this option will not be raised and will leave the `response.body` and `response.data` to stay `nil`.\n\nYou can either pass the LHC error class you want to be ignored or an array of LHC error classes.\n\n```ruby\nresponse = LHC.get('http://something', ignore: LHC::NotFound)\n\nresponse.body # nil\nresponse.data # nil\nresponse.error_ignored? # true\nresponse.request.error_ignored? # true\n```\n\n## Configuration\n\nIf you want to configure LHC, do it on initialization (like in a Rails initializer, `environment.rb` or `application.rb`), otherwise you could run into the problem that certain configurations can only be set once.\n\nYou can use `LHC.configure` to prevent the initialization problem.\nTake care that you only use `LHC.configure` once, because it is actually reseting previously made configurations and applies the new once.\n\n```ruby\n\n  LHC.configure do |c|\n    c.placeholder :datastore, 'http://datastore'\n    c.endpoint :feedbacks, '{+datastore}/feedbacks', params: { has_reviews: true }\n    c.interceptors = [CachingInterceptor, MonitorInterceptor, TrackingIdInterceptor]\n  end\n\n```\n\n### Configuring endpoints\n\nYou can configure endpoints, for later use, by giving them a name, a url and some parameters (optional).\n\n```ruby\n  LHC.configure do |c|\n    c.endpoint(:feedbacks, 'http://datastore/v2/feedbacks', params: { has_reviews: true })\n    c.endpoint(:find_feedback, 'http://datastore/v2/feedbacks/{id}')\n  end\n\n  LHC.get(:feedbacks) # GET http://datastore/v2/feedbacks\n  LHC.get(:find_feedback, params:{ id: 123 }) # GET http://datastore/v2/feedbacks/123\n```\n\nExplicit request options override configured options.\n\n```ruby\n  LHC.get(:feedbacks, params: { has_reviews: false }) # Overrides configured params\n```\n\n### Configuring placeholders\n\nYou can configure global placeholders, that are used when generating urls from url-templates.\n\n```ruby\n  LHC.configure do |c|\n    c.placeholder(:datastore, 'http://datastore')\n    c.endpoint(:feedbacks, '{+datastore}/feedbacks', { params: { has_reviews: true } })\n  end\n\n  LHC.get(:feedbacks) # http://datastore/v2/feedbacks\n```\n\n### Configuring scrubs\n\nYou can filter out sensitive request data from your log files and rollbar by appending them to `LHS.config.scrubs`. These values will be marked `[FILTERED]` in the log and on rollbar. Also nested parameters are being filtered.\nThe scrubbing configuration affects all request done by LHC independent of the endpoint. You can scrub any attribute within `:params`, `:headers` or `:body`. For `:auth` you either can choose `:bearer` or `:basic` (default is both).\n\nLHS scrubs per default:\n- Bearer Token within the Request Header\n- Basic Auth `username` and `password` within the Request Header\n- `password` and `password_confirmation` within the Request Body\n\nEnhance the default scrubbing by pushing the name of the parameter, which should be scrubbed, as string to the existing configuration.\nYou can also add multiple parameters at once by pushing multiple strings.\n\nExample:\n```ruby\n  LHC.configure do |c|\n    c.scrubs[:params] \u003c\u003c 'api_key'\n    c.scrubs[:body].push('user_token', 'secret_key')\n  end\n```\n\nFor disabling scrubbing, add following configuration:\n```ruby\n  LHC.configure do |c|\n    c.scrubs = {}\n  end\n```\n\nIf you want to turn off `:bearer` or `:basic` scrubbing, then just overwrite the `:auth` configuration.\n\nExample:\n```ruby\n  LHC.configure do |c|\n    c.scrubs[:auth] = [:bearer]\n  end\n```\n\nIf your app has a different authentication strategy than Bearer Authentication or Basic Authentication then you can filter the data by scrubbing the whole header:\n```ruby\n  LHC.configure do |c|\n    c.scrubs[:headers] \u003c\u003c 'Authorization'\n  end\n```\n\n## Interceptors\n\nTo monitor and manipulate the HTTP communication done with LHC, you can define interceptors that follow the (Inteceptor Pattern)[https://en.wikipedia.org/wiki/Interceptor_pattern].\nThere are some interceptors that are part of LHC already, so called [Core Interceptors](#core-interceptors), that cover some basic usecases.\n\n### Quick start: Configure/Enable Interceptors\n\n```ruby\nLHC.configure do |c|\n  c.interceptors = [LHC::Auth, LHC::Caching, LHC::DefaultTimeout, LHC::Logging, LHC::Monitoring, LHC::Prometheus, LHC::Retry, LHC::Rollbar, LHC::Zipkin]\nend\n```\n\nYou can only set the list of global interceptors once and you can not alter it after you set it.\n\n### Interceptors on local request level\n\nYou can override the global list of interceptors on local request level:\n\n```ruby\n  interceptors = LHC.config.interceptors\n  interceptors -= [LHC::Caching] # remove caching\n  interceptors += [LHC::Retry] # add retry\n  LHC.request({url: 'http://local.ch', retry: 2, interceptors: interceptors})\n\n  LHC.request({url: 'http://local.ch', interceptors: []}) # no interceptor for this request at all\n```\n\n### Core Interceptors\n\n#### Authentication Interceptor\n\nAdd the auth interceptor to your basic set of LHC interceptors.\n\n```ruby\n  LHC.configure do |c|\n    c.interceptors = [LHC::Auth]\n  end\n```\n\n##### Bearer Authentication\n\n```ruby\n  LHC.get('http://local.ch', auth: { bearer: -\u003e { access_token } })\n```\n\nAdds the following header to the request:\n```\n  'Authorization': 'Bearer 123456'\n```\n\nAssuming the method `access_token` responds on runtime of the request with `123456`.\n\n##### Basic Authentication\n\n```ruby\n  LHC.get('http://local.ch', auth: { basic: { username: 'steve', password: 'can' } })\n```\n\nAdds the following header to the request:\n```\n  'Authorization': 'Basic c3RldmU6Y2Fu'\n```\n\nWhich is the base64 encoded credentials \"username:password\".\n\n##### Body Authentication\n\n```ruby\n  LHC.post('http://local.ch', auth: { body: { userToken: 'dheur5hrk3' } })\n```\n\nAdds the following to body of all requests:\n\n```\n  {\n    \"userToken\": \"dheur5hrk3\"\n  }\n```\n\n##### Reauthenticate\n\nThe current implementation offers only reauthentication for _client access tokens_.\nMake sure that your interceptors contain `LHC::Auth` and `LHC::Retry`, whereas `LHC::Retry` comes _after_ `LHC::Auth` in the chain.\nProvide the refresh token as following:\n```ruby\nLHC.get('http://local.ch', auth: { bearer: -\u003e { access_token }, refresh_client_token: -\u003e { TokenRefreshUtil.client_access_token(true) })\n```\nWhere `TokenRefreshUtil.client_access_token(true)` forces a refresh of the token and returns the new token. It is also expected that this implementation will handle invalidating caches if necessary.\n\nYou can also set a global `refresh_client_token`. This is not recommended for apps with multiple endpoint and different access tokens.\n```ruby\nLHC::Auth.refresh_client_token = -\u003e { TokenRefreshUtil.client_access_token(true) }\n```\n\nReauthentication will be initiated if:\n\n* setup is correct\n* `response.success?` is false and an `LHC::Unauthorized` was observed\n* reauthentication wasn't already attempted once\n\nIf this is the case, this happens:\n\n* refresh the client token, by calling `refresh_client_token`\n* the authentication header will be updated with the new token\n* `LHC::Retry` will be triggered by adding `retry: { max: 1 }` to the request options\n\n#### Caching Interceptor\n\nAdd the cache interceptor to your basic set of LHC interceptors.\n\n```ruby\n  LHC.configure do |c|\n    c.interceptors = [LHC::Caching]\n  end\n```\n\nYou can configure your own cache (default Rails.cache) and logger (default Rails.logger):\n\n```ruby\n  LHC::Caching.cache = ActiveSupport::Cache::MemoryStore.new\n```\n\nCaching is not enabled by default, although you added it to your basic set of interceptors.\nIf you want to have requests served/stored and stored in/from cache, you have to enable it by request.\n\n```ruby\n  LHC.get('http://local.ch', cache: true)\n```\n\nYou can also enable caching when configuring an endpoint in LHS.\n\n```ruby\n  class Feedbacks \u003c LHS::Service\n    endpoint '{+datastore}/v2/feedbacks', cache: true\n  end\n```\n\nOnly GET requests are cached by default. If you want to cache any other request method, just configure it:\n\n```ruby\n  LHC.get('http://local.ch', cache: { methods: [:get] })\n```\n\nResponses served from cache are marked as served from cache:\n\n```ruby\n  response = LHC.get('http://local.ch', cache: true)\n  response.from_cache? # true\n```\n\nYou can also use a central http cache to be used by the `LHC::Caching` interceptor.\n\nIf you configure a local and a central cache, LHC will perform multi-level-caching.\nLHC will try to retrieve cached information first from the central, in case of a miss from the local cache, while writing back into both.\n\n```ruby\n  LHC::Caching.central = {\n    read: 'redis://$PASSWORD@central-http-cache-replica.namespace:6379/0',\n    write: 'redis://$PASSWORD@central-http-cache-master.namespace:6379/0'\n  }\n```\n\n##### Options\n\n```ruby\n  LHC.get('http://local.ch', cache: { key: 'key' expires_in: 1.day, race_condition_ttl: 15.seconds, use: ActiveSupport::Cache::MemoryStore.new })\n```\n\n`expires_in` - lets the cache expires every X seconds.\n\n`key` - Set the key that is used for caching by using the option. Every key is prefixed with `LHC_CACHE(v1): `.\n\n`race_condition_ttl` - very useful in situations where a cache entry is used very frequently and is under heavy load.\nIf a cache expires and due to heavy load several different processes will try to read data natively and then they all will try to write to cache.\nTo avoid that case the first process to find an expired cache entry will bump the cache expiration time by the value set in `race_condition_ttl`.\n\n`use` - Set an explicit cache to be used for this request. If this option is missing `LHC::Caching.cache` is used.\n\n#### Default Timeout Interceptor\n\nApplies default timeout values to all requests made in an application, that uses LHC.\n\n```ruby\n  LHC.configure do |c|\n    c.interceptors = [LHC::DefaultTimeout]\n  end\n```\n\n`timeout` default: 15 seconds\n`connecttimeout` default: 2 seconds\n\n##### Overwrite defaults\n\n```ruby\nLHC::DefaultTimeout.timeout = 5 # seconds\nLHC::DefaultTimeout.connecttimeout = 3 # seconds\n```\n\n#### Logging Interceptor\n\nThe logging interceptor logs all requests done with LHC to the Rails logs.\n\n##### Installation\n\n```ruby\n  LHC.configure do |c|\n    c.interceptors = [LHC::Logging]\n  end\n\n  LHC::Logging.logger = Rails.logger\n```\n\n##### What and how it logs\n\nThe logging Interceptor logs basic information about the request and the response:\n\n```ruby\nLHC.get('http://local.ch')\n# Before LHC request\u003c70128730317500\u003e GET http://local.ch at 2018-05-23T07:53:19+02:00 Params={} Headers={\\\"User-Agent\\\"=\u003e\\\"Typhoeus - https://github.com/typhoeus/typhoeus\\\", \\\"Expect\\\"=\u003e\\\"\\\"}\n# After LHC response for request\u003c70128730317500\u003e: GET http://local.ch at 2018-05-23T07:53:28+02:00 Time=0ms URL=http://local.ch:80/\n```\n\n##### Configure\n\nYou can configure the logger beeing used by the logging interceptor:\n\n```ruby\nLHC::Logging.logger = Another::Logger\n```\n\n#### Monitoring Interceptor\n\nThe monitoring interceptor reports all requests done with LHC to a given StatsD instance.\n\n##### Installation\n\n```ruby\n  LHC.configure do |c|\n    c.interceptors = [LHC::Monitoring]\n  end\n```\n\nYou also have to configure statsd in order to have the monitoring interceptor report.\n\n```ruby\n  LHC::Monitoring.statsd = \u003cyour-instance-of-statsd\u003e\n```\n\n##### Environment\n\nBy default, the monitoring interceptor uses Rails.env to determine the environment. In case you want to configure that, use:\n\n```ruby\nLHC::Monitoring.env = ENV['DEPLOYMENT_TYPE'] || Rails.env\n```\n\n##### What it tracks\n\nIt tracks request attempts with `before_request` and `after_request` (counts).\n\nIn case your workers/processes are getting killed due limited time constraints,\nyou are able to detect deltas with relying on \"before_request\", and \"after_request\" counts:\n\n###### Before and after request tracking\n\n```ruby\n  \"lhc.\u003capp_name\u003e.\u003cenv\u003e.\u003chost\u003e.\u003chttp_method\u003e.before_request\", 1\n  \"lhc.\u003capp_name\u003e.\u003cenv\u003e.\u003chost\u003e.\u003chttp_method\u003e.after_request\", 1\n```\n\n###### Response tracking\n\nIn case of a successful response it reports the response code with a count and the response time with a gauge value.\n\n```ruby\n  LHC.get('http://local.ch')\n\n  \"lhc.\u003capp_name\u003e.\u003cenv\u003e.\u003chost\u003e.\u003chttp_method\u003e.count\", 1\n  \"lhc.\u003capp_name\u003e.\u003cenv\u003e.\u003chost\u003e.\u003chttp_method\u003e.200\", 1\n  \"lhc.\u003capp_name\u003e.\u003cenv\u003e.\u003chost\u003e.\u003chttp_method\u003e.time\", 43\n```\n\nIn case of a unsuccessful response it reports the response code with a count but no time:\n\n```ruby\n  LHC.get('http://local.ch')\n\n  \"lhc.\u003capp_name\u003e.\u003cenv\u003e.\u003chost\u003e.\u003chttp_method\u003e.count\", 1\n  \"lhc.\u003capp_name\u003e.\u003cenv\u003e.\u003chost\u003e.\u003chttp_method\u003e.500\", 1\n```\n\n###### Timeout tracking\n\nTimeouts are also reported:\n\n```ruby\n  \"lhc.\u003capp_name\u003e.\u003cenv\u003e.\u003chost\u003e.\u003chttp_method\u003e.timeout\", 1\n```\n\nAll the dots in the host are getting replaced with underscore, because dot is the default separator in graphite.\n\n###### Caching tracking\n\nWhen you want to track caching stats please make sure you have enabled the `LHC::Caching` and the `LHC::Monitoring` interceptor.\n\nMake sure that the `LHC::Caching` is listed before `LHC::Monitoring` interceptor when configuring interceptors:\n\n```ruby\nLHC.configure do |c|\n  c.interceptors = [LHC::Caching, LHC::Monitoring]\nend\n```\n\nIf a response was served from cache it tracks:\n\n```ruby\n  \"lhc.\u003capp_name\u003e.\u003cenv\u003e.\u003chost\u003e.\u003chttp_method\u003e.cache.hit\", 1\n```\n\nIf a response was not served from cache it tracks:\n\n```ruby\n  \"lhc.\u003capp_name\u003e.\u003cenv\u003e.\u003chost\u003e.\u003chttp_method\u003e.cache.miss\", 1\n```\n\n##### Configure\n\nIt is possible to set the key for Monitoring Interceptor on per request basis:\n\n```ruby\n  LHC.get('http://local.ch', monitoring_key: 'local_website')\n\n  \"local_website.count\", 1\n  \"local_website.200\", 1\n  \"local_website.time\", 43\n```\n\nIf you use this approach you need to add all namespaces (app, environment etc.) to the key on your own.\n\n\n#### Prometheus Interceptor\n\nLogs basic request/response information to prometheus.\n\n```ruby\n  require 'prometheus/client'\n\n  LHC.configure do |c|\n    c.interceptors = [LHC::Prometheus]\n  end\n\n  LHC::Prometheus.client = Prometheus::Client\n  LHC::Prometheus.namespace = 'web_location_app'\n```\n\n```ruby\n  LHC.get('http://local.ch')\n```\n\n- Creates a prometheus counter that receives additional meta information for: `:code`, `:success` and `:timeout`.\n\n- Creates a prometheus histogram for response times in milliseconds.\n\n\n#### Retry Interceptor\n\nIf you enable the retry interceptor, you can have LHC retry requests for you:\n\n```ruby\n  LHC.configure do |c|\n    c.interceptors = [LHC::Retry]\n  end\n\n  response = LHC.get('http://local.ch', retry: true)\n```\n\nIt will try to retry the request up to 3 times (default) internally, before it passes the last response back, or raises an error for the last response.\n\nConsider, that all other interceptors will run for every single retry.\n\n##### Limit the amount of retries while making the request\n\n```ruby\n  LHC.get('http://local.ch', retry: { max: 1 })\n```\n\n##### Change the default maximum of retries of the retry interceptor\n\n```ruby\n  LHC::Retry.max = 3\n```\n\n##### Retry all requests\n\nIf you want to retry all requests made from your application, you just need to configure it globally:\n\n```ruby\n  LHC::Retry.all = true\n  configuration.interceptors = [LHC::Retry]\n```\n\n##### Do not retry certain response codes\n\nIf you do not want to retry based on certain response codes, use retry in combination with explicit `ignore`:\n\n```ruby\n  LHC.get('http://local.ch', ignore: LHC::NotFound, retry: { max: 1 })\n```\n\nOr if you use `LHC::Retry.all`:\n\n```ruby\nLHC.get('http://local.ch', ignore: LHC::NotFound)\n```\n\n#### Rollbar Interceptor\n\nForward errors to rollbar when exceptions occur during http requests.\n\n```ruby\n  LHC.configure do |c|\n    c.interceptors = [LHC::Rollbar]\n  end\n```\n\n```ruby\n  LHC.get('http://local.ch')\n```\n\nIf it raises, it forwards the request and response object to rollbar, which contain all necessary data.\n\n##### Forward additional parameters\n\n```ruby\n  LHC.get('http://local.ch', rollbar: { tracking_key: 'this particular request' })\n```\n\n#### Throttle\n\nThe throttle interceptor allows you to raise an exception if a predefined quota of a provider request limit is reached in advance.\n\n```ruby\n  LHC.configure do |c|\n    c.interceptors = [LHC::Throttle]\n  end\n```\n```ruby\noptions = {\n  throttle: {\n    track: true,\n    break: '80%',\n    provider: 'local.ch',\n    limit: { header: 'Rate-Limit-Limit' },\n    remaining: { header: 'Rate-Limit-Remaining' },\n    expires: { header: 'Rate-Limit-Reset' }\n  }\n}\n\nLHC.get('http://local.ch', options)\n# { headers: { 'Rate-Limit-Limit' =\u003e 100, 'Rate-Limit-Remaining' =\u003e 19 } }\n\nLHC.get('http://local.ch', options)\n# raises LHC::Throttle::OutOfQuota: Reached predefined quota for local.ch\n```\n\n**Options Description**\n* `track`: enables tracking of current limit/remaining requests of rate-limiting\n* `break`: quota in percent after which errors are raised. Percentage symbol is optional, values will be converted to integer (e.g. '23.5' will become 23)\n* `provider`: name of the provider under which throttling tracking is aggregated,\n* `limit`:\n  * a hard-coded integer\n  * a hash pointing at the response header containing the limit value\n  * a proc that receives the response as argument and returns the limit value\n* `remaining`:\n  * a hash pointing at the response header containing the current amount of remaining requests\n  * a proc that receives the response as argument and returns the current amount of remaining requests\n* `expires`:\n  * a hash pointing at the response header containing the timestamp when the quota will reset\n  * a proc that receives the response as argument and returns the timestamp when the quota will reset\n\n\n#### Zipkin\n\n** Zipkin 0.33 breaks our current implementation of the Zipkin interceptor **\n\nZipkin is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in microservice architectures [Zipkin Distributed Tracing](https://zipkin.io/).\n\nAdd the zipkin interceptor to your basic set of LHC interceptors.\n\n```ruby\n  LHC.configure do |c|\n    c.interceptors = [LHC::Zipkin]\n  end\n```\n\nThe following configuration needs to happen in the application that wants to run this interceptor:\n\n1. Add `gem 'zipkin-tracer', '\u003c 0.33.0'` to your Gemfile.\n2. Add the necessary Rack middleware and configuration\n\n```ruby\nconfig.middleware.use ZipkinTracer::RackHandler, {\n  service_name: 'service-name', # name your service will be known as in zipkin\n  service_port: 80, # the port information that is sent along the trace\n  json_api_host: 'http://zipkin-collector', # the zipkin endpoint\n  sample_rate: 1 # sample rate, where 1 = 100% of all requests, and 0.1 is 10% of all requests\n}\n```\n\n### Create an interceptor from scratch\n\n```ruby\n  class TrackingIdInterceptor \u003c LHC::Interceptor\n\n    def before_request\n      request.params[:tid] = 123\n    end\n  end\n```\n\n```ruby\n  LHC.configure do |c|\n    c.interceptors = [TrackingIdInterceptor]\n  end\n```\n\n#### Interceptor callbacks\n\n`before_raw_request` is called before the raw typhoeus request is prepared/created.\n\n`before_request` is called when the request is prepared and about to be executed.\n\n`after_request` is called after request was started.\n\n`before_response` is called when response started to arrive.\n\n`after_response` is called after the response arrived completely.\n\n\n#### Interceptor request/response\n\nEvery interceptor can directly access their instance [request](#request) or [response](#response).\n\n#### Provide a response replacement through an interceptor\n\nInside an interceptor, you are able to provide a response, rather then doing a real request.\nThis is useful for implementing e.g. caching.\n\n```ruby\nclass LHC::Cache \u003c LHC::Interceptor\n\n  def before_request(request)\n    cached_response = Rails.cache.fetch(request.url)\n    return LHC::Response.new(cached_response) if cached_response\n  end\nend\n```\n\nTake care that having more than one interceptor trying to return a response will cause an exception.\nYou can access the request.response to identify if a response was already provided by another interceptor.\n\n```ruby\n  class RemoteCacheInterceptor \u003c LHC::Interceptor\n\n    def before_request(request)\n      return unless request.response.nil?\n      return LHC::Response.new(remote_cache)\n    end\n  end\n```\n\n## Testing\n\nWhen writing tests for your application when using LHC, please make sure you require the lhc rspec test helper:\n\n```ruby\n# spec/spec_helper.rb\n\nrequire 'lhc/rspec'\n```\n\n## License\n\n[GNU General Public License Version 3.](https://www.gnu.org/licenses/gpl-3.0.en.html)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flocal-ch%2Flhc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flocal-ch%2Flhc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flocal-ch%2Flhc/lists"}