[py] implement proxy and keep alive with client config
diff --git a/py/selenium/webdriver/chrome/remote_connection.py b/py/selenium/webdriver/chrome/remote_connection.py
index f289ff1..7f2fbf6 100644
--- a/py/selenium/webdriver/chrome/remote_connection.py
+++ b/py/selenium/webdriver/chrome/remote_connection.py
@@ -18,6 +18,7 @@
 
 from selenium.webdriver import DesiredCapabilities
 from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection
+from selenium.webdriver.remote.client_config import ClientConfig
 
 
 class ChromeRemoteConnection(ChromiumRemoteConnection):
@@ -26,8 +27,9 @@
     def __init__(
         self,
         remote_server_addr: str,
-        keep_alive: bool = True,
-        ignore_proxy: typing.Optional[bool] = False,
+        keep_alive: typing.Optional[bool] = None,
+        ignore_proxy: typing.Optional[bool] = None,
+        client_config: typing.Optional[ClientConfig] = None,
     ) -> None:
         super().__init__(
             remote_server_addr=remote_server_addr,
@@ -35,4 +37,5 @@
             browser_name="chrome",
             keep_alive=keep_alive,
             ignore_proxy=ignore_proxy,
+            client_config=client_config,
         )
diff --git a/py/selenium/webdriver/chrome/webdriver.py b/py/selenium/webdriver/chrome/webdriver.py
index 517f14a..09f363e 100644
--- a/py/selenium/webdriver/chrome/webdriver.py
+++ b/py/selenium/webdriver/chrome/webdriver.py
@@ -14,9 +14,11 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+import typing
 
 from selenium.webdriver.chromium.webdriver import ChromiumDriver
 
+from ..remote.client_config import ClientConfig
 from .options import Options
 from .service import Service
 
@@ -28,7 +30,8 @@
         self,
         options: Options = None,
         service: Service = None,
-        keep_alive: bool = True,
+        keep_alive: typing.Optional[bool] = None,
+        client_config: typing.Optional[ClientConfig] = None,
     ) -> None:
         """Creates a new instance of the chrome driver. Starts the service and
         then creates new instance of chrome driver.
@@ -36,9 +39,10 @@
         :Args:
          - options - this takes an instance of ChromeOptions
          - service - Service object for handling the browser driver if you need to pass extra details
-         - keep_alive - Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
+         - keep_alive - Deprecated: Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
+         - client_config - configuration values for the http client
         """
         service = service if service else Service()
         options = options if options else Options()
 
-        super().__init__(options=options, service=service, keep_alive=keep_alive)
+        super().__init__(options=options, service=service, keep_alive=keep_alive, client_config=client_config)
diff --git a/py/selenium/webdriver/chromium/remote_connection.py b/py/selenium/webdriver/chromium/remote_connection.py
index c2e7c9a..ee896dd 100644
--- a/py/selenium/webdriver/chromium/remote_connection.py
+++ b/py/selenium/webdriver/chromium/remote_connection.py
@@ -16,6 +16,7 @@
 # under the License.
 import typing
 
+from selenium.webdriver.remote.client_config import ClientConfig
 from selenium.webdriver.remote.remote_connection import RemoteConnection
 
 
@@ -25,10 +26,11 @@
         remote_server_addr: str,
         vendor_prefix: str,
         browser_name: str,
-        keep_alive: bool = True,
-        ignore_proxy: typing.Optional[bool] = False,
+        keep_alive: typing.Optional[bool] = None,
+        ignore_proxy: typing.Optional[bool] = None,
+        client_config: typing.Optional[ClientConfig] = None,
     ) -> None:
-        super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy)
+        super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy, client_config=client_config)
         self.browser_name = browser_name
         self._commands["launchApp"] = ("POST", "/session/$sessionId/chromium/launch_app")
         self._commands["setPermissions"] = ("POST", "/session/$sessionId/permissions")
diff --git a/py/selenium/webdriver/chromium/webdriver.py b/py/selenium/webdriver/chromium/webdriver.py
index 0c12cad..864cb67 100644
--- a/py/selenium/webdriver/chromium/webdriver.py
+++ b/py/selenium/webdriver/chromium/webdriver.py
@@ -14,12 +14,14 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+import typing
 import warnings
 
 from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection
 from selenium.webdriver.common.driver_finder import DriverFinder
 from selenium.webdriver.common.options import ArgOptions
 from selenium.webdriver.common.service import Service
