Core Concepts

Home

REST Client


© 2019-2022 rxmicro.io. Free use of this software is granted under the terms of the Apache License 2.0.

Copies of this entity may be made for Your own use and for distribution to others, provided that You do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.

If You find errors or omissions in this entity, please don’t hesitate to submit an issue or open a pull request with a fix.

REST Controller is a class that contains at least one declarative HTTP request handler.

Each REST-based microservice project must contain at least one REST Controller.
(The REST Controller is the entry point for the client.)

To create REST controllers, the RxMicro framework provides the following modules:

Due to transit dependencies only two modules usually need to be added to a project: rxmicro.rest.server.netty and rxmicro.rest.server.exchange.json.
(All other modules are automatically added to the project!)

1. REST Controller Implementation Requirements.

1.1. REST Controller Class Requirements.

REST Controller is a Java class:

import io.rxmicro.rest.method.GET;

final class MicroService {

    @GET("/")
    void requestHandler() {

    }
}

that must meet the following requirements:

  1. The class must extend the java.lang.Object one.

  2. The class couldn’t be an abstract one.

  3. The class couldn’t be a nested one.

  4. The class must contain an accessible (not private) constructor without parameters.
    (The last requirement can be ignored if Your project depends on rxmicro.cdi module.)

A REST Controller class must be a public one, only if it contains the method: public static void main(final String[] args). Otherwise, a REST Controller class can be nonpublic.

1.2. HTTP Request Handler Requirements.

HTTP request handler is a method, that must meet the following requirements:

  1. The method couldn’t be a private one.

  2. The method couldn’t be an abstract one.

  3. The method couldn’t be a synchronized one.

  4. The method couldn’t be a static one.

  5. The method couldn’t be a native one.

  6. The method must be annotated by at least one of the following annotations:

  7. The method must return a void type or one of the following reactive types:

  8. If the method returns a reactive type, that this type must be parametrized by a HTTP response model type. (The additional types such as java.lang.Void and java.util.Optional are supported also. (Read more: core.html)).

The Flux and Flowable types are developed to handle data stream that may arrive within a certain period of time.

For such cases the HTTP protocol provides the special Transfer-Encoding mechanism.

But this mechanism is not supported by the RxMicro framework yet, so the Flux and Flowable types cannot be used in the HTTP request handler.

The RxMicro framework supports the following parameter types for the HTTP request handler:

  1. Handler without parameters.
    (This type is recommended for the simplest stateless microservices without parameters.)

  2. List of primitive parameters.
    (This type is recommended for microservices, the behavior of which depends on 1-2 parameters.)

  3. Custom class modeling an HTTP request.
    (This type is recommended for microservices, the behavior of which depends on 3 or more parameters.)

When using the Project Reactor and RxJava reactive libraries, You need:

  1. Add dependencies to pom.xml.

  2. Add modules to module-info.java.

Adding dependencies to pom.xml:

<dependencies>
    <dependency>
        <groupId>io.projectreactor</groupId>
        <artifactId>reactor-core</artifactId>
        <version>${projectreactor.version}</version> (1)
    </dependency>
    <dependency>
        <groupId>io.reactivex.rxjava3</groupId>
        <artifactId>rxjava</artifactId>
        <version>${rxjava.version}</version> (2)
    </dependency>
</dependencies>

Adding modules to module-info.java:

module example {
    requires reactor.core; (1)
    requires io.reactivex.rxjava3; (2)
}
1 The reactor.core module when using the Project Reactor library.
2 The io.reactivex.rxjava3 module when using the RxJava library.

2. RxMicro Annotations

The RxMicro framework supports the following RxMicro Annotations, which are used to create and configure REST Controllers.

Table 1. Supported RxMicro Annotations.
Annotation Description

@GET

Denotes a method, that must handle a GET HTTP request.

@POST

Denotes a method, that must handle a POST HTTP request.

@PUT

Denotes a method, that must handle a PUT HTTP request.

@DELETE

Denotes a method, that must handle a DELETE HTTP request.

@PATCH

Denotes a method, that must handle a PATCH HTTP request.

@HEAD

Denotes a method, that must handle a HEAD HTTP request.

@OPTIONS

Denotes a method, that must handle a OPTIONS HTTP request.

@BaseUrlPath

Denotes a base URL path for the all request handlers at the REST controller.

@Version

Denotes a version of the REST controller.

@Header

Denotes that a field of Java model class is a HTTP header.

@HeaderMappingStrategy

Declares a strategy of header name formation based on Java model field name analysis.

(By default, the CAPITALIZE_WITH_HYPHEN strategy is used. Thus, by using this strategy, the Header-Name name header corresponds to the headerName field name.)

@AddHeader

Denotes a static HTTP header that must be added to the response, created by the request handler.

@SetHeader

Denotes a static HTTP header that must be set to the response, created by the request handler.

@RepeatHeader

Denotes the header, which name needs to be repeated for each element in the list.

(This annotation applies only to fields with the java.util.List<?> type.)

@Parameter

Denotes that a field of Java model class is a HTTP parameter.

@ParameterMappingStrategy

Declares a strategy of parameter name formation based on Java model field name analysis.

(By default, the LOWERCASE_WITH_UNDERSCORED strategy is used. Thus, by using this strategy, the header_name name header corresponds to the headerName field name.)

@PathVariable

Denotes that a field of Java model class is a path variable.

@RemoteAddress

Declares the Java model field as a field, in which the RxMicro framework must inject the remote client connection address.

@RequestMethod

Declares the Java model field as a field, in which the RxMicro framework must inject a method of the received request.

(This feature is useful for request logging when one handler supports different HTTP methods.)

@RequestUrlPath

Declares the Java model field as a field, in which the RxMicro framework must inject URL path of the received request.

(This feature is useful for request logging using path-variables.)

@RequestBody

Declares the Java model field as a field, in which the RxMicro framework must inject a body of the received request.

@ResponseStatusCode

Indicates to the RxMicro framework that the value of the Java model field should be used as a status code to be sent to the client.

@ResponseBody

Indicates to the RxMicro framework that the value of the Java model field should be used as a body to be sent to the client.

@RequestId

Declares the Java model field as a field, in which the RxMicro framework must inject a unique request ID.

@SetStatusCode

Declares a status code, which should be sent to the client in case of successful execution of the HTTP request handler.

@NotFoundMessage

Declares a message returned by the handler in case of no result.

@RestServerGeneratorConfig

Allows to configure the process of code generation by the RxMicro Annotation Processor for REST controllers.

@EnableCrossOriginResourceSharing

Activates the Cross Origin Resource Sharing for all request handlers in the REST controller.

3. Return Types

The HTTP request handler supports two categories of returned results:

  • HTTP response without body;

  • HTTP response with body;

3.1. Supported Return Types for HTTP Response without Body

The RxMicro framework supports the following return result types for an HTTP response without body:

final class RestControllerWithoutBody {

    (1)
    @GET("/void/void1")
    void void1() {
        //do something
    }

    @GET("/void/void2")
    void void2(final Request request) {
        //do something with request
    }

    @GET("/void/void3")
    void void3(final String requestParameter) {
        //do something with requestParameter
    }

    (2)

    @GET("/jse/completedFuture1")
    CompletableFuture<Void> completedFuture1() {
        return CompletableFuture.completedFuture(null);
    }

    @GET("/jse/completedFuture2")
    CompletableFuture<Void> completedFuture1(final Request request) {
        return CompletableFuture.completedFuture(null);
    }

    @GET("/jse/completedFuture3")
    CompletableFuture<Void> completedFuture1(final String requestParameter) {
        return CompletableFuture.completedFuture(null);
    }

    (3)

    @GET("/jse/completionStage1")
    CompletionStage<Void> completionStage1() {
        return CompletableFuture.completedStage(null);
    }

    @GET("/jse/completionStage2")
    CompletionStage<Void> completionStage2(final Request request) {
        return CompletableFuture.completedStage(null);
    }

    @GET("/jse/completionStage3")
    CompletionStage<Void> completionStage3(final String requestParameter) {
        return CompletableFuture.completedStage(null);
    }

    (4)

    @GET("/spring-reactor/mono1")
    Mono<Void> mono1() {
        return Mono.just("").then();
    }

    @GET("/spring-reactor/mono2")
    Mono<Void> mono2(final String requestParameter) {
        return Mono.just("").then();
    }

    @GET("/spring-reactor/mono3")
    Mono<Void> mono4(final Request request) {
        return Mono.just("").then();
    }

    (5)

    @GET("/rxjava3/completable1")
    Completable completable1() {
        return Completable.complete();
    }

    @GET("/rxjava3/completable2")
    Completable completable2(final Request request) {
        return Completable.complete();
    }

    @GET("/rxjava3/completable3")
    Completable completable3(final String requestParameter) {
        return Completable.complete();
    }
}
1 The void type is supported mainly for test purposes.
(The void type can also be applied when the request handler does not use any blocking operations. In cases where the request handler uses blocking operations, one of the reactive types must be used instead of the void type.)
2 The CompletableFuture<Void> type is recommended when using the java.util.concurrent library.
3 Instead of the CompletableFuture<Void> type, can also be used the CompletionStage<Void> type.
4 When using the Project Reactor library, only the Mono<Void> type can be used.
5 When using the RxJava library, only the Completable type can be used.

