Skip to content

Tutorial Part 4: Create Client Wrapper

Jeremy Barlow edited this page Mar 27, 2018 · 6 revisions

The previous portion of this tutorial ("Create Application") walked through the steps necessary to create an application that exposes a Geolocation DXL service through the use of OpenDXL Bootstrap.

Additionally, the basic sample included with the generated application was updated to invoke the Geolocation service. The majority of code in the updated sample is shown below:

request_topic = "/mycompany/service/geolocation/host_lookup"
req = Request(request_topic)

# Create dictionary with host to lookup and response format 
req_dict = {"format": "json", "host": "mcafee.com"}

# Convert dictionary to JSON and set as DXL request payload
MessageUtils.dict_to_json_payload(req, req_dict)

res = client.sync_request(req, timeout=30)
if res.message_type is not Message.MESSAGE_TYPE_ERROR:
    print("Response for geolocation_service_hostlookup: '{0}'".format(MessageUtils.decode_payload(res)))
else:
    print("Error invoking service with topic '{0}': {1} ({2})".format(
        request_topic, res.error_message, res.error_code))

While not overly complex, there are several aspects of this code worth noting:

  • ~8 lines are required to invoke the service (with error checking)
  • The invoker must have knowledge of the DXL topic associated with the service (/mycompany/service/geolocation/host_lookup)
  • The invoker must have knowledge of the payload format for the DXL request message associated with the service (JSON format, the fields, values associated with fields, etc.)
  • Field values are not validated prior to sending to the server (valid output formats, etc.)
  • When invoking the service a reasonable timeout must be specified (based on the service being invoked)
  • Any errors resulting from the service invocation must be handled
  • The invoker must have knowledge of the payload format for the DXL response message

All of the items listed above can be eliminated through the use of a client wrapper. The purpose of a client wrapper is to allow users to access the features of a DXL fabric (services and events) without having to focus on lower-level details such as message topics, message formats, etc.

The remainder of this portion of the tutorial will focus on creating a client wrapper for the Geolocation service.

NOTE: The MessageUtils class which is included in the OpenDXL Bootstrap library is used throughout the code presented in this portion of the tutorial. MessageUtils provides several convenience methods for setting and retrieving the payload on DXL messages (Python dictionary to JSON payload, etc.).

For complete details of the MessageUtils class please refer to the Message Utilities Class Documentation.

Workspace Directory

The first step is to open a command prompt/shell in the workspace directory that was created in the previous portion of this tutorial (See, "Create Workspace Directory")

At this point the contents of the opendxl-geolocation directory should appear as follows:

opendxl-geolocation\
    app\
    application-template.config

Generate Client Wrapper

In this step the client wrapper will be generated by using the OpenDXL Bootstrap client template (client-template).

The Bootstrap application requires a configuration file that defines the properties of the client that is going to be generated. The properties for the client generated in this tutorial are shown below.

[Client]
name=geolocationclient
fullName=Geolocation Client
clientClassName=GeolocationClient
copyright=Copyright 2018
includeExampleMethod=yes

For complete details about the client-template configuration file and its related properties refer to this page.

In this particular case, a client class named GeoLocationClient will be generated. Additionally, the includeExampleMethod property is set to yes which will result in an example method being generated. This method will be modified later to include Geolocation-specific functionality.

Create client template configuration file

Create a file in the opendxl-geolocation directory named client-template.config. Paste the contents of the configuration listed above into this file and save it.

At this point the contents of the opendxl-geolocation directory should appear as follows:

opendxl-geolocation\
    app\
    application-template.config
    client-template.config

Run Bootstrap

At this point the OpenDXL Bootstrap application will be executed resulting in the Geolocation client being created.

To generate the client, execute the following command from the opendxl-geolocation directory.

python -m dxlbootstrap client-template client-template.config client

The command above creates a project using the client-template with the properties in the configuration file created previously (client-template.config). The output of the generation is placed in the client output directory. For complete details about the Bootstrap application command line and its related arguments refer to this page.