+from selenium.webdriver.remote.client_config import ClientConfig
 from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
 
 
@@ -31,10 +33,11 @@
         self,
         browser_name: str = None,
         vendor_prefix: str = None,
-        options: ArgOptions = ArgOptions(),
+        options: ArgOptions = None,
         service: Service = None,
-        keep_alive: bool = True,
-        remote_connection: ChromiumRemoteConnection = None,
+        keep_alive: typing.Optional[bool] = None,
+        remote_connection: typing.Optional[ChromiumRemoteConnection] = None,
+        client_config: typing.Optional[ClientConfig] = None,
     ) -> None:
         """Creates a new WebDriver instance of the ChromiumDriver. Starts the
         service and then creates new WebDriver instance of ChromiumDriver.
@@ -44,7 +47,8 @@
          - vendor_prefix - Company prefix to apply to vendor-specific WebDriver extension commands.
          - options - this takes an instance of ChromiumOptions
          - service - Service object for handling the browser driver if you need to pass extra details
-         - keep_alive - Whether to configure ChromiumRemoteConnection to use HTTP keep-alive.
+         - keep_alive - Deprecated: Whether to configure ChromiumRemoteConnection to use HTTP keep-alive.
+         - client_config - configuration values for the http client
         """
 
         if browser_name:
@@ -73,7 +77,12 @@
         command_executor = remote_connection if remote_connection else self.service.service_url
 
         try:
-            super().__init__(command_executor=command_executor, options=options, keep_alive=keep_alive)
+            super().__init__(
+                command_executor=command_executor,
+                options=options,
+                keep_alive=keep_alive,
+                client_config=client_config,
+            )
         except Exception:
             self.quit()
             raise
diff --git a/py/selenium/webdriver/common/options.py b/py/selenium/webdriver/common/options.py
index 6a4bea0..e92c450 100644
--- a/py/selenium/webdriver/common/options.py
+++ b/py/selenium/webdriver/common/options.py
@@ -15,6 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 import typing
+import warnings
 from abc import ABCMeta
 from abc import abstractmethod
 
@@ -31,6 +32,7 @@
         self._proxy = None
         self.set_capability("pageLoadStrategy", "normal")
         self.mobile_options = None
+        self._ignore_local_proxy = False
 
     @property
     def capabilities(self):
@@ -225,12 +227,22 @@
     def default_capabilities(self):
         """Return minimal capabilities necessary as a dictionary."""
 
