{"id":21497085,"url":"https://github.com/financial-times/resilient-jersey-wrapper","last_synced_at":"2025-07-15T19:33:22.938Z","repository":{"id":9648776,"uuid":"62887979","full_name":"Financial-Times/resilient-jersey-wrapper","owner":"Financial-Times","description":"Resilient jersey wrapper","archived":false,"fork":false,"pushed_at":"2024-04-30T09:50:51.000Z","size":164,"stargazers_count":3,"open_issues_count":32,"forks_count":1,"subscribers_count":28,"default_branch":"master","last_synced_at":"2024-05-01T11:58:34.564Z","etag":null,"topics":["universal-publishing"],"latest_commit_sha":null,"homepage":null,"language":"Java","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/Financial-Times.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":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-07-08T12:44:14.000Z","updated_at":"2024-05-01T11:58:34.565Z","dependencies_parsed_at":"2024-04-14T15:57:01.548Z","dependency_job_id":"0a43b6a3-ab0a-4b71-8c2b-aa8db4f46f04","html_url":"https://github.com/Financial-Times/resilient-jersey-wrapper","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Financial-Times%2Fresilient-jersey-wrapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Financial-Times%2Fresilient-jersey-wrapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Financial-Times%2Fresilient-jersey-wrapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Financial-Times%2Fresilient-jersey-wrapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Financial-Times","download_url":"https://codeload.github.com/Financial-Times/resilient-jersey-wrapper/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226065657,"owners_count":17568247,"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":["universal-publishing"],"created_at":"2024-11-23T16:21:26.617Z","updated_at":"2024-11-23T16:21:27.197Z","avatar_url":"https://github.com/Financial-Times.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Resilient Client\n\n[![Circle CI](https://circleci.com/gh/Financial-Times/resilient-jersey-wrapper/tree/master.png?style=shield)](https://circleci.com/gh/Financial-Times/resilient-jersey-wrapper/tree/master)\n\nAn implementation of the Jersey HTTP client API using the supplied Apache HttpClient implementation libraries.\n\nThis client adds the following resilience features to the standard feature set:\n\n* failover of requests to nodes within a cluster\n* random assignment of load to configured nodes (load balancing)\n* configuration of available nodes (XOR import from DNS)\n* control over the degree and timing of continued retry attempts\n* extension points for these features\n\nBy default the client balances requests across nodes in a primary, followed by a secondary group. Requests are\nretried when it is safe to do so and the default policy is to retry immediately on the next available host until all\nhosts have been tried once.\n\nAn exponential back-off policy is available which permits for retry counts different from the number of available nodes.\n\n# Static Endpoint Configuration\n\n        Client client = ResilientClientBuilder.in(environment)\n                        .using(configuration.getVarnish())\n                        .build();:\n        shortName: \"methode\"\n        jerseyClient:\n            timeout: 5000ms\n        primaryNodes: [\"localhost:9080:9081\", \"localhost:9080:9081\"]\n        secondaryNodes: null\n\nThe YAML above can be read into an EndpointConfiguration object by the Jackson YAML parser (or automatically by DropWizard)\n\n    Client client = ResilientClientBuilder.in(environment)\n                    .using(configuration.getEndpointConfiguration())\n                    .build();\n\nIf you attempt to access a host that is not listed in the primary or secondary nodes collection then yu will get an\nerror as the configuration cannot blend load balancing approaches.\n\n## Using one DNS entry per datacentre\n\nIf the hostnames listed in your EndpointConfiguration correspond to clusters of hosts (e.g. in two or more data centres)\nthen you can round off the configuration by having the client resolve all the additional host IP addresses from DNS:\n\n     endpointConfiguration:\n         shortName: \"methode\"\n         jerseyClient:\n             timeout: 5000ms\n         primaryNodes: [\"dc1.example.com:9080:9081\", \"dc2.example.com:9080:9081\"]\n         secondaryNodes: [\"dc2.example.com:9080:9081\"]\n         resilienceStrategy: LOAD_BALANCED_IP_STRATEGY\n\nThis will add the IP addresses to the pool of nodes before load balancing randomly.\n\n# Dynamic DNS Driven configuration\n\n    Client client = ResilientClientBuilder.in(environment)\n                    .usingDNS()\n                    .named(\"name\")\n                    .build()\n\nThis sets up the client without any fixed connection. The nodes are produced by resolving the DNS entry for whatever host\nis requested via the Jersey API.\n\n# Exponential Backoff and Retry\n\nContinuationPolicy objects encapsulate workflow logic that controls whether and when the transaction continues to be\nattempted. In this example, the client continues to retry 5 times with a short initial delay.\n\n    ContinuationPolicy fiveQuickAttempts =  new ExponentialBackoffContinuationPolicy(5,500);\n\n    Client client = ResilientClientBuilder.in(environment).usingDNS()\n                .named(\"name\")\n                .withContinuationPolicy(fiveQuickAttempts)\n                .build();\n\nThe 500 millisecond delay results in the following schedule of attempts.\n\n1. Immediate\n1. after 500 milliseconds\n1. 1500 milliseconds\n1. 3500 milliseconds\n1. 7500 milliseconds\n\nThe first attempt is immediate in every case, the formula used for the remaining intervals is (in [Tex](http://bit.ly/1v4Biff)):\n\n    interval = initialDelay*2^{attempt-1}\n\nIn the table above, the interval is added to the total elapsed time. In real life, the schedule will be delayed by the\ncumulative time taken for attempts to fail.\n\n# MDC -\u003e User-Agent transaction ID forwarding\n\nAccess logs often make no accommodation for transaction_id, so Resilient Client, by default, encodes a `transaction_id` into the\n`User-Agent` header.\n\nYou can pull this back out again using the Splunk `rex` command:\n\n    source=/var/log*transformer*dw-access.log useragent=\"Resilient Client*\"\n       | rex field=useragent \"transaction_id=(?\u003ctransaction_id\u003e[^\\)]+)\\)\"\n       | table useragent transaction_id\n\nWhere does the transaction ID come from? The ResilientClient object has a default policy of reading this field from the\n[Mapped Diagnostic Context](http://logback.qos.ch/manual/mdc.html) feature of SL4J. The key consulted is \"transaction_id\"\nand this is expected to contain e.g. \"transaction_id=xxxx\", that is, the value contains the key. This is consistent with\n[internal FT tools](http://git.svc.ft.com:8080/projects/APILIBS/repos/jax-rs-transaction-id-handling/browse/src/main/java/com/ft/api/util/transactionid/TransactionIdFilter.java).\n\nThis behaviour is implemented as an embedded Guava [Supplier](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Supplier.html)\nwhich returns the entire User-Agent string.\n\nYou can override this behaviour as follows:\n\n    client.setUserAgentSupplier(Suppliers.ofInstance(\"My User-Agent\"));\n\n\n# Known short comings\n\n* The name is incorrect. Technically, this is not an API wrapper but a sub-type of the Jersey implementation.\n* Port centric. To permit testing within the build the API makes host:port pairs first class citizens. This is\n  occasionally inconvenient.\n* DropWizard centric. The requirement to provide an \"environment\" is trivial on DropWizard but may be onerous on other platforms\n* Default MDC to user agent mapping is FT centric.\n\n## IP v4/6 inconsistency\n\nIn test runs on Windows machines the following stack trace can sometimes be observed:\n\n    INFO  [2015-01-30 15:50:31,980] com.ft.jerseyhttpwrapper.ResilientClient: transaction_id=tid_kshspxsqdn [REQUEST STARTED] short_name=SemanticWriter [dw-153 - POST /ingest]\n    WARN  [2015-01-30 15:50:31,981] com.ft.jerseyhttpwrapper.ResilientClient: transaction_id=tid_kshspxsqdn Unexpected error communicating with server. [dw-153 - POST /ingest]\n    ! com.sun.jersey.api.client.ClientHandlerException: java.lang.IllegalArgumentException: Host name may not be null\n    ! at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:187) ~[jersey-apache-client4-1.18.1.jar:1.18.1]\n    ! at com.sun.jersey.api.client.filter.GZIPContentEncodingFilter.handle(GZIPContentEncodingFilter.java:120) ~[jersey-client-1.18.1.jar:1.18.1]\n    ! at com.sun.jersey.api.client.Client.handle(Client.java:652) ~[jersey-client-1.18.1.jar:1.18.1]\n    ! at com.ft.jerseyhttpwrapper.ResilientClient.handle(ResilientClient.java:134) ~[resilient-jersey-wrapper-0.3-SNAPSHOT.jar:na]\n    ...\n    ...\n    ! at java.lang.Thread.run(Thread.java:724) ~[na:1.7.0_25]\n    ! Caused by: java.lang.IllegalArgumentException: Host name may not be null\n    ! at org.apache.http.HttpHost.\u003cinit\u003e(HttpHost.java:79) ~[httpcore-4.2.2.jar:4.2.2]\n    ! at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.getHost(ApacheHttpClient4Handler.java:198) ~[jersey-apache-client4-1.18.1.jar:1.18.1]\n    ! at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:167) ~[jersey-apache-client4-1.18.1.jar:1.18.1]\n    ! ... 62 common frames omitted\n\nTo suppress this exception add the following system property:\n\n    -Djava.net.preferIPv4Stack=true\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffinancial-times%2Fresilient-jersey-wrapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffinancial-times%2Fresilient-jersey-wrapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffinancial-times%2Fresilient-jersey-wrapper/lists"}