If the generation is successful, the message Generation succeeded will be displayed. The contents of the opendxl-geolocation directory should now appear as follows:

opendxl-geolocation\
    app\
    client\
    application-template.config
    client-template.config

Modify Client

In this step the Geolocation functionality will be integrated into the generated client.

Change to the client directory

At this point change to the generated client directory:

cd client

The contents of the client directory should appear as follows:

opendxl-geolocation\
    client\
        dist.py
        doc\
        geolocationclient\
        LICENSE
        MANIFEST.in
        README
        README.md
        sample\
        setup.py

The geolocationclient directory contains the Python code for the generated client.

Modify GeolocationClient class

In this step the Geolocation client class (GeolocationClient) will be modified to invoke the Geolocation DXL service (created in the previous section of this tutorial).

NOTE: The generated GeolocationClient class derives from the Client class that is included in the OpenDXL Bootstrap library. Some of the methods from this base class will be utilized in the following steps.

For complete details of the base Client class please refer to the Base Client Class Documentation.

Edit client Python file

Open the client.py file within the geolocationclient directory for editing. This file contains the GeolocationClient class.

Add supported output formats

Add the list of valid output formats to the top of the class definition as shown below:

class GeolocationClient(Client):
    """
    The "Geolocation Client" client wrapper class.
    """

    # The valid output formats
    OUT_FORMATS = ["json", "csv", "xml", "dict"]

    ...

This allows users of the client to determine the list of output formats that are supported by its methods.

The dict output format has been added to the list of formats supported by the Geolocation web service (json, csv, and xml). The dict output format will result in a Python dictionary being returned from the invoked method.

Add Lookup method

Replace the entire my_example_method implementation:

def my_example_method(self):

    ...

    return MessageUtils.json_payload_to_dict(response)    

With the following method (host_lookup):

def host_lookup(self, host, out_format="dict"):
    """
    Performs Geolocation lookup for specified host

    :param host: The host to lookup
    :param out_format: The output format
    :return: The result of the lookup
    """
    # Validate specified output format
    if out_format not in GeolocationClient.OUT_FORMATS:
        raise Exception("Unknown format: {0}".format(out_format))

    # Create the DXL request message
    request = Request("/mycompany/service/geolocation/host_lookup")

    # Set the payload on the request message (Python dictionary to JSON payload)
    MessageUtils.dict_to_json_payload(
        request,
        {
            "host": host,
            "format": ("json" if out_format is "dict" else out_format)
        })

    # Perform a synchronous DXL request
    response = self._dxl_sync_request(request)

    if out_format is "dict":
        return MessageUtils.json_payload_to_dict(response)
    else:
        return MessageUtils.decode_payload(response)

Details regarding the host_lookup method are listed below:

  • The method has two parameters:
    • host: The host or IP address for which to lookup Geolocation information.
    • out_format: The output format to return from the method. The OUT_FORMATS list contains the possible values for this parameter. The default output format is dict which results in a Python dictionary being returned.
  • The specified out_format is validated against the list of supported OUT_FORMATS. If an invalid output format is specified an Exception is raised.
  • A DXL request message is created with the topic associated with the Geolocation DXL service (/mycompany/service/geolocation/host_lookup).
  • A Python dictionary (dict) is created and with the host and out_format specified. This dictionary is passed to the MessageUtils utility class which converts the Python dictionary to JSON and sets it on the payload of the DXL request message. If the out_format specified is dict the output format sent to the Geolocation DXL service is json (the JSON output will be converted to a Python dictionary within the client prior to returning).
  • The Geolocation DXL service is invoked by calling the _dxl_sync_request method which is defined in the base Client class (see Base Client Class Documentation). The _dxl_sync_request method contains the details for checking whether an error has occurred and raising an appropriate Exception. It also utilizes the response_timeout that is associated with the client instance when making the request.
  • The payload of the received DXL response message is decoded and returned to the invoker of the host_lookup method. If the output format is dict the payload is converted from JSON to a Python dictionary (dict) and returned.

