Files
opensearch-pyd/opensearchpy/connection/http_requests.py
T

257 lines
8.5 KiB
Python
Raw Normal View History

# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.
#
# Modifications Copyright OpenSearch Contributors. See
# GitHub history for details.
#
# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
2020-04-23 11:22:08 -05:00
2022-10-04 00:15:18 +05:30
2013-08-25 16:39:56 +02:00
import time
2015-01-29 23:46:01 +01:00
import warnings
2019-05-10 09:16:33 -06:00
2021-08-17 12:02:41 +05:30
try:
import requests
REQUESTS_AVAILABLE = True
except ImportError:
REQUESTS_AVAILABLE = False
from ..compat import reraise_exceptions, string_types, urlencode
2019-05-10 09:16:33 -06:00
from ..exceptions import (
ConnectionError,
ConnectionTimeout,
ImproperlyConfigured,
2019-05-10 09:16:33 -06:00
SSLError,
)
from .base import Connection
2019-05-10 09:16:33 -06:00
2013-08-25 16:39:56 +02:00
class RequestsHttpConnection(Connection):
2013-09-24 22:55:04 +02:00
"""
Connection using the `requests` library.
:arg http_auth: optional http auth information as either ':' separated
string or a tuple. Any value will be passed into requests as `auth`.
:arg use_ssl: use ssl for the connection if `True`
:arg verify_certs: whether to verify SSL certificates
2019-03-28 20:20:16 +01:00
:arg ssl_show_warn: show warning when verify certs is disabled
:arg ca_certs: optional path to CA bundle. Defaults to configured OpenSSL
bundles from environment variables and then certifi before falling
back to the standard requests bundle to improve consistency with
other Connection implementations
:arg client_cert: path to the file containing the private key and the
certificate, or cert only if using client_key
:arg client_key: path to the file containing the private key if using
separate cert and key files (client_cert will contain only the cert)
2016-07-12 18:13:11 +02:00
:arg headers: any custom http headers to be add to requests
:arg http_compress: Use gzip compression
2020-03-11 16:33:15 -05:00
:arg opaque_id: Send this value in the 'X-Opaque-Id' HTTP header
For tracing all requests made by this transport.
:arg pool_maxsize: Maximum connection pool size used by pool-manager
For custom connection-pooling on current session
2013-09-24 22:55:04 +02:00
"""
2013-10-11 10:37:04 +02:00
2019-05-10 09:16:33 -06:00
def __init__(
self,
host="localhost",
port=None,
2019-05-10 09:16:33 -06:00
http_auth=None,
use_ssl=False,
verify_certs=True,
ssl_show_warn=True,
ca_certs=None,
client_cert=None,
client_key=None,
headers=None,
http_compress=None,
2020-03-11 16:33:15 -05:00
opaque_id=None,
pool_maxsize=None,
2019-05-10 09:16:33 -06:00
**kwargs
):
if not REQUESTS_AVAILABLE:
raise ImproperlyConfigured(
"Please install requests to use RequestsHttpConnection."
)
# Initialize Session so .headers works before calling super().__init__().
self.session = requests.Session()
for key in list(self.session.headers):
self.session.headers.pop(key)
# Mount http-adapter with custom connection-pool size. Default=10
if pool_maxsize and isinstance(pool_maxsize, int):
2023-01-02 14:25:48 -08:00
pool_adapter = requests.adapters.HTTPAdapter(pool_maxsize=pool_maxsize)
self.session.mount("http://", pool_adapter)
self.session.mount("https://", pool_adapter)
super(RequestsHttpConnection, self).__init__(
host=host,
port=port,
use_ssl=use_ssl,
headers=headers,
http_compress=http_compress,
2020-03-11 16:33:15 -05:00
opaque_id=opaque_id,
**kwargs
)
if not self.http_compress:
# Need to set this to 'None' otherwise Requests adds its own.
self.session.headers["accept-encoding"] = None
if http_auth is not None:
if isinstance(http_auth, (tuple, list)):
http_auth = tuple(http_auth)
elif isinstance(http_auth, string_types):
2019-05-10 09:16:33 -06:00
http_auth = tuple(http_auth.split(":", 1))
2013-09-24 22:55:04 +02:00
self.session.auth = http_auth
2020-08-27 10:28:14 -05:00
self.base_url = "%s%s" % (
self.host,
self.url_prefix,
)
self.session.verify = verify_certs
if not client_key:
self.session.cert = client_cert
elif client_cert:
# cert is a tuple of (certfile, keyfile)
self.session.cert = (client_cert, client_key)
if ca_certs:
if not verify_certs:
2019-05-10 09:16:33 -06:00
raise ImproperlyConfigured(
"You cannot pass CA certificates when verify SSL is off."
)
self.session.verify = ca_certs
elif verify_certs:
ca_certs = self.default_ca_certs()
if ca_certs:
self.session.verify = ca_certs
2013-08-25 16:39:56 +02:00
if not ssl_show_warn:
requests.packages.urllib3.disable_warnings()
2019-03-28 20:20:16 +01:00
if self.use_ssl and not verify_certs and ssl_show_warn:
2015-01-29 23:46:01 +01:00
warnings.warn(
2019-05-10 09:16:33 -06:00
"Connecting to %s using SSL with verify_certs=False is insecure."
% self.host
2019-05-10 09:16:33 -06:00
)
2015-01-29 23:46:01 +01:00
2019-05-10 09:16:33 -06:00
def perform_request(
self,
method,
url,
params=None,
body=None,
timeout=None,
allow_redirects=True,
ignore=(),
headers=None,
2019-05-10 09:16:33 -06:00
):
url = self.base_url + url
headers = headers or {}
if params:
2021-08-17 12:02:41 +05:30
url = "%s?%s" % (url, urlencode(params or {}))
2013-08-25 16:39:56 +02:00
orig_body = body
if self.http_compress and body:
body = self._gzip_compress(body)
headers["content-encoding"] = "gzip"
2013-08-25 16:39:56 +02:00
start = time.time()
request = requests.Request(method=method, headers=headers, url=url, data=body)
prepared_request = self.session.prepare_request(request)
2019-05-10 09:16:33 -06:00
settings = self.session.merge_environment_settings(
prepared_request.url, {}, None, None, None
)
send_kwargs = {
"timeout": timeout or self.timeout,
"allow_redirects": allow_redirects,
}
send_kwargs.update(settings)
2013-08-25 16:39:56 +02:00
try:
response = self.session.send(prepared_request, **send_kwargs)
2013-08-25 16:39:56 +02:00
duration = time.time() - start
raw_data = response.content.decode("utf-8", "surrogatepass")
except reraise_exceptions:
raise
2016-12-15 16:46:05 +11:00
except Exception as e:
2019-05-10 09:16:33 -06:00
self.log_request_fail(
method,
url,
prepared_request.path_url,
orig_body,
2019-05-10 09:16:33 -06:00
time.time() - start,
exception=e,
)
2016-12-15 16:46:05 +11:00
if isinstance(e, requests.exceptions.SSLError):
2019-05-10 09:16:33 -06:00
raise SSLError("N/A", str(e), e)
2016-12-15 16:46:05 +11:00
if isinstance(e, requests.Timeout):
2019-05-10 09:16:33 -06:00
raise ConnectionTimeout("TIMEOUT", str(e), e)
raise ConnectionError("N/A", str(e), e)
2013-08-25 16:39:56 +02:00
# raise warnings if any from the 'Warnings' header.
warnings_headers = (
(response.headers["warning"],) if "warning" in response.headers else ()
)
self._raise_warnings(warnings_headers)
2013-08-25 16:39:56 +02:00
# raise errors based on http status codes, let the client handle those if needed
2019-05-10 09:16:33 -06:00
if (
not (200 <= response.status_code < 300)
and response.status_code not in ignore
):
self.log_request_fail(
method,
url,
response.request.path_url,
orig_body,
2019-05-10 09:16:33 -06:00
duration,
response.status_code,
raw_data,
)
self._raise_error(
response.status_code,
raw_data,
response.headers.get("Content-Type"),
)
2013-08-25 16:39:56 +02:00
2019-05-10 09:16:33 -06:00
self.log_request_success(
method,
url,
response.request.path_url,
orig_body,
2019-05-10 09:16:33 -06:00
response.status_code,
raw_data,
duration,
)
2013-08-25 16:39:56 +02:00
return response.status_code, response.headers, raw_data
@property
def headers(self):
return self.session.headers
def close(self):
"""
Explicitly closes connections
"""
self.session.close()