Repository: elastic/apm-agent-python

Issue:
__init__() got an unexpected keyword argument 'assert_hostname'
After upgrading to Version 5.1.1 (recent version 4.2.1) the flask app running on below error:
```shell
Traceback (most recent call last):
  File "\elasticapm\transport\http.py", line 79, in send
    "POST", url, body=data, headers=self._headers, timeout=self._timeout, preload_content=False
  File "\urllib3\poolmanager.py", line 324, in urlopen
    response = conn.urlopen(method, u.request_uri, **kw)
  File "\elasticapm\instrumentation\packages\base.py", line 136, in call_if_sampling
    return wrapped(*args, **kwargs)
  File "\elasticapm\instrumentation\packages\base.py", line 136, in call_if_sampling
    return wrapped(*args, **kwargs)
  File "\urllib3\connectionpool.py", line 588, in urlopen
    conn = self._get_conn(timeout=pool_timeout)
  File "\urllib3\connectionpool.py", line 248, in _get_conn
    return conn or self._new_conn()
  File "\urllib3\connectionpool.py", line 209, in _new_conn
    strict=self.strict, **self.conn_kw)
  File "\urllib3\connection.py", line 114, in __init__
    _HTTPConnection.__init__(self, *args, **kw)
TypeError: __init__() got an unexpected keyword argument 'assert_hostname'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "\elasticapm\transport\base.py", line 208, in _flush
    self.send(data)
  File "\elasticapm\transport\http.py", line 92, in send
    raise TransportException(message, data, print_trace=print_trace)
elasticapm.transport.base.TransportException: Unable to reach APM Server: __init__() got an unexpected keyword argument 'assert_hostname' (url: http://<apm-server url>:8200/intake/v2/events)
```

The NO_PROXY env variable is used.


**To Reproduce**

1. apm.init_app()
2. Running on TransportException

**Expected behavior**: 
Connect to apm-server host and add apm data.

**Environment (please complete the following information)**
- OS: Windows/Linux (Conda environment)
- Python version: 3.6.8
- Framework and version: Flask 1.1.1
- APM Server version: 7.3.0
- Agent version: 5.1.1


**Additional context**

Add any other context about the problem here.

- Agent config options 
    ELASTIC_APM = {
        'SERVICE_NAME': '<name>',
        'SERVER_URL': 'http://%s:8200' % ELASTIC_HOST,
        'SECRET_TOKEN': '',
        'DEBUG': True,
        'ENVIRONMENT': 'development',
        'VERIFY_SERVER_CERT': False,
        'SERVICE_VERSION': app_version
    }
- `requirements.txt`:
aniso8601==6.0.0
apispec==2.0.2
asn1crypto==0.24.0
atomicwrites==1.3.0
attrs==19.1.0
blinker==1.4
blueprint==3.4.2
certifi==2019.3.9
cffi==1.12.2
chardet==3.0.4
Click==7.0
colorama==0.4.1
cryptography==2.6.1
ecdsa==0.13
elastic-apm==4.2.1
elasticsearch==7.0.2
Flask==1.1.1
flask-apispec==0.8.1
Flask-JWT==0.3.2
Flask-PyMongo==2.2.0
flask-restplus==0.12.1
future==0.17.1
hvac==0.7.2
idna==2.8
itsdangerous==1.1.0
Jinja2==2.10.1
jsonschema==3.0.1
MarkupSafe==1.1.1
marshmallow==2.19.2
more-itertools==7.0.0
pluggy==0.9.0
psutil==5.6.1
py==1.8.0
pyasn1==0.4.5
pycparser==2.19
PyJWT==1.4.2
pymongo==3.7.2
pyOpenSSL==19.0.0
pyrsistent==0.14.11
pytest==4.4.0
pytest-flask==0.14.0
pytest-mock==1.10.3
python-dateutil==2.8.0
python3-logstash==0.4.80
pytz==2018.9
PyYAML==5.1.1
requests==2.21.0
rsa==4.0
six==1.12.0
urllib3==1.24.1
webargs==5.2.0
Werkzeug==0.15.1
wincertstore==0.2


Discussion:
Looks like we're passing the `assert_hostname` pieces for cert validation even for `http`, where certs are definitely not relevant. I'm looking into it.
Ah, I think I see the problem. Can you try removing your `'VERIFY_SERVER_CERT': False,` config? I think the issue you're seeing is due to combining https config values with an http `SERVER_URL`. If removing that config value fixes the error, then it's still a bug but I can verify that I've diagnosed the issue correctly.

Resolving patch:
diff --git a/elasticapm/transport/http.py b/elasticapm/transport/http.py
--- a/elasticapm/transport/http.py
+++ b/elasticapm/transport/http.py
@@ -51,12 +51,12 @@ def __init__(self, url, **kwargs):
         super(Transport, self).__init__(url, **kwargs)
         url_parts = compat.urlparse.urlparse(url)
         pool_kwargs = {"cert_reqs": "CERT_REQUIRED", "ca_certs": certifi.where(), "block": True}
-        if self._server_cert:
+        if self._server_cert and url_parts.scheme != "http":
             pool_kwargs.update(
                 {"assert_fingerprint": self.cert_fingerprint, "assert_hostname": False, "cert_reqs": ssl.CERT_NONE}
             )
             del pool_kwargs["ca_certs"]
-        elif not self._verify_server_cert:
+        elif not self._verify_server_cert and url_parts.scheme != "http":
             pool_kwargs["cert_reqs"] = ssl.CERT_NONE
             pool_kwargs["assert_hostname"] = False
         proxies = compat.getproxies_environment()


Test patch:
diff --git a/tests/transports/test_urllib3.py b/tests/transports/test_urllib3.py
--- a/tests/transports/test_urllib3.py
+++ b/tests/transports/test_urllib3.py
@@ -188,6 +188,38 @@ def test_ssl_verify_disable(waiting_httpsserver):
         transport.close()
 
 
+def test_ssl_verify_disable_http(waiting_httpserver):
+    """
+    Make sure that ``assert_hostname`` isn't passed in for http requests, even
+    with verify_server_cert=False
+    """
+    waiting_httpserver.serve_content(code=202, content="", headers={"Location": "http://example.com/foo"})
+    transport = Transport(waiting_httpserver.url, verify_server_cert=False)
+    try:
+        url = transport.send(compat.b("x"))
+        assert url == "http://example.com/foo"
+    finally:
+        transport.close()
+
+
+def test_ssl_cert_pinning_http(waiting_httpserver):
+    """
+    Won't fail, as with the other cert pinning test, since certs aren't relevant
+    for http, only https.
+    """
+    waiting_httpserver.serve_content(code=202, content="", headers={"Location": "http://example.com/foo"})
+    transport = Transport(
+        waiting_httpserver.url,
+        server_cert=os.path.join(os.path.dirname(__file__), "wrong_cert.pem"),
+        verify_server_cert=True,
+    )
+    try:
+        url = transport.send(compat.b("x"))
+        assert url == "http://example.com/foo"
+    finally:
+        transport.close()
+
+
 def test_ssl_cert_pinning(waiting_httpsserver):
     waiting_httpsserver.serve_content(code=202, content="", headers={"Location": "https://example.com/foo"})
     cur_dir = os.path.dirname(os.path.realpath(__file__))