How To Create A Backend Service

A quick summary of How To Create A Backend Service

Create a backend service

A backend service is a collection of Python files without a direct user interface (see Yoeman-Generator). We will show this by creating a REST service.

Prerequisites

This tutorial requires you to complete the following steps / tutorials:

Summary

cd <workspace_directory>
cd myserverplugin
yo phovea:add-extension
# type: namespace, id: hello-rest, filename: hello_rest
# extras: namespace=/api/hello_rest
# change myserverplugin/hello_rest.py according to the snippet below
# start/restart server
# access http://localhost:8080/api/hello_rest
# or
# access http://localhost:9000/api/hello_rest

Tutorial

Phovea allows to register additional REST namespaces via the namespace extension point. During startup the server looks up all extensions of this extension point and reserves a namespace for it. A namespace in this context is a unique server URL prefix, such as api/hello_world. By convention all server namespaces have to start with /api followed by unique name. The TDP core itself registers a new REST namespace under /api/tdp. Under this namespace all TDP core REST services are located.

REST services are implemented using the Flask framework. Flask allows to easily map HTTP URLs to python functions. In addition, for convenience Phovea is wrapping and reexporting methods from Flask avoiding a direct dependency on it. Therefore, the important module import reduces to phovea_server.ns.

Register namespace extensions

As for every extension, we first need to register a new extension using the Phovea Yeoman generator.

cd <workspace_directory>
cd myserverplugin
yo phovea:add-extension

The extension type is namespace. ID something like hello_rest and the filename should be hello_rest. Following extra parameter are needed:

namespace=/api/hello_rest

[[images/phovea_rest_api/image1.png]]

In this example, we reserve the namespace /api/hello_rest such that all HTTP requests starting with /api/hello_rest are redirect to the newly created myserverplugin/hello_rest.py plugin. In addition, the URL is truncated such that the extension doesn’t have to consider the /api/hello_rest prefix. Thus /api/hello_rest/test will be mapped to /test.

The content of myserverplugin/hello_rest.py should be replaced with the following snippet

from phovea_server.ns import Namespace
from phovea_server.util import jsonify
import logging

app = Namespace(__name__)
_log = logging.getLogger(__name__)


@app.route('/')
def _hello():
 return jsonify({
   'message': 'Hello Rest'
 })


def create():
 return app

At the beginning the phovea_server.ns namespace module is imported which is Phovea wrapper around Flask. In addition the jsonify method of phovea_server.util is used to convert a Python dictionary/array to a JSON compatible HTTP response. Phovea’s internal jsonify method is an improved method of Flasks internal jsonify method providing support for serializing numpy and pandas objects, too. In addition the fast ujson module is used to serialize the data resulting in a performance improvement.

Then, a namespace application object is created being the central management tool for the REST api. The app instance is returned as part of the create method. The create method is used by Phovea as the central point to instantiate a new Python extension. The Namespace class is a wrapper around Flask allowing to use the same methods and annotations to register new URL matching pattern. In this example, a simple pattern matching the root of the REST API is implemented. It returns a simple JSON object containing a simple hello rest message.

Testing the REST API

The newly created REST API can be tested via two different URLs. First via http://localhost:8080/api/hello_rest. Second via http://localhost:9000/api/hello_rest

Both return the same result:
[[images/phovea_rest_api/image3.png]]
[[images/phovea_rest_api/image4.png]]

The difference is the following. Internally the Phovea server running within a docker container is accessible via port 9000. In addition, the internal development test server started using npm start:... has a configured proxy such that all requests starting with /api/ are internally proxied to the Phovea server running at port 9000. Thus, http://localhost:8080/api/hello_rest will internally be proxied to http://localhost:9000/api/hello_rest. The proxy within the development server as well as the deployed NGINX server is used to avoid the need for cross-origin resource sharing (CORS). Usually when a website is requesting services from another domain such as port 8080 is using services from port 9000 the target server has to explicitly allow this source domain, otherwise the web browser blocks for security reasons the operation. However, by using a proxy both REST API and the website itself are coming from the same domain avoiding this problem.

Using Request and Path parameters

Usually REST API use variable paths and request arguments to generate the response. The following snippet show a simple example how to access request parameters as well as do URL matching. More information can be found in the Flask documentation.

from phovea_server.ns import Namespace, request, abort

# ...

@app.route('/greet/<name>')
def _greet(name):
 lang = request.values.get('lang', 'en')
 template = ''
 if lang == 'en':
   template = 'Hello {name}'
 elif lang == 'de':
   template = 'Hallo {name}'
 elif lang == 'es':
   template = 'Hola {name}'
 else:
   abort(400, 'invalid language: ' + lang)

 return jsonify({
   'message': template.format(name=name)
 })


# ...

def create():
 return app

First, we have to import additional elements from the phovea_server.ns module: request for accessing request parameters and abort for aborting requests and returning HTTP error codes.

The <name> syntax is used to perform URL matching. In this case the next URL part after /greet/ should be provided as a function argument using the name name. For example, /api/hello_rest/greet/datavisyn will result in executing the _greet method with name=datavisyn as argument.

request.values provides access to both GET and POST arguments. Internally it is a Multidict having the same interface as a regular dictionary with additions for handling multiple values for one key. The .get(name, default_value) allows to provide default values for a dictionary as opposed to [name] which will result in an exception in case the key doesn’t exist. The greeting method accepts the lang parameter which specifies in which language the name should be greeted. In case of an unknown language abort(http_error_code, error_message’) is used to abort the request and return the HTTP 400 status code = Bad Request.

Testing REST API 2

[[images/phovea_rest_api/image7.png]]
[[images/phovea_rest_api/image6.png]]
[[images/phovea_rest_api/image2.png]]


Expert knowledge

Flask URL matching logic has several options to specify the type of the path element, for example, <test:int> will automatically parse the matching URL part as integer. <test:path> is a variant of a string that also allows / as part of the pattern, such that /greet/<name:path> with /greet/tom/jones result in name=tom/jones without the :path annotation, Flask will throw an error


Conclusion

Lessons learned:

  • How to register a new REST API using the namespace extension
  • How to test the REST API via two different URLs
  • How to access Request parameters and perform URL matching