All the above mentioned handlers return an HTTP response without body:

@RxMicroRestBasedMicroServiceTest(RestControllerWithoutBody.class)
final class RestControllerWithoutBodyTest {

    private BlockingHttpClient blockingHttpClient;

    @ParameterizedTest
    @ValueSource(strings = {
            "/void/void1",
            "/void/void2",
            "/void/void3",

            "/jse/completedFuture1",
            "/jse/completedFuture2",
            "/jse/completedFuture3",

            "/jse/completionStage1",
            "/jse/completionStage2",
            "/jse/completionStage3",

            "/spring-reactor/mono1",
            "/spring-reactor/mono2",
            "/spring-reactor/mono3",

            "/rxjava3/completable1",
            "/rxjava3/completable2",
            "/rxjava3/completable3"
    })
    void Should_support_HTTP_responses_without_body(final String urlPath) {
        final ClientHttpResponse response = blockingHttpClient.get(urlPath);

        assertTrue(response.isBodyEmpty(), "Body not empty: " + response.getBody()); (1)
    }
}
1 When testing all RestControllerWithoutBody class handlers, each handler returns an HTTP response without body.

The RxMicro framework recommends for request handlers that depend on 3 or more HTTP headers, HTTP parameters or path variables to create separate classes of request model.

Upon implementation of HTTP headers, HTTP parameters or path variables directly into the handler, the code becomes hard to read!

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

3.2. Supported Return Types for HTTP Response with Body

The RxMicro framework supports the following return result types for an HTTP response with body:

final class RestControllerWithBody {

    (1)

    @GET("/jse/completedFuture1")
    CompletableFuture<Response> completedFuture1() {
        return CompletableFuture.completedFuture(new Response());
    }

    @GET("/jse/completedFuture2")
    CompletableFuture<Response> completedFuture2(final Request request) {
        return CompletableFuture.completedFuture(new Response());
    }

    @GET("/jse/completedFuture3")
    CompletableFuture<Response> completedFuture3(final String requestParameter) {
        return CompletableFuture.completedFuture(new Response());
    }

    @GET("/jse/completedFuture4")
    CompletableFuture<Optional<Response>> completedFuture4() {
        return CompletableFuture.completedFuture(Optional.of(new Response()));
    }

    @GET("/jse/completedFuture5")
    CompletableFuture<Optional<Response>> completedFuture5(final Request request) {
        return CompletableFuture.completedFuture(Optional.of(new Response()));
    }

    @GET("/jse/completedFuture6")
    CompletableFuture<Optional<Response>> completedFuture6(final String requestParameter) {
        return CompletableFuture.completedFuture(Optional.of(new Response()));
    }

    (2)

    @GET("/jse/completionStage1")
    CompletionStage<Response> completionStage1() {
        return CompletableFuture.completedStage(new Response());
    }

    @GET("/jse/completionStage2")
    CompletionStage<Response> completionStage2(final Request request) {
        return CompletableFuture.completedStage(new Response());
    }

    @GET("/jse/completionStage3")
    CompletionStage<Response> completionStage3(final String requestParameter) {
        return CompletableFuture.completedStage(new Response());
    }

    @GET("/jse/completionStage4")
    CompletionStage<Optional<Response>> completionStage4() {
        return CompletableFuture.completedStage(Optional.of(new Response()));
    }

    @GET("/jse/completionStage5")
    CompletionStage<Optional<Response>> completionStage5(final Request request) {
        return CompletableFuture.completedStage(Optional.of(new Response()));
    }

    @GET("/jse/completionStage6")
    CompletionStage<Optional<Response>> completionStage6(final String requestParameter) {
        return CompletableFuture.completedStage(Optional.of(new Response()));
    }

    (3)

    @GET("/spring-reactor/mono1")
    Mono<Response> mono1() {
        return Mono.just(new Response());
    }

    @GET("/spring-reactor/mono2")
    Mono<Response> mono2(final Request request) {
        return Mono.just(new Response());
    }

    @GET("/spring-reactor/mono3")
    Mono<Response> mono3(final String requestParameter) {
        return Mono.just(new Response());
    }

    (4)

    @GET("/rxjava3/single1")
    Single<Response> single1() {
        return Single.just(new Response());
    }

    @GET("/rxjava3/single2")
    Single<Response> single2(final Request request) {
        return Single.just(new Response());
    }

    @GET("/rxjava3/single3")
    Single<Response> single3(final String requestParameter) {
        return Single.just(new Response());
    }

    @GET("/rxjava3/maybe1")
    Maybe<Response> maybe1() {
        return Maybe.just(new Response());
    }

    @GET("/rxjava3/maybe2")
    Maybe<Response> maybe2(final Request request) {
        return Maybe.just(new Response());
    }

    @GET("/rxjava3/maybe3")
    Maybe<Response> maybe3(final String requestParameter) {
        return Maybe.just(new Response());
    }
}
1 The CompletableFuture<MODEL> type is recommended when using the java.util.concurrent library.
2 Instead of the CompletableFuture<MODEL> type, can also be used the CompletionStage<MODEL> type.
3 When using the Project Reactor library, only the Mono<MODEL> type can be used.
4 When using the RxJava library, the Single<MODEL> or Maybe<MODEL> can be used.

Note that the reactive types must be parameterized by the HTTP response model class!

All the above mentioned handlers return an HTTP response with body:

@RxMicroRestBasedMicroServiceTest(RestControllerWithBody.class)
final class RestControllerWithBodyTest {

    private BlockingHttpClient blockingHttpClient;

    @ParameterizedTest
    @ValueSource(strings = {
            "/jse/completedFuture1",
            "/jse/completedFuture2",
            "/jse/completedFuture3",
            "/jse/completedFuture4",
            "/jse/completedFuture5",
            "/jse/completedFuture6",

            "/jse/completionStage1",
            "/jse/completionStage2",
            "/jse/completionStage3",
            "/jse/completionStage4",
            "/jse/completionStage5",
            "/jse/completionStage6",

            "/spring-reactor/mono1",
            "/spring-reactor/mono2",
            "/spring-reactor/mono3",

            "/rxjava3/single1",
            "/rxjava3/single2",
            "/rxjava3/single3",

            "/rxjava3/maybe1",
            "/rxjava3/maybe2",
            "/rxjava3/maybe3"
    })
    void Should_support_HTTP_responses_with_body(final String urlPath) {
        final ClientHttpResponse response = blockingHttpClient.get(urlPath);

        assertEquals(
                jsonObject("message", "Hello World!"), (1)
                response.getBody()
        );
    }
}
1 When testing all RestControllerWithBody class handlers, each handler returns an HTTP response containing a JSON object, with the Hello World! message.

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

4. Routing of Requests

The RxMicro framework supports request routing based on HTTP method, URL Path and HTTP body:

4.1. Routing of Requests Based on HTTP Method

The RxMicro framework allows handling identical HTTP requests, differing only in HTTP method, by different handlers:

final class RoutingUsingHTTPMethod {

    @GET("/")
    void get() {
        System.out.println("GET");
    }

    @HEAD("/")
    void head() {
        System.out.println("HEAD");
    }

    @OPTIONS("/")
    void options() {
        System.out.println("OPTIONS");
    }
}
@RxMicroRestBasedMicroServiceTest(RoutingUsingHTTPMethod.class)
final class RoutingUsingHTTPMethodTest {

    private BlockingHttpClient blockingHttpClient;

    private SystemOut systemOut;

    @ParameterizedTest
    @ValueSource(strings = {"GET", "HEAD", "OPTIONS"})
    void Should_route_to_valid_request_handler(final String method) {
        blockingHttpClient.send(method, "/");
        assertEquals(method, systemOut.asString());
    }
}

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

4.2. Routing of Requests Based on URL Path

The RxMicro framework allows handling HTTP requests on different URL Paths by different handlers:

final class RoutingUsingUrlPath {

    @GET("/urlPath1")
    void get1() {
        System.out.println("/urlPath1");
    }

    @GET("/urlPath2")
    void get2() {
        System.out.println("/urlPath2");
    }

    @GET("/${type}")
    void get3(final @PathVariable String type) { (1)
        System.out.println("/${type}: " + type);
    }
}
1 In addition to static URL Paths, the RxMicro framework also supports path variables.
@RxMicroRestBasedMicroServiceTest(RoutingUsingUrlPath.class)
final class RoutingUsingUrlPathTest {

    private BlockingHttpClient blockingHttpClient;

    private SystemOut systemOut;

    @ParameterizedTest
    @CsvSource({
            "/urlPath1,     /urlPath1",
            "/urlPath2,     /urlPath2",
            "/urlPath3,     /${type}: urlPath3",
            "/put,          /${type}: put",
            "/get,          /${type}: get"
    })
    void Should_route_to_valid_request_handler(final String urlPath,
                                               final String expectedOut) {
        blockingHttpClient.get(urlPath);
        assertEquals(expectedOut, systemOut.asString());
    }
}

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

4.3. Routing of Requests Based on HTTP Body

Some HTTP methods allows transferring request parameters both in the start line and in the request body.

