httpbin(1): HTTP Request & Response Service
A Kenneth Reitz Project.
Run locally:
docker pull kennethreitz/httpbin
docker run -p 80:80 kennethreitz/httpbin
See http://httpbin.org for more information.
A Kenneth Reitz Project.
Run locally:
docker pull kennethreitz/httpbin
docker run -p 80:80 kennethreitz/httpbin
See http://httpbin.org for more information.
I'm using https://httpbin.org and it looks like chunked transfer encoding does not work any more as expected:
$ curl -X POST https://httpbin.org/post \
-d '{"message":"BLA"}' \
-H 'Content-Type: application/json' \
-H 'Transfer-Encoding: chunked'
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Connect-Time": "1",
"Connection": "close",
"Content-Type": "application/json",
"Host": "httpbin.org",
"Total-Route-Time": "0",
"Transfer-Encoding": "chunked",
"User-Agent": "curl/7.50.1",
"Via": "1.1 vegur",
"X-Request-Id": "2a06bd5b-06dc-4ced-ac89-fe83cbb6771c"
},
"json": null,
"origin": "<removed>",
"url": "https://httpbin.org/post"
}
Observe that data
and json
do not contain the posted data. The same request works as expected without chunked transfer encoding:
$ curl -X POST https://httpbin.org/post \
-d '{"message":"BLA"}' \
-H 'Content-Type: application/json'
{
"args": {},
"data": "{\"message\":\"BLA\"}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Connect-Time": "0",
"Connection": "close",
"Content-Length": "17",
"Content-Type": "application/json",
"Host": "httpbin.org",
"Total-Route-Time": "0",
"User-Agent": "curl/7.50.1",
"Via": "1.1 vegur",
"X-Request-Id": "dfbaa440-9871-4a3c-b482-35a68459722a"
},
"json": {
"message": "BLA"
},
"origin": "<removed>",
"url": "https://httpbin.org/post"
}
Am I missing something?
All the /redirect-to
endpoints are returning 404s.
$ curl -v -X GET "http://httpbin.org/redirect-to?url=http://httpbin.org/get"
* Trying 34.235.192.52...
* TCP_NODELAY set
* Connected to httpbin.org (34.235.192.52) port 80 (#0)
> GET /redirect-to?url=http://httpbin.org/get HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Server: awselb/2.0
< Date: Sat, 20 Jun 2020 06:48:23 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 0
< Connection: keep-alive
<
* Connection #0 to host httpbin.org left intact
* Closing connection 0
bug
When running the self tests for the pypi distfile of 0.6.2 on NetBSD 8.99.7/amd64 with python-3.6.3, I see the following test failure:
httpbin (unittest.loader._FailedTest) ... ERROR
======================================================================
ERROR: httpbin (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: httpbin
Traceback (most recent call last):
File "/usr/pkg/lib/python3.6/unittest/loader.py", line 462, in _find_test_path
package = self._get_module_from_name(name)
File "/usr/pkg/lib/python3.6/unittest/loader.py", line 369, in _get_module_from_name
__import__(name)
File "/scratch/www/py-httpbin/work/httpbin-0.6.2/httpbin/__init__.py", line 3, in <module>
from .core import *
File "/scratch/www/py-httpbin/work/httpbin-0.6.2/httpbin/core.py", line 61, in <module>
common = Common(app)
File "/usr/pkg/lib/python3.6/site-packages/flask_common.py", line 95, in __init__
self.init_app(app)
File "/usr/pkg/lib/python3.6/site-packages/flask_common.py", line 112, in init_app
app.wsgi_app = WhiteNoise(app.wsgi_app, root=url_for('static', filename='')[1:])
File "/usr/pkg/lib/python3.6/site-packages/whitenoise/base.py", line 61, in __init__
self.add_files(root, prefix)
File "/usr/pkg/lib/python3.6/site-packages/whitenoise/base.py", line 96, in add_files
self.update_files_dictionary(root, prefix)
File "/usr/pkg/lib/python3.6/site-packages/whitenoise/base.py", line 101, in update_files_dictionary
stat_cache = dict(scantree(root))
File "/usr/pkg/lib/python3.6/site-packages/whitenoise/scantree.py", line 21, in scantree
for entry in scandir(root):
FileNotFoundError: [Errno 2] No such file or directory: 'static/'
For ease of deployment it might be helpful to have a Docker container that runs httpbin. This is a simple first step down that road.
Before merging, it might be worth trying to optimise this image somewhat. For example, it might be better to use Debian as the base rather than Ubuntu. It might also be worth trying to remove the artifacts of the build step, make the number of gunicorn workers parameterizable, or something else interesting. I'm happy to make those changes and test them.
Added the header Access-Control-Expose-Headers=WWW-Authenticate to the unauthorized response of digest authentication to enable ajax applications to have access to this header and perform the digest auth. Fixes #205.
Also created the 'hidden-digest-auth' service which returns 404 status coded instead of 401, preventing the browser to prompt the user for the credentials in ajax applications. Closes #209.
https://pypi.python.org/pypi/httpbin has old contact info.
setup.py
looks to already be updated. So a python setup.py register
is probably all that is needed.
Bonus points to convert the description to RST so it can be displayed nicely.
Everything works as expected from a web browser. As it should.
But I can't get my Java client code to work. I'm 99% sure I'm generating the correct MD5s, but could somebody please walk me through the steps? Must be something I don't understand there. Here's what I'm doing now
p.s. If it helps I'd be happy to try this at some planned time, to a planned user/password, and let one of you view the log files on the httpbin side to try to see what is happening.
bugAs of earlier today my http client unit tests started failing because the digest-auth
endpoint returns HTTP 500 instead of 200 for correct credentials. Example:
curl https://eu.httpbin.org/digest-auth/auth/jerry/secret -v -u jerry:secret
Has there been a new deployment? It was working OK for the past months, maybe even years.
Here's a further fix for /redirect-to
POST
support as discussed in #476. The previous change allowed POST
to send url
and status_code
in form-data, but broke query-string support for same (a-la GET
)
url
(required) and status_code
can now still appear in the query-string for GET or POST, but for POST/PATCH/PUT if they appear in the body form-data then the values from there are used in-favour of any in the query-string. This allows testing "traditional" POST
with a form payload.
/cc #476 @eturk1
See https://github.com/kennethreitz/flask-common/issues/4 .
Currently it is not possible to install httpbin
on Windows machines. If fails with the following error:
$ pip install httpbin
Collecting httpbin
Downloading httpbin-0.6.2-py2.py3-none-any.whl (87kB)
100% |████████████████████████████████| 92kB 163kB/s
Collecting brotlipy (from httpbin)
Using cached brotlipy-0.7.0-cp36-cp36m-win_amd64.whl
Requirement already satisfied: decorator in c:\users\a\appdata\local\programs\python\python36\lib\site-packages (from httpbin)
Collecting flask-limiter (from httpbin)
Using cached Flask-Limiter-0.9.5.1.tar.gz
Collecting Flask-Common (from httpbin)
Using cached Flask-Common-0.2.0.tar.gz
Requirement already satisfied: itsdangerous in c:\users\a\appdata\local\programs\python\python36\lib\site-packages (from httpbin)
Requirement already satisfied: Flask in c:\users\a\appdata\local\programs\python\python36\lib\site-packages (from httpbin)
Requirement already satisfied: six in c:\users\a\appdata\local\programs\python\python36\lib\site-packages (from httpbin)
Collecting raven[flask] (from httpbin)
Downloading raven-6.2.1-py2.py3-none-any.whl (285kB)
100% |████████████████████████████████| 286kB 427kB/s
Requirement already satisfied: MarkupSafe in c:\users\a\appdata\local\programs\python\python36\lib\site-packages (from httpbin)
Collecting cffi>=1.0.0 (from brotlipy->httpbin)
Downloading cffi-1.11.2-cp36-cp36m-win_amd64.whl (166kB)
100% |████████████████████████████████| 174kB 846kB/s
Collecting limits (from flask-limiter->httpbin)
Using cached limits-1.2.1.tar.gz
Collecting Gunicorn (from Flask-Common->httpbin)
Using cached gunicorn-19.7.1-py2.py3-none-any.whl
Collecting WhiteNoise (from Flask-Common->httpbin)
Downloading whitenoise-3.3.1-py2.py3-none-any.whl
Collecting crayons (from Flask-Common->httpbin)
Using cached crayons-0.1.2.tar.gz
Collecting maya (from Flask-Common->httpbin)
Downloading maya-0.3.3-py2.py3-none-any.whl
Collecting flask_cache (from Flask-Common->httpbin)
Using cached Flask-Cache-0.13.1.tar.gz
Collecting meinheld (from Flask-Common->httpbin)
Using cached meinheld-0.6.1.tar.gz
Complete output from command python setup.py egg_info:
Are you really running a posix compliant OS ?
Be posix compliant is mandatory
----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in C:\Users\a\AppData\Local\Temp\pip-build-prc752k3\meinheld\
httpbin 0.5.0 didn't have this issue. Thanks!
A basic implementation of range requests: http://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7233.html
Allows querying http://httpbin.org/range-request/1024?duration=s&chunk_size=code, specifying Range headers that allow selecting specific portions of the resource to GET.
Hi Team,
I cloned the httpbin repo with tag v0.6.1 and tried to build docker image using command docker build , but it seems to fails while installing the python dependency got this error: Downloading limits-1.6.tar.gz (37 kB)
Collecting WhiteNoise
Downloading whitenoise-5.2.0-py2.py3-none-any.whl (19 kB)
ERROR: Package 'WhiteNoise' requires a different Python: 2.7.18 not in '>=3.5, <4'
WARNING: You are using pip version 20.0.2; however, version 20.3.4 is available.
You should consider upgrading via the '/usr/local/bin/python -m pip install --upgrade pip' command.
Seems it not taking proper version mentioned in pipfile.lock, can you please help on this
(Technically it doesn't fail, but deps aren't installed because pipenv throws an exception in the process substitution in the Dockerfile. It appear process substitution needs special treatment to catch errors, and that's why the error is eaten.)
I'm opening this more of as a courtesy, I don't think this is actively maintained anymore. :shrug:
Pipenv dropped support for Python 3.6
Description of bug:
Running docker compose build && docker compose up
would fail with the message ERROR: for httpbin Cannot start service httpbin: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "gunicorn": executable file not found in $PATH: unknown
since pipenv
was failing to set up any libraries.
fix httpbin/core.py: use Responce class instead of BaseResponse see: https://github.com/pallets/werkzeug/pull/2276
fix tests: TestClient doesn't provide 'Content-Length' header by default anymore see: https://github.com/pallets/werkzeug/issues/2347
With the following code that sends multiple files to https://httpbin.org/anything only one of them is returned in the payload.
const form = new FormData();
const files = [
new File(
[
'data:image/png;name=owlbert.png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAMCAYAAABbayygAAABV0lEQVR4AWNwL/Bh0FGQ9FWUENUGsZExPz8/h5gArzmIDRZw1VfpSfeyagIJgiRBYkCg7mOl72akIt0KVwhSVB5o9SPESutJWajzquJgx/lRDganc7zNX3obq9SiKKxN8P/fmB33vybc7j+MHe1k8t9RSy4NrrA4K2Xp1k0b/peUlPzPjfL5v3bpgv9NTc3/48JD/sOsBju4JDnq6MS+3v9uLlb/pzeY/l82r+9/cIA/GNtrK2wFqQH7uDzY/gXQOrBpbemi/xO9DH4B2WCrQe4GqWHQVRDfBnLXpDTX/z3xTii4xM/if4iF5n+QGgZjZamvIIH5RT5wPKvQC0wDDQAr1FMQ/8YgK8zfAzIeqgCOp+V5gBW6Giq9A6kB+9pUXTiqINjwJ9B6uKKmBHuwW7XkhFeAYg2sMMWXQTvJh/2Uu6nciTgXvVsg7Gsp+xAkZqHPIA1SAwCKnrxJusHahgAAAABJRU5ErkJggg==',
],
'owlbert.png',
{ type: 'image/png' }
),
new File(
[
'data:image/png;name=owlbert-shrub.png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACNUlEQVR4AWJkIA2wCQHqJgcouZ0ADv+SmayR28PibPOP2jafzdp6Lh/v2bVt2+1zbdvtbtbMpGfmvmD8jStc9uB3v75gdNmqtJqsa6qCDEBPRVH7XpKk1P9yHLZv3mG6keV9BIsWGqNuvNaiy+YoARFIXFXwf6Frjz/X9oS4LNRkty4QH/0MSnnWDHORHV3pJsiozRk0zWW/9MEi0A9ffqOhf5RRDW6SGLT/ZXcT8C1hWvao8iF1c4ecKx5eejW1bii1y1asW14P5XsUNkogmvVQg2aPLOnrqss9qE81OYlAucRrN/LrqlBSWYufv34jq7gKseA7MCr0IOhffForGsTWDBJnqE2/B0H+hKRRG3DtxEGk6QBZQ9UFClMSnTJe/cCVI5uQJJpx+8AyOBUPPv+JIpJjgJqCZzKT0AGdAgT/1OLtSzsKI2/gspnxvGExZYFADcoSsqM10SADF4jC4TCCUDN8nih+eoIgPAdGeajBNxyMH20JwsPNMwSicUjBCAwmAZ6GkCmA7I+qCxTGRHSkVzZiCRmKAsRkuVmWiENrt6gL5Di7iw4oogFSINIkCTU09hNAP64CREPUBbzAx9ABrc2I3w2NeY5DMByHu8AGY3YyeoKIuSmBWCD6OuqLPJXe/Dpvkb3FuoDbdENKuBVJ4nxZJpKgWoUXCMcYUwBwDfR8FxZPN4wfWK2Zunq7tGLmZMOc7x7ZUn/RuDmrX0H/4FffQ2teylCecuWmDNsTnainfwFPuNZTR3MemwAAAABJRU5ErkJggg==',
],
'owlbert-shrub.png',
{ type: 'image/png' }
)
];
for (const file of files) {
form.append('files', file, file.name);
}
fetch('https://httpbin.org/anything', {
method: 'post',
body: form,
})
.then(res => res.json())
.then(console.log);
{
"args": {},
"data": "",
"files": {
"files": "data:image/png;name=owlbert.png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAMCAYAAABbayygAAABV0lEQVR4AWNwL/Bh0FGQ9FWUENUGsZExPz8/h5gArzmIDRZw1VfpSfeyagIJgiRBYkCg7mOl72akIt0KVwhSVB5o9SPESutJWajzquJgx/lRDganc7zNX3obq9SiKKxN8P/fmB33vybc7j+MHe1k8t9RSy4NrrA4K2Xp1k0b/peUlPzPjfL5v3bpgv9NTc3/48JD/sOsBju4JDnq6MS+3v9uLlb/pzeY/l82r+9/cIA/GNtrK2wFqQH7uDzY/gXQOrBpbemi/xO9DH4B2WCrQe4GqWHQVRDfBnLXpDTX/z3xTii4xM/if4iF5n+QGgZjZamvIIH5RT5wPKvQC0wDDQAr1FMQ/8YgK8zfAzIeqgCOp+V5gBW6Giq9A6kB+9pUXTiqINjwJ9B6uKKmBHuwW7XkhFeAYg2sMMWXQTvJh/2Uu6nciTgXvVsg7Gsp+xAkZqHPIA1SAwCKnrxJusHahgAAAABJRU5ErkJggg=="
},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Content-Length": "1784",
"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundarywCjQuPHebN5OoDBI",
"Host": "httpbin.org",
"Origin": "http://httpbin.org",
"Referer": "http://httpbin.org/",
"Sec-Ch-Ua": "\".Not/A)Brand\";v=\"99\", \"Google Chrome\";v=\"103\", \"Chromium\";v=\"103\"",
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": "\"macOS\"",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "cross-site",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
"X-Amzn-Trace-Id": "REDACTED"
},
"json": null,
"method": "POST",
"origin": "REDACTED",
"url": "https://httpbin.org/anything"
}
However, both files are actually being delivered but only owlbert.png
was returned:
In order to rule out that this was an HTTPBin issue I spun up a small Express server to test this out with multer and the multiple files are returned:
const express = require('express')
const multer = require('multer')
const upload = multer({ dest: 'uploads/' })
const app = express()
app.post('/', upload.array('files', 12), function (req, res, next) {
console.log(req.files)
})
const port = 3000;
app.listen(port);
console.log(`Express app started on port ${port}`);
And the logged out data from sending the same POST request to http://localhost:3000/:
[
{
fieldname: 'files',
originalname: 'owlbert.png',
encoding: '7bit',
mimetype: 'image/png',
destination: 'uploads/',
filename: '4c9a0a2c4360b21d9da215270d4ce7a5',
path: 'uploads/4c9a0a2c4360b21d9da215270d4ce7a5',
size: 575
},
{
fieldname: 'files',
originalname: 'owlbert-shrub.png',
encoding: '7bit',
mimetype: 'image/png',
destination: 'uploads/',
filename: '5456576fa1b1f17b517b15085d618327',
path: 'uploads/5456576fa1b1f17b517b15085d618327',
size: 877
}
]
I also thought that maybe it was because I was sending the payload as files
instead of files[]
, but making a request with that returned similar results:
{
"args": {},
"data": "",
"files": {
"files[]": "data:image/png;name=owlbert.png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAMCAYAAABbayygAAABV0lEQVR4AWNwL/Bh0FGQ9FWUENUGsZExPz8/h5gArzmIDRZw1VfpSfeyagIJgiRBYkCg7mOl72akIt0KVwhSVB5o9SPESutJWajzquJgx/lRDganc7zNX3obq9SiKKxN8P/fmB33vybc7j+MHe1k8t9RSy4NrrA4K2Xp1k0b/peUlPzPjfL5v3bpgv9NTc3/48JD/sOsBju4JDnq6MS+3v9uLlb/pzeY/l82r+9/cIA/GNtrK2wFqQH7uDzY/gXQOrBpbemi/xO9DH4B2WCrQe4GqWHQVRDfBnLXpDTX/z3xTii4xM/if4iF5n+QGgZjZamvIIH5RT5wPKvQC0wDDQAr1FMQ/8YgK8zfAzIeqgCOp+V5gBW6Giq9A6kB+9pUXTiqINjwJ9B6uKKmBHuwW7XkhFeAYg2sMMWXQTvJh/2Uu6nciTgXvVsg7Gsp+xAkZqHPIA1SAwCKnrxJusHahgAAAABJRU5ErkJggg=="
},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Content-Length": "1788",
"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryB0dcYW2WEwUoiUgo",
"Host": "httpbin.org",
"Origin": "http://httpbin.org",
"Referer": "http://httpbin.org/",
"Sec-Ch-Ua": "\".Not/A)Brand\";v=\"99\", \"Google Chrome\";v=\"103\", \"Chromium\";v=\"103\"",
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": "\"macOS\"",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "cross-site",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
"X-Amzn-Trace-Id": "REDACTED"
},
"json": null,
"method": "POST",
"origin": "REDACTED",
"url": "https://httpbin.org/anything"
}
Hello,
httpbin uses an old version of Swagger-UI
under the hood which is vulnerable to DOM XSS by adding a malicious yaml file as value to the query parameter url
swagger: '2.0'
info:
title: Example yaml.spec
description: |
<math><mtext><option><FAKEFAKE><option></option><mglyph><svg><mtext><textarea><a title="</textarea><img src='#' onerror='alert(window.origin)'>">
paths:
/accounts:
get:
responses:
'200':
description: No response was specified
tags:
- accounts
operationId: findAccounts
summary: Finds all accounts
This is a bug in older SwaggerUI version that use an older version of DOMPurify to sanitize html. You can read more about it here: https://www.vidocsecurity.com/blog/hacking-swagger-ui-from-xss-to-account-takeovers
Run javascript on the victim's broswer
Upgrade Swagger-UI version to the latest which mitigates this.
I sent you an email regarding this and got no reply so I am opening an issue here. Since there is anorher XSS reported 2 years ago or so and you haven't responded to that, I assume you don't care about XSS in general
However, I decided to report this for 2 reasons
Best Regards,
Fix a couple Python3 bugs with the random byte endpoints, fix a bug when uploading files without a Content-Type header set.
Source code(tar.gz)Introduction statsd is a client for Etsy's statsd server, a front end/proxy for the Graphite stats collection and graphing server. Links The source: h
Async http client/server framework Key Features Supports both client and server side of HTTP protocol. Supports both client and server Web-Sockets out
🔄 🌐 Handle thousands of HTTP requests, disk writes, and other I/O-bound tasks simultaneously with Python's quintessential async libraries.
🔨 Jawbreaker 🔨 Jawbreaker is a Python obfuscator written in Python3, using double encoding in base16, base32, base64, HTTP requests and a Hastebin-l
HTTP Core Do one thing, and do it well. The HTTP Core package provides a minimal low-level HTTP client, which does one thing only. Sending HTTP reques
Gigabot+ Bot que responde automáticamente as perguntas do giga unitel LINK DOWNLOAD: Gigabot.exe O script pode apresentar alguns erros, pois não tive
aiosonic - lightweight Python asyncio http client Very fast, lightweight Python asyncio http client Here is some documentation. There is a performance
aiohttp-openmetrics This project contains a simple middleware and /metrics route
crypto_finder What Where Documentation http://localhost:8899/docs Maintainer nordzisko Crypto Finder aiohttp application Application that connects to
PySimpleSOAP / soap2py Python simple and lightweight SOAP library for client and server webservices interfaces, aimed to be as small and easy as possi
httptools is a Python binding for the nodejs HTTP parser. The package is available on PyPI: pip install httptools. APIs httptools contains two classes
pathprober Probe and discover HTTP pathname using brute-force methodology and filtered by specific word or 2 words at once. Purpose Brute-forcing webs
Hyper: HTTP/2 Client for Python This project is no longer maintained! Please use an alternative, such as HTTPX or others. We will not publish further
Asynchronous Python HTTP Requests for Humans Small add-on for the python requests http library. Makes use twisted's ThreadPool, so that the requests'A
httpbin(1): HTTP Request & Response Service
Requests Requests is a simple, yet elegant HTTP library. import requests r = requests.get('https://api.github.com/user', auth=('user', 'pass')
HTTP Request Smuggling Detection Tool HTTP request smuggling is a high severity vulnerability which is a technique where an attacker smuggles an ambig
Cat Requests What is this? Cat requests allows you to both get the HTTP response code of the website you wish and it displays it to your screen as a c
Ritchie Formula Repo Documentation Contribute to the Ritchie community This repository contains rit formulas which can be executed by the ritchie-cli.
urllib3 is a powerful, user-friendly HTTP client for Python. Much of the Python ecosystem already uses urllib3 and you should too. urllib3 brings many