Skip to content

Proxy

Proxy adapter for use in odin-control.

This module implements a simple proxy adapter, allowing requests to be proxied to one or more remote HTTP resources, typically further odin-control instances.

Tim Nicholls, Ashley Neaves, Josh Harris STFC Detector Systems Software Group.

ProxyAdapter

Bases: ApiAdapter, BaseProxyAdapter

Proxy adapter class for odin-control.

This class implements a proxy adapter, allowing odin-control to forward requests to other HTTP services.

Source code in src/odin_control/adapters/proxy.py
class ProxyAdapter(ApiAdapter, BaseProxyAdapter):
    """
    Proxy adapter class for odin-control.

    This class implements a proxy adapter, allowing odin-control to forward requests to
    other HTTP services.
    """

    version = __version__

    def __init__(self, **kwargs):
        """
        Initialise the ProxyAdapter.

        This constructor initialises the adapter instance. The base class adapter is initialised
        with the keyword arguments and then the proxy targets and paramter tree initialised by the
        proxy adapter mixin.

        :param kwargs: keyword arguments specifying options
        """
        # Initialise the base class
        super(ProxyAdapter, self).__init__(**kwargs)

        requests_log = logging.getLogger("urllib3")
        requests_log.setLevel(logging.INFO)

        # Initialise the proxy targets and parameter trees
        self.initialise_proxy(ProxyTarget)

    @response_types("application/json", default="application/json")
    def get(self, path, request):
        """
        Handle an HTTP GET request.

        This method handles an HTTP GET request, returning a JSON response. The request is
        passed to the adapter proxy and resolved into responses from the requested proxy targets.

        :param path: URI path of request
        :param request: HTTP request object
        :return: an ApiAdapterResponse object containing the appropriate response
        """
        get_metadata = wants_metadata(request)

        self.proxy_get(path, get_metadata)
        (response, status_code) = self._resolve_response(path, get_metadata)

        return ApiAdapterResponse(response, status_code=status_code)

    @request_types("application/json", "application/vnd.odin-native")
    @response_types("application/json", default="application/json")
    def put(self, path, request):
        """
        Handle an HTTP PUT request.

        This method handles an HTTP PUT request, returning a JSON response. The request is
        passed to the adapter proxy to set data on the remote targets and resolved into responses
        from those targets.

        :param path: URI path of request
        :param request: HTTP request object
        :return: an ApiAdapterResponse object containing the appropriate response
        """
        # Decode the request body from JSON, handling and returning any errors that occur. Otherwise
        # send the PUT request to the remote target
        try:
            body = decode_request_body(request)
        except (TypeError, ValueError) as type_val_err:
            response = {
                "error": "Failed to decode PUT request body: {}".format(
                    str(type_val_err)
                )
            }
            status_code = 415
        else:
            self.proxy_set(path, body)
            (response, status_code) = self._resolve_response(path)

        return ApiAdapterResponse(response, status_code=status_code)

__init__(**kwargs)

Initialise the ProxyAdapter.

This constructor initialises the adapter instance. The base class adapter is initialised with the keyword arguments and then the proxy targets and paramter tree initialised by the proxy adapter mixin.

Parameters:

Name Type Description Default
kwargs

keyword arguments specifying options

{}
Source code in src/odin_control/adapters/proxy.py
def __init__(self, **kwargs):
    """
    Initialise the ProxyAdapter.

    This constructor initialises the adapter instance. The base class adapter is initialised
    with the keyword arguments and then the proxy targets and paramter tree initialised by the
    proxy adapter mixin.

    :param kwargs: keyword arguments specifying options
    """
    # Initialise the base class
    super(ProxyAdapter, self).__init__(**kwargs)

    requests_log = logging.getLogger("urllib3")
    requests_log.setLevel(logging.INFO)

    # Initialise the proxy targets and parameter trees
    self.initialise_proxy(ProxyTarget)

get(path, request)

Handle an HTTP GET request.

This method handles an HTTP GET request, returning a JSON response. The request is passed to the adapter proxy and resolved into responses from the requested proxy targets.

Parameters:

Name Type Description Default
path

URI path of request

required
request

HTTP request object

required

Returns:

Type Description

an ApiAdapterResponse object containing the appropriate response

Source code in src/odin_control/adapters/proxy.py
@response_types("application/json", default="application/json")
def get(self, path, request):
    """
    Handle an HTTP GET request.

    This method handles an HTTP GET request, returning a JSON response. The request is
    passed to the adapter proxy and resolved into responses from the requested proxy targets.

    :param path: URI path of request
    :param request: HTTP request object
    :return: an ApiAdapterResponse object containing the appropriate response
    """
    get_metadata = wants_metadata(request)

    self.proxy_get(path, get_metadata)
    (response, status_code) = self._resolve_response(path, get_metadata)

    return ApiAdapterResponse(response, status_code=status_code)

