© 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. |
TODO
1. Request Id
To track an HTTP request when using a microservice architecture, the RxMicro framework provides an HTTP request identification feature.
1.1. Basic Rules
If the current request is identified, the provided unique id is used during the life-cycle of the current request. If the request is not identified, the RxMicro framework generates a unique id, which is further used in the life-cycle of the current request.
To store the request id, the
Request-Id
HTTP additional header is used.
If the HTTP request id is necessary for business logic, a separate field in the HTTP request Java model must be created:
public final class Request {
(1)
//@Header(HttpHeaders.REQUEST_ID)
(2)
@RequestId
String requestId;
public String getRequestId() {
return requestId;
}
}
1 | For convenience, instead of specifying an HTTP header using the @Header annotation and the
Request-Id name, |
2 | the special @RequestId annotation can be used, which is an alternative to the @Header(HttpHeaders.REQUEST_ID) configuration. |
Having declared a field in the HTTP request Java model, You can use its value in the handler:
final class MicroService {
@GET("/")
void handle(final Request request) {
System.out.println(request.getRequestId()); (1)
}
}
1 | Displaying the value of the current request id in the console. |
The following test describes the basic requirements for the behavior of the request id function:
@RxMicroRestBasedMicroServiceTest(MicroService.class)
final class MicroServiceTest {
@WithConfig
private static final RestServerConfig CONFIG = new RestServerConfig()
.setRequestIdGeneratorProvider(restServerConfig -> () -> "TestRequestId") (6)
.setDevelopmentMode(true);
private BlockingHttpClient blockingHttpClient;
private SystemOut systemOut;
@Test
void Should_generate_RequestId_automatically() {
final ClientHttpResponse response = blockingHttpClient.get("/");
assertEquals("TestRequestId", systemOut.asString()); (1)
assertEquals("TestRequestId", response.getHeaders().getValue(REQUEST_ID)); (2)
}
@Test
void Should_use_provided_RequestId() {
final ClientHttpResponse response =
blockingHttpClient.get("/", HttpHeaders.of(REQUEST_ID, "Qwerty")); (3)
assertEquals("Qwerty", systemOut.asString()); (4)
assertEquals("Qwerty", response.getHeaders().getValue(REQUEST_ID)); (5)
}
}
1 | For HTTP requests without id, the RxMicro framework must generate a unique id automatically. (This id can be used in the HTTP request handler body.) |
2 | Each HTTP response must contain the required
Request-Id
HTTP header with the value of the generated request id.(If the configuration parameter RestServerConfig.returnGeneratedRequestId = false , the HTTP response will not contain the Request-Id header.) |
3 | If the current HTTP request already contains an id, the RxMicro framework must use it instead of generating a new value. |
4 | The id set by the client can be used in the HTTP request handler body. |
5 | Each HTTP response must contain the required
Request-Id
HTTP header with a client specified request id value.(If the configuration parameter RestServerConfig.returnGeneratedRequestId = false , the HTTP response will not contain the Request-Id header.) |
6 | For test purposes it is necessary to set deterministic request id provider with constant value. |
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 |
1.2. Supported Generator Providers
The RxMicro framework provides the following predefined request id generator providers.
Type | Implementation class | Description |
---|---|---|
Generates unique 16 bytes (128 bits) request id According to specification this generator is similar to the
|
||
Generates unique 12 bytes (96 bits) request id |
||
Generates unique 12 bytes (96 bits) request id Each request id contains 52 random and 44 deterministic bits. |
||
Generates unique 12 bytes (96 bits) request id Each request id contains 44 deterministic bits + 24 incremental counter bits + 28 checksum bits. |
||
|
Generates unique 12 bytes (96 bits) request id This is default request id generator provider. By default this request id generator provider tries to use |
To change the request id generator provider, You must use the
RestServerConfig
configuration class:
new Configs.Builder()
.withConfigs(new RestServerConfig()
.setRequestIdGeneratorProvider(
PredefinedRequestIdGeneratorProvider.UUID_128_BITS (1)
)
)
.build();
or
export rest-server.requestIdGeneratorProvider=\
@io.rxmicro.rest.server.PredefinedRequestIdGeneratorProvider:UUID_128_BIT
or using any other supported config types
To get additional information about how custom type can be used as valid config parameters read core.html |
2. Request Tracing Usage Example
The following example demonstrates how request tracing feature can be used:
public final class Request implements RequestIdSupplier { (1)
(2)
@RequestId
String requestId;
@PathVariable
Long id;
@Override
public String getRequestId() {
return requestId;
}
public Long getId() {
return id;
}
}
public final class AccountController {
@Inject
private AccountService accountService;
@GET("/account/${id}")
Mono<Response> findById(final Request request) {
return accountService.findById(request);
}
}
public final class AccountService {
(1)
private static final Logger LOGGER = LoggerFactory.getLogger(AccountService.class);
@Inject
private AccountRepository accountRepository;
public Mono<Response> findById(final Request request) {
LOGGER.debug(
request, (2)
"Finding account by id=?...", request.getId()
);
return accountRepository.findById(request, request.getId())
.switchIfEmpty(Mono.error(() -> {
LOGGER.error(
request, (3)
"Account not found by id=?!", request.getId()
);
return new AccountNotFound404Exception();
}))
.map(account -> {
LOGGER.debug(
request, (4)
"Account exists by id=?: ?",
request.getId(), account
);
return new Response(
account.getEmail(), account.getFirstName(), account.getLastName()
);
});
}
}
@PostgreSQLRepository
public interface AccountRepository {
@Select("SELECT * FROM ${table} WHERE ${by-id-filter}")
Mono<Account> findById(RequestIdSupplier requestIdSupplier, long id); (1)
}
The `jul.properties' config classpath resource:
(1)
io.rxmicro.logger.jul.PatternFormatter.pattern=%date{HH:mm:ss.SSS} {%id} [%level] %logger{0}: %message%n
io.rxmicro.rest.server.level=TRACE
io.rxmicro.examples.monitoring.request.tracing.level=TRACE
Invoke the test request:
:$ curl -v localhost:8080/account/1
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /account/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: /
>
< HTTP/1.1 200 OK
< Content-Length: 88
< Server: RxMicro-NettyServer/0.7-SNAPSHOT
< Date: Wed, 25 Nov 2020 17:30:51 GMT
< Content-Type: application/json
< Request-Id: AkinnfVzx1752012 (1)
<
* Connection #0 to host localhost left intact
{"email":"richard.hendricks@piedpiper.com","firstName":"Richard","lastName":"Hendricks"}
The log output:
19:30:50.494 {null} [TRACE] NettyClientConnectionController: Client connection created: Channel=eaaf0399, IP=/127.0.0.1:33312
19:30:50.589 {AkinnfVzx1752012} [TRACE] NettyRequestHandler: HTTP request: (Channel=eaaf0399, IP=/127.0.0.1:33312):
GET /account/1 HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.58.0
Accept: /
content-length: 0
19:30:50.591 {AkinnfVzx1752012} [DEBUG] AccountService: Finding account by id=1...
19:30:50.998 {AkinnfVzx1752012} [TRACE] AccountRepository: Execute SQL 'SELECT id, email, first_name, last_name FROM account WHERE id = $1' with params: [1] using connection: class='PooledConnection', id='c1275307250'...
19:30:51.074 {AkinnfVzx1752012} [TRACE] AccountRepository: SQL 'SELECT id, email, first_name, last_name FROM account WHERE id = $1' with params: [1] executed successful
19:30:51.148 {AkinnfVzx1752012} [DEBUG] AccountService: Account exists by id=1: Account{id=1, email=richard.hendricks@piedpiper.com, firstName=Richard, lastName=Hendricks}
19:30:51.166 {AkinnfVzx1752012} [TRACE] AccountRepository: Connection closed: class='PooledConnection', id='c1275307250', signal='onComplete'
19:30:51.167 {AkinnfVzx1752012} [TRACE] NettyRequestHandler: HTTP response: (Channel=eaaf0399, Duration=588.093042ms):
HTTP/1.1 200 OK
Content-Length: 88
Server: RxMicro-NettyServer/0.7-SNAPSHOT
Date: Wed, 25 Nov 2020 17:30:51 GMT
Content-Type: application/json
Request-Id: AkinnfVzx1752012
{"email":"richard.hendricks@piedpiper.com","firstName":"Richard","lastName":"Hendricks"}
For more information, we recommend that You familiarize yourself with the following examples: |
When compiling, the RxMicro framework searches for When changing the |