© 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:
-
The
rxmicro.rest
is a basic module that defines basicRxMicro Annotations
, required when using theREST
architecture of building program systems; -
The
rxmicro.rest.server
is a basic HTTP server module used to create REST controllers and run REST-based microservices; -
The
rxmicro.rest.server.netty
is an HTTP server implementation module based onNetty
; -
The
rxmicro.rest.server.exchange.json
is a module for converting Java models toJSON
format and vice versa;
Due to transit dependencies only two modules usually need to be added to a 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:
-
The class must extend the
java.lang.Object
one. -
The class couldn’t be an
abstract
one. -
The class couldn’t be a nested one.
-
The class must contain an accessible (not private) constructor without parameters.
(The last requirement can be ignored if Your project depends onrxmicro.cdi
module.)
A REST Controller class must be a public one, only if it contains the method: |
1.2. HTTP Request Handler Requirements.
HTTP request handler is a method, that must meet the following requirements:
-
The method couldn’t be a
private
one. -
The method couldn’t be an
abstract
one. -
The method couldn’t be a
synchronized
one. -
The method couldn’t be a
static
one. -
The method couldn’t be a
native
one. -
The method must be annotated by at least one of the following annotations:
-
The method must return a
void
type or one of the following reactive types: -
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
andjava.util.Optional
are supported also. (Read more: core.html)).
The
For such cases the HTTP protocol provides the special |
The RxMicro framework supports the following parameter types for the HTTP request handler:
-
Handler without parameters.
(This type is recommended for the simpleststateless
microservices without parameters.) -
List of primitive parameters.
(This type is recommended for microservices, the behavior of which depends on 1-2 parameters.) -
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:
Adding dependencies to
Adding modules to
|
2. RxMicro Annotations
The RxMicro framework supports the following RxMicro Annotations
, which are used to create and configure REST Controllers.
Annotation | Description |
---|---|
Denotes a method, that must handle a |
|
Denotes a method, that must handle a |
|
Denotes a method, that must handle a |
|
Denotes a method, that must handle a |
|
Denotes a method, that must handle a |
|
Denotes a method, that must handle a |
|
Denotes a method, that must handle a |
|
Denotes a base URL path for the all request handlers at the REST controller. |
|
Denotes a version of the REST controller. |
|
Denotes that a field of Java model class is a HTTP header. |
|
Declares a strategy of header name formation based on Java model field name analysis. (By default, the
|
|
Denotes a static HTTP header that must be added to the response, created by the request handler. |
|
Denotes a static HTTP header that must be set to the response, created by the request handler. |
|
Denotes the header, which name needs to be repeated for each element in the list. (This annotation applies only to fields with the |
|
Denotes that a field of Java model class is a HTTP parameter. |
|
Declares a strategy of parameter name formation based on Java model field name analysis. (By default, the
|
|
Denotes that a field of Java model class is a |
|
Declares the Java model field as a field, in which the RxMicro framework must inject the remote client connection address. |
|
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.) |
|
Declares the Java model field as a field, in which the RxMicro framework must inject (This feature is useful for request logging using |
|
Declares the Java model field as a field, in which the RxMicro framework must inject a body of the received request. |
|
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. |
|
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. |
|
Declares the Java model field as a field, in which the RxMicro framework must inject a unique request ID. |
|
Declares a status code, which should be sent to the client in case of successful execution of the HTTP request handler. |
|
Declares a message returned by the handler in case of no result. |
|
Allows to configure the process of code generation by the |
|
Activates the |
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 Upon implementation of
HTTP headers, HTTP parameters or |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
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 When changing the |
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 When changing the |
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 When changing the |
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 For the |
@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 When changing the |
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 |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
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 (This behavior is achieved by using the
|
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 So if the |
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 When changing the |
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.
Reactive type | Static method |
---|---|
|
|
|
|
|
|
|
|
|
|
|
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:
Exception class | Status code | Description |
---|---|---|
|
A base class to inform the client about the need to perform (Instead of using a base class, it is recommended to use one of the following child ones: |
|
A class that signals the need to perform |
||
A class that signals the need to perform |
||
A class signaling that the client has sent a bad request. |
||
A class signaling that an internal error has occurred during execution. |
||
A class signaling that the HTTP response, generated by the request handler, contains validation errors. |
||
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 When changing the |
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 |
The predefined redirection classes ( 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
FYI: The
|
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
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:
-
If the field is annotated by the
@Header
annotation with an explicit indication of the HTTP header name, the specified name is used; -
If no HTTP header name is specified in the
@Header
annotation, the RxMicro framework checks for the@HeaderMappingStrategy
annotation above the model class; -
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.) -
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 When changing the |
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 Using the |
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 |
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 |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
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 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 When changing the |
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:
-
If the field is annotated by the
@Parameter
annotation with an explicit indication of the HTTP parameter name, the specified name is used; -
If no HTTP parameter name is specified in the
@Parameter
annotation, the RxMicro framework checks for the@ParameterMappingStrategy
annotation above the model class; -
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.) -
If the
@ParameterMappingStrategy
annotation is missing, the model class field name is used as the HTTP parameter name.
Unlike the Thus, if the model field is not annotated by any annotation, then by default it is assumed that there is the |
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 |
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 When changing the |
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 Using the |
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 |
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:
|
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
|
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
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, So for simplicity it is recommended to always use the model field name as the |
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
Upon implementation of
|
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 When changing the |
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 Using the |
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 When changing the |
12.2. Supported Internal Data Types
Java type | RxMicro Annotation | Is the annotation required? | Description |
---|---|---|---|
None |
- |
HTTP request model |
|
None |
- |
HTTP protocol version |
|
None |
- |
HTTP header model |
|
No |
Remote client address |
||
Yes |
Remote client address |
||
Yes |
URL Path of the current request |
||
Yes |
HTTP request method |
||
|
Yes |
HTTP request body content |
Currently only |
Java type | RxMicro Annotation | Is the annotation required? | Description |
---|---|---|---|
None |
- |
HTTP protocol version |
|
None |
- |
HTTP header model |
|
Yes |
HTTP response status code |
||
|
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
|
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
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
|
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
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 When changing the |
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 |
The project source code used in the current subsection is available at the following link: |
When compiling, the RxMicro framework searches for When changing the |
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 (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
or
or using any other supported config types Thus the RxMicro team recommends the following approach:
|