Therefore, in addition to standard routing types, the RxMicro framework also supports routing based on the HTTP body availability:

final class RoutingUsingHttpBody {

    @GET("/")
    @HEAD("/")
    @OPTIONS("/")
    @DELETE("/")
    @PATCH("/")
    @POST(value = "/", httpBody = false)
    @PUT(value = "/", httpBody = false)
    void handleRequestsWithoutBody(final String parameter) { (1)
        System.out.println("without body");
    }

    @PATCH(value = "/", httpBody = true)
    @POST("/")
    @PUT("/")
    void handleRequestsWithBody(final String parameter) { (2)
        System.out.println("with body");
    }
}
1 The request handler with parameters transferred in the start line.
2 The request handler with parameters transferred in the request body.

For the GET, HEAD, OPTIONS and DELETE HTTP methods, the request parameters are always transferred in the start line.

For the POST, PUT and PATCH HTTP methods, the request parameters can be transferred both in the start line and in the request body. (To configure the parameter transfer type, the httpBody option is used.)

@RxMicroRestBasedMicroServiceTest(RoutingUsingHttpBody.class)
final class RoutingUsingHttpBodyTest {

    private BlockingHttpClient blockingHttpClient;

    private SystemOut systemOut;

    @ParameterizedTest
    @ValueSource(strings = {"GET", "HEAD", "POST", "PUT", "PATCH", "OPTIONS", "DELETE"})
    void Should_route_to_handleRequestsWithoutBody(final String method) {
        blockingHttpClient.send(
                method,
                "/?parameter=test" (1)
        );
        assertEquals("without body", systemOut.asString());
    }

    @ParameterizedTest
    @ValueSource(strings = {"POST", "PUT", "PATCH"})
    void Should_route_to_handleRequestsWithBody(final String method) {
        blockingHttpClient.send(
                method,
                "/",
                jsonObject("parameter", "test") (2)
        );
        assertEquals("with body", systemOut.asString());
    }
}
1 Parameter transfer in the start line.
2 Parameter transfer in the request body as JSON object.

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

5. Not Found Logic

The RxMicro framework supports Not Found Logic for HTTP request handlers.

To activate this feature it’s necessary to return a reactive type that supports optional result:

final class NotFoundMicroService {

    @GET("/get1")
    CompletableFuture<Optional<Response>> getOptional1(final Boolean found) { (1)
        return completedFuture(found ? Optional.of(new Response()) : Optional.empty());
    }

    @GET("/get2")
    Mono<Response> getOptional2(final Boolean found) { (2)
        return found ? Mono.just(new Response()) : Mono.empty();
    }

    @GET("/get3")
    Maybe<Response> getOptional3(final Boolean found) { (3)
        return found ? Maybe.just(new Response()) : Maybe.empty();
    }
}
1 When using CompletableFuture/CompletionStage it is necessary to use the java.util.Optional contract, since the CompletableFuture/CompletionStage reactive types do not support optional result by default.
2 Unlike CompletableFuture/CompletionStage, the Mono reactive type supports optional result.
3 Unlike CompletableFuture/CompletionStage, the Maybe reactive type also supports optional result.

When handling requests, the RxMicro framework checks the handler result:

  • If the handler returns a response model, the RxMicro framework will convert it to an HTTP response with the 200 status and JSON representation of this model.

  • If the handler returns an empty result, the RxMicro framework generates an HTTP response with the 404 and the standard "Not Found" error message.

@RxMicroRestBasedMicroServiceTest(NotFoundMicroService.class)
final class NotFoundMicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    @ParameterizedTest
    @ValueSource(strings = {"/get1", "/get2", "/get3"})
    void Should_return_found_response(final String urlPath) {
        final ClientHttpResponse response = blockingHttpClient.get(urlPath + "?found=true");

        assertEquals(jsonObject("message", "Hello World!"), response.getBody()); (1)
        assertEquals(200, response.getStatusCode()); (1)
    }

    @ParameterizedTest
    @ValueSource(strings = {"/get1", "/get2", "/get3"})
    void Should_return_not_found_response(final String urlPath) {
        final ClientHttpResponse response = blockingHttpClient.get(urlPath + "?found=false");

        assertEquals(jsonErrorObject("Not Found"), response.getBody()); (2)
        assertEquals(404, response.getStatusCode()); (2)
    }
}
1 In case there is a result, the result is returned in JSON format, and the HTTP response status is 200.
2 If there is no result, a standard error message is returned, and the HTTP response status is 404.

The RxMicro framework provides an option to customize the Not Found message:

final class CustomizeNotFoundMicroService {

    @GET("/")
    (1)
    @NotFoundMessage("Custom not found message")
    CompletableFuture<Optional<Response>> getOptional1(final Boolean found) {
        return completedFuture(found ? Optional.of(new Response()) : Optional.empty());
    }
}
1 The @NotFoundMessage annotation allows You to specify a message that will be displayed to the client in case of missing result instead of the standard "Not Found" message.
@Test
void Should_return_custom_not_found_message() {
    final ClientHttpResponse response = blockingHttpClient.get("/?found=false");

    assertEquals(jsonErrorObject("Custom not found message"), response.getBody()); (1)
    assertEquals(404, response.getStatusCode()); (2)
}
1 If there is no result, the @NotFoundMessage message, which is set by the annotation, is returned.
2 The HTTP status is 404.
(The RxMicro framework does not allow overriding the HTTP status for Not Found logic!)

For more control over the HTTP response generated in case of an error, use exception instead of Not Found Logic feature!

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

6. Exceptions Usage

Each reactive type of returned request handler result supports two states:

  • Successful completion signal.

  • Completion signal with an error.

The feature of a successful completion signal consists in its uniqueness, i.e. if such a signal has occurred, it ensures successful completion of the business task. The feature of a completion signal with an error is that different types of errors may occur during the execution of the business task:

  • validation error;

  • data source connection error;

  • computational algorithm error, etc.

It means that each request handler can return only one successful result and several results with errors.

So the RxMicro framework introduces the error concept. An error means any unsuccessful result.

For simplified error handling, the RxMicro framework recommends using HTTP status codes for each error category!

In case the HTTP code status is not sufficient, the RxMicro framework recommends using an additional text description.

For this purpose, the RxMicro framework defines a standard JSON model which is returned in case of any error:

{
  "message": "Not Found"
}

Thus, in case of an error, the client determines the error category basing on HTTP status code analysis. For more information, the client should analyze a text message.

6.1. Basic Class of Exceptions

When handling an HTTP request, the RxMicro framework defines the HttpErrorException base exception class.

All custom exception types must extend this base class!

For all child classes which extend the HttpErrorException class, when creating an exception instance the stack trace is not filled, as this information is redundant.

(This behavior is achieved by using the protected RuntimeException(String, Throwable, boolean, boolean) protected constructor.)

6.2. Using the User Defined Exceptions

If the request handler must return an HTTP status code other than the successful one, a separate exception class must be created:

public final class CustomNotFoundException extends HttpErrorException { (1)

    private static final int STATUS_CODE = 404; (2)

    public CustomNotFoundException(final String message) { (3)
        super(STATUS_CODE, message);
    }
}
1 The custom exception class must extend the HttpErrorException class.
2 Each class must define the static int STATUS_CODE field with a status code that identifies the error category. (This name is constant and is used by the rxmicro.documentation module when building the project documentation.)
3 If You want to display a detailed message to the client, You need to add the constructor parameter.
(If You want to return to the client only the HTTP status without the HTTP body, then create a constructor without parameters.)

The RxMicro framework does not support null as a business value!

So if the null value is passed to the exception constructor as a message, then the RxMicro framework will return an HTTP response without a message body!
(i.e. the new CustomNotFoundException(null) and new CustomNotFoundException() constructions are identical in terms of the RxMicro framework!)

You can use two approaches to return an HTTP status code other than successful one:

final class MicroService {

    @PUT(value = "/business-object-1", httpBody = false)
    CompletableFuture<Void> updateObject1(final Integer id) {
        return failedFuture(new CustomNotFoundException("Object not found by id=" + id)); (1)
    }

    @PUT(value = "/business-object-2", httpBody = false)
    CompletableFuture<Void> updateObject2(final Integer id) {
        throw new CustomNotFoundException("Object not found by id=" + id); (2)
    }
}
1 Use the reactive type method to generate a completion signal with an error.
2 Create and throw an exception instance.

From the client’s point of view, the two above-mentioned approaches are exactly the same:

@RxMicroRestBasedMicroServiceTest(MicroService.class)
final class MicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    @ParameterizedTest
    @ValueSource(strings = {"/business-object-1", "/business-object-2"})
    void Should_return_404(final String urlPath) {
        final ClientHttpResponse response = blockingHttpClient.put(urlPath + "?id=0");

        assertEquals(jsonErrorObject("Object not found by id=0"), response.getBody()); (1)
        assertEquals(404, response.getStatusCode()); (2)
    }
}
1 As a detailed error message, the message passed to the exception constructor is returned.
(The jsonErrorObject("…​") utility method is synonymous with the jsonObject("message", "…​") method.)
2 The returned status code equals the status code defined in the exception class in the int STATUS_CODE field.

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

6.3. Error Signal Generation Methods