put(path, request)

Handle an HTTP PUT request.

This method handles an HTTP PUT request, returning a JSON response. The request is passed to the adapter proxy to set data on the remote targets and resolved into responses from those targets.

Parameters:

Name Type Description Default
path

URI path of request

required
request

HTTP request object

required

Returns:

Type Description

an ApiAdapterResponse object containing the appropriate response

Source code in src/odin_control/adapters/proxy.py
@request_types("application/json", "application/vnd.odin-native")
@response_types("application/json", default="application/json")
def put(self, path, request):
    """
    Handle an HTTP PUT request.

    This method handles an HTTP PUT request, returning a JSON response. The request is
    passed to the adapter proxy to set data on the remote targets and resolved into responses
    from those targets.

    :param path: URI path of request
    :param request: HTTP request object
    :return: an ApiAdapterResponse object containing the appropriate response
    """
    # Decode the request body from JSON, handling and returning any errors that occur. Otherwise
    # send the PUT request to the remote target
    try:
        body = decode_request_body(request)
    except (TypeError, ValueError) as type_val_err:
        response = {
            "error": "Failed to decode PUT request body: {}".format(
                str(type_val_err)
            )
        }
        status_code = 415
    else:
        self.proxy_set(path, body)
        (response, status_code) = self._resolve_response(path)

    return ApiAdapterResponse(response, status_code=status_code)

ProxyTarget

Bases: BaseProxyTarget

Proxy adapter target class.

This class implements a proxy target, its parameter tree and associated status information for use in the ProxyAdapter.

Source code in src/odin_control/adapters/proxy.py
class ProxyTarget(BaseProxyTarget):
    """
    Proxy adapter target class.

    This class implements a proxy target, its parameter tree and associated status information
    for use in the ProxyAdapter.
    """

    def __init__(self, name, url, request_timeout):
        """
        Initialise the ProxyTarget object.

        This constructor initialises the ProxyTarget, delegating the full initialisation to the
        base class and then populating data and metadata from the remote target

        :param name: name of the proxy target
        :param url: URL of the remote target
        :param request_timeout: request timeout in seconds
        """

        # Initialise the base class
        super(ProxyTarget, self).__init__(name, url, request_timeout)

        # Initialise the data and metadata trees from the remote target
        self.remote_get()
        self.remote_get(get_metadata=True)

    def _send_request(self, request, path, get_metadata=False):
        """
        Send a request to the remote target and update data.

        This internal method sends a request to the remote target using the requests library and
        handles the response, updating target data accordingly.

        :param request: HTTP request to transmit to target
        :param path: path of data being updated
        :param get_metadata: flag indicating if metadata is to be requested
        """

        # Send the request to the remote target, handling any exceptions that occur
        try:
            response = requests.request(
                method=request.method,
                url=request.url,
                headers=request.headers,
                timeout=request.timeout,
                data=request.data,
            )

            # If an error status code was returned from the server, raise an exception for handling
            # below
            response.raise_for_status()

            # Construct a proxy response object for processing
            proxy_response = ProxyResponse(
                status_code=response.status_code, body=response.content
            )

        except requests.exceptions.RequestException as error:

            # Map the various requests exception types to the appropriate status code
            if isinstance(error, requests.exceptions.ConnectionError):
                status_code = 502
            elif isinstance(error, requests.exceptions.Timeout):
                status_code = 408
            else:
                status_code = error.response.status_code

            # Construct a proxy error object for processing
            proxy_response = ProxyError(
                status_code=status_code, error_string=str(error)
            )

        except Exception as error:

            # Handle a general exception as a server error
            proxy_response = ProxyError(status_code=500, error_string=str(error))

        # Process the response from the target, updating data as appropriate
        self._process_response(proxy_response, path, get_metadata)

__init__(name, url, request_timeout)

Initialise the ProxyTarget object.

This constructor initialises the ProxyTarget, delegating the full initialisation to the base class and then populating data and metadata from the remote target

Parameters:

Name Type Description Default
name

name of the proxy target

required
url

URL of the remote target

required
request_timeout

request timeout in seconds

required
Source code in src/odin_control/adapters/proxy.py
def __init__(self, name, url, request_timeout):
    """
    Initialise the ProxyTarget object.

    This constructor initialises the ProxyTarget, delegating the full initialisation to the
    base class and then populating data and metadata from the remote target

    :param name: name of the proxy target
    :param url: URL of the remote target
    :param request_timeout: request timeout in seconds
    """

    # Initialise the base class
    super(ProxyTarget, self).__init__(name, url, request_timeout)

    # Initialise the data and metadata trees from the remote target
    self.remote_get()
    self.remote_get(get_metadata=True)