# junction-client
An embeddable, dynamically configurable HTTP library.
## About
Junction is a library that allows you to dynamically configure application level
HTTP routing, load balancing, and resilience by writing a few lines of
configuration and dynamically pushing it to your client. Imagine having all of
the features of a rich HTTP proxy that's as easy to work with as the HTTP
library you're already using.
Junction can be statically configured, with plain old software, or dynamically
configured with any gRPC-xDS compatible configuration server. Check out [ezbake]
for a simple server that lets you save Junction config using the [Kubernetes
Gateway API][gateway_api]
[ezbake]: https://github.com/junction-labs/ezbake
[gateway_api]: https://gateway-api.sigs.k8s.io/
### Project status
Junction is alpha software, developed in the open. We're still iterating rapidly
on core code and APIs. At this stage you should expect occasional breaking
changes as the library evolves. At the moment, Junction only supports talking to
Kubernetes services, with support for routing to any existing DNS name coming
very soon.
### Features
Junction allows you to statically or dynamically configure:
* Routing traffic to backends based on the HTTP method, path, headers, or query parameters.
* Timeouts
* Retries
* Weighted traffic splitting
* Load balancing
Junction differs from existing libraries and proxies in that we aim to do all of
this in your native programming language, with low overhead and deep integration
into your existing HTTP client. This approach means that Junction can provide
dynamic config, but still provide first class support for things like unit
testing and debugging, putting developers back in control of their applications.
### Roadmap
We're starting small and focused with Junction. Our first goal is getting
configuration right. Our next goal is allowing you to push configuration that
you've developed and tested to a centralized config server.
### Supported Languages and HTTP Libraries
| Language | Core Library | Supported HTTP Libraries |
|----------|-------------------|--------------------------|
| Rust | `junction-core` | None |
| Python | `junction` | [requests] and [urllib3] |
[requests]: https://pypi.org/project/requests/
[urllib3]: https://github.com/urllib3/urllib3
## Using a Junction client
Junction clients are part of your application and function as a cache for
dynamic configuration. All Junction clients can also be configured with static
configuration that acts as a default when a server doesn't have any dynamic
configuration for a route or a target. When you make an HTTP request, Junction
matches it against existing configuration, decides where and how to send the
request, and then hands back control to your HTTP library.
In all of our libraries, Junction clients expect to be able to talk to a dynamic
configuration server. The details of how you specify which config server to talk
to are specific to each language - we want it to feel natural. For now, the
easiest control plane to use is
[ezbake](https://github.com/junction-labs/ezbake), our sample self-hosted
control plane.
### Python
#### Making Requests
In Python, Junction is available as a standalone client and as a drop-in replacement
for `requests.Session` or `urllib3.PoolManager`.
Using junction as a drop-in replacement for requests is as easy as:
```python
import junction.requests as requests
session = requests.Session()
resp = session.get("http://my-service.prod.svc.cluster.local")
```
Just by creating a client and making requests with a Junction session, you're using
dynamic discovery and load-balancing.
#### Creating Config
Setting up Junction Routes and Backends is almost as easy as using the client.
For example, if you wanted to shard a memcached cluster by your internal user-id,
you'd declare it like this:
```python
memcached = {
"id": {"type": "kube", "name": "nginx", "namespace": "web"},
"lb": {
"type": "RingHash",
"hash_params": [
{"type": "Header", "name": "x-user"},
],
},
}
```
Junction includes typing information for both Routes and Backends. Because Junction
fully runs in your client, you can unit test your Routes as you create them with no
network connection required.
```python
import junction.config
import typing
my_service = {"type": "kube", "name": "cool", "namespace": "widgets"}
my_test_service = {"type": "kube", "name": "cooler", "namespace": "widgets"}
# create a retry policy that can be re-used for
retry_policy: junction.config.RouteRetry = {
"attempts": 3,
"backoff": 0.5,
"codes": [500, 503],
}
# create a new routing policy.
#
# all Junction config comes with python3 typing info
routes: typing.List[junction.config.Route] = [
{
"id": "my-route",
"hostnames": ["cool.widgets.svc.cluster.local"],
"rules": [
{
"backends": [my_test_service],
"retry": retry_policy,
"matches": [{"path": {"value": "/v2/users"}}],
},
{
"backends": [my_service],
"retry": retry_policy,
},
],
},
]
# assert that requests with no path go to the cool service like normal
(_, _, matched_backend) = junction.check_route(
routes, "GET", "http://cool.widgets.svc.cluster.local", {}
)
assert matched_backend["name"] == "cool"
# assert that requests to /v2/users go to an even cooler service
(_, _, matched_backend) = junction.check_route(
routes, "GET", "http://cool.widgets.svc.cluster.local/v2/users", {}
)
assert matched_backend["name"] == "cooler"
```
For a more complete example configuration see the [sample] project. It's a
runnable example containing routing tables, load balancing, retry policies, and
more.
[sample]: https://github.com/junction-labs/junction-client/tree/main/junction-python/samples/routing-and-load-balancing
### NodeJS
NodeJS support is coming soon! Please reach out to
[info@junctionlabs.io](mailto:info@junctionlabs.io) if you run NodeJS services
and are interested in being an early access design partner.
### Rust
The core of Junction is written in Rust and is available in the
[`junction-core`](https://github.com/junction-labs/junction-client/tree/main/crates/junction-core)
crate. At the moment, we don't have an integration with an HTTP library
available, but you can use the core client to dynamically fetch config and
resolve addresses.
See the `examples` directory for [an
example](https://github.com/junction-labs/junction-client/blob/main/crates/junction-core/examples/get-endpoints.rs)
of how to use junction to resolve an address.
Raw data
{
"_id": null,
"home_page": "https://junctionlabs.io",
"name": "junction-python",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "service discovery, http",
"author": null,
"author_email": "Ben Linsay <blinsay@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/57/28/a8ec2722036e9e4c52af51fa64b141579c02fd9d06182c15a73f7edaca4d/junction_python-0.2.1.tar.gz",
"platform": null,
"description": "# junction-client\n\nAn embeddable, dynamically configurable HTTP library.\n\n## About\n\nJunction is a library that allows you to dynamically configure application level\nHTTP routing, load balancing, and resilience by writing a few lines of\nconfiguration and dynamically pushing it to your client. Imagine having all of\nthe features of a rich HTTP proxy that's as easy to work with as the HTTP\nlibrary you're already using.\n\nJunction can be statically configured, with plain old software, or dynamically\nconfigured with any gRPC-xDS compatible configuration server. Check out [ezbake]\nfor a simple server that lets you save Junction config using the [Kubernetes\nGateway API][gateway_api]\n\n[ezbake]: https://github.com/junction-labs/ezbake\n[gateway_api]: https://gateway-api.sigs.k8s.io/\n\n### Project status\n\nJunction is alpha software, developed in the open. We're still iterating rapidly\non core code and APIs. At this stage you should expect occasional breaking\nchanges as the library evolves. At the moment, Junction only supports talking to\nKubernetes services, with support for routing to any existing DNS name coming\nvery soon.\n\n### Features\n\nJunction allows you to statically or dynamically configure:\n\n* Routing traffic to backends based on the HTTP method, path, headers, or query parameters.\n* Timeouts\n* Retries\n* Weighted traffic splitting\n* Load balancing\n\nJunction differs from existing libraries and proxies in that we aim to do all of\nthis in your native programming language, with low overhead and deep integration\ninto your existing HTTP client. This approach means that Junction can provide\ndynamic config, but still provide first class support for things like unit\ntesting and debugging, putting developers back in control of their applications.\n\n### Roadmap\n\nWe're starting small and focused with Junction. Our first goal is getting\nconfiguration right. Our next goal is allowing you to push configuration that\nyou've developed and tested to a centralized config server.\n\n### Supported Languages and HTTP Libraries\n\n| Language | Core Library | Supported HTTP Libraries |\n|----------|-------------------|--------------------------|\n| Rust | `junction-core` | None |\n| Python | `junction` | [requests] and [urllib3] |\n\n[requests]: https://pypi.org/project/requests/\n[urllib3]: https://github.com/urllib3/urllib3\n\n## Using a Junction client\n\nJunction clients are part of your application and function as a cache for\ndynamic configuration. All Junction clients can also be configured with static\nconfiguration that acts as a default when a server doesn't have any dynamic\nconfiguration for a route or a target. When you make an HTTP request, Junction\nmatches it against existing configuration, decides where and how to send the\nrequest, and then hands back control to your HTTP library.\n\nIn all of our libraries, Junction clients expect to be able to talk to a dynamic\nconfiguration server. The details of how you specify which config server to talk\nto are specific to each language - we want it to feel natural. For now, the\neasiest control plane to use is\n[ezbake](https://github.com/junction-labs/ezbake), our sample self-hosted\ncontrol plane.\n\n### Python\n\n#### Making Requests\n\nIn Python, Junction is available as a standalone client and as a drop-in replacement\nfor `requests.Session` or `urllib3.PoolManager`.\n\nUsing junction as a drop-in replacement for requests is as easy as:\n\n```python\nimport junction.requests as requests\n\nsession = requests.Session()\nresp = session.get(\"http://my-service.prod.svc.cluster.local\")\n```\n\nJust by creating a client and making requests with a Junction session, you're using\ndynamic discovery and load-balancing.\n\n#### Creating Config\n\nSetting up Junction Routes and Backends is almost as easy as using the client.\nFor example, if you wanted to shard a memcached cluster by your internal user-id,\nyou'd declare it like this:\n\n```python\nmemcached = {\n \"id\": {\"type\": \"kube\", \"name\": \"nginx\", \"namespace\": \"web\"},\n \"lb\": {\n \"type\": \"RingHash\",\n \"hash_params\": [\n {\"type\": \"Header\", \"name\": \"x-user\"},\n ],\n },\n}\n```\n\nJunction includes typing information for both Routes and Backends. Because Junction\nfully runs in your client, you can unit test your Routes as you create them with no\nnetwork connection required.\n\n```python\nimport junction.config\nimport typing\n\nmy_service = {\"type\": \"kube\", \"name\": \"cool\", \"namespace\": \"widgets\"}\nmy_test_service = {\"type\": \"kube\", \"name\": \"cooler\", \"namespace\": \"widgets\"}\n\n# create a retry policy that can be re-used for\nretry_policy: junction.config.RouteRetry = {\n \"attempts\": 3,\n \"backoff\": 0.5,\n \"codes\": [500, 503],\n}\n\n# create a new routing policy.\n#\n# all Junction config comes with python3 typing info\nroutes: typing.List[junction.config.Route] = [\n {\n \"id\": \"my-route\",\n \"hostnames\": [\"cool.widgets.svc.cluster.local\"],\n \"rules\": [\n {\n \"backends\": [my_test_service],\n \"retry\": retry_policy,\n \"matches\": [{\"path\": {\"value\": \"/v2/users\"}}],\n },\n {\n \"backends\": [my_service],\n \"retry\": retry_policy,\n },\n ],\n },\n]\n\n\n# assert that requests with no path go to the cool service like normal\n(_, _, matched_backend) = junction.check_route(\n routes, \"GET\", \"http://cool.widgets.svc.cluster.local\", {}\n)\nassert matched_backend[\"name\"] == \"cool\"\n\n# assert that requests to /v2/users go to an even cooler service\n(_, _, matched_backend) = junction.check_route(\n routes, \"GET\", \"http://cool.widgets.svc.cluster.local/v2/users\", {}\n)\nassert matched_backend[\"name\"] == \"cooler\"\n```\n\nFor a more complete example configuration see the [sample] project. It's a\nrunnable example containing routing tables, load balancing, retry policies, and\nmore.\n\n[sample]: https://github.com/junction-labs/junction-client/tree/main/junction-python/samples/routing-and-load-balancing\n\n### NodeJS\n\nNodeJS support is coming soon! Please reach out to\n[info@junctionlabs.io](mailto:info@junctionlabs.io) if you run NodeJS services\nand are interested in being an early access design partner.\n\n### Rust\n\nThe core of Junction is written in Rust and is available in the\n[`junction-core`](https://github.com/junction-labs/junction-client/tree/main/crates/junction-core)\ncrate. At the moment, we don't have an integration with an HTTP library\navailable, but you can use the core client to dynamically fetch config and\nresolve addresses.\n\nSee the `examples` directory for [an\nexample](https://github.com/junction-labs/junction-client/blob/main/crates/junction-core/examples/get-endpoints.rs)\nof how to use junction to resolve an address.\n\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "An embeddable, dynamically configurable HTTP library",
"version": "0.2.1",
"project_urls": {
"Homepage": "https://junctionlabs.io",
"Repository": "https://github.com/junction-labs/junction-client"
},
"split_keywords": [
"service discovery",
" http"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "c73cc0e6ee3272259c216a373eaac22a3d7c6663087f81536ff2d9b687b154f3",
"md5": "7263ef1ce69eb218f5a566826ff7a467",
"sha256": "3fc55711ca664049c587c492b6461d7badc5b25782a1ed3b640b9e11ba64ed03"
},
"downloads": -1,
"filename": "junction_python-0.2.1-cp38-abi3-macosx_10_12_x86_64.whl",
"has_sig": false,
"md5_digest": "7263ef1ce69eb218f5a566826ff7a467",
"packagetype": "bdist_wheel",
"python_version": "cp38",
"requires_python": ">=3.9",
"size": 4489481,
"upload_time": "2024-12-10T04:15:57",
"upload_time_iso_8601": "2024-12-10T04:15:57.760279Z",
"url": "https://files.pythonhosted.org/packages/c7/3c/c0e6ee3272259c216a373eaac22a3d7c6663087f81536ff2d9b687b154f3/junction_python-0.2.1-cp38-abi3-macosx_10_12_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "a14617624b3675b42126f22b0424b0a6da12c640363349f95c8585f5af38c9e3",
"md5": "f00a8626923bc90e92361682a3249397",
"sha256": "91c8b9dd716cdc14e89960df9144b5aaec837a202b32ffa07ebef52da2882895"
},
"downloads": -1,
"filename": "junction_python-0.2.1-cp38-abi3-macosx_11_0_arm64.whl",
"has_sig": false,
"md5_digest": "f00a8626923bc90e92361682a3249397",
"packagetype": "bdist_wheel",
"python_version": "cp38",
"requires_python": ">=3.9",
"size": 4367266,
"upload_time": "2024-12-10T04:16:01",
"upload_time_iso_8601": "2024-12-10T04:16:01.199191Z",
"url": "https://files.pythonhosted.org/packages/a1/46/17624b3675b42126f22b0424b0a6da12c640363349f95c8585f5af38c9e3/junction_python-0.2.1-cp38-abi3-macosx_11_0_arm64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "cf20b12071e6247b496aa89273daf139070be280a8bb85b74465353e73b578a6",
"md5": "403364fd5eed144a9f86b0f50ba3aac1",
"sha256": "aa4e4fb1926f5a9dfb72b4f08fa69bbe5ecf9689542bceb8685e2cb7dbbb04b7"
},
"downloads": -1,
"filename": "junction_python-0.2.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"has_sig": false,
"md5_digest": "403364fd5eed144a9f86b0f50ba3aac1",
"packagetype": "bdist_wheel",
"python_version": "cp38",
"requires_python": ">=3.9",
"size": 4941973,
"upload_time": "2024-12-10T04:16:04",
"upload_time_iso_8601": "2024-12-10T04:16:04.048973Z",
"url": "https://files.pythonhosted.org/packages/cf/20/b12071e6247b496aa89273daf139070be280a8bb85b74465353e73b578a6/junction_python-0.2.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "ce6511b656d7ce6cedfecd6459f707948f817da3c0e63b137925066ec4229d37",
"md5": "53fcc53beb662ed347fbcd5864d3187a",
"sha256": "1ac9f1ae8a857f87382ce330ab162b0973b7066c0cd9dbf1b1fbb4649cf87566"
},
"downloads": -1,
"filename": "junction_python-0.2.1-cp38-abi3-manylinux_2_24_aarch64.whl",
"has_sig": false,
"md5_digest": "53fcc53beb662ed347fbcd5864d3187a",
"packagetype": "bdist_wheel",
"python_version": "cp38",
"requires_python": ">=3.9",
"size": 4812303,
"upload_time": "2024-12-10T04:16:06",
"upload_time_iso_8601": "2024-12-10T04:16:06.886578Z",
"url": "https://files.pythonhosted.org/packages/ce/65/11b656d7ce6cedfecd6459f707948f817da3c0e63b137925066ec4229d37/junction_python-0.2.1-cp38-abi3-manylinux_2_24_aarch64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "5ebadc29867398a634cb0c3768a8706c8dd3e75679f3824c0ebee34a1df3a09b",
"md5": "b99b6f756913dd1265babb2b7f156ef2",
"sha256": "adbc8ddcb2ca81f3b54710cb37c62ecdc3c3d12e42a350849fbffdaa70e00fda"
},
"downloads": -1,
"filename": "junction_python-0.2.1-cp38-abi3-musllinux_1_2_aarch64.whl",
"has_sig": false,
"md5_digest": "b99b6f756913dd1265babb2b7f156ef2",
"packagetype": "bdist_wheel",
"python_version": "cp38",
"requires_python": ">=3.9",
"size": 4968569,
"upload_time": "2024-12-10T04:16:10",
"upload_time_iso_8601": "2024-12-10T04:16:10.157438Z",
"url": "https://files.pythonhosted.org/packages/5e/ba/dc29867398a634cb0c3768a8706c8dd3e75679f3824c0ebee34a1df3a09b/junction_python-0.2.1-cp38-abi3-musllinux_1_2_aarch64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "27d12bd6120c095ad934d5284db27ed65599b18aea79009a7d7c9e33ca6ff244",
"md5": "f983754787e921ddc98ff65573ef798c",
"sha256": "d5a05fe9445764343a3eda415e02e36367fb5bb742c8f3deb83004f2d7083662"
},
"downloads": -1,
"filename": "junction_python-0.2.1-cp38-abi3-musllinux_1_2_x86_64.whl",
"has_sig": false,
"md5_digest": "f983754787e921ddc98ff65573ef798c",
"packagetype": "bdist_wheel",
"python_version": "cp38",
"requires_python": ">=3.9",
"size": 5111431,
"upload_time": "2024-12-10T04:16:12",
"upload_time_iso_8601": "2024-12-10T04:16:12.064085Z",
"url": "https://files.pythonhosted.org/packages/27/d1/2bd6120c095ad934d5284db27ed65599b18aea79009a7d7c9e33ca6ff244/junction_python-0.2.1-cp38-abi3-musllinux_1_2_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "ad02829b5c583f7ab639d43c3b6524322fa4f21617f7d3f13f4bdf2ea7f0b7c4",
"md5": "d936bf916a4b44ccfbadba08b940378e",
"sha256": "fdc7a78d2e12855bac20cc8a6278b935fbc348b4fa02ca2e434c163704170713"
},
"downloads": -1,
"filename": "junction_python-0.2.1-cp38-abi3-win_amd64.whl",
"has_sig": false,
"md5_digest": "d936bf916a4b44ccfbadba08b940378e",
"packagetype": "bdist_wheel",
"python_version": "cp38",
"requires_python": ">=3.9",
"size": 4051385,
"upload_time": "2024-12-10T04:16:13",
"upload_time_iso_8601": "2024-12-10T04:16:13.842408Z",
"url": "https://files.pythonhosted.org/packages/ad/02/829b5c583f7ab639d43c3b6524322fa4f21617f7d3f13f4bdf2ea7f0b7c4/junction_python-0.2.1-cp38-abi3-win_amd64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "5728a8ec2722036e9e4c52af51fa64b141579c02fd9d06182c15a73f7edaca4d",
"md5": "68e31c9a3c0e0eb37c8b297ec61ac1ab",
"sha256": "28874fa3a4b44c2ba4641f5c29340f2b15bb5285609c31f3b12e1f7fdc04e0c0"
},
"downloads": -1,
"filename": "junction_python-0.2.1.tar.gz",
"has_sig": false,
"md5_digest": "68e31c9a3c0e0eb37c8b297ec61ac1ab",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 162969,
"upload_time": "2024-12-10T04:16:16",
"upload_time_iso_8601": "2024-12-10T04:16:16.827538Z",
"url": "https://files.pythonhosted.org/packages/57/28/a8ec2722036e9e4c52af51fa64b141579c02fd9d06182c15a73f7edaca4d/junction_python-0.2.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-10 04:16:16",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "junction-labs",
"github_project": "junction-client",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "junction-python"
}