+    def ignore_local_proxy_environment_variables(self) -> None:
+        """By calling this you will ignore HTTP_PROXY and HTTPS_PROXY from
+        being picked up and used."""
+        warnings.warn(
+            "setting ignore proxy in Options has been deprecated, "
+            "set ProxyType.DIRECT in ClientConfig and pass to WebDriver constructor instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self._ignore_local_proxy = True
+
 
 class ArgOptions(BaseOptions):
     def __init__(self) -> None:
         super().__init__()
         self._arguments = []
-        self._ignore_local_proxy = False
 
     @property
     def arguments(self):
@@ -250,11 +262,6 @@
         else:
             raise ValueError("argument can not be null")
 
-    def ignore_local_proxy_environment_variables(self) -> None:
-        """By calling this you will ignore HTTP_PROXY and HTTPS_PROXY from
-        being picked up and used."""
-        self._ignore_local_proxy = True
-
     def to_capabilities(self):
         return self._caps
 
diff --git a/py/selenium/webdriver/edge/remote_connection.py b/py/selenium/webdriver/edge/remote_connection.py
index c91fb03..8ee7625 100644
--- a/py/selenium/webdriver/edge/remote_connection.py
+++ b/py/selenium/webdriver/edge/remote_connection.py
@@ -18,6 +18,7 @@
 
 from selenium.webdriver import DesiredCapabilities
 from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection
+from selenium.webdriver.remote.client_config import ClientConfig
 
 
 class EdgeRemoteConnection(ChromiumRemoteConnection):
@@ -26,8 +27,9 @@
     def __init__(
         self,
         remote_server_addr: str,
-        keep_alive: bool = True,
-        ignore_proxy: typing.Optional[bool] = False,
+        keep_alive: typing.Optional[bool] = None,
+        ignore_proxy: typing.Optional[bool] = None,
+        client_config: typing.Optional[ClientConfig] = None,
     ) -> None:
         super().__init__(
             remote_server_addr=remote_server_addr,
@@ -35,4 +37,5 @@
             browser_name="MicrosoftEdge",
             keep_alive=keep_alive,
             ignore_proxy=ignore_proxy,
+            client_config=client_config,
         )
diff --git a/py/selenium/webdriver/edge/webdriver.py b/py/selenium/webdriver/edge/webdriver.py
index 6fc1116..e7742f0 100644
--- a/py/selenium/webdriver/edge/webdriver.py
+++ b/py/selenium/webdriver/edge/webdriver.py
@@ -14,9 +14,11 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+import typing
 
 from selenium.webdriver.chromium.webdriver import ChromiumDriver
 
+from ..remote.client_config import ClientConfig
 from .options import Options
 from .service import Service
 
@@ -28,7 +30,8 @@
         self,
         options: Options = None,
         service: Service = None,
-        keep_alive: bool = True,
+        keep_alive: typing.Optional[bool] = None,
+        client_config: typing.Optional[ClientConfig] = None,
     ) -> None:
         """Creates a new instance of the edge driver. Starts the service and
         then creates new instance of edge driver.
@@ -37,8 +40,9 @@
          - options - this takes an instance of EdgeOptions
          - service - Service object for handling the browser driver if you need to pass extra details
          - keep_alive - Whether to configure EdgeRemoteConnection to use HTTP keep-alive.
+         - client_config - configuration values for the http client
         """
         service = service if service else Service()
         options = options if options else Options()
 
-        super().__init__(options=options, service=service, keep_alive=keep_alive)
+        super().__init__(options=options, service=service, keep_alive=keep_alive, client_config=client_config)
diff --git a/py/selenium/webdriver/firefox/remote_connection.py b/py/selenium/webdriver/firefox/remote_connection.py
index b99a369..31dc753 100644
--- a/py/selenium/webdriver/firefox/remote_connection.py
+++ b/py/selenium/webdriver/firefox/remote_connection.py
@@ -14,16 +14,24 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+import typing
 
 from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
+from selenium.webdriver.remote.client_config import ClientConfig
 from selenium.webdriver.remote.remote_connection import RemoteConnection
 
 
 class FirefoxRemoteConnection(RemoteConnection):
     browser_name = DesiredCapabilities.FIREFOX["browserName"]
 
-    def __init__(self, remote_server_addr, keep_alive=True, ignore_proxy=False) -> None:
-        super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy)
+    def __init__(
+        self,
+        remote_server_addr,
+        keep_alive: typing.Optional[bool] = None,
+        ignore_proxy: typing.Optional[bool] = None,
+        client_config: typing.Optional[ClientConfig] = None,
+    ) -> None:
+        super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy, client_config=client_config)
 
         self._commands["GET_CONTEXT"] = ("GET", "/session/$sessionId/moz/context")
         self._commands["SET_CONTEXT"] = ("POST", "/session/$sessionId/moz/context")
diff --git a/py/selenium/webdriver/firefox/webdriver.py b/py/selenium/webdriver/firefox/webdriver.py
index e0121df..3d5600a 100644
--- a/py/selenium/webdriver/firefox/webdriver.py
+++ b/py/selenium/webdriver/firefox/webdriver.py
@@ -17,6 +17,7 @@
 import base64
 import logging
 import os
+import typing
 import warnings
 import zipfile
 from contextlib import contextmanager
@@ -25,6 +26,7 @@
 from selenium.webdriver.common.driver_finder import DriverFinder
 from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
 
+from ..remote.client_config import ClientConfig
 from .options import Options
 from .service import Service
 
@@ -41,15 +43,17 @@
         self,
         options: Options = None,
         service: Service = None,
