From 3e71616a6a7c282a601ad10752d9814b9cb077c0 Mon Sep 17 00:00:00 2001 From: Natkeeran Date: Fri, 2 Nov 2018 15:38:16 -0400 Subject: [PATCH 1/6] homarus --- Homarus/README.md | 26 ++++ Homarus/cfg/config.example.yaml | 25 ++++ Homarus/cfg/config.yaml | 35 +++++ Homarus/composer.json | 43 ++++++ Homarus/phpunit.xml.dist | 30 +++++ Homarus/src/Controller/HomarusController.php | 132 +++++++++++++++++++ Homarus/src/app.php | 29 ++++ Homarus/src/index.php | 4 + Homarus/static/convert.ttl | 18 +++ Homarus/static/identify.ttl | 18 +++ 10 files changed, 360 insertions(+) create mode 100644 Homarus/README.md create mode 100644 Homarus/cfg/config.example.yaml create mode 100644 Homarus/cfg/config.yaml create mode 100644 Homarus/composer.json create mode 100644 Homarus/phpunit.xml.dist create mode 100644 Homarus/src/Controller/HomarusController.php create mode 100644 Homarus/src/app.php create mode 100644 Homarus/src/index.php create mode 100644 Homarus/static/convert.ttl create mode 100644 Homarus/static/identify.ttl diff --git a/Homarus/README.md b/Homarus/README.md new file mode 100644 index 00000000..3cf735a8 --- /dev/null +++ b/Homarus/README.md @@ -0,0 +1,26 @@ +# Homarus + +### Apache Config +``` +# managed by Ansible + +Alias "/homarus" "/var/www/html/Crayfish/Homarus/src" + + FallbackResource /homarus/index.php + Require all granted + DirectoryIndex index.php + SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 + +``` + +### Testing +* Clone this microservice into `/var/www/html/Crayfish` +* Install the depencies using composer `composer install` +* Place the above confir in `/etc/apache2/conf-available` as `Homarus.conf`. +* Enable the conf: `sudo a2enconf Homarus`. Restart Apache: `sudo systemctl restart apache2` +* Create a test video by going here: `http://localhost:8080/fcrepo/rest/` and uploading a binary +* Test via curl as below or using Postman + +``` +curl -H "Authorization: Bearer islandora" -H "Accept: video/x-msvideo" -H "Apix-Ldp-Resource:http://localhost:8080/fcrepo/rest/testvideo" http://localhost:8000/homarus/convert --output output.avi +``` diff --git a/Homarus/cfg/config.example.yaml b/Homarus/cfg/config.example.yaml new file mode 100644 index 00000000..68e95ce5 --- /dev/null +++ b/Homarus/cfg/config.example.yaml @@ -0,0 +1,25 @@ +--- +homarus: + # path to the convert executable + executable: ffmpeg + formats: + valid: + default: video/mp4 + +fedora_resource: + base_url: http://localhost:8080/fcrepo/rest + +log: + # Valid log levels are: + # DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY, NONE + # log level none won't open logfile + level: DEBUG + file: /var/log/islandora/homarus.log + +syn: + # toggles JWT security for service + enable: True + # Path to the syn config file for authentication. + # example can be found here: + # https://github.com/Islandora-CLAW/Syn/blob/master/conf/syn-settings.example.xml + config: ../syn-settings.xml \ No newline at end of file diff --git a/Homarus/cfg/config.yaml b/Homarus/cfg/config.yaml new file mode 100644 index 00000000..303e6f78 --- /dev/null +++ b/Homarus/cfg/config.yaml @@ -0,0 +1,35 @@ +--- + +# managed by Ansible + +homarus: + # path to the convert executable + executable: ffmpeg + mime_types: + valid: + - video/mp4 + - video/x-msvideo + - video/ogg + default_video: video/mp4 + mime_to_format: + - video/mp4_mp4 + - video/x-msvideo_avi + - video/ogg_ogg + +fedora_resource: + base_url: http://localhost:8080/fcrepo/rest + +log: + # Valid log levels are: + # DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY, NONE + # log level none won't open logfile + level: DEBUG + file: /var/log/islandora/homarus.log + +syn: + # toggles JWT security for service + enable: True + # Path to the syn config file for authentication. + # example can be found here: + # https://github.com/Islandora-CLAW/Syn/blob/master/conf/syn-settings.example.xml + config: ../syn-settings.xml \ No newline at end of file diff --git a/Homarus/composer.json b/Homarus/composer.json new file mode 100644 index 00000000..40ce72b8 --- /dev/null +++ b/Homarus/composer.json @@ -0,0 +1,43 @@ +{ + "name": "islandora/homarus", + "description": "FFmpeg as a web service", + "type": "project", + "require": { + "islandora/crayfish-commons": "dev-master" + }, + "license": "MIT", + "authors": [ + { + "name": "Islandora Foundation", + "email": "community@islandora.ca", + "role": "Owner" + }, + { + "name": "Jonathan Green", + "email": "jonathan.green@lyrasis.org", + "role": "Maintainer" + } + ], + "autoload": { + "psr-4": { + "Islandora\\Homarus\\": "src/" + } + }, + "scripts": { + "check": [ + "phpcs --standard=PSR2 src tests", + "phpcpd --names *.php src" + ], + "test": [ + "@check", + "phpunit" + ] + }, + "require-dev": { + "symfony/browser-kit": "^3.0", + "symfony/css-selector": "^3.0", + "phpunit/phpunit": "^5.0", + "squizlabs/php_codesniffer": "^2.0", + "sebastian/phpcpd": "^3.0" + } +} diff --git a/Homarus/phpunit.xml.dist b/Homarus/phpunit.xml.dist new file mode 100644 index 00000000..e11b112f --- /dev/null +++ b/Homarus/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + ./tests/ + + + + + + + + + ./src/index.php + ./src/app.php + + ./src + + + diff --git a/Homarus/src/Controller/HomarusController.php b/Homarus/src/Controller/HomarusController.php new file mode 100644 index 00000000..e14df423 --- /dev/null +++ b/Homarus/src/Controller/HomarusController.php @@ -0,0 +1,132 @@ +cmd = $cmd; + $this->formats = $formats; + $this->default_format = $default_format; + $this->executable = $executable; + $this->log = $log; + $this->mime_to_format = $mime_to_format; + } + + /** + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Symfony\Component\HttpFoundation\Response|\Symfony\Component\HttpFoundation\StreamedResponse + */ + public function convert(Request $request) { + $this->log->info('Ffmpeg Convert request.'); + + // Short circuit if there's no Apix-Ldp-Resource header. + if (!$request->headers->has("Apix-Ldp-Resource")) + { + $this->log->debug("Malformed request, no Apix-Ldp-Resource header present"); + return new Response( + "Malformed request, no Apix-Ldp-Resource header present", + 400 + ); + } else { + $source = $request->headers->get('Apix-Ldp-Resource'); + } + + // Find the format + $content_types = $request->getAcceptableContentTypes(); + $content_type = $this->get_content_type($content_types); + $format = $this->get_ffmpeg_format($content_type); + + $cmd_params = ""; + if($format == "mp4") { + $cmd_params = " -vcodec libx264 -preset medium -acodec aac -strict -2 -ab 128k -ac 2 -async 1 -movflags frag_keyframe+empty_moov "; + } + + // Arguments to ffmpeg command are sent as a custom header + $args = $request->headers->get('X-Islandora-Args'); + $this->log->debug("X-Islandora-Args:", ['args' => $args]); + + $cmd_string = "$this->executable -i $source $cmd_params -f $format -"; + $this->log->info('Ffempg Command:', ['cmd' => $cmd_string]); + + // Return response. + try { + return new StreamedResponse( + $this->cmd->execute($cmd_string, $source), + 200, + ['Content-Type' => $content_type] + ); + } catch (\RuntimeException $e) { + $this->log->error("RuntimeException:", ['exception' => $e]); + return new Response($e->getMessage(), 500); + } + } + + + private function get_content_type($content_types) { + $content_type = null; + foreach ($content_types as $type) { + if (in_array($type, $this->formats)) { + $content_type = $type; + break; + } + } + + if ($content_type === null) { + $content_type = $this->default_format; + $this->log->info('Falling back to default content type'); + } + return $content_type; + } + + private function get_ffmpeg_format($content_type){ + foreach ($this->mime_to_format as $format) { + if (strpos($format, $content_type) !== false) { + $this->log->info("does it get here"); + $format_info = explode("_", $format); + break; + } + } + return $format_info[1]; + } + +} diff --git a/Homarus/src/app.php b/Homarus/src/app.php new file mode 100644 index 00000000..13a77441 --- /dev/null +++ b/Homarus/src/app.php @@ -0,0 +1,29 @@ +register(new IslandoraServiceProvider()); +$app->register(new YamlConfigServiceProvider(__DIR__ . '/../cfg/config.yaml')); + +$app['homarus.controller'] = function ($app) { + return new HomarusController( + $app['crayfish.cmd_execute_service'], + $app['crayfish.homarus.mime_types.valid'], + $app['crayfish.homarus.mime_types.default_video'], + $app['crayfish.homarus.executable'], + $app['monolog'], + $app['crayfish.homarus.mime_to_format'] + ); +}; + +$app->get('/convert', "homarus.controller:convert"); + +return $app; diff --git a/Homarus/src/index.php b/Homarus/src/index.php new file mode 100644 index 00000000..3e23d477 --- /dev/null +++ b/Homarus/src/index.php @@ -0,0 +1,4 @@ +run(); diff --git a/Homarus/static/convert.ttl b/Homarus/static/convert.ttl new file mode 100644 index 00000000..ccda85c7 --- /dev/null +++ b/Homarus/static/convert.ttl @@ -0,0 +1,18 @@ +@prefix apix: . +@prefix owl: . +@prefix ebucore: . +@prefix ldp: . +@prefix islandora: . +@prefix rdfs: . + +<> a apix:Extension; + rdfs:label "Ffmpeg Service"; + rdfs:comment "ffmpeg as a microservice"; + apix:exposesService islandora:ConvertService; + apix:exposesServiceAt "svc:convert"; + apix:bindsTo <#class> . + +<#class> owl:intersectionOf ( + ldp:NonRDFSource + [ a owl:Restriction; owl:onProperty ebucore:hasMimeType; owl:hasValue "video/mp4", "video/x-msvideo", "video/ogg" ] +) . \ No newline at end of file diff --git a/Homarus/static/identify.ttl b/Homarus/static/identify.ttl new file mode 100644 index 00000000..af9be90f --- /dev/null +++ b/Homarus/static/identify.ttl @@ -0,0 +1,18 @@ +@prefix apix: . +@prefix owl: . +@prefix ebucore: . +@prefix ldp: . +@prefix islandora: . +@prefix rdfs: . + +<> a apix:Extension; + rdfs:label "Ffmpeg Identification Service"; + rdfs:comment "ffmpeg's identify as a microservice"; + apix:exposesService islandora:IdentifyService; + apix:exposesServiceAt "svc:identify"; + apix:bindsTo <#class> . + +<#class> owl:intersectionOf ( + ldp:NonRDFSource + [ a owl:Restriction; owl:onProperty ebucore:hasMimeType; owl:hasValue "video/mp4", "video/x-msvideo", "video/ogg" ] +) . From 74a02b4c9fe13fe9aa12723ec6df0f8c53bc9071 Mon Sep 17 00:00:00 2001 From: Natkeeran Date: Fri, 2 Nov 2018 18:00:33 -0400 Subject: [PATCH 2/6] Delete config.yaml config.yaml will be created by ansible --- Homarus/cfg/config.yaml | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 Homarus/cfg/config.yaml diff --git a/Homarus/cfg/config.yaml b/Homarus/cfg/config.yaml deleted file mode 100644 index 303e6f78..00000000 --- a/Homarus/cfg/config.yaml +++ /dev/null @@ -1,35 +0,0 @@ ---- - -# managed by Ansible - -homarus: - # path to the convert executable - executable: ffmpeg - mime_types: - valid: - - video/mp4 - - video/x-msvideo - - video/ogg - default_video: video/mp4 - mime_to_format: - - video/mp4_mp4 - - video/x-msvideo_avi - - video/ogg_ogg - -fedora_resource: - base_url: http://localhost:8080/fcrepo/rest - -log: - # Valid log levels are: - # DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY, NONE - # log level none won't open logfile - level: DEBUG - file: /var/log/islandora/homarus.log - -syn: - # toggles JWT security for service - enable: True - # Path to the syn config file for authentication. - # example can be found here: - # https://github.com/Islandora-CLAW/Syn/blob/master/conf/syn-settings.example.xml - config: ../syn-settings.xml \ No newline at end of file From 6b8710056fa6af76119f4a4d429784fd3c39f5dd Mon Sep 17 00:00:00 2001 From: Natkeeran Date: Tue, 6 Nov 2018 13:44:40 -0500 Subject: [PATCH 3/6] changes requested by Danny 1 --- Homarus/README.md | 52 ++++++++++++++++---- Homarus/cfg/config.example.yaml | 24 ++++++--- Homarus/composer.json | 4 +- Homarus/src/Controller/HomarusController.php | 12 +++++ Homarus/src/app.php | 1 + Homarus/static/identify.ttl | 18 ------- 6 files changed, 75 insertions(+), 36 deletions(-) delete mode 100644 Homarus/static/identify.ttl diff --git a/Homarus/README.md b/Homarus/README.md index 3cf735a8..3dd108f0 100644 --- a/Homarus/README.md +++ b/Homarus/README.md @@ -1,9 +1,29 @@ # Homarus -### Apache Config -``` -# managed by Ansible +## Introduction + +[FFmpeg](https://www.ffmpeg.org/) as a microservice. + +## Installation +- Install `ffmpeg`. On Ubuntu, this can be done with `sudo apt-get install ffmpeg`. +- Copy `/var/www/html/Crayfish/Homarus/cfg/config.default.yml` to `/var/www/html/Crayfish/Homarus/cfg/config.yml` +- Copy `/var/www/html/Crayfish/Hypercube/syn-settings.xml` to `/var/www/html/Crayfish/Homarus/syn-settings.xml` +- Clone this repository somewhere in your web root (example: `/var/www/html/Crayfish/Homarus`). +- Install `composer`. [Install instructions here.][4] +- `$ cd /path/to/Homarus` and run `$ composer install` +- Then either + - For production, configure your web server appropriately (e.g. add a VirtualHost for Homarus in Apache) OR + - For development, run the PHP built-in webserver `$ php -S localhost:8888 -t src` from Homarus root. + + +### Apache2 +To use Homarus with Apache you need to configure your Virtualhost with a few options: +- Redirect all requests to the Homarus index.php file +- Make sure Hypercube has access to Authorization headers + +Here is an example configuration for Apache 2.4: +```apache Alias "/homarus" "/var/www/html/Crayfish/Homarus/src" FallbackResource /homarus/index.php @@ -13,14 +33,26 @@ Alias "/homarus" "/var/www/html/Crayfish/Homarus/src" ``` -### Testing -* Clone this microservice into `/var/www/html/Crayfish` -* Install the depencies using composer `composer install` -* Place the above confir in `/etc/apache2/conf-available` as `Homarus.conf`. -* Enable the conf: `sudo a2enconf Homarus`. Restart Apache: `sudo systemctl restart apache2` -* Create a test video by going here: `http://localhost:8080/fcrepo/rest/` and uploading a binary -* Test via curl as below or using Postman +This will put the Homarus at the /homarus endpoint on the webserver. + +## Configuration + +If your homarus installation is not on your path, then you can configure Homarus to use a specific executable by editing `executable` entry in [config.yaml](./cfg/config.example.yaml). +You also will need to set the `fedora base url` entry to point to your Fedora installation. + +## Usage +This will return the an avi file for the test video file in Fedora. ``` curl -H "Authorization: Bearer islandora" -H "Accept: video/x-msvideo" -H "Apix-Ldp-Resource:http://localhost:8080/fcrepo/rest/testvideo" http://localhost:8000/homarus/convert --output output.avi ``` + +## Maintainers + +Current maintainers: + +* [Natkeeran](https://github.com/Natkeeran) + +## License + +[MIT](https://opensource.org/licenses/MIT) diff --git a/Homarus/cfg/config.example.yaml b/Homarus/cfg/config.example.yaml index 68e95ce5..d390be0c 100644 --- a/Homarus/cfg/config.example.yaml +++ b/Homarus/cfg/config.example.yaml @@ -1,10 +1,18 @@ --- homarus: - # path to the convert executable + # path to the ffmpeg executable executable: ffmpeg - formats: + mime_types: valid: - default: video/mp4 + - video/mp4 + - video/x-msvideo + - video/ogg + default: image/jpeg + mime_to_format: + valid: + - video/mp4_mp4 + - video/x-msvideo_avi + - video/ogg_ogg fedora_resource: base_url: http://localhost:8080/fcrepo/rest @@ -13,7 +21,7 @@ log: # Valid log levels are: # DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY, NONE # log level none won't open logfile - level: DEBUG + level: /var/log/islandora/homarus.log file: /var/log/islandora/homarus.log syn: @@ -21,5 +29,9 @@ syn: enable: True # Path to the syn config file for authentication. # example can be found here: - # https://github.com/Islandora-CLAW/Syn/blob/master/conf/syn-settings.example.xml - config: ../syn-settings.xml \ No newline at end of file + # https://github.com/Islandora-CLAW/Syn/blob/master/conf/syn-settings.example$ + config: ../syn-settings.xml + + + + diff --git a/Homarus/composer.json b/Homarus/composer.json index 40ce72b8..eaac1de2 100644 --- a/Homarus/composer.json +++ b/Homarus/composer.json @@ -13,8 +13,8 @@ "role": "Owner" }, { - "name": "Jonathan Green", - "email": "jonathan.green@lyrasis.org", + "name": "Natkeeran L.Kanthan", + "email": "nat.ledchumykanthan@utoronto.ca", "role": "Maintainer" } ], diff --git a/Homarus/src/Controller/HomarusController.php b/Homarus/src/Controller/HomarusController.php index e14df423..bda261b4 100644 --- a/Homarus/src/Controller/HomarusController.php +++ b/Homarus/src/Controller/HomarusController.php @@ -129,4 +129,16 @@ private function get_ffmpeg_format($content_type){ return $format_info[1]; } + /** + * @return \Symfony\Component\HttpFoundation\BinaryFileResponse + */ + public function convertOptions() + { + return new BinaryFileResponse( + __DIR__ . "/../../static/convert.ttl", + 200, + ['Content-Type' => 'text/turtle'] + ); + } + } diff --git a/Homarus/src/app.php b/Homarus/src/app.php index 13a77441..dca69e57 100644 --- a/Homarus/src/app.php +++ b/Homarus/src/app.php @@ -24,6 +24,7 @@ ); }; +$app->options('/convert', "homarus.controller:convertOptions"); $app->get('/convert', "homarus.controller:convert"); return $app; diff --git a/Homarus/static/identify.ttl b/Homarus/static/identify.ttl deleted file mode 100644 index af9be90f..00000000 --- a/Homarus/static/identify.ttl +++ /dev/null @@ -1,18 +0,0 @@ -@prefix apix: . -@prefix owl: . -@prefix ebucore: . -@prefix ldp: . -@prefix islandora: . -@prefix rdfs: . - -<> a apix:Extension; - rdfs:label "Ffmpeg Identification Service"; - rdfs:comment "ffmpeg's identify as a microservice"; - apix:exposesService islandora:IdentifyService; - apix:exposesServiceAt "svc:identify"; - apix:bindsTo <#class> . - -<#class> owl:intersectionOf ( - ldp:NonRDFSource - [ a owl:Restriction; owl:onProperty ebucore:hasMimeType; owl:hasValue "video/mp4", "video/x-msvideo", "video/ogg" ] -) . From 6d3b4b22335d58d1545bf65e3c76d5a241d4d4d2 Mon Sep 17 00:00:00 2001 From: Natkeeran Date: Wed, 7 Nov 2018 15:04:29 -0500 Subject: [PATCH 4/6] update example config --- Homarus/cfg/config.example.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Homarus/cfg/config.example.yaml b/Homarus/cfg/config.example.yaml index d390be0c..f3b36314 100644 --- a/Homarus/cfg/config.example.yaml +++ b/Homarus/cfg/config.example.yaml @@ -7,7 +7,7 @@ homarus: - video/mp4 - video/x-msvideo - video/ogg - default: image/jpeg + default_video: video/mp4 mime_to_format: valid: - video/mp4_mp4 @@ -21,7 +21,7 @@ log: # Valid log levels are: # DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY, NONE # log level none won't open logfile - level: /var/log/islandora/homarus.log + level: DEBUG file: /var/log/islandora/homarus.log syn: From 54eaabf269c6f76894a3a0c5cae1698609c83eb1 Mon Sep 17 00:00:00 2001 From: Natkeeran Date: Wed, 7 Nov 2018 16:50:39 -0500 Subject: [PATCH 5/6] update README --- Homarus/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Homarus/README.md b/Homarus/README.md index 3dd108f0..07ee4340 100644 --- a/Homarus/README.md +++ b/Homarus/README.md @@ -6,9 +6,9 @@ ## Installation - Install `ffmpeg`. On Ubuntu, this can be done with `sudo apt-get install ffmpeg`. +- Clone this repository somewhere in your web root (example: `/var/www/html/Crayfish/Homarus`). - Copy `/var/www/html/Crayfish/Homarus/cfg/config.default.yml` to `/var/www/html/Crayfish/Homarus/cfg/config.yml` - Copy `/var/www/html/Crayfish/Hypercube/syn-settings.xml` to `/var/www/html/Crayfish/Homarus/syn-settings.xml` -- Clone this repository somewhere in your web root (example: `/var/www/html/Crayfish/Homarus`). - Install `composer`. [Install instructions here.][4] - `$ cd /path/to/Homarus` and run `$ composer install` - Then either @@ -37,7 +37,7 @@ This will put the Homarus at the /homarus endpoint on the webserver. ## Configuration -If your homarus installation is not on your path, then you can configure Homarus to use a specific executable by editing `executable` entry in [config.yaml](./cfg/config.example.yaml). +If your ffmpeg installation is not on your path, then you can configure homarus to use a specific executable by editing `executable` entry in [config.yaml](./cfg/config.example.yaml). You also will need to set the `fedora base url` entry to point to your Fedora installation. From 58cac9032a94e6962031e69cb0a144c36801e0e9 Mon Sep 17 00:00:00 2001 From: Natkeeran Date: Thu, 8 Nov 2018 14:43:10 -0500 Subject: [PATCH 6/6] add basic test setup --- Homarus/phpunit.xml.dist | 2 +- .../Homarus/Tests/HomarusControllerTest.php | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 Homarus/tests/Islandora/Homarus/Tests/HomarusControllerTest.php diff --git a/Homarus/phpunit.xml.dist b/Homarus/phpunit.xml.dist index e11b112f..c14c1607 100644 --- a/Homarus/phpunit.xml.dist +++ b/Homarus/phpunit.xml.dist @@ -11,7 +11,7 @@ syntaxCheck="false" > - + ./tests/ diff --git a/Homarus/tests/Islandora/Homarus/Tests/HomarusControllerTest.php b/Homarus/tests/Islandora/Homarus/Tests/HomarusControllerTest.php new file mode 100644 index 00000000..0a3c319f --- /dev/null +++ b/Homarus/tests/Islandora/Homarus/Tests/HomarusControllerTest.php @@ -0,0 +1,44 @@ +prophesize(CmdExecuteService::class)->reveal(); + $controller = new HomarusController( + $mock_service, + [], + '', + 'convert', + $this->prophesize(Logger::class)->reveal(), + '' + ); + + $response = $controller->convertOptions(); + $this->assertTrue($response->getStatusCode() == 200, 'Convert OPTIONS should return 200'); + $this->assertTrue( + $response->headers->get('Content-Type') == 'text/turtle', + 'Convert OPTIONS should return turtle' + ); + } + +}