Giter Club home page Giter Club logo

moodle-webservice_restful's Introduction

RESTful API

Webservice protocol plugin for Moodle enabling developers to use a RESTful API.

Introduction

The default protocols included in Moodle are RPC-like protocols, even the rest one, and thus do not use verbose URLs, HTTP methods or HTTP status codes. This plugin attempts to remedy this by mapping URLs and HTTP methods to existing external functions.

GET /webservice/restful/index.php/courses/2

HTTP/1.1 200 OK
Content-Length: 4211
Content-Type: application/json

{
    "id": 2,
    "fullname": "An Awesome Course",
    "shortname": "AwesomeCourse",
    ...
}

Pre-requisites

This version has only been tested with the following but could work in different configurations.

  • Moodle 3.4
  • PHP 7.0

Installation

NOT PRODUCTION READY: This plugin is yet in alpha, do not use it in production!

  • Place the content of this repository in the folder webservice/restful.
  • Navigate to Site Administration > Notifications to install the plugin.

The API

The root of all the following is webservice/restful/index.php.

Route Method Comment
/courses GET List all courses
/courses POST Create a new course
/courses/ID GET Get a course
/courses/ID PATCH Update a course
/courses/ID DELETE Delete a course
/courses/ID/duplicate POST Duplicate a course

How to use

Enable everything needed as for any other webservice protocol, and create a token.

  • The requests are made at /webservice/restful/index.php/API_ROUTE
  • The header Content-Type should read application/json
  • The request body must be in JSON
  • The token must be passed using the header Authorization: Bearer TOKEN

Example GET:

GET /webservice/restful/index.php/courses/2 HTTP/1.1
Accept: */*
Authorization: Bearer 10787a782d5cea26d69e103729d594f7
Host: localhost


HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Length: 4211
Content-Type: application/json
Date: Thu, 28 Sep 2017 11:47:55 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.25 (Ubuntu)

{
    "id": 2,
    "fullname": "An Awesome Course",
    "shortname": "AwesomeCourse",
    ...
}

With HTTPie:

http https://example.com/webservice/restful/index.php/courses/2 Authorization:'Bearer 10787a782d5cea26d69e103729d594f7'

With curl:

curl localhost/ws/webservice/restful/index.php/courses/2 -H 'Authorization: Bearer 10787a782d5cea26d69e103729d594f7'

Example POST

POST /webservice/restful/index.php/courses HTTP/1.1
Accept: application/json, */*
Accept-Encoding: gzip, deflate
Authorization: Bearer 10787a782d5cea26d69e103729d594f7
Content-Length: 89
Content-Type: application/json

{
    "categoryid": "1",
    "fullname": "Another Awesome Course",
    "shortname": "AnotherAwesome"
}

HTTP/1.1 201 Created
Connection: Keep-Alive
Content-Length: 39
Content-Type: application/json
Date: Thu, 28 Sep 2017 11:54:17 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.25 (Ubuntu)

{
    "id": 12,
    "shortname": "AnotherAwesome"
}

With HTTPie:

http POST localhost/ws/webservice/restful/index.php/courses \
    Authorization:'Bearer 10787a782d5cea26d69e103729d594f7' \
    fullname="Another Awesome Course" \
    shortname="AnotherAwesome" \
    categoryid=1

With curl:

curl -X POST localhost/ws/webservice/restful/index.php/courses \
    -H "Authorization: Bearer 10787a782d5cea26d69e103729d594f7" \
    -H 'Content-Type: application/json' \
    -d '{"fullname": "Another Awesome Course", "shortname": "AnotherAwesome", "categoryid": 1}'

Development

This plugin is still in a very early stage, and thus the API design could drastically change by the time it is deemed production ready. Also note that performance have not been considered at all at this stage. However, if you are interested in learning how to define a new route, here is some documentation.

The plugin works by analysing the URL and mapping it to a route. A route is constituted of a regex, and a list of methods that can be handled. Each method contains a handler function which is responsible for returning a response.

There are no interfaces, classes, or strong types defined. Rather functions are used to construct data in the expected format. For instance, a response will be an associative array containing the response code, its text and maybe some data. To construct such a response, you would use the function make_response, or a more appropriate one such as content_created.