-        keep_alive: bool = True,
+        keep_alive: typing.Optional[bool] = None,
+        client_config: typing.Optional[ClientConfig] = None,
     ) -> None:
         """Creates a new instance of the Firefox driver. Starts the service and
         then creates new instance of Firefox driver.
 
         :Args:
-         - options - Instance of ``options.Options``.
+         - options - (Optional) Instance of ``options.Options``.
          - service - (Optional) service instance for managing the starting and stopping of the driver.
-         - keep_alive - Whether to configure remote_connection.RemoteConnection to use HTTP keep-alive.
+         - keep_alive - Deprecated: Whether to configure remote_connection.RemoteConnection to use HTTP keep-alive.
+         - client_config - configuration values for the http client
         """
 
         self.service = service if service else Service()
@@ -59,7 +63,12 @@
         self.service.start()
 
         try:
-            super().__init__(command_executor=self.service.service_url, options=options, keep_alive=keep_alive)
+            super().__init__(
+                command_executor=self.service.service_url,
+                options=options,
+                keep_alive=keep_alive,
+                client_config=client_config,
+            )
         except Exception:
             self.quit()
             raise
diff --git a/py/selenium/webdriver/ie/webdriver.py b/py/selenium/webdriver/ie/webdriver.py
index def009d..0fd6f35 100644
--- a/py/selenium/webdriver/ie/webdriver.py
+++ b/py/selenium/webdriver/ie/webdriver.py
@@ -14,10 +14,12 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+import typing
 
 from selenium.webdriver.common.driver_finder import DriverFinder
 from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
 
+from ..remote.client_config import ClientConfig
 from .options import Options
 from .service import Service
 
@@ -30,16 +32,18 @@
         self,
         options: Options = None,
         service: Service = None,
-        keep_alive=True,
+        keep_alive: typing.Optional[bool] = None,
+        client_config: typing.Optional[ClientConfig] = None,
     ) -> None:
         """Creates a new instance of the Ie driver.
 
         Starts the service and then creates new instance of Ie driver.
 
         :Args:
-         - options - IE Options instance, providing additional IE options
+         - options - (Optional) IE Options instance, providing additional IE options
          - service - (Optional) service instance for managing the starting and stopping of the driver.
          - keep_alive - Deprecated: Whether to configure RemoteConnection to use HTTP keep-alive.
+         - client_config - configuration values for the http client
         """
 
         self.service = service if service else Service()
@@ -49,7 +53,12 @@
         self.service.start()
 
         try:
-            super().__init__(command_executor=self.service.service_url, options=options, keep_alive=keep_alive)
+            super().__init__(
+                command_executor=self.service.service_url,
+                options=options,
+                keep_alive=keep_alive,
+                client_config=client_config,
+            )
         except Exception:
             self.quit()
             raise
diff --git a/py/selenium/webdriver/remote/client_config.py b/py/selenium/webdriver/remote/client_config.py
new file mode 100644
index 0000000..795dbdd
--- /dev/null
+++ b/py/selenium/webdriver/remote/client_config.py
@@ -0,0 +1,57 @@
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC 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.
+
+from selenium.webdriver.common.proxy import Proxy
+from selenium.webdriver.common.proxy import ProxyType
+
+
+class ClientConfig:
+    def __init__(
+        self,
+        keep_alive: bool = True,
+        proxy: Proxy = Proxy({"proxyType": ProxyType.SYSTEM}),
+    ) -> None:
+        self.keep_alive = keep_alive
+        self.proxy = proxy
+
+    @property
+    def keep_alive(self) -> bool:
+        """:Returns: The keep alive value"""
+        return self._keep_alive
+
+    @keep_alive.setter
+    def keep_alive(self, value: bool) -> None:
+        """Toggles the keep alive value.
+
+        :Args:
+         - value: whether to keep the http connection alive
+        """
+        self._keep_alive = value
+
+    @property
+    def proxy(self) -> Proxy:
+        """:Returns: The proxy used for communicating to the driver/server"""
+        return self._proxy
+
+    @proxy.setter
+    def proxy(self, proxy: Proxy) -> None:
+        """Provides the information for communicating with the driver or server.
+
+        :Args:
+         - value: the proxy information to use to communicate with the driver or server
+        """
+        self._proxy = proxy
diff --git a/py/selenium/webdriver/remote/remote_connection.py b/py/selenium/webdriver/remote/remote_connection.py
index 54da8cd..1b49ccc 100644
--- a/py/selenium/webdriver/remote/remote_connection.py
+++ b/py/selenium/webdriver/remote/remote_connection.py
@@ -20,6 +20,8 @@
 import platform
 import socket
 import string