To return an HTTP response with an error it is necessary to use the appropriate reactive type method to generate the error signal.

Table 2. Error signal generation method.
Reactive type Static method

CompletableFuture

CompletableFuture.failedFuture

CompletionStage

CompletableFuture.failedStage

Mono

Mono.error

Completable

Completable.error

Single

Single.error

Maybe

Maybe.error

Using the error signal generation method instead of throwing an exception instance requires writing more code but saves processor resources!

So it’s up to You to determine which method to use in Your project!

6.4. Predefined Classes of Exceptions

The RxMicro framework defines the following predefined exception classes:

Table 3. List of predefined exception classes.
Exception class Status code Description

RedirectException

300..399

A base class to inform the client about the need to perform redirect.

(Instead of using a base class, it is recommended to use one of the following child ones: TemporaryRedirectException or PermanentRedirectException.)

TemporaryRedirectException

307

A class that signals the need to perform Temporary Redirect.

PermanentRedirectException

308

A class that signals the need to perform Permanent Redirect.

ValidationException

400

A class signaling that the client has sent a bad request.

InternalHttpErrorException

500

A class signaling that an internal error has occurred during execution.

UnexpectedResponseException

500

A class signaling that the HTTP response, generated by the request handler, contains validation errors.

HttpClientTimeoutException

504

A class signaling that the HTTP client didn’t receive a response from the server in given time.

7. Overriding the Status Code

By default, if there are no errors during HTTP request handling, the HTTP server returns the 200 status code. If You need to change the status code returned by default, You must use the @SetStatusCode annotation:

final class MicroService {

    @GET("/200")
    void success() {
    }

    @GET("/307")
    @SetStatusCode(307)
    void permanentRedirect() {
    }

    @GET("/404")
    @SetStatusCode(404)
    void notFound() {
    }

    @GET("/500")
    @SetStatusCode(500)
    void internalError() {
    }
}
@RxMicroRestBasedMicroServiceTest(MicroService.class)
final class MicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    @ParameterizedTest
    @ValueSource(ints = {200, 307, 404, 500})
    void Should_return_valid_status_codes(final int expectedStatus) {
        final ClientHttpResponse response = blockingHttpClient.get("/" + expectedStatus);

        assertEquals(expectedStatus, response.getStatusCode());
    }
}

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

8. Redirecting of Requests

The RxMicro framework supports request redirection:

final class MicroService {

    @PUT(value = "/old-url")
    CompletableFuture<Void> redirect() {
        return failedFuture(new PermanentRedirectException("/new-url")); (1)
    }

    (2)
    @PUT(value = "/new-url")
    void put(final String parameter) {
        System.out.println(parameter);
    }
}
1 To redirect a request, You need to return the PermanentRedirectException instance, indicating a new URL Path.
2 Once the HTTP response with request redirection is returned, the HTTP client will automatically repeat the request to a new URL Path.

If temporary redirection is required, the TemporaryRedirectException class must be used instead of the PermanentRedirectException class!

The predefined redirection classes (TemporaryRedirectException and PermanentRedirectException) return the 307 and 308 HTTP statuses.

According to the HTTP protocol rules, the HTTP method is preserved at such redirection!

@RxMicroRestBasedMicroServiceTest(MicroService.class)
final class MicroServiceTest {

    (1)
    @BlockingHttpClientSettings(followRedirects = ENABLED)
    private BlockingHttpClient blockingHttpClient;

    private SystemOut systemOut;

    @Test
    void Should_redirect() {
        blockingHttpClient.put(
                "/old-url",
                jsonObject("parameter", "test")
        ); (2)

        assertEquals("test", systemOut.asString()); (3)
    }
}
1 By default, the predefined HTTP client, that is used to perform test requests to the microservice, does not support redirection. Therefore, automatic redirection must be activated using the @BlockingHttpClientSettings(followRedirects = ENABLED) setting.
2 The PUT request to the /put1 URL Path is performed.
3 Consequently, the MicroService.put handler is automatically performed after the MicroService.redirect handler.

Besides using the PermanentRedirectException/TemporaryRedirectException exceptions, a custom model class or the @SetStatusCode and @SetHeader annotation composition can be used for redirection:

@PUT(value = "/old-url1")
CompletableFuture<RedirectResponse> redirect1() {
    return completedFuture(new RedirectResponse("/new-url")); (1)
}

@PUT(value = "/old-url2")
(2)
@SetStatusCode(307)
@SetHeader(name = LOCATION, value = "/new-url")
void redirect2() {
}
1 Using a custom model class with the Location header.
2 Using the @SetStatusCode and @SetHeader annotation composition.

FYI: The RedirectResponse model class:

public final class RedirectResponse {

    @ResponseStatusCode
    final Integer status = 307;

    @Header(LOCATION)
    final String location;

    public RedirectResponse(final String location) {
        this.location = requireNonNull(location);
    }
}

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

9. HTTP Headers Support

9.1. Basic Rules

The RxMicro framework supports HTTP headers in request and response models:

(1)
@HeaderMappingStrategy
public final class Request {

    (2)
    @Header
    String endpointVersion;

    (3)
    @Header("UseProxy")
    Boolean useProxy;

    public String getEndpointVersion() {
        return endpointVersion;
    }

    public Boolean getUseProxy() {
        return useProxy;
    }
}
(1)
@HeaderMappingStrategy
public final class Response {

    (2)
    @Header
    final String endpointVersion;

    (3)
    @Header("UseProxy")
    final Boolean useProxy;

    public Response(final Request request) {
        this.endpointVersion = request.getEndpointVersion();
        this.useProxy = request.getUseProxy();
    }

    public Response(final String endpointVersion,
                    final Boolean useProxy) {
        this.endpointVersion = endpointVersion;
        this.useProxy = useProxy;
    }
}
1 The @HeaderMappingStrategy annotation allows setting common conversion rules for all header names from HTTP to Java format and vice versa in the current model class.
(By default, the CAPITALIZE_WITH_HYPHEN strategy is used. The field name is used as the basic name, and then, following the rules of the specified strategy, the HTTP header name is generated.)
2 In order to declare a model field as the HTTP header field, it is necessary to use the @Header annotation.
3 Using the @Header annotation, it is possible to specify the HTTP header name, which does not correspond to the used strategy declared by the @HeaderMappingStrategy annotation.

The RxMicro framework uses the following algorithm to define the HTTP header name for the specified model field:

  1. If the field is annotated by the @Header annotation with an explicit indication of the HTTP header name, the specified name is used;

  2. If no HTTP header name is specified in the @Header annotation, the RxMicro framework checks for the @HeaderMappingStrategy annotation above the model class;

  3. If the model class is annotated by the @HeaderMappingStrategy annotation, then the specified naming strategy is used.
    (The field name is used as the basic name, and then, following the rules of the specified strategy, the HTTP header name is generated.)

  4. If the @HeaderMappingStrategy annotation is missing, the model class field name is used as the HTTP header name.

After creating model classes, it is necessary to create a request handler that uses the following models:

final class SimpleUsageMicroService {

    @GET("/get1")
    CompletableFuture<Response> get1(final Request request) { (1)
        return completedFuture(new Response(request));
    }

    (2)
    @GET("/get2")
    (3)
    @HeaderMappingStrategy
    CompletableFuture<Response> get2(final @Header String endpointVersion,          (4)
                                     final @Header("UseProxy") Boolean useProxy) {  (5)
        return completedFuture(new Response(endpointVersion, useProxy));
    }
}
1 If a separate model class has been created for an HTTP request, then this class must be passed to the method parameter that handles the HTTP request.
2 Besides supporting HTTP request model classes, the RxMicro framework supports request handlers that accept HTTP headers as method parameters.
3 To define a common naming strategy for all HTTP headers which are passed as method parameters, the request handler must be annotated by the @HeaderMappingStrategy annotation.
4 To declare a method parameter as an HTTP header field, use the @Header annotation.
5 Using the @Header annotation, it is possible to specify an HTTP header name that does not correspond to the strategy used, declared by the @HeaderMappingStrategy annotation.

The RxMicro framework recommends for request handlers that depend on 3 or more HTTP headers to create separate classes of request model.

Upon implementation of HTTP headers directly into the handler, the code becomes hard to read!

Despite the different approaches to HTTP header handling support, from the client’s point of view the two above-mentioned handlers are absolutely equal:

@RxMicroRestBasedMicroServiceTest(SimpleUsageMicroService.class)
final class SimpleUsageMicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    @ParameterizedTest
    @ValueSource(strings = {"/get1", "/get2"})
    void Should_process_HTTP_headers(final String path) {
        final ClientHttpResponse response = blockingHttpClient.get(
                path, (1)
                HttpHeaders.of(
                        "Endpoint-Version", "v1",   (2)
                        "UseProxy", true            (3)
                )
        );

        final HttpHeaders responseHeaders = response.getHeaders();
        assertEquals("v1", responseHeaders.getValue("Endpoint-Version"));   (4)
        assertEquals("true", responseHeaders.getValue("UseProxy"));         (4)
    }
}
1 When performing a request to different URL Paths, the result is the same.
2 The Endpoint-Version name corresponds to the endpointVersion field of the request model.
(This correspondence is formed basing on the default strategy use, which is defined by the @HeaderMappingStrategy annotation.)
3 The UseProxy name corresponds to the useProxy field of the request model, since this name is specified in the @Header annotation.
4 After executing the request in the resulting HTTP response, the Endpoint-Version and UseProxy headers are equal to v1 and true respectively. (The handler returns the same header values it received from an HTTP request.)

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

