Request Checkers
Introduction
Request Checkers feature allows to check the request so that, if it does not fulfill some conditions, it returns 400 BAD REQUEST response code thus enforcing a well defined structure to documents.
The checkers collection metadata
In RESTHeart, not only documents but also dbs and collections (and files buckets, schema stores, etc.) have properties. Some properties are metadata, i.e. have a special meaning for RESTheart that controls its behavior.
The collection metadata property checkers
allows to declare checkers
to be applied to write requests.
checkers is an array of checker
objects. A checker object has
the following format:
{ "name": <checker_name>,"args": <arguments>, "skipNotSupported": <boolean> }
Property
|
Description
|
Mandatory
|
---|---|---|
name |
name of the checker. |
Yes |
args |
arguments to be passed to the checker | no |
skipNotSupported |
if true, skip the checking if this checker does not support the request (Checker.doesSupportRequests()) |
no |
Global Checkers
Global Checkers are applied to all requests.
Global Checkers can be defined programmatically instantiating GlobalChecker
objects:
/**
*
* @param checker
* @param predicate checker is applied only to requests that resolve
* the predicate
* @param skipNotSupported
* @param args
* @param confArgs
*/
public GlobalChecker(Checker checker,
RequestContextPredicate predicate,
boolean skipNotSupported,
BsonValue args,
BsonValue confArgs)
and adding them to the list CheckerHandler.getGlobalCheckers()
// a predicate that resolves POST /db/coll and PUT /db/coll/docid requests
RequestContextPredicate predicate = new RequestContextPredicate() {
@Override
public boolean resolve(HttpServerExchange hse, RequestContext context) {
return (context.isPost() && context.isCollection())
|| (context.isPut() && context.isDocument());
}
};
// Let's use the predefined ContentSizeChecker to limit write requests size
Checker checker = new ContentSizeChecker();
// ContentSizeChecker requires argument max, use 1024 Kbyte
BsonDocument args = new BsonDocument("max", new BsonInt32(1024*1024));
// if the checker requires configuration arguments, define them here
BsonDocument confArgs = null;
GlobalChecker globalChecker = new GlobalChecker(checker, predicate, true, args, confArgs);
// finally add it to global checker list
CheckerHandler.getGlobalCheckers().add(globalChecker);
You can use an Initializer to add Global Checkers.
Checkers
RESTHeart comes with 3 ready-to-be-used checkers:
- checkContent to enforce a structure to documents using json path expressions
- checkContentSize to check the request size
- jsonSchemaChecker to enforce a structure to documents using json schema; more information at Document validation with JSON schema
Custom checkers can also be implemented.
Schema validation with checkContent checker
checkContent is a checker shipped with RESTHeart that allows to enforce a schema to the documents of a collection.
The schema definition is passed via the checker metadata args property as an array of conditions. A condition has the following format
{ "path": <json_path>, "type": <property_type>, "regex": <regex>, "nullable": boolean, "optional": boolean }
If the type is ‘object’ the properties mandatoryFields and optionalFields apply as well:
{ "path": <json_path>, "type": "object", "mandatoryFields": [ <field_names> ], "optionalFields": [ <field_names>] }
Property
|
Description
|
Mandatory
|
Default value |
---|---|---|---|
path |
The json path expression that selects the property to verify the condition on. |
Yes | |
type |
The type that the selected property must have. It can be the following BSON types:
|
Yes | |
regex |
If specified, this regular expression must match the property (or its string representation). | No | null |
nullable |
If true, no check will be performed if the value of the selected property is null. | No | false |
optional |
If true, no check will be performed if the property is missing. | No | false |
|
If the property type is 'object', this is the array of the properties that the object must have. If specified, the object cannot have any other field, as long as they are not listed in the optionalFields array. |
No | null |
optionalFields |
If the property type is 'object', this is the array of the properties that the object is allowed optionally to have. If specified, the object cannot have any other field, as long as they are not listed in the mandatoryFields array. |
No | null |
Json path expressions
A Json path expressions identifies a part of a Json document.
- It uses the dot notation where the special symbol $ identifies the document itself.
- The special char * selects all the properties of an object.
- The special string [*] selects all the elements of an array.
the [n] notation is not supported, i.e. you cannot use the following json path expression $.array.[3] to select the n-th element of an array.
For example, given the following document:
{
"_id": {
"$oid": "55f6ccf4c2e6be404fdef3dd"
},
"string": "hello",
"object": {
"pi": 3.14,
"href": "https://en.wikipedia.org/wiki/Pi"
},
"array": [1, 2, 3]
}
The following table shows what document parts, different json path expressions select:
json path expr | selected part |
---|---|
$ | the whole document, that is an object |
$.string | the property string with value "hello" |
$.object | the object with value:
|
$.object.* | the 2 properties pi and href with values 3.14 (number) and "https://en.wikipedia.org/wiki/Pi" (string) respectively |
$.object.pi | the property pi with numeric value 3.14 |
$.array | the array with value [ 1, 2, 3] |
$.array.[*] | the 3 elements of the array with numeric values 1, 2 and 3. |
Example
The following example creates the collection user enforcing its document to have following fields:
name | type | mandatory | notes |
---|---|---|---|
_id | string | yes | the string must satisfy the given regex, that makes sure that the string is a valid email address. |
name | string | yes | |
password | string | yes | |
roles | array of strings | yes | |
bio | string | no |
$ http -a a:a PUT 127.0.0.1:8080/test/users \
checkers:='[{\
"name":"checkContent","args":[\
{"path":"$", "type":"object", "mandatoryFields": ["_id", "name", "password", "roles"], "optionalFields": ["bio"]},\
{"path":"$._id", "type":"string", "regex": "^\\u0022[A-Z0-9._%+-]+@[A-Z0-9.-]+\\u005C.[A-Z]{2,6}\\u0022$"},\
{"path":"$.password", "type":"string"},\
{"path":"$.roles", "type":"array"},\
{"path":"$.roles.[*]", "type":"string"},\
{"path":"$.name", "type":"string"},\
{"path":"$.bio", "type":"string", "nullable": true}
]\
}]'
Limiting file size with checkContentSize
checkContentSize is a checker shipped with RESTHeart that allows to check the size of a request. It is very useful with file resources to limit the maximum size of the uploaded file.
The following example, creates a file bucket resource, limiting the file size from 64 to 32768 bytes:
$ http -a a:a PUT 127.0.0.1:8080/test/icons.files descr="icons" \
checkers:='[{"name":"checkContentSize","args":{"min": 64, "max": 32768}}]
Custom Checkers
A checker is a java class that implements the interface org.restheart.metadata.checkers.Checker.
It only requires to implement the method check() with 3 arguments:
- HttpServerExchange exchange
- RequestContext context (that is the suggested way to retrieve the information of the request such as the payload)
- BsonValue args (the arguments passed via the args property of the transformer metadata object)
- BsonValue confArgs (the arguments passed via the args property specified in the configuration file)
boolean check(
HttpServerExchange exchange,
RequestContext context,
BsonValue contentToCheck,
BsonValue args);
/**
* Specify when the checker should be performed: with BEFORE_WRITE the
* checkers gets the request data (that may use the dot notation and update
* operators); with AFTER_WRITE the data is optimistically written to the db
* and rolled back eventually. Note that AFTER_WRITE helps checking data
* with dot notation and update operators since the data to check is
* retrieved normalized from the db.
*
* @param context
* @return BEFORE_WRITE or AFTER_WRITE
*/
PHASE getPhase(RequestContext context);
/**
*
* @param context
* @return true if the checker supports the requests
*/
boolean doesSupportRequests(RequestContext context);
Once a checker has been implemented, it can be given a name (to be used as the name property of the checker metadata object) in the configuration file.
The following is the default configuration file section declaring the two off-the-shelf checkers provided with RESTHeart (checkContent and checkContentSize) and a third, custom one.
metadata-named-singletons:
- group: checkers
interface: org.restheart.hal.metadata.singletons.Checker
singletons:
- name: checkContent
class: org.restheart.hal.metadata.singletons.SimpleContentChecker
- name: checkContentSize
class: org.restheart.hal.metadata.singletons.ContentSizeChecker
- name: myChecker
class: com.whatever.MyChecker
The class of the custom checker must be added to the java classpath. See (How to package custom code)[/docs/v3/custom-code-packaging-howto] for more information.
For example, RESTHeart could be started with the following command:
$ java -server -classpath restheart.jar:custom-checker.jar org.restheart.Bootstrapper restheart.yml
The following code, is an example checker that checks if the number property is an integer between 0 and 10.
package com.whatever;
public class MyChecker implements Checker {
boolean check(HttpServerExchange exchange, RequestContext context, BsonValue args) {
BsonValue content = context.getContent();
BsonValue _value = content.get("number");
if (context.getMethod() == RequestContext.METHOD.PATCH) {
if (_value == null) {
// if this is a PATCH and property value is not in the request, it won't be modified
return true;
}
}
if (_value != null && _value.isNumber()) {
Integer value = _value..asInt32().getValue();
return value < 10 && value > 0;
} else {
return false; // BAD REQUEST
}
}
}