Home

Awesome

vertx-rest-storage

Java CI with Maven codecov GitHub contributors

GitHub release Maven Central

Persistence for REST resources in the filesystem, Aws S3 storage or a redis database.

Stores resources in a hierarchical way according to their URI. It actually implements a generic CRUD REST service.

It uses usual mime mapping to determine content type, so you can also use it as a web server. Without extension, JSON is assumed.

The following methods are supported on leaves (documents):

The following methods are supported on intermediate nodes (collections):

Runs either as a module or can be integrated into an existing application by instantiating the RestStorageHandler class directly.

Run it

  1. clone the repository
  2. Install and start Redis
  1. run mvn install -Dmaven.test.skip=true
  2. run the fatjar with `java -jar build/libs/rest-storage-x.x.x-all.jar
  3. you get a rest-storage, that stores to the filesystem in the directory where you started it. If you want to use the rest-storage with redis, you have to pass the configuration over a json file with -conf conf.json

Features

GET

Invoking GET request on a leave (document) returns the content of the resource.

GET /storage/resources/resource_1

Invoking GET request on a collection returns a list of collection members.

GET /storage/resources/

Parameters

ParameterDescription
limitdefines the amount of returned resources
offsetdefines the amount of resources to skip. Can be used in combination with limit to provide paging functionality
Examples

Given a collection of ten items (res1-res10) under the path /server/tests/offset/resources/

RequestReturned items
GET /server/tests/offset/resources/?limit=10all
GET /server/tests/offset/resources/?limit=99all
GET /server/tests/offset/resources/?limit=5res1,res10,res2,res3,res4
GET /server/tests/offset/resources/?offset=2res2,res3,res4,res5,res6,res7,res8,res9
GET /server/tests/offset/resources/?offset=11no items (empty array)
GET /server/tests/offset/resources/?offset=2&limit=-1res2,res3,res4,res5,res6,res7,res8,res9
GET /server/tests/offset/resources/?offset=0&limit=3res1,res10,res2
GET /server/tests/offset/resources/?offset=1&limit=10res10,res2,res3,res4,res5,res6,res7,res8,res9

The returned json response look like this:

{
  "resources": [
    "res1",
    "res10",
    "res2",
    "res3",
    "res4"
  ]
}

DELETE

Invoking DELETE request on a leave (document) deletes the resource.

DELETE /storage/resources/resource_1

Invoking DELETE request on a collection deletes the collection and all its children.

DELETE /storage/resources/

Parameters

ParameterDescription
recursiveWhen configuration property confirmCollectionDelete is set to true, the url parameter recursive=true has to be added to delete collections.

StorageExpand

The StorageExpand feature expands the hierarchical resources and returns them as a single concatenated json resource.

Having the following resources in the storage:

key: data:test:collection:resource1     value: {"myProp1": "myVal1"}
key: data:test:collection:resource2     value: {"myProp2": "myVal2"}
key: data:test:collection:resource3     value: {"myProp3": "myVal3"}

would lead to this result

{
    "collection" : {
        "resource1" : {
            "myProp1": "myVal1"
        },
        "resource2" : {
            "myProp2": "myVal2"
        },
        "resource3" : {
            "myProp3": "myVal3"
        }
    }
}
Usage

To use the StorageExpand feature you have to make a POST request to the desired collection to expand having the url parameter storageExpand=true. Also, you wil have to send the names of the sub resources in the body of the request. Using the example above, the request would look like this:

POST /yourStorageURL/collection?storageExpand=true with the body:

{
    "subResources" : ["resource1", "resource2", "resource3"]
}

The amount of sub resources that can be provided is defined in the configuration by the property maxStorageExpandSubresources.

To override this for a single request, add the following request header with an appropriate value:

x-max-expand-resources: 1500

Reject PUT requests on low memory (redis only)

The redis storage provides a feature to reject PUT requests when the memory gets low. The information about the used memory is provided by the redis INFO command.

Attention: The stats received by the INFO command depend on the redis version. The required stats are used_memory and total_system_memory. Without these stats, the feature is disabled!

Configuration

To enable the feature, set the rejectStorageWriteOnLowMemory property (ModuleConfiguration) to true. Additionally, the freeMemoryCheckIntervalMs property can be changed to modify the interval for current memory usage calculation.

Usage

To define the importance level of PUT requests, add the following header:

x-importance-level: 75

The value defines the percentage of used memory which is not allowed to be exceeded to accept the PUT request. In the example above, the Request will only be acepted when the currently used memory is lower than 75%. When the currently used memory is higher than 75%, the request will be rejected with a status code 507 Insufficient Storage.

The higher the x-importance-level value, the more important the request. When no x-importance-level header is provided, the request is handled with the highest importance.

Lock Mechanism

The lock mechanism allows you to lock a resource for a specified time. This way only the owner of the lock is able to write or delete the given resource. To lock a resource, you have to add the following headers to your PUT / DELETE request.

HeadersTypeDefault valueDescription
x-lockStringThe owner of the lock.
x-lock-modesilentsilentAny PUT or DELETE performed on this resource without the valid owner will have no effect and get 200 OK back.
rejectAny PUT or DELETE performed on this resource without the valid owner will have no effect and get 409 Conflict back.
x-lock-expire-afterlong300Defines the lock lifetime. The default value is set to 300 seconds.
x-expire-afterlongDefines the lifetime of a resource

Warning: The lock will always be removed if you perform a DELETE on a collection containing a locked resource. There is no check for locks in collections.

Store data compressed

In order to optimize the memory usage when using the redis storage, it's possible to store resources compressed using the gzip compression algorithm.

To store a resource compressed, add the following header to the PUT request:

x-stored-compressed: true

When making a GET request to a compressed resource, the resource will be uncompressed before returning. No additional header is required!

Restrictions

The data compression feature is not compatible with all vertx-rest-storage features. The following listing contains the restrictions of this feature:

Configuration

The following configuration values are available:

PropertyTypeDefault valueDescription
rootcommon.The prefix for the directory or redis key
storageTypecommonfilesystemThe storage implementation to use. Choose between filesystem or redis
portcommon8989The port the mod listens to when HTTP API is enabled.
httpRequestHandlerEnabledcommontrueWhen set to false, the storage is accessible throught the event bus only.
httpRequestHandlerAuthenticationEnabledcommonfalseEnable / disable authentication for the HTTP API
httpRequestHandlerUsernamecommonThe username for the HTTP API authentication
httpRequestHandlerPasswordcommonThe password for the HTTP API authentication
prefixcommon/The part of the URL path before this handler (aka "context path" in JEE terminology)
storageAddresscommonresource-storageThe eventbus address the mod listens to.
editorConfigcommonAdditional configuration values for the editor
confirmCollectionDeletecommonfalseWhen set to true, an additional recursive=true url parameter has to be set to delete collections
maxStorageExpandSubresourcescommon1000The amount of sub resources to expand. When limit exceeded, 413 Payload Too Large is returned
redisHostredislocalhostThe host where redis is running on
redisPortredis6379The port where redis is running on
redisReconnectAttemptsredis0The amount of reconnect attempts when connection to redis is lost. Use -1 for continuous reconnects or 0 for no reconnects at all
redisReconnectDelaySecredis30The delay (in seconds) between each reconnect attempt
redisPoolRecycleTimeoutMsredis180000The timeout [ms] when the connection pool is recycled. Use -1 when having reconnect feature enabled.
expirablePrefixredisrest-storage:expirableThe prefix for expirable data redis keys
resourcesPrefixredisrest-storage:resourcesThe prefix for resources redis keys
collectionsPrefixredisrest-storage:collectionsThe prefix for collections redis keys
deltaResourcesPrefixredisdelta:resourcesThe prefix for delta resources redis keys
deltaEtagsPrefixredisdelta:etagsThe prefix for delta etags redis keys
lockPrefixredisrest-storage:locksThe prefix for lock redis keys
resourceCleanupAmountredis100000The maximum amount of resources to clean in a single cleanup run
resourceCleanupIntervalSecredisThe interval (in seconds) how often to perform the storage cleanup. When set to null no periodic storage cleanup is performed
rejectStorageWriteOnLowMemoryredisfalseWhen set to true, PUT requests with the x-importance-level header can be rejected when memory gets low
freeMemoryCheckIntervalMsredis60000The interval in milliseconds to calculate the actual memory usage
redisReadyCheckIntervalMsredis-1The interval in milliseconds to calculate the "ready state" of redis. When value < 1, no "ready state" will be calculated
awsS3Regions3The region of AWS S3 server, with local service such localstack, also need set a valid region
s3BucketNames3The S3 bucket name
s3AccessKeyIds3The s3 access key Id
s3SecretAccessKeys3The s3 secret access key
localS3s3Set to true in order to use a local S3 instance instead of AWS
localS3Endpoints3The endpoint/host to use in case that localS3 is set to true, e.g. 127.0.0.1 (in my case it had to be an IP)
localS3Ports3The port to use in case that localS3 is set to true, e.g. 4566
createBucketIfNotExists3create bucket if bucket not exist, related permission required

Configuration util

The configurations have to be passed as JsonObject to the module. For a simplified configuration the ModuleConfigurationBuilder can be used.

Example:

ModuleConfiguration config = with()
		.redisHost("anotherhost")
		.redisPort(1234)
		.editorConfig(new JsonObject().put("myKey", "myValue"))
		.build();

JsonObject json = config.asJsonObject();

Properties not overridden will not be changed. Thus remaining default.

To use default values only, the ModuleConfiguration constructor without parameters can be used:

JsonObject json  = new ModuleConfiguration().asJsonObject();

Storage types

Currently, there are three storage types supported. File system storage, S3 storage and redis storage.

File System Storage

The data is stored hierarchically on the file system. This is the default storage type when not overridden in the configuration.

S3 storage

The data is stored in a S3 instance.

AWS S3

See https://aws.amazon.com/s3 it is also possible to use a local instance using https://docs.localstack.cloud/user-guide/aws/s3/

docker run --rm -p 4566:4566 -v ./s3:/var/lib/localstack localstack/localstack:s3-latest

Redis Storage

The data is stored in a redis database. Caution: The redis storage implementation does not currently support streaming. Avoid transferring too big payloads since they will be entirely copied in memory.

Dependencies