RAPP Platform Wiki
v0.6.0
RAPP Platform is a collection of ROS nodes and back-end processes that aim to deliver ready-to-use generic services to robots
|
RAPP Web Services are implemented on top of the hop.js framework.
Hop.js is a multitier extension of JavaScript. It allows a single JavaScript program to describe the client-side and the server-side components of a Web application.
Its runtime environment ensures a consistent execution of the application on the server and on the client. Hop programs execute in the context of a builtin web server. They define services, which are super JavaScript functions that get automatically invoked when HTTP requests are received.
A framework has been developed, build ontop of hop.js, for easily implementing Web Services for the RAPP Platform. Zero knowledge of hop.js is required. Additionally, Web Services are fully parametrized through single configuration files:
If you are not familiar with server-side applications, you might also have to read on Nodejs.
For easily implementing and launching Web Services for the RAPP Platform, the RAPP Web Services framework has been developed. It consists of a WebService implementation. build ontop of hop.js and an engine that allows to assign specific Web Services to Worker threads (Web Workers).
Web Services are implemented in a single function implementation:
```js function svcImpl(req, resp, ros){ // Web service implementation here. } ```
This is the onrequest callback function to feed to the engine and will be called as soon as a request arrives.
The req (request) and res (response) objects are passed so you can access the request properties and craft and return responses. Additionally a ros object is passed that allows connections to ROS thought the rosbridge-websocket transport layer.
The req
object has the following properties:
header
: Request header.```js console.log(req.header)
{ host: 'localhost:9001', content-length: 679, accept-encoding: 'gzip, deflate', accept: '*/*', user-agent: 'rapp-platform-api/python', accept-token: 'rapp_token', connection: 'keep-alive', content-type: 'multipart/form-data; boundary=595d1046de7f4c958ca662c37140215a' }
```
socket
: Connection socket```js console.log(req.socket)
{ hostname: 'localhost', hostAddress: '127.0.0.1', localAddress: '127.0.0.1', port: 43661 }
```
body
: Request body```js console.log(req.body)
{ fast: true }
```
files
: In case of uploading files, this field contains the paths to the uploaded files. Access the files by name using dot notation. For example a service receives a single-file in fieldname single_file
and an array of files in fieldname file_array
:```js console.log(req.files)
{ single_file: ["PATH"], file_array: ["PATH_FILE_1", "PATH_FILE_2"] }
```
Note: Note that even if it is a single-file, the single_file
property of req.files
(req.files.single_file
) is an Array.
username
: This is the username of the client that requested access to the RAPP Platform resources (Services). It is automatically applied to the req
object, after appliance of the RAPP Authentication on request arrival. Note that before execution of the onrequest callback function, we apply authentication to the request. If the authentication is not successful, an HTTP 401 Unauthorized error is returned to the client.```js console.log(req.username)
"RAPP_USER"
```
The resp
object has the following properties (methods):
```js function svcImpl(req, resp, ros){ ...
var response = {error: ''}; resp.sendJson(response); } ```
sendServerError()
: Respond with HTTP 500 Internal Server Error```js function svcImpl(req, resp, ros){ ...
resp.sendServerError(); } ```
sendUnauthorized()
: Respond with HTTP 401 Unauthorized Client Error```js function svcImpl(req, resp, ros){ ...
resp.sendUnauthorized(); } ```
Web Services are fully parametrized through the services.json file. This file includes Web Services to be launched (along with the Web Service parameters), that was previously declared in the workers.json file.
Below is the face_detection entry:
```js "face_detection": { "launch": true, "anonymous": false, "name": "face_detection", "url_name": "face_detection", "namespace": "", "ros_connection": true, "timeout": 45000 } ```
Web Service configuration parameters:
launch
(Boolean): If true this Web Service will be launched.anonymous
(Boolean): If true, this service will be anonymous, which means that it will be assigned a random url path.name
(String): The service name.urlname
(String): The service urlname. Service name can be different from the urlname.namespace
(String): Namespace for the urlname to append as a prefix to the service url name. For example, a service with urlname="faca_detection" and namespace="computervision" will be translated to **/computervision/face_detection**timeout
(String): Request timeout value.ros_connection
(Boolean): If true, a ros object that allowes for calls to the ROS Services will be passed to the onrequest callback function.Web services run within server-side workers (Web Workers). A worker can include more than one web service. We consider server-side workers to be forked processes, thus allowing concurrent execution.
To run a Web Service within a Web Worker, just specify the service name in the services field of the worker in the worker.json file.
For example, the weather_report worker holds the weather_report_current and weather_report_forecast Web Services:
```js "weather_report": { "launch": true, "path": "workers/weather_report.js", "services": [ "weather_report_forecast", "weather_report_current" ] } ```
Web Worker configuration parameters:
launch
(Boolean): Weather to launch the Web Worker or not.path
(String): Path to the Web Worker source file. Relative to the rapp_web_services directoryservices
: (Array): Services to launch under the Web Worker thread.Source files are stored under the services directory, of the rapp_web_services package.
Web Services are automatically loaded from single .js files, as node.js modules. Make sure you export the Web Service implementation function:
```js function svcImpl(req, resp, ros){ // Web service implementation here. }
...
module.exports = svcImpl;
```
The following example illustrates the implementation of a WebService that connects to the Face-Detection RAPP-Platform backend Service
ROS Service Message: ```bash
Header header
string imageFilepath
geometry_msgs/PointStamped[] faces_up_left geometry_msgs/PointStamped[] faces_down_right string error ```
and the Face-Detection ROS Service url path is: /rapp/rapp_face_detection/detect_faces
Web Service Request:
file
: Image file.fast
(Bool): If true, detection will take less time but it will be less accurate.Web Service Response:
faces
(Array): Detected faces.error
(String): Error message.First, create the face_detection svc.js
file:
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_web_services/services $ mkdir face_detection && cd face_detection $ touch svc.js ```
Open the svc.js file with your favorite editor:
```bash $ vim svc.js ```
Implement the structure of the client-response
and ros-msg
objects:
```js var clientRes = function(faces, error) { return { faces: [], error: '' } }
var rosReqMsg = function(imageFilepath, fast) { return { imageFilepath: '', fast: false } } ```
Assign ROS Service url path to a global variable:
```js var rosSrvUrlPath = "/rapp/rapp_face_detection/detect_faces"; ```
Next, implement the service onrequest callback function:
```js function svcImpl(req, resp, ros) { // If no image file received, return to client with an error if( ! req.files.file ){ // Create a client response object response = new client_res(); response.error = "No image file received"; // Send response (application/json) resp.sendJson(response); return; }
// Create a ROS Service Request Message and fill values from client request var rosMsg = new rosReqMsg(); rosMsg.imageFilename = req.files.file[0]; rosMsg.fast = req.body.fast;
/***
/***
/***
function parseRosbridgeMsg( rosbridge_msg ) { var faces_up_left = rosbridge_msg.faces_up_left; var faces_down_right = rosbridge_msg.faces_down_right; var error = rosbridge_msg.error; var numFaces = faces_up_left.length;
// Create a new response object var response = new client_res();
if( error ){ // If ROS Service responded with an error response.error = error; return response; }
for (var ii = 0; ii < numFaces; ii++) { var face = { up_left_point: {x: 0, y:0}, down_right_point: {x: 0, y: 0} };
face.up_left_point.x = faces_up_left[ii].point.x; face.up_left_point.y = faces_up_left[ii].point.y; face.down_right_point.x = faces_down_right[ii].point.x; face.down_right_point.y = faces_down_right[ii].point.y; response.faces.push( face ); }
return response; } ```
Export the service onrequest callback function (svcImpl
):
```js module.exports = svcImpl ```
Next you will need to create the Web Worker to launch the Web Service:
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_web_services/workers $ touch face_detection.js ```
Open the face_detection.js file with your favorite editor:
```bash $ vim face_detection.js ```
Import the workerUtils module:
```js var path = require('path');
var ENV = require( path.join(__dirname, '../..', 'env.js') );
// Include it even if not used!!! Sets properties to the thread's global scope. var workerUtils = require(path.join(ENV.PATHS.INCLUDE_DIR, 'common', 'worker_utils.js')); ```
Next, you will have to set the worker name and call to launch all services registred to this Web Worker:
```js // Set worker thread name under the global scope. (WORKER.name) workerUtils.setWorkerName('face_detection');
// Launch all services assigned to this worker thread. // Search in workers.json config file for assigned web services. workerUtils.launchSvcAll(); ```
We need to tell the run-engine to launch the, newly implemented, Web Worker.
The workers.json
file containes Web Workers entries. It is located under:
```bash ~/rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_web_services/config/services ```
Append the following entry in the workers.json file:
```js "face_detection": { "launch": true, "path": "workers/face_detection.js", "services": [ "face_detection" ] } ```
Finally append the following web-service entry in the services.json file (under the same directory):
```js "face_detection": { "launch": true, "anonymous": false, "name": "face_detection", "url_name": "face_detection", "namespace": "", "authentication": true, "ros_connection": true, "timeout": 45000 } ```
Now the RAPP Platform is ready to receive requests for the newly created face_detection service.
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_web_services $ pm2 start server.yaml ```
You will notice the following output from the logs:
```bash info: [Service Handler] Registered worker service {http://rapp-platform-local:9001/hop/face_detection} under worker thread {face_detection} info: [Service Handler] { worker: 'face_detection', path: '/hop/face_detection', url: 'http://rapp-platform-local:9001/hop/face_detection', frame: [Function] } ```
You can check on already implemented Web Services here.
The RAPP WebService code API is documented [here]().
Documentation of the RAPP Web Services package can be found here.