+import typing
+import warnings
 from base64 import b64encode
 from urllib import parse
 
@@ -28,7 +30,10 @@
 
 from selenium import __version__
 
+from ..common.proxy import Proxy
+from ..common.proxy import ProxyType
 from . import utils
+from .client_config import ClientConfig
 from .command import Command
 from .errorhandler import ErrorCode
 
@@ -128,8 +133,8 @@
 class RemoteConnection:
     """A connection with the Remote WebDriver server.
 
-    Communicates with the server using the WebDriver wire protocol:
-    https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
+    Communicates with the server using the w3c WebDriver protocol:
+    https://w3c.github.io/webdriver
     """
 
     browser_name = None
@@ -205,11 +210,19 @@
 
         return headers
 
-    def _get_proxy_url(self):
-        if self._url.startswith("https://"):
-            return os.environ.get("https_proxy", os.environ.get("HTTPS_PROXY"))
-        if self._url.startswith("http://"):
-            return os.environ.get("http_proxy", os.environ.get("HTTP_PROXY"))
+    def _get_proxy_url(self, proxy=None):
+        proxy = Proxy({"proxyType": ProxyType.SYSTEM}) if proxy is None else proxy
+
+        if proxy.proxyType == ProxyType.SYSTEM:
+            if self._url.startswith("https://"):
+                return os.environ.get("https_proxy", os.environ.get("HTTPS_PROXY"))
+            if self._url.startswith("http://"):
+                return os.environ.get("http_proxy", os.environ.get("HTTP_PROXY"))
+        else:
+            if self._url.startswith("https://"):
+                return proxy.sslProxy
+            if self._url.startswith("http://"):
+                return proxy.http_proxy
 
     def _identify_http_proxy_auth(self):
         url = self._proxy_url
@@ -242,31 +255,37 @@
 
         return urllib3.PoolManager(**pool_manager_init_args)
 
-    def __init__(self, remote_server_addr: str, keep_alive: bool = False, ignore_proxy: bool = False):
-        self.keep_alive = keep_alive
+    def __init__(
+        self,
+        remote_server_addr: str,
+        keep_alive: typing.Optional[bool] = None,
+        ignore_proxy: typing.Optional[bool] = None,
+        client_config: typing.Optional[ClientConfig] = None,
+    ) -> None:
         self._url = remote_server_addr
+        client_config = ClientConfig() if client_config is None else client_config
 
-        # Env var NO_PROXY will override this part of the code
-        _no_proxy = os.environ.get("no_proxy", os.environ.get("NO_PROXY"))
-        if _no_proxy:
-            for npu in _no_proxy.split(","):
-                npu = npu.strip()
-                if npu == "*":
-                    ignore_proxy = True
-                    break
-                n_url = parse.urlparse(npu)
-                remote_add = parse.urlparse(self._url)
-                if n_url.netloc:
-                    if remote_add.netloc == n_url.netloc:
-                        ignore_proxy = True
-                        break
-                else:
-                    if n_url.path in remote_add.netloc:
-                        ignore_proxy = True
-                        break
+        if keep_alive is not None:
+            warnings.warn(
+                "setting keep_alive in RemoteConnection has been deprecated, pass it in with ClientConfig instead",
+                DeprecationWarning,
+                stacklevel=2,
+            )
+            client_config.keep_alive = keep_alive
 
-        self._proxy_url = self._get_proxy_url() if not ignore_proxy else None
-        if keep_alive:
+        if ignore_proxy is not None:
+            warnings.warn(
+                "setting ignore_proxy in RemoteConnection has been deprecated, pass it in with ClientConfig instead",
+                DeprecationWarning,
+                stacklevel=2,
+            )
+            if ignore_proxy:
+                client_config.ignore_proxy = Proxy({"proxyType": ProxyType.DIRECT})
+
+        self._keep_alive = client_config.keep_alive
+
+        self._proxy_url = self._get_proxy_url(client_config.proxy) if self._use_proxy(client_config.proxy) else None
+        if self._keep_alive:
             self._conn = self._get_connection_manager()
         self._commands = remote_commands
 