9.2. Supported Data Types

The RxMicro framework supports the following Java types, which can be HTTP request model headers:

  • ? extends Enum<?>;

  • java.lang.Boolean;

  • java.lang.Byte;

  • java.lang.Short;

  • java.lang.Integer;

  • java.lang.Long;

  • java.math.BigInteger;

  • java.lang.Float;

  • java.lang.Double;

  • java.math.BigDecimal;

  • java.lang.Character;

  • java.lang.String;

  • java.time.Instant;

For floating point numbers, it is suggested to use the java.math.BigDecimal type instead of java.lang.Float or java.lang.Double.

Using the java.math.BigDecimal allows excluding errors of decimal number notation. This is particularly important if the decimal number is used to represent currency-related data!

and also the java.util.List<T>, java.util.Set<T> and the java.util.Map<String, T> type is parameterized by any of the above mentioned primitive types.

If the java.util.Map<String, T> type is used, then REST model can contain dynamic properties, where keys are the names of properties and values are the properties values.

final class ListHeaderMicroService {

    @GET("/")
    @HeaderMappingStrategy
    void consume(final @Header List<Status> supportedStatuses) {
        System.out.println(supportedStatuses);
    }
}
@RxMicroRestBasedMicroServiceTest(ListHeaderMicroService.class)
final class ListHeaderMicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    private SystemOut systemOut;

    static Stream<Arguments> supportedListProvider() {
        return Stream.of(
                arguments("created|approved|rejected"),                         (1)
                arguments(new Status[]{created, approved, rejected}, 0),        (2)
                arguments(new String[]{"created", "approved", "rejected"}, 0),  (3)
                arguments(List.of(created, approved, rejected)),                (4)
                arguments(List.of("created", "approved", "rejected")),          (5)
                arguments(HttpHeaders.of(                                       (6)
                        "Supported-Statuses", "created",
                        "Supported-Statuses", "approved",
                        "Supported-Statuses", "rejected"
                )),
                arguments(HttpHeaders.of(                                       (6)
                        "Supported-Statuses", "created|approved",
                        "Supported-Statuses", "rejected"
                ))
        );
    }

    @ParameterizedTest
    @MethodSource("supportedListProvider")
    void Should_support_list_headers(final Object headerValue) {
        blockingHttpClient.get(
                "/",
                headerValue instanceof HttpHeaders ?
                        (HttpHeaders) headerValue :
                        HttpHeaders.of("Supported-Statuses", headerValue) (7)
        );

        assertEquals(
                "[created, approved, rejected]", (8)
                systemOut.asString()
        );
    }
}
1 If the HTTP header is a list of values, the list elements are transferred via the HTTP protocol as a string separated by the | symbol.
(For HTTP response headers it is possible to activate the repeating headers mode.)
2 Besides transferring the list using a comma-separated string, the BlockingHttpClient component also supports:
an array of enumerations,
3 an array of string values,
4 a list of enumerations,
5 a list of string values.
(The BlockingHttpClient component converts these types to a comma-separated string automatically!)
6 Besides transferring HTTP headers as a comma-separated string, the BlockingHttpClient component also supports repeatable HTTP headers with different values.
7 All specified value types for the HTTP header, which is a list of values, are transferred as the java.lang.Object type. (The BlockingHttpClient component automatically converts them to a comma-separated string and transfers via the HTTP protocol.)
8 The RxMicro framework converts different types to a list of enumerations and displays it in the console.

9.3. Static HTTP headers

For setting of the static headers, the RxMicro framework provides the @AddHeader and @SetHeader annotations:

(1)
@SetHeader(name = "Mode", value = "Demo")
final class StaticHeadersMicroService {

    @GET("/get1")
    void get1() {
    }

    @GET("/get2")
    (2)
    @SetHeader(name = "Debug", value = "true")
    void get2() {
    }
}
1 If the HTTP header is set for the REST controller, it is added to the HTTP responses for each handler.
2 If the HTTP header is set for the handler, it is added to the HTTP responses only for that handler.

From the client’s point of view, static headers do not differ from any others:

@Test
void Should_use_parent_header_only() {
    final ClientHttpResponse response = blockingHttpClient.get("/get1");

    assertEquals("Demo", response.getHeaders().getValue("Mode"));
}

@Test
void Should_use_parent_and_child_headers() {
    final ClientHttpResponse response = blockingHttpClient.get("/get2");

    assertEquals("Demo", response.getHeaders().getValue("Mode"));
    assertEquals("true", response.getHeaders().getValue("Debug"));
}

In order to understand the differences between the @AddHeader and @SetHeader annotations, take a look to the following example:

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

9.4. Repeating HTTP headers

If the HTTP header of an HTTP response is a list of values, the list elements are transferred by default via the HTTP protocol as a string separated by the | symbol.

If You want the HTTP header to be repeated for each value, You need to use the @RepeatHeader annotation:

@Header
List<Status> singleHeader = List.of(created, approved, rejected);

@Header
@RepeatHeader
List<Status> repeatingHeader = List.of(created, approved, rejected);

The use of the @RepeatHeader annotation is supported for response models only!

For request models it makes no sense, because the RxMicro framework converts any of the supported formats into a list of values.

As a result of converting the Java model to HTTP response, the result will be as follows:

assertEquals(
        "created|approved|rejected", (1)
        response.getHeaders().getValue("Single-Header")
);
assertEquals(
        List.of("created", "approved", "rejected"), (2)
        response.getHeaders().getValues("Repeating-Header")
);
1 By default, HTTP header list elements are transferred via the HTTP protocol as a string separated by the | symbol.
2 If the field is annotated by the @RepeatHeader annotation, then the header is repeated for each element of the list.

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

10. HTTP Parameters Handling

10.1. Basic Rules

The RxMicro framework supports HTTP parameters in request and response models:

(1)
@ParameterMappingStrategy
public final class Request {

    (2)
    String endpointVersion;

    (3)
    @Parameter("use-Proxy")
    Boolean useProxy;

    public String getEndpointVersion() {
        return endpointVersion;
    }

    public Boolean getUseProxy() {
        return useProxy;
    }
}
(1)
@ParameterMappingStrategy
public final class Response {

    (2)
    final String endpointVersion;

    (3)
    @Parameter("use-Proxy")
    final Boolean useProxy;

    public Response(final Request request) {
        this.endpointVersion = request.getEndpointVersion();
        this.useProxy = request.getUseProxy();
    }

    public Response(final String endpointVersion,
                    final Boolean useProxy) {
        this.endpointVersion = endpointVersion;
        this.useProxy = useProxy;
    }
}
1 The @ParameterMappingStrategy annotation allows setting common conversion rules for all parameter names from HTTP to Java format and vice versa in the current model class. (By default, the LOWERCASE_WITH_UNDERSCORED strategy is used. The field name is used as the basic name, and then, following the rules of the specified strategy, the HTTP parameter name is generated.)
2 In order to declare a model field as the HTTP parameter field, it is necessary to use the @Parameter annotation. (Unlike the @Header annotation, the @Parameter annotation is optional. Thus, if the model field is not annotated by any annotation, then by default it is assumed that there is the @Parameter annotation above the model field.)
3 Using the @Parameter annotation, it is possible to specify the HTTP parameter name, which does not correspond to the used strategy declared by the @ParameterMappingStrategy annotation.

The RxMicro framework uses the following algorithm to define the HTTP parameter name for the specified model field:

  1. If the field is annotated by the @Parameter annotation with an explicit indication of the HTTP parameter name, the specified name is used;

  2. If no HTTP parameter name is specified in the @Parameter annotation, the RxMicro framework checks for the @ParameterMappingStrategy annotation above the model class;

  3. If the model class is annotated by the @ParameterMappingStrategy annotation, then the specified naming strategy is used. (The field name is used as the basic name, and then, following the rules of the specified strategy, the HTTP parameter name is generated.)

  4. If the @ParameterMappingStrategy annotation is missing, the model class field name is used as the HTTP parameter name.

Unlike the @Header annotation, the @Parameter annotation is optional!

Thus, if the model field is not annotated by any annotation, then by default it is assumed that there is the @Parameter annotation above the model field!

After creating model classes, it is necessary to create an HTTP request handler that uses the following models:

final class SimpleUsageMicroService {

    @GET("/get1")
    @POST("/post1")
    CompletableFuture<Response> get1(final Request request) { (1)
        return completedFuture(new Response(request));
    }