The important point to note about this method is that it hides the lower-level DXL details such as message topics, message formats, etc. from the user of the method in addition to providing client-side validation of parameters.

Modify Sample

In this step the Geolocation functionality will be integrated into the basic sample that is included with the generated client.

Edit Basic Sample file

Open the basic_sample.py file within the sample\basic directory for editing.

Change the following code in the sample from:

# Invoke the example method
resp_dict = client.my_example_method()

To the following:

host = "mcafee.com"
resp_dict = client.host_lookup(host)

The changes above achieve the following:

  • The host_lookup method is invoked with the host mcafee.com. Since an out_format is not specified, the output format will default to a Python dictionary (dict).

Next, after the statement that prints out the response from the initial host_lookup invocation:

# Print out the response (convert dictionary to JSON for pretty printing)
print("Response:\n{0}".format(
    MessageUtils.dict_to_json(resp_dict, pretty_print=True)))

Add the following lines:

print("\nCSV:\n{0}".format(client.host_lookup(host, out_format="csv")))
print("\nXML:\n{0}".format(client.host_lookup(host, out_format="xml")))
print("\nJSON:\n{0}".format(client.host_lookup(host, out_format="json")))

The changes above achieve the following:

  • The host_lookup method is invoked with the host mcafee.com for each possible output format (csv, xml, and json). The results for each of these lookup invocations is displayed.

Run Sample

In this step, the Geolocation client basic sample will be executed.

Ensure the application that exposes the Geolocation service is running (see "Run Application", above).

A new command prompt/shell will need to be opened for executing the sample.

Populate DXL client configuration file for samples

Prior to running the sample, the dxlclient.config file located in the sample directory of the client must be modified to include the information necessary to connect to the DXL fabric.

The steps to populate this configuration file are the same as those documented in the OpenDXL Python SDK, see the OpenDXL Python SDK Samples Configuration page for more information.

Run basic sample

To run the basic sample, execute the following command from within the client directory (opendxl-geolocation\client):

python sample\basic\basic_sample.py

The output should appear similar to the following:

Response:
{
    "city": "Santa Clara",
    "country_code": "US",
    "country_name": "United States",
    "ip": "161.69.29.243",
    "latitude": 37.3961,
    "longitude": -121.9617,
    "metro_code": 807,
    "region_code": "CA",
    "region_name": "California",
    "time_zone": "America/Los_Angeles",
    "zip_code": "95054"
}

CSV:
161.69.29.243,US,United States,CA,California,Santa Clara,95054,America/Los_Angeles,37.3961,-121.9617,807

XML:
<Response>
        <IP>161.69.29.243</IP>
        <CountryCode>US</CountryCode>
        <CountryName>United States</CountryName>
        <RegionCode>CA</RegionCode>
        <RegionName>California</RegionName>
        <City>Santa Clara</City>
        <ZipCode>95054</ZipCode>
        <TimeZone>America/Los_Angeles</TimeZone>
        <Latitude>37.3961</Latitude>
        <Longitude>-121.9617</Longitude>
        <MetroCode>807</MetroCode>
</Response>

JSON:
{"ip":"161.69.29.243","country_code":"US","country_name":"United States",
"region_code":"CA","region_name":"California","city":"Santa Clara",
"zip_code":"95054","time_zone":"America/Los_Angeles","latitude":37.3961,"longitude":-121.9617,"metro_code":807}

The output above contains the response for each of the four invocations of the Geolocation DXL service. The first invocation is the JSON representation of the dictionary (dict) that was returned from the host_lookup method.

In summary, the client wrapper has reduced the approximately eight lines of code required to make a Geolocation DXL service invocation to one. Additionally, the users of this client are not exposed to the lower-level details of Geolocation service-specific DXL topics and message formats.

Continue to next step, Packaging for distribution

Clone this wiki locally