@@ -303,12 +322,12 @@
         """
         LOGGER.debug(f"{method} {url} {body}")
         parsed_url = parse.urlparse(url)
-        headers = self.get_remote_connection_headers(parsed_url, self.keep_alive)
+        headers = self.get_remote_connection_headers(parsed_url, self._keep_alive)
         response = None
         if body and method not in ("POST", "PUT"):
             body = None
 
-        if self.keep_alive:
+        if self._keep_alive:
             response = self._conn.request(method, url, body=body, headers=headers)
             statuscode = response.status
         else:
@@ -347,6 +366,27 @@
             LOGGER.debug("Finished Request")
             response.close()
 
+    def _use_proxy(self, proxy: Proxy):
+        if proxy.proxyType == ProxyType.DIRECT:
+            return False
+
+        _no_proxy = os.environ.get("no_proxy", os.environ.get("NO_PROXY"))
+        if _no_proxy:
+            for npu in _no_proxy.split(","):
+                npu = npu.strip()
+                if npu == "*":
+                    return False
+                n_url = parse.urlparse(npu)
+                remote_add = parse.urlparse(self._url)
+                if n_url.netloc:
+                    if remote_add.netloc == n_url.netloc:
+                        return False
+                else:
+                    if n_url.path in remote_add.netloc:
+                        return False
+
+        return True
+
     def close(self):
         """Clean up resources when finished with the remote_connection."""
         if hasattr(self, "_conn"):
diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py
index 281aa85..ba48c1a 100644
--- a/py/selenium/webdriver/remote/webdriver.py
+++ b/py/selenium/webdriver/remote/webdriver.py
@@ -50,7 +50,10 @@
 )
 from selenium.webdriver.support.relative_locator import RelativeBy
 
+from ..common.proxy import Proxy
+from ..common.proxy import ProxyType
 from .bidi_connection import BidiConnection
+from .client_config import ClientConfig
 from .command import Command
 from .errorhandler import ErrorHandler
 from .file_detector import FileDetector
@@ -90,7 +93,7 @@
     return {"capabilities": {"firstMatch": [{}], "alwaysMatch": always_match}}
 
 
-def get_remote_connection(capabilities, command_executor, keep_alive, ignore_local_proxy=False):
+def get_remote_connection(capabilities, command_executor, client_config):
     from selenium.webdriver.chrome.remote_connection import ChromeRemoteConnection
     from selenium.webdriver.edge.remote_connection import EdgeRemoteConnection
     from selenium.webdriver.firefox.remote_connection import FirefoxRemoteConnection
@@ -99,7 +102,7 @@
     candidates = [ChromeRemoteConnection, EdgeRemoteConnection, SafariRemoteConnection, FirefoxRemoteConnection]
     handler = next((c for c in candidates if c.browser_name == capabilities.get("browserName")), RemoteConnection)
 
-    return handler(command_executor, keep_alive=keep_alive, ignore_proxy=ignore_local_proxy)
+    return handler(command_executor, client_config=client_config)
 
 
 def create_matches(options: List[BaseOptions]) -> Dict:
@@ -163,9 +166,10 @@
     def __init__(
         self,
         command_executor="http://127.0.0.1:4444",
-        keep_alive=True,
+        keep_alive: typing.Optional[bool] = None,
         file_detector=None,
         options: Union[BaseOptions, List[BaseOptions]] = None,
+        client_config: typing.Optional[ClientConfig] = None,
     ) -> None:
         """Create a new driver that will issue commands using the wire
         protocol.
@@ -173,26 +177,37 @@
         :Args:
          - command_executor - Either a string representing URL of the remote server or a custom
              remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'.
-         - keep_alive - Whether to configure remote_connection.RemoteConnection to use
+         - keep_alive - Deprecated: Whether to configure remote_connection.RemoteConnection to use
              HTTP keep-alive. Defaults to True.
          - file_detector - Pass custom file detector object during instantiation. If None,
              then default LocalFileDetector() will be used.
          - options - instance of a driver options.Options class