    (2)
    @GET("/get2")
    @POST("/post2")
    (3)
    @ParameterMappingStrategy
    CompletableFuture<Response> get2(final String endpointVersion,                      (4)
                                     final @Parameter("use-Proxy") Boolean useProxy) {  (5)
        return completedFuture(new Response(endpointVersion, useProxy));
    }
}
1 If a separate model class has been created for an HTTP request, then this class must be passed by the method parameter that handles the HTTP request.
2 Besides supporting HTTP request model classes, the RxMicro framework supports request handlers that accept HTTP request parameters as method parameters.
3 To define a common naming strategy for all HTTP parameters which are passed as method parameters, the request handler must be annotated by the @ParameterMappingStrategy annotation.
4 To declare a method parameter as an HTTP parameter field, You do not need to use any additional annotations.
5 Using the @Parameter annotation, it is possible to specify an HTTP parameter name that does not correspond to the strategy used, declared by the @ParameterMappingStrategy annotation.

Note that the RxMicro framework does not distinguish whether HTTP request parameters will be transferred to the handler using the start line or HTTP body!

Therefore, if the parameters are the same, it is possible to use the same handler with the specified request model to handle both GET (parameters are transferred in the start line) and POST (parameters are transferred in the HTTP body) request.

The RxMicro framework recommends for request handlers that depend on 3 or more HTTP parameters to create separate classes of request model.

Upon implementation of HTTP parameters directly into the handler, the code becomes hard to read!

Despite the different approaches to HTTP parameter handling support, from the client’s point of view the two above-mentioned handlers are absolutely equal to GET and POST requests respectively:

@RxMicroRestBasedMicroServiceTest(SimpleUsageMicroService.class)
final class SimpleUsageMicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    @ParameterizedTest
    @ValueSource(strings = {"/get1", "/get2"})
    void Should_process_HTTP_query_params(final String path) {
        final ClientHttpResponse response = blockingHttpClient.get(
                path, (1)
                QueryParams.of(
                        "endpoint_version", "v1",   (2)
                        "use-Proxy", true           (3)
                )
        );

        assertHttpBody(response);
    }

    @ParameterizedTest
    @ValueSource(strings = {"/post1", "/post2"})
    void Should_process_HTTP_body_params(final String path) {
        final ClientHttpResponse response = blockingHttpClient.post(
                path, (1)
                jsonObject(
                        "endpoint_version", "v1",   (2)
                        "use-Proxy", true           (3)
                )
        );

        assertHttpBody(response);
    }

    private void assertHttpBody(final ClientHttpResponse response) {
        assertEquals(
                jsonObject(
                        "endpoint_version", "v1", (4)
                        "use-Proxy", true         (4)
                ),
                response.getBody()
        );
    }
}
1 When performing a request to different URL Paths, the result is the same. (Despite the differences in parameter transfer for GET and `POST`requests.)
2 The endpoint_version name corresponds to the endpointVersion field of the request model. (This correspondence is formed basing on the default strategy use, which is defined by the @ParameterMappingStrategy annotation.)
3 The use-Proxy name corresponds to the useProxy field of the request model, since this name is specified in the @Parameter annotation.
4 After executing the request in the resulting HTTP response, the endpoint_version and use-Proxy HTTP parameters are equal to v1 and true respectively. (The handler returns the same header values it received from an HTTP request.)

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

10.2. Supported Data Types

The RxMicro framework supports the following Java types, which can be HTTP request model parameters:

  • ? extends Enum<?>;

  • java.lang.Boolean;

  • java.lang.Byte;

  • java.lang.Short;

  • java.lang.Integer;

  • java.lang.Long;

  • java.math.BigInteger;

  • java.lang.Float;

  • java.lang.Double;

  • java.math.BigDecimal;

  • java.lang.Character;

  • java.lang.String;

  • java.time.Instant;

  • ? extends Object;

  • java.util.List<? extends Object>;

  • java.util.Set<? extends Object>;

  • java.util.Map<java.lang.String, ? extends Object>;

For floating point numbers, it is suggested to use the java.math.BigDecimal type instead of java.lang.Float or java.lang.Double.

Using the java.math.BigDecimal allows excluding errors of decimal number notation. This is particularly important if the decimal number is used to represent currency-related data!

and also the java.util.List<T>, java.util.Set<T> and the java.util.Map<String, T> type is parameterized by any of the above mentioned primitive types.

If the java.util.Map<String, T> type is used, then REST model can contain dynamic properties, where keys are the names of properties and values are the properties values.

final class ListQueryParamMicroService {

    @GET("/")
    @ParameterMappingStrategy
    void consume(final List<Status> supportedStatuses) {
        System.out.println(supportedStatuses);
    }
}
@RxMicroRestBasedMicroServiceTest(ListQueryParamMicroService.class)
final class ListQueryParamMicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    private SystemOut systemOut;

    static Stream<Arguments> supportedListProvider() {
        return Stream.of(
                arguments("created|approved|rejected"),                         (1)
                arguments(new Status[]{created, approved, rejected}, 0),        (2)
                arguments(new String[]{"created", "approved", "rejected"}, 0),  (3)
                arguments(List.of(created, approved, rejected)),                (4)
                arguments(List.of("created", "approved", "rejected")),          (5)
                arguments(QueryParams.of(                                       (6)
                        "supported_statuses", "created",
                        "supported_statuses", "approved",
                        "supported_statuses", "rejected"
                )),
                arguments(QueryParams.of(                                       (0)
                        "supported_statuses", "created|approved",
                        "supported_statuses", "rejected"
                ))
        );
    }

    @ParameterizedTest
    @MethodSource("supportedListProvider")
    void Should_support_list_headers(final Object queryParamValue) {
        blockingHttpClient.get(
                "/",
                queryParamValue instanceof QueryParams ?
                        (QueryParams) queryParamValue :
                        QueryParams.of("supported_statuses", queryParamValue) (7)
        );

        assertEquals(
                "[created, approved, rejected]", (8)
                systemOut.asString()
        );
    }
}
1 If the HTTP parameter is a list of values, the list elements are transferred via the HTTP protocol as a string separated by the | symbol.
2 Besides transferring the list using a comma-separated string, the BlockingHttpClient component also supports:
an array of enumerations,
3 an array of string values,
4 a list of enumerations,
5 a list of string values.
(The BlockingHttpClient component converts these types to a comma-separated string automatically!)
6 Besides transferring HTTP parameters as a comma-separated string, the BlockingHttpClient component also supports repeatable query params with different values.
7 All specified value types for the HTTP parameter, which is a list of values, are transferred as the java.lang.Object type. (The BlockingHttpClient component automatically converts them to a comma-separated string and transfers via the HTTP protocol.)
8 The RxMicro framework converts different types to a list of enumerations and displays it in the console.

10.3. Complex Model Support

Unlike HTTP headers, HTTP parameters can be transferred in the request body.

Therefore, besides primitives the RxMicro framework also supports nested JSON objects and arrays.

Therefore, the list of supported types includes the following types:

  • ? extends Object - custom class of the nested Java model;

  • java.util.List<? extends Object> - list of the nested Java model custom classes;

  • java.util.Set<? extends Object> - set of the nested Java model custom classes;

  • java.util.Map<String, ? extends Object> - map of the nested Java model custom classes;

Nested Model Example:

@ParameterMappingStrategy
public final class NestedModel {

    String stringParameter;

    BigDecimal bigDecimalParameter;

    Instant instantParameter;
}

As well as examples of more complex Java models that use a nested model:

@ParameterMappingStrategy
public final class ComplexRequest {

    Integer integerParameter; (1)

    Status enumParameter; (1)

    List<Status> enumsParameter; (2)

    NestedModel nestedModelParameter; (3)

    List<NestedModel> nestedModelsParameter; (4)
}
@ParameterMappingStrategy
public final class ComplexResponse {

    final Integer integerParameter; (1)

    final Status enumParameter; (1)

    final List<Status> enumsParameter; (2)

    final NestedModel nestedModelParameter; (3)

    final List<NestedModel> nestedModelsParameter; (4)

    public ComplexResponse(final ComplexRequest request) {
        this.integerParameter = request.integerParameter;
        this.enumParameter = request.enumParameter;
        this.enumsParameter = request.enumsParameter;
        this.nestedModelParameter = request.nestedModelParameter;
        this.nestedModelsParameter = request.nestedModelsParameter;
    }
}
1 Primitive JSON type field.
2 Primitive JSON array type.
3 Nested JSON object.
4 JSON array of JSON nested objects.

After creating model classes, it is necessary to create a request handler that uses the following models:

final class ComplexModelMicroService {

    @POST("/")
    CompletableFuture<ComplexResponse> handle(final ComplexRequest request) {
        return completedFuture(new ComplexResponse(request));
    }
}

Since the request is transferred in an HTTP body, the sent and received JSON objects must be the same:

@RxMicroRestBasedMicroServiceTest(ComplexModelMicroService.class)
final class ComplexModelMicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    @Test
    void Should_support_complex_models() {
        final Object jsonObject = jsonObject(
                "integer_parameter", 1,
                "enum_parameter", created,
                "enums_parameter", jsonArray(created, approved),
                "nested_model_parameter", jsonObject(
                        "string_parameter", "test",
                        "big_decimal_parameter", new BigDecimal("3.14"),
                        "instant_parameter", Instant.now()
                ),
                "nested_models_parameter", jsonArray(
                        jsonObject(
                                "string_parameter", "test1",
                                "big_decimal_parameter", new BigDecimal("1.1"),
                                "instant_parameter", Instant.now()
                        ),
                        jsonObject(
                                "string_parameter", "test2",
                                "big_decimal_parameter", new BigDecimal("1.2"),
                                "instant_parameter", Instant.now()
                        )
                )
        );
        final ClientHttpResponse response = blockingHttpClient.post("/", jsonObject);

        assertEquals(jsonObject, response.getBody());
    }
}

