Service Wrapper Sample ====================== Prior to running this sample make sure you have completed the samples configuration step (:doc:`sampleconfig`). This sample demonstrates how simple it is to wrap an existing service and expose it on the DXL fabric. In this particular case, the `OpenWeatherMap `_ "current weather" data is exposed as a DXL service. This service wrapper delegates to the `OpenWeatherMap REST API `_. OpenWeather API key ******************* Before running this sample you must obtain an "API key" from OpenWeatherMap: http://openweathermap.org/appid Once you receive the API key, you must replace it in the ``openweather_common.py`` file as shown below: .. code-block:: python # The API key for invoking the weather service API_KEY = "01234567890123456789012345678901" Service Wrapper *************** The first step is to start the "service wrapper". This script will remain running and receive :class:`dxlclient.message.Request` messages from clients that are querying weather information. To start the "service wrapper", execute the ``sample\servicewrapper\openweather_service_wrapper.py`` script as follows: .. parsed-literal:: c:\\dxlclient-python-sdk-\ |version|\>python sample\\servicewrapper\\openweather_service_wrapper.py The output should appear similar to the following: .. code-block:: python 2016-10-11 12:58:43,065 dxlclient.client - INFO - Waiting for broker list... 2016-10-11 12:58:43,065 dxlclient.client - INFO - Checking brokers... 2016-10-11 12:58:43,066 dxlclient.client - INFO - Trying to connect... 2016-10-11 12:58:43,066 dxlclient.client - INFO - Trying to connect to broker {Unique id: {284bca2e-79e1-11e6-159d-005056812aa3}, Host name: 10.84.200.124, Port: 8883}... 2016-10-11 12:58:43,315 dxlclient.client - INFO - Connected to broker {284bca2e-79e1-11e6-159d-005056812aa3} 2016-10-11 12:58:43,315 dxlclient.client - INFO - Launching event loop... 2016-10-11 12:58:43,316 dxlclient.client - INFO - Connected with result code 0 2016-10-11 12:58:43,316 dxlclient.client - INFO - Subscribing to /mcafee/client/{5b18f1cc-adac-4f20-88d9-c23d9c531ada} 2016-10-11 12:58:43,329 dxlclient.client - INFO - Message received for topic /mcafee/client/{5b18f1cc-adac-4f20-88d9-c23d9c531ada} 2016-10-11 12:58:43,335 __main__ - INFO - Weather service is running... The majority of code for the service wrapper is shown below: .. code-block:: python # The OpenWeatherMap "Current Weather" URL (see http://openweathermap.org/current) CURRENT_WEATHER_URL = "http://api.openweathermap.org/data/2.5/weather?{0}&APPID={1}" # The "current weather" topic SERVICE_CURRENT_WEATHER_TOPIC = "/openweathermap/service/openweathermap/current" # Create the client with DxlClient(config) as client: # Connect to the fabric client.connect() # # Register the service # # Create "Current Weather" incoming request callback class CurrentWeatherCallback(RequestCallback): def on_request(self, request): try: # Extract information from request query = request.payload.decode(encoding="UTF-8") logger.info("Service received request payload: " + query) # Send HTTP request to OpenWeatherMap req = URLRequest( CURRENT_WEATHER_URL.format(query, API_KEY), None, {'Content-Type': 'text/json'}) f = urlopen(req) weather_response = f.read() f.close() # Create the response message response = Response(request) # Populate the response payload response.payload = weather_response # Send the response client.send_response(response) except Exception as ex: print str(ex) # Send error response client.send_response(ErrorResponse( request, error_message=str(ex).encode(encoding="UTF-8"))) # Create service registration object info = ServiceRegistrationInfo(client, SERVICE_NAME) # Add a topic for the service to respond to info.add_topic(SERVICE_CURRENT_WEATHER_TOPIC, CurrentWeatherCallback()) # Register the service with the fabric (wait up to 10 seconds for registration to complete) client.register_service_sync(info, 10) logger.info("Weather service is running...") # Wait forever while True: time.sleep(60) The service wrapper registers a :class:`dxlclient.callbacks.RequestCallback` that will be invoked when "current weather" query :class:`dxlclient.message.Request` messages are received. The actual query (which can be weather by zip code, location, city, etc.) is extracted from the :class:`dxlclient.message.Request` message's :attr:`dxlclient.message.Message.payload` attribute. The `OpenWeatherMap REST API `_ is invoked via HTTP with the query that was received in the DXL request message's payload. A DXL :class:`dxlclient.message.Response` message is created with a payload containing the result of invoking the `OpenWeatherMap REST API `_ and sent back to the invoking DXL client via the :func:`dxlclient.client.DxlClient.send_response` method of the :class:`dxlclient.client.DxlClient` instance. Service Invoker *************** The next step is to execute the "service invoker". This script must be executed in a separate command prompt (or shell), leaving the "service wrapper" running. To start the "service invoker", execute the ``sample\servicewrapper\openweather_service_invoker.py`` script as follows: .. parsed-literal:: c:\\dxlclient-python-sdk-\ |version|\>python sample\\servicewrapper\\openweather_service_invoker.py The output should appear similar to the following (query for the current weather for zip code 97140): .. code-block:: python 2016-10-11 13:20:50,565 dxlclient.client - INFO - Waiting for broker list... 2016-10-11 13:20:50,566 dxlclient.client - INFO - Checking brokers... 2016-10-11 13:20:50,568 dxlclient.client - INFO - Trying to connect... 2016-10-11 13:20:50,569 dxlclient.client - INFO - Trying to connect to broker {Unique id: {284bca2e-79e1-11e6-159d-005056812aa3}, Host name: 10.84.200.124, Port: 8883}... 2016-10-11 13:20:50,808 dxlclient.client - INFO - Connected to broker {284bca2e-79e1-11e6-159d-005056812aa3} 2016-10-11 13:20:50,808 dxlclient.client - INFO - Launching event loop... 2016-10-11 13:20:50,809 dxlclient.client - INFO - Connected with result code 0 2016-10-11 13:20:50,809 dxlclient.client - INFO - Subscribing to /mcafee/client/{383105c5-17f7-4b40-bb90-7ed17bf3f315} 2016-10-11 13:20:51,336 dxlclient.client - INFO - Message received for topic /mcafee/client/{383105c5-17f7-4b40-bb90-7ed17bf3f315} Client received response payload: { "base": "stations", "clouds": { "all": 0 }, "cod": 200, "coord": { "lat": 45.36, "lon": -122.84 }, "dt": 1476216689, "id": 5751632, "main": { "grnd_level": 1010.59, "humidity": 66, "pressure": 1010.59, "sea_level": 1034.15, "temp": 287.158, "temp_max": 287.158, "temp_min": 287.158 }, "name": "Sherwood", "sys": { "country": "US", "message": 0.171, "sunrise": 1476195849, "sunset": 1476235829 }, "weather": [ { "description": "clear sky", "icon": "01d", "id": 800, "main": "Clear" } ], "wind": { "deg": 83.0013, "speed": 3.1 } } The majority of code for the service invoker is shown below: .. code-block:: python # The "current weather" topic SERVICE_CURRENT_WEATHER_TOPIC = "/openweathermap/service/openweathermap/current" # Create the "Current Weather" request req = Request(SERVICE_CURRENT_WEATHER_TOPIC) # Populate the request payload # Examples include: # By ZIP code: zip=97140,us # By geographic coordinates: lat=35&lon=139 # By city name: q=London,uk req.payload = "zip=97140,us".encode() # Send the request and wait for a response (synchronous) res = client.sync_request(req) # Extract information from the response (if an error did not occur) if res.message_type != Message.MESSAGE_TYPE_ERROR: response_dict = json.loads(res.payload.decode(encoding="UTF-8")) print("Client received response payload: \n" + \ json.dumps(response_dict, sort_keys=True, indent=4, separators=(',', ': '))) else: logger.error("Error: %s (%s)", res.error_message, res.error_code) A DXL :class:`dxlclient.message.Request` message is created and its payload is set to the query (zip code, location, city, etc.) to perform against the `OpenWeatherMap REST API `_. A synchronous request is sent to the DXL service via the :func:`dxlclient.client.DxlClient.sync_request` method of the :class:`dxlclient.client.DxlClient` instance. The results of the query are extracted from the :class:`dxlclient.message.Response` that was received and displayed.