Module glean.net.http_client

This module contains a ping uploader based on the Python stdlib's http.client module.

Expand source code
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
This module contains a ping uploader based on the Python stdlib's http.client
module.
"""

import http.client
import logging
import socket
from typing import Dict, Union
import urllib.parse


from . import base_uploader
from .ping_uploader import UploadResult


log = logging.getLogger("glean")


class HttpClientUploader(base_uploader.BaseUploader):
    # The timeout, in seconds, to use for all operations with the server.
    _DEFAULT_TIMEOUT = 10

    @classmethod
    def upload(
        cls, url: str, data: bytes, headers: Dict[str, str]
    ) -> Union[
        UploadResult,
        UploadResult.UNRECOVERABLE_FAILURE,
        UploadResult.RECOVERABLE_FAILURE,
        UploadResult.HTTP_STATUS,
    ]:
        """
        Synchronously upload a ping to a server.

        Args:
            url (str): The URL path to upload the data to.
            data (str): The serialized text data to send.
            headers (dict of (str, str)): HTTP headers to send.
        """
        parsed_url = urllib.parse.urlparse(url)
        if parsed_url.scheme == "http":
            conn = http.client.HTTPConnection(
                parsed_url.hostname or "",
                port=parsed_url.port or 80,
                timeout=cls._DEFAULT_TIMEOUT,
            )
        elif parsed_url.scheme == "https":
            conn = http.client.HTTPSConnection(
                parsed_url.hostname or "",
                port=parsed_url.port or 443,
                timeout=cls._DEFAULT_TIMEOUT,
            )
        else:
            # If we don't know the URL scheme, log an error and mark this as an unrecoverable
            # error, like if it were a malformed URL.
            log.error(f"Unknown URL scheme {parsed_url.scheme}")
            return UploadResult.UNRECOVERABLE_FAILURE(0)

        try:
            conn.request(
                "POST",
                parsed_url.path,
                body=data,
                headers=headers,
            )
            response = conn.getresponse()
        except http.client.InvalidURL as e:
            log.error(f"Could not upload telemetry due to malformed URL: '{url}' {e}")
            return UploadResult.UNRECOVERABLE_FAILURE(0)
        except http.client.HTTPException as e:
            log.debug(f"http.client.HTTPException while uploading ping: '{url}' {e}")
            return UploadResult.RECOVERABLE_FAILURE(0)
        except socket.gaierror as e:
            log.debug(f"socket.gaierror while uploading ping: '{url}' {e}")
            return UploadResult.RECOVERABLE_FAILURE(0)
        except OSError as e:
            log.debug(f"OSError while uploading ping: '{url}' {e}")
            return UploadResult.RECOVERABLE_FAILURE(0)
        except Exception as e:
            log.error(f"Unknown Exception while uploading ping: '{url}' {e}")
            return UploadResult.RECOVERABLE_FAILURE(0)

        status_code = response.status

        conn.close()

        return UploadResult.HTTP_STATUS(status_code)


__all__ = ["HttpClientUploader"]

Classes

class HttpClientUploader (*args, **kwargs)

The logic for uploading pings. This leaves the actual upload implementation to the user-provided delegate.

Expand source code
class HttpClientUploader(base_uploader.BaseUploader):
    # The timeout, in seconds, to use for all operations with the server.
    _DEFAULT_TIMEOUT = 10

    @classmethod
    def upload(
        cls, url: str, data: bytes, headers: Dict[str, str]
    ) -> Union[
        UploadResult,
        UploadResult.UNRECOVERABLE_FAILURE,
        UploadResult.RECOVERABLE_FAILURE,
        UploadResult.HTTP_STATUS,
    ]:
        """
        Synchronously upload a ping to a server.

        Args:
            url (str): The URL path to upload the data to.
            data (str): The serialized text data to send.
            headers (dict of (str, str)): HTTP headers to send.
        """
        parsed_url = urllib.parse.urlparse(url)
        if parsed_url.scheme == "http":
            conn = http.client.HTTPConnection(
                parsed_url.hostname or "",
                port=parsed_url.port or 80,
                timeout=cls._DEFAULT_TIMEOUT,
            )
        elif parsed_url.scheme == "https":
            conn = http.client.HTTPSConnection(
                parsed_url.hostname or "",
                port=parsed_url.port or 443,
                timeout=cls._DEFAULT_TIMEOUT,
            )
        else:
            # If we don't know the URL scheme, log an error and mark this as an unrecoverable
            # error, like if it were a malformed URL.
            log.error(f"Unknown URL scheme {parsed_url.scheme}")
            return UploadResult.UNRECOVERABLE_FAILURE(0)

        try:
            conn.request(
                "POST",
                parsed_url.path,
                body=data,
                headers=headers,
            )
            response = conn.getresponse()
        except http.client.InvalidURL as e:
            log.error(f"Could not upload telemetry due to malformed URL: '{url}' {e}")
            return UploadResult.UNRECOVERABLE_FAILURE(0)
        except http.client.HTTPException as e:
            log.debug(f"http.client.HTTPException while uploading ping: '{url}' {e}")
            return UploadResult.RECOVERABLE_FAILURE(0)
        except socket.gaierror as e:
            log.debug(f"socket.gaierror while uploading ping: '{url}' {e}")
            return UploadResult.RECOVERABLE_FAILURE(0)
        except OSError as e:
            log.debug(f"OSError while uploading ping: '{url}' {e}")
            return UploadResult.RECOVERABLE_FAILURE(0)
        except Exception as e:
            log.error(f"Unknown Exception while uploading ping: '{url}' {e}")
            return UploadResult.RECOVERABLE_FAILURE(0)

        status_code = response.status

        conn.close()

        return UploadResult.HTTP_STATUS(status_code)

Ancestors

Static methods

def upload(url: str, data: bytes, headers: Dict[str, str]) ‑> Union[glean._uniffi.glean.UploadResult, glean._uniffi.glean.UploadResult.UNRECOVERABLE_FAILURE, glean._uniffi.glean.UploadResult.RECOVERABLE_FAILURE, glean._uniffi.glean.UploadResult.HTTP_STATUS]

Synchronously upload a ping to a server.

Args

url : str
The URL path to upload the data to.
data : str
The serialized text data to send.

headers (dict of (str, str)): HTTP headers to send.

Expand source code
@classmethod
def upload(
    cls, url: str, data: bytes, headers: Dict[str, str]
) -> Union[
    UploadResult,
    UploadResult.UNRECOVERABLE_FAILURE,
    UploadResult.RECOVERABLE_FAILURE,
    UploadResult.HTTP_STATUS,
]:
    """
    Synchronously upload a ping to a server.

    Args:
        url (str): The URL path to upload the data to.
        data (str): The serialized text data to send.
        headers (dict of (str, str)): HTTP headers to send.
    """
    parsed_url = urllib.parse.urlparse(url)
    if parsed_url.scheme == "http":
        conn = http.client.HTTPConnection(
            parsed_url.hostname or "",
            port=parsed_url.port or 80,
            timeout=cls._DEFAULT_TIMEOUT,
        )
    elif parsed_url.scheme == "https":
        conn = http.client.HTTPSConnection(
            parsed_url.hostname or "",
            port=parsed_url.port or 443,
            timeout=cls._DEFAULT_TIMEOUT,
        )
    else:
        # If we don't know the URL scheme, log an error and mark this as an unrecoverable
        # error, like if it were a malformed URL.
        log.error(f"Unknown URL scheme {parsed_url.scheme}")
        return UploadResult.UNRECOVERABLE_FAILURE(0)

    try:
        conn.request(
            "POST",
            parsed_url.path,
            body=data,
            headers=headers,
        )
        response = conn.getresponse()
    except http.client.InvalidURL as e:
        log.error(f"Could not upload telemetry due to malformed URL: '{url}' {e}")
        return UploadResult.UNRECOVERABLE_FAILURE(0)
    except http.client.HTTPException as e:
        log.debug(f"http.client.HTTPException while uploading ping: '{url}' {e}")
        return UploadResult.RECOVERABLE_FAILURE(0)
    except socket.gaierror as e:
        log.debug(f"socket.gaierror while uploading ping: '{url}' {e}")
        return UploadResult.RECOVERABLE_FAILURE(0)
    except OSError as e:
        log.debug(f"OSError while uploading ping: '{url}' {e}")
        return UploadResult.RECOVERABLE_FAILURE(0)
    except Exception as e:
        log.error(f"Unknown Exception while uploading ping: '{url}' {e}")
        return UploadResult.RECOVERABLE_FAILURE(0)

    status_code = response.status

    conn.close()

    return UploadResult.HTTP_STATUS(status_code)

Inherited members