For automatic conversion of Java types to JSON types, it is recommended to use the JsonFactory test class from the rxmicro-test-json module.

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

11. The path variables Support

11.1. Basic Rules

The RxMicro framework supports path variables in request models:

public final class Request {

    (1)
    @PathVariable
    String category;

    (2)
    @PathVariable("class")
    String type;

    @PathVariable
    String subType;

    public String getCategory() {
        return category;
    }

    public String getType() {
        return type;
    }

    public String getSubType() {
        return subType;
    }
}
1 In order to declare a model field as the path variable, it is necessary to use the @PathVariable annotation.
2 Using the @PathVariable annotation, it is possible to specify the path variable name.
(If no name is specified, the model field name is used as the path variable name.)

Unlike HTTP headers and parameters that are available also on the client side, path variables are available only within the HTTP request handlers.

So for simplicity it is recommended to always use the model field name as the path variable name!

After creating model classes, it is necessary to create an HTTP request handler that uses the following model:

final class MicroService {

    (1)
    @GET("/1/${category}/${class}/${subType}")
    @GET("/1/${category}/${class}_${subType}")
    @GET("/1/${category}-${class}-${subType}")
    @GET("/1-${category}-${class}-${subType}")
    void consume(final Request request) {
        System.out.println(format(
                "?-?-?",
                request.getCategory(), request.getType(), request.getSubType()
        ));
    }

    (1)
    @GET("/2/${category}/${class}/${subType}")
    @GET("/2/${category}/${class}_${subType}")
    @GET("/2/${category}-${class}-${subType}")
    @GET("/2-${category}-${class}-${subType}")
    void consume(final @PathVariable String category,       (2)
                 final @PathVariable("class") String type,  (3)
                 final @PathVariable String subType) {
        System.out.println(format(
                "?-?-?",
                category, type, subType
        ));
    }
}
1 When using path variables in request models, be sure to use all path variables in the URL Path. (Based on the analysis of the URL Path, and considering all path variables, the RxMicro framework selects the required HTTP request handler.)
2 In order to declare a method parameter as path variable, You must use the @PathVariable annotation.
3 Using @PathVariable annotation it is possible to specify the path variable name, which does not match the name of the method parameter.

The RxMicro framework recommends for request handlers that depend on 3 or more path variables to create separate classes of request model.

Upon implementation of path variables directly into the handler, the code becomes hard to read!

Despite the different approaches to path variables handling support, from the client’s point of view the two above-mentioned handlers are absolutely equal:

@RxMicroRestBasedMicroServiceTest(MicroService.class)
final class MicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    private SystemOut systemOut;

    @ParameterizedTest
    @CsvSource({
            "/1/category/class/subType,     category-class-subType",
            "/1/category/class_subType,     category-class-subType",
            "/1/category-class-subType,     category-class-subType",
            "/1-category-class-subType,     category-class-subType",

            "/2/category/class/subType,     category-class-subType",
            "/2/category/class_subType,     category-class-subType",
            "/2/category-class-subType,     category-class-subType",
            "/2-category-class-subType,     category-class-subType",

            "/1/5/6/7,                      5-6-7",
            "/1/5/6_7,                      5-6-7",
            "/1/5-6-7,                      5-6-7",
            "/1-5-6-7,                      5-6-7",

            "/2/5/6/7,                      5-6-7",
            "/2/5/6_7,                      5-6-7",
            "/2/5-6-7,                      5-6-7",
            "/2-5-6-7,                      5-6-7",
    })
    void Should_support_path_variables(final String path,           (1)
                                       final String expectedOut) {  (2)
        blockingHttpClient.get(path);

        assertEquals(expectedOut, systemOut.asString());
    }
}
1 The current path, which corresponds to one of the specified path templates.
2 The expected string containing all extracted path variables from the current path, displayed in the console.

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

11.2. Supported Data Types

The RxMicro framework supports the following Java types, which can be path variables of the request model:

  • ? extends Enum<?>;

  • java.lang.Boolean;

  • java.lang.Byte;

  • java.lang.Short;

  • java.lang.Integer;

  • java.lang.Long;

  • java.math.BigInteger;

  • java.lang.Float;

  • java.lang.Double;

  • java.math.BigDecimal;

  • java.lang.Character;

  • java.lang.String;

  • java.time.Instant;

For floating point numbers, it is suggested to use the java.math.BigDecimal type instead of java.lang.Float or java.lang.Double.

Using the java.math.BigDecimal allows excluding errors of decimal number notation. This is particularly important if the decimal number is used to represent currency-related data!

12. Support of Internal Data Types

12.1. Basic Rules

The RxMicro framework works with internal data. Due to this, the developer can extract additional information about the HTTP request and gain more control over the generated HTTP response.

To work with internal data, it is necessary to create request and response models:

public final class Request {

    (1)
    @RemoteAddress
    String remoteAddress1;

    (2)
    @RequestUrlPath
    String urlPath;

    (3)
    @RequestMethod
    String method;

    (4)
    HttpVersion httpVersion;

    (5)
    HttpHeaders headers;

    (6)
    @RequestBody
    byte[] bodyBytes;

    public String getRemoteAddress1() {
        return remoteAddress1;
    }

    public String getUrlPath() {
        return urlPath;
    }

    public String getMethod() {
        return method;
    }

    public HttpVersion getHttpVersion() {
        return httpVersion;
    }

    public HttpHeaders getHeaders() {
        return headers;
    }

    public byte[] getBodyBytes() {
        return bodyBytes;
    }
}
1 To extract the remote address of the client connection, it is necessary to use the @RemoteAddress annotation.
2 To extract the current URL path, it is necessary to use the @RequestUrlPath annotation.
(This feature is useful for request logging using path-variables.)
3 To extract the current HTTP method, it is necessary to use the @RequestMethod annotation.
(This feature is useful for request logging when one handler supports different HTTP methods.)
4 To extract the HTTP protocol version, it is necessary to use the HttpVersion type.
5 To extract all HTTP headers, it is necessary to use the HttpHeaders type.
6 To extract the request body content as a byte array, it is necessary to use the @RequestBody annotation.
public final class Response {

    (1)
    @ResponseStatusCode
    final Integer status;

    (2)
    final HttpVersion version;

    (3)
    final HttpHeaders headers;

    (4)
    @ResponseBody
    final byte[] body;

    public Response(final Integer status,
                    final HttpVersion version,
                    final HttpHeaders headers,
                    final byte[] body) {
        this.status = status;
        this.version = version;
        this.headers = headers;
        this.body = body;
    }
}
1 To override the HTTP status code, it is necessary to use the @ResponseStatusCode annotation.
2 To set the HTTP protocol version, it is necessary to use the HttpVersion type.
(Currently only 1.0 and 1.1 HTTP versions are supported.)
3 To set all HTTP headers, it is necessary to use the HttpHeaders type.
4 To set the response body content as a byte array, it is necessary to use the @ResponseBody annotation.

After creating model classes, it is necessary to create an HTTP request handler that uses the following models:

final class MicroService {

    @POST("/")
    CompletableFuture<Response> handle(final Request request) {
        return completedFuture(new Response(
                201,
                request.getHttpVersion(),
                HttpHeaders.of(
                        "Remote-Address", request.getRemoteAddress1(),
                        "Url-Path", request.getUrlPath(),
                        "Method", request.getMethod(),
                        "Custom-Header", request.getHeaders().getValue("Custom-Header")
                ),
                request.getBodyBytes()
        ));
    }
}

After creating the REST controller, let’s check that everything works:

@RxMicroRestBasedMicroServiceTest(MicroService.class)
final class MicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    @Test
    void Should_support_internal_types() {
        final Object body = jsonObject("message", "Hello World!");
        final ClientHttpResponse response = blockingHttpClient.post(
                "/",
                HttpHeaders.of("Custom-Header", "Custom-Value"),
                body
        );

        assertEquals(body, response.getBody());
        assertEquals(201, response.getStatusCode());
        assertSame(HTTP_1_1, response.getVersion());
        final HttpHeaders responseHeaders = response.getHeaders();
        final String remoteAddress = responseHeaders.getValue("Remote-Address");
        assertTrue(
                remoteAddress.contains("127.0.0.1"),
                "Invalid Remote-Address: " + remoteAddress
        );
        assertEquals("/", responseHeaders.getValue("Url-Path"));
        assertEquals("POST", responseHeaders.getValue("Method"));
        assertEquals("Custom-Value", responseHeaders.getValue("Custom-Header"));
    }
}

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

12.2. Supported Internal Data Types

Table 4. Supported internal data types for a request model.
Java type RxMicro Annotation Is the annotation required? Description

HttpRequest

None

-

HTTP request model

HttpVersion

None

-

HTTP protocol version

HttpHeaders

None

-

HTTP header model

SocketAddress

@RemoteAddress

No

Remote client address

String

@RemoteAddress

Yes

Remote client address

String