Quick tutorial

Let's create a new route to get a category.

[
    'regex' => '/categories/([0-9]+)',
]

Notice that the regex does not include the usual start and end delimiters (^$), those will be added automatically later. And notice that we are capturing the category ID.

Now, we will add the GET method, which will use the existing external function core_course_get_categories. There is a function to help us create a pipeline for handling requests going through to the external API: external_api_method. Its first argument is the name of the external function, its second argument will be an associative array containing modifying functions, but let's leave that aside for now.

[
    'regex' => '/categories/([0-9]+)',
    'methods' => [
        'GET' => external_api_method('core_course_get_categories')
    ]
]

If we test this like this, and request /categories/1, the function will return all the categories. That is because the external function core_course_get_categories did not receive any parameter, in which case it returns all the categories. How do we map the ID from the route to the call of the external function then? By using the argsmapper function to provide to external_api_method. In argsmapper you will construct the arguments expected by the external function.

The function core_course_get_categories expects an array containing criteria under which there are rows of criterion, each containing key and value, for us it looks a bit like this:

[
    'criteria' => [
        [
            'key' => 'id',
            'value' => CATEGORY_ID
        ]
    ]
]

So let's add our argsmapper function:

[
    'regex' => '/categories/([0-9]+)',
    'methods' => [
        'GET' => external_api_method('core_course_get_categories', [
            'argsmapper' => function($routeargs, $request, $options) {
                return [
                    'criteria' => [
                        [
                            'key' => 'id',
                            'value' => $routeargs[0]
                        ]
                    ]
                ];
            }
        ])
    ]
]

Notice that the argsmapper function receives three parameters: the route arguments from our regex, and the request and options which we will not cover now. It's important that you note that the $routeargs are indexed from 0, not 1.

And now it works!

GET /categories/1

HTTP/1.1 200 OK

[
    {
        "id": 1,
        "name": "Miscellaneous",
        ...
    }
]

However, there is still a quirk here. The category is returned in the form of an array of categories, that is not what we want when we only expect to get one. Also, when we request a category that does not exist, we still get a 200 OK with an empty array.

GET /categories/123456

HTTP/1.1 200 OK

[]

So, what we need to do now, is to add the resultmapper function. Its purpose is to manipulate the result in order to return what we need, the same way we mapped the arguments to the function. It will receive the same arguments as argsmapper, except that it will also receive the result. Here is how it looks like.

[
    'regex' => '/categories/([0-9]+)',
    'methods' => [
        'GET' => external_api_method('core_course_get_categories', [
            'argsmapper' => function($routeargs, $request, $options) {
                return [
                    'criteria' => [
                        [
                            'key' => 'id',
                            'value' => $routeargs[0]
                        ]
                    ]
                ];
            },
            'resultmapper' => function($result, $args, $request, $options) {
                return reset($result) ?: null;
            }
        ])
    ]
]

That's it! Now /categories/1 just returns the category object. And when we request the category that does not exist, we get a 404 Not Found response without a body. Oh, and if you are wondering what ?: null means, well that's simply to ensure that the return value is null when $result is empty. When null is returned from a GET request, external_api_method automatically assumes that it is a Not Found.

Before you ask

Are you validating the parameters?

Yes, the function external_api_method takes care of validating both the parameters, and the return values of the external functions.

What if I want to return another status code?

You can define the function responsemaker and return your own reponse.

Can I capture exceptions raised by the external function?

The function errorhandler can be specified to handle different type of responses based on the exceptions captured from the external function execution. Exceptions triggered at other stages of the request will be handled separately.

Do I need to configure Apache to rewrite URLs?

No, this relies on slasharguments like a lot of other things in Moodle.

TODO

  • Add lots of other routes
  • Write tests, lots of tests
  • Document the API (routes, verbs, parameters)
  • Propertly handle request and header content types
  • Use HTTP headers for altering external_settings parameters
  • Check caching-related response headers

License

Licensed under the GNU GPL License.

moodle-webservice_restful's People

Contributors

fmcorz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.