+         - client_config - configuration values for the http client
         """
+        if client_config is None:
+            client_config = ClientConfig()
+
+        if keep_alive is not None:
+            warnings.warn(
+                "setting keep_alive directly in WebDriver has been deprecated, pass it in with ClientConfig instead",
+                DeprecationWarning,
+                stacklevel=2,
+            )
+            client_config.keep_alive = keep_alive
 
         if isinstance(options, list):
             capabilities = create_matches(options)
-            _ignore_local_proxy = False
         else:
             capabilities = options.to_capabilities()
-            _ignore_local_proxy = options._ignore_local_proxy
+            if options._ignore_local_proxy:
+                client_config.proxy = Proxy({"proxyType": ProxyType.DIRECT})
+
         self.command_executor = command_executor
         if isinstance(self.command_executor, (str, bytes)):
             self.command_executor = get_remote_connection(
                 capabilities,
                 command_executor=command_executor,
-                keep_alive=keep_alive,
-                ignore_local_proxy=_ignore_local_proxy,
+                client_config=client_config,
             )
         self._is_remote = True
         self.session_id = None
diff --git a/py/selenium/webdriver/safari/remote_connection.py b/py/selenium/webdriver/safari/remote_connection.py
index a7929e9..360ff92 100644
--- a/py/selenium/webdriver/safari/remote_connection.py
+++ b/py/selenium/webdriver/safari/remote_connection.py
@@ -14,16 +14,24 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+import typing
 
 from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
+from selenium.webdriver.remote.client_config import ClientConfig
 from selenium.webdriver.remote.remote_connection import RemoteConnection
 
 
 class SafariRemoteConnection(RemoteConnection):
     browser_name = DesiredCapabilities.SAFARI["browserName"]
 
-    def __init__(self, remote_server_addr: str, keep_alive: bool = True, ignore_proxy: bool = False) -> None:
-        super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy)
+    def __init__(
+        self,
+        remote_server_addr: str,
+        keep_alive: typing.Optional[bool] = None,
+        ignore_proxy: typing.Optional[bool] = None,
+        client_config: typing.Optional[ClientConfig] = None,
+    ) -> None:
+        super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy, client_config=client_config)
         self._commands["GET_PERMISSIONS"] = ("GET", "/session/$sessionId/apple/permissions")
         self._commands["SET_PERMISSIONS"] = ("POST", "/session/$sessionId/apple/permissions")
         self._commands["ATTACH_DEBUGGER"] = ("POST", "/session/$sessionId/apple/attach_debugger")
diff --git a/py/selenium/webdriver/safari/webdriver.py b/py/selenium/webdriver/safari/webdriver.py
index d29b152..f5c6104 100644
--- a/py/selenium/webdriver/safari/webdriver.py
+++ b/py/selenium/webdriver/safari/webdriver.py
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
+import typing
 import warnings
 
 from selenium.common.exceptions import WebDriverException
@@ -22,6 +22,7 @@
 from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
 
 from ..common.driver_finder import DriverFinder
+from ..remote.client_config import ClientConfig
 from .options import Options
 from .service import Service
 
@@ -34,19 +35,21 @@
     def __init__(
         self,
         reuse_service=False,
-        keep_alive=True,
+        keep_alive: typing.Optional[bool] = None,
         options: Options = None,
         service: Service = None,
+        client_config: typing.Optional[ClientConfig] = None,
     ) -> None:
         """Creates a new Safari driver instance and launches or finds a running
         safaridriver service.
 
         :Args:
-         - reuse_service - If True, do not spawn a safaridriver instance; instead, connect to an already-running service that was launched externally.
-         - keep_alive - Whether to configure SafariRemoteConnection to use
-             HTTP keep-alive. Defaults to True.
+         - reuse_service - Deprecated: If True, do not spawn a safaridriver instance;
+             instead, connect to an already-running service that was launched externally.
+         - keep_alive - Deprecated: Whether to configure remote_connection.RemoteConnection to use HTTP keep-alive.
          - options - Instance of ``options.Options``.
          - service - Service object for handling the browser driver if you need to pass extra details
+         - client_config - configuration values for the http client
         """
         if reuse_service:
             warnings.warn(
@@ -65,7 +68,12 @@
             self.service.start()
 
         try:
-            super().__init__(command_executor=self.service.service_url, options=options, keep_alive=keep_alive)
+            super().__init__(
+                command_executor=self.service.service_url,
+                options=options,
+                keep_alive=keep_alive,
+                client_config=client_config,
+            )
         except Exception:
             self.quit()
             raise