@RequestUrlPath

Yes

URL Path of the current request

String

@RequestMethod

Yes

HTTP request method

byte[]

@RequestBody

Yes

HTTP request body content

Currently only 1.0 and 1.1 HTTP versions are supported.

Table 5. Supported internal data types for a response model.
Java type RxMicro Annotation Is the annotation required? Description

HttpVersion

None

-

HTTP protocol version

HttpHeaders

None

-

HTTP header model

Integer

@ResponseStatusCode

Yes

HTTP response status code

byte[]

@ResponseBody

Yes

HTTP response body content

13. Versioning of REST Controllers

The RxMicro framework supports versioning of REST Controllers using two strategies:

  • Versioning based on HTTP header analysis with the Api-Version name.

  • Versioning based on URL Path fragment analysis.

13.1. Versioning Based on HTTP Header Analysis

The RxMicro framework allows creating identical REST controllers that differ only in version:

@Version(value = "v1", strategy = Version.Strategy.HEADER) (1)
final class OldMicroService {

    @PATCH("/patch")
    void update() {
        System.out.println("old");
    }
}
1 REST controller of the old v1 version, using the Version.Strategy.HEADER strategy;
@Version(value = "v2", strategy = Version.Strategy.HEADER) (1)
final class NewMicroService {

    @PATCH("/patch")
    void update() {
        System.out.println("new");
    }
}
1 REST controller of the new v2 version, using the Version.Strategy.HEADER strategy;

Note that the rules for defining a handler are the same for two different classes!

The correct selection of the appropriate REST controller handler can be checked with the following test:

@RxMicroRestBasedMicroServiceTest({OldMicroService.class, NewMicroService.class}) (1)
final class MicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    private SystemOut systemOut;

    @ParameterizedTest
    @CsvSource({
            "v1,    old",
            "v2,    new"
    })
    void Should_route_to_valid_request_handler(final String versionHeaderValue,
                                               final String expectedOut) {
        blockingHttpClient.patch(
                "/patch",    (2)
                HttpHeaders.of(
                        API_VERSION, (3)
                        versionHeaderValue
                )
        );

        assertEquals(expectedOut, systemOut.asString());
    }
}
1 The @RxMicroRestBasedMicroServiceTest annotation allows You to run several REST controllers in test mode.
2 The test runs the PATCH request to a URL Path: /.
3 To specify the handler version, the standard Api-Version HTTP header is used.

If only REST controllers of a certain version need to be tested, then the BlockingHttpClient component can be set up for operation of only certain versions of REST controller using the @BlockingHttpClientSettings annotation.

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

13.2. Versioning Based on URL Path Fragment Analysis

The RxMicro framework allows creating identical REST controllers that differ only in version:

@Version(value = "v1", strategy = Version.Strategy.URL_PATH) (1)
final class OldMicroService {

    @PATCH("/patch")
    void update() {
        System.out.println("old");
    }
}
1 REST controller of the old v1 version, using the Version.Strategy.URL_PATH strategy;
@Version(value = "v2", strategy = Version.Strategy.URL_PATH) (1)
final class NewMicroService {

    @PATCH("/patch")
    void update() {
        System.out.println("new");
    }
}
1 REST controller of the new v2 version, using the Version.Strategy.URL_PATH strategy;

Note that the rules for defining a handler are the same for two different classes!

The correct selection of the appropriate REST controller handler can be checked with the following test:

@RxMicroRestBasedMicroServiceTest({OldMicroService.class, NewMicroService.class}) (1)
final class MicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    private SystemOut systemOut;

    @ParameterizedTest
    @CsvSource({
            "/v1,    old",
            "/v2,    new"
    })
    void Should_route_to_valid_request_handler(final String urlVersionPath,
                                               final String expectedOut) {
        blockingHttpClient.patch(
                urlVersionPath + "/patch"    (2)
        );

        assertEquals(expectedOut, systemOut.asString());
    }
}
1 The @RxMicroRestBasedMicroServiceTest annotation allows You to run several REST controllers in test mode.
2 The test runs the PATCH request to different URL Paths;

If only REST controllers of a certain version need to be tested, then the BlockingHttpClient component can be set up for operation of only certain versions of REST controller using the @BlockingHttpClientSettings annotation.

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

14. Base URL Path for All Handlers

To configure a base URL Path for all HTTP request handlers, the RxMicro framework provides the @BaseUrlPath annotation:

@BaseUrlPath("base-url-path")
final class MicroService {

    @GET("/path")
    void path() {
    }
}
@RxMicroRestBasedMicroServiceTest(MicroService.class)
final class MicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    @Test
    void Should_support_base_url() {
        final ClientHttpResponse response = blockingHttpClient.get("/base-url-path/path");

        assertEquals(200, response.getStatusCode());
    }
}

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

15. CORS Support

The RxMicro framework supports the Cross Origin Resource Sharing (CORS).

To activate this function it is necessary to add the @EnableCrossOriginResourceSharing annotation:

@EnableCrossOriginResourceSharing (1)
final class MicroService {

    @PATCH("/")
    void handle() {
        System.out.println("handle");
    }
}
1 The Cross Origin Resource Sharing (CORS) feature is activated for all handlers of the specified REST controller.

When activating this feature, the RxMicro framework automatically adds a standard handler:

@RxMicroRestBasedMicroServiceTest(MicroService.class)
final class MicroServiceTest {

    private BlockingHttpClient blockingHttpClient;

    private SystemOut systemOut;

    @Test
    void Should_handle_PATCH_request() {
        blockingHttpClient.patch("/");

        assertEquals("handle", systemOut.asString());
    }

    (1)
    @Test
    void Should_support_CORS_Options_request() {
        final ClientHttpResponse response = blockingHttpClient.options(
                "/",
                HttpHeaders.of(
                        ORIGIN, "test.rxmicro.io",
                        ACCESS_CONTROL_REQUEST_METHOD, PATCH
                )
        );

        assertEquals(ORIGIN, response.getHeaders().getValue(VARY));
        assertEquals("*", response.getHeaders().getValue(ACCESS_CONTROL_ALLOW_ORIGIN));
        assertEquals(PATCH.name(), response.getHeaders().getValue(ACCESS_CONTROL_ALLOW_METHODS));
    }
}
1 The standard handler can handle OPTIONS requests with additional HTTP headers.

For more information on the Cross Origin Resource Sharing (CORS) support, check out the following examples:

The project source code used in the current subsection is available at the following link:

When compiling, the RxMicro framework searches for RxMicro Annotations in the source code and generates additional classes necessary for the integral work of the microservice.

When changing the RxMicro Annotations in the source code, DON’T FORGET to recompile the ALL source code, not just the changed file, for the changes to take effect: mvn clean compile.

16. Request ID

The Request Id feature described at Monitoring section.

17. Configuring the Code Generation Process

The RxMicro framework provides an ability to configure the code generation process for REST controllers. For this purpose, it is necessary to use the @RestServerGeneratorConfig annotation, that annotates the module-info.java module descriptor:

import io.rxmicro.rest.model.GenerateOption;
import io.rxmicro.rest.model.ServerExchangeFormatModule;
import io.rxmicro.rest.server.RestServerGeneratorConfig;

@RestServerGeneratorConfig(
        exchangeFormat = ServerExchangeFormatModule.AUTO_DETECT, (1)
        generateRequestValidators = GenerateOption.AUTO_DETECT,  (2)
        generateResponseValidators = GenerateOption.DISABLED     (3)
)
module rest.controller.generator {
    requires rxmicro.rest.server.netty;
}
1 The exchangeFormat parameter allows You to specify a format for message exchange with a client.
(By default, it is used the message exchange format added to the module-info.java descriptor. If several modules supporting the message exchange format are added to the module-info.java descriptor, then using the exchangeFormat parameter, You need to specify which module should be used for REST controllers.)
2 The generateRequestValidators parameter allows enabling/disabling the option of generating HTTP request validators for all handlers in the project. (The AUTO_DETECT value means that validators will be generated only if the developer adds the rxmicro.validation module to the module-info.java descriptor.)
3 The generateResponseValidators parameter allows enabling/disabling the option of generating HTTP response validators for all handlers in the project. (The DISABLED value means that validators won’t be generated by the RxMicro Annotation Processor.)

The RxMicro team strong recommends using the AUTO_DETECT generate option always! (This is default value for @RestServerGeneratorConfig annotation).

(HTTP response validation can be useful for identifying errors in business task implementation algorithms. For example, instead of returning an incorrect response model to a client, the microservice will throw an error. This approach increases the speed of error search and debugging of the source code that performs the business task.)

FYI: By default the response validators are generated but not invoked! To activate the validation of responses it is necessary to set enableAdditionalValidations property:

new Configs.Builder()
        .withConfigs(new RestServerConfig()
                .setEnableAdditionalValidations(true)) (1)
        .build();
1 Enables the response validators

or

export rest-server.enableAdditionalValidations=true (1)
1 Enables the response validators

or using any other supported config types

Thus the RxMicro team recommends the following approach:

  • Your project configuration must enable generation of validators for all possible model classes.

  • Your development and staging environment must enable additional validation.

  • Your production environment must disable additional validation!

Core Concepts

Home

REST Client