I get the following exception using Spring React.
My method:
public Mono<A> getA(
#PathVariable(value = "id") Long id) {
return <<some service call that returns Mono<A> >>;
}
Exception:
019-03-22 17:55:59,518 priority=ERROR app_name=sync
thread=reactor-http-nio-1 location=DefaultErrorWebExceptionHandler
line=218 Failed to handle request [GET
http://localhost:10019/ext/stubhub/event/9874282/eventSectionZone]
reactor.core.Exceptions$ReactorRejectedExecutionException: Scheduler
unavailable at
reactor.core.Exceptions.failWithRejected(Exceptions.java:249) at
com.tu.sync.node.stubhub.remoterepository.EventRepository.lambda$getSectionZoneData$6(EventRepository.java:111)
at
org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec$StatusHandler.apply(DefaultWebClient.java:492)
at
org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.lambda$bodyToPublisher$5(DefaultWebClient.java:440)
at java.util.Optional.map(Optional.java:215) at
org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.bodyToPublisher(DefaultWebClient.java:440)
at
org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.lambda$bodyToMono$0(DefaultWebClient.java:400)
at
reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:118)
at
reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)
Your API-Method is missing a #RequestMapping().
That should fix it.
Related
I'm using a Spring Boot Scheduler to run a query on the DB daily to find some records based on a condition and update the records returned. Fetching the records using JPA works fine, but when I loop through them, update them, and try to save each updated record I get the following error:
Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
Caused by: javax.persistence.RollbackException: Error while committing the transaction at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:81) at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:104) at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:562) ... 30 more Caused by: java.lang.NullPointerException at com.xxx.yyy.config.JpaAuditingConfiguration.auditorProvider$lambda-0(JpaAuditingConfiguration.kt:15) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208) at com.sun.proxy.$Proxy168.getCurrentAuditor(Unknown Source) at java.base/java.util.Optional.map(Optional.java:265) at org.springframework.data.auditing.AuditingHandler.getAuditor(AuditingHandler.java:109) at org.springframework.data.auditing.AuditingHandler.markModified(AuditingHandler.java:104) at org.springframework.data.jpa.domain.support.AuditingEntityListener.touchForUpdate(AuditingEntityListener.java:112).
Here is the scheduler code I have. If I run the same code inside my service and call it using an endpoint everything works fine:
#Component
class Scheduler(
private val repository: Repository
) {
#Scheduled(cron = "0 0 2 * * *")
fun expire() {
val records = repository.findRecords()
for (record in records) {
try {
// Call some external API using record.id but this part is commented out for now until the saving works
record.active = false
repository.save(record)
} catch (ex: Exception) {
logger.error("Error expiring record " + record.id)
logger.error("Exception: ${ex.printStackTrace()}")
continue
}
}
}
}
the null pointer exception happens in the JpaAuditingConfiguration config I use for storing the created_at and last_modified_at dates. Here is the code I have for that class:
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorProvider")
class JpaAuditingConfiguration {
#Bean
fun auditorProvider(): AuditorAware<String> {
return AuditorAware { Optional.of(SecurityContextHolder.getContext().authentication.name) }
}
}
Your JpaAuditingConfiguration requires the security context to be non null when you make modifications. When you're running your task in a scheduler there is no active request, so no active session, and therefore your authentication is null.
Usually, this is solved by making a special app user and manually authenticating them in your scheduled task.
I am running a JHipster 6.1.2 Gateway with Websockets and am trying to restrict access to a messaging topic, so that users can only subscribe to topics of the institution they belong to. So basically I want to perform a check on the id from the subscription path.
My current solution is based on https://stackoverflow.com/a/44895369/4246074 and looks as follows:
WebsocketSecurityConfiguration.java:
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
//...
//User can only subscribe to own institution topic
.simpSubscribeDestMatchers("/topic/institution.{id}")
.access("#institutionIdGuard.checkInstitutionId(#id)")
//...
}
InstitutionIdGuard.java:
#Component
public class InstitutionIdGuard {
public boolean checkInstitutionId(Long institutionId) {
//validation logic for institutionId would go here
return true;
}
The problem:
Apparently the SpEL expression can't access {id} from the path because i get a nullpointer error with the following log:
2019-08-08 10:30:08.367 ERROR 31097 --- [ XNIO-1 I/O-1] o.s.w.s.m.StompSubProtocolHandler : Failed to send client message to application via MessageChannel in session j2a0jlos. Sending STOMP ERROR to client.
org.springframework.messaging.MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception is java.lang.IllegalArgumentException: Failed to evaluate expression '#institutionIdGuard.checkInstitutionId(#id)'
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:146)
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:122)
at org.springframework.web.socket.messaging.StompSubProtocolHandler.handleMessageFromClient(StompSubProtocolHandler.java:284)
at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.handleMessage(SubProtocolWebSocketHandler.java:324)
at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75)
at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.handleMessage(LoggingWebSocketHandlerDecorator.java:56)
at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.handleMessage(ExceptionWebSocketHandlerDecorator.java:58)
at org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession.delegateMessages(AbstractSockJsSession.java:386)
at org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession.handleMessage(WebSocketServerSockJsSession.java:195)
at org.springframework.web.socket.sockjs.transport.handler.SockJsWebSocketHandler.handleTextMessage(SockJsWebSocketHandler.java:93)
at org.springframework.web.socket.handler.AbstractWebSocketHandler.handleMessage(AbstractWebSocketHandler.java:43)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleTextMessage(StandardWebSocketHandlerAdapter.java:113)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.access$000(StandardWebSocketHandlerAdapter.java:42)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:84)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:81)
at io.undertow.websockets.jsr.FrameHandler$7.run(FrameHandler.java:286)
at io.undertow.websockets.jsr.ServerWebSocketContainer$1.call(ServerWebSocketContainer.java:170)
at io.undertow.websockets.jsr.ServerWebSocketContainer$1.call(ServerWebSocketContainer.java:167)
at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
at io.undertow.websockets.jsr.ServerWebSocketContainer.invokeEndpointMethod(ServerWebSocketContainer.java:604)
at io.undertow.websockets.jsr.ServerWebSocketContainer.invokeEndpointMethod(ServerWebSocketContainer.java:594)
at io.undertow.websockets.jsr.FrameHandler.invokeTextHandler(FrameHandler.java:266)
at io.undertow.websockets.jsr.FrameHandler.onFullTextMessage(FrameHandler.java:317)
at io.undertow.websockets.core.AbstractReceiveListener$2.complete(AbstractReceiveListener.java:156)
at io.undertow.websockets.core.AbstractReceiveListener$2.complete(AbstractReceiveListener.java:152)
at io.undertow.websockets.core.BufferedTextMessage.read(BufferedTextMessage.java:105)
at io.undertow.websockets.core.AbstractReceiveListener.readBufferedText(AbstractReceiveListener.java:152)
at io.undertow.websockets.core.AbstractReceiveListener.bufferFullMessage(AbstractReceiveListener.java:90)
at io.undertow.websockets.jsr.FrameHandler.onText(FrameHandler.java:182)
at io.undertow.websockets.core.AbstractReceiveListener.handleEvent(AbstractReceiveListener.java:44)
at io.undertow.websockets.core.AbstractReceiveListener.handleEvent(AbstractReceiveListener.java:33)
at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92)
at io.undertow.server.protocol.framed.AbstractFramedChannel$FrameReadListener.handleEvent(AbstractFramedChannel.java:951)
at io.undertow.server.protocol.framed.AbstractFramedChannel$FrameReadListener.handleEvent(AbstractFramedChannel.java:932)
at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92)
at org.xnio.conduits.ReadReadyHandler$ChannelListenerHandler.readReady(ReadReadyHandler.java:66)
at org.xnio.nio.NioSocketConduit.handleReady(NioSocketConduit.java:88)
at org.xnio.nio.WorkerThread.run(WorkerThread.java:561)
Caused by: java.lang.IllegalArgumentException: Failed to evaluate expression '#institutionIdGuard.checkInstitutionId(#id)'
at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:30)
at org.springframework.security.messaging.access.expression.MessageExpressionVoter.vote(MessageExpressionVoter.java:57)
at org.springframework.security.messaging.access.expression.MessageExpressionVoter.vote(MessageExpressionVoter.java:39)
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:63)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233)
at org.springframework.security.messaging.access.intercept.ChannelSecurityInterceptor.preSend(ChannelSecurityInterceptor.java:69)
at org.springframework.messaging.support.AbstractMessageChannel$ChannelInterceptorChain.applyPreSend(AbstractMessageChannel.java:178)
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:132)
... 37 common frames omitted
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method checkInstitutionId(null) cannot be found on type com.mycompany.websocketgateway.security.InstitutionIdGuard
at org.springframework.expression.spel.ast.MethodReference.findAccessorForMethod(MethodReference.java:225)
at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:134)
at org.springframework.expression.spel.ast.MethodReference.access$000(MethodReference.java:54)
at org.springframework.expression.spel.ast.MethodReference$MethodValueRef.getValue(MethodReference.java:390)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:90)
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:114)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:300)
at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:26)
... 44 common frames omitted
I would be grateful for any ideas how to make my solution work or other other ways of performing checks in the id.
I found the solution on the Spring Security issue tracker. Apparently before Spring Security 5.2 you can pass the implicit message variable to the SpEL expression
.simpSubscribeDestMatchers("/topic/institution.*")
.access("#institutionIdGuard.checkInstitutionId(authentication, message)")
Then in the verification method its possible to get the path from the message and do your own verification with it:
public boolean checkInstitutionId(Authentication authentication, Message<?> message) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(message);
String topic = sha.getDestination();
String id = topic.replace("/topic/institution/", "");
//validation logic for institutionId would go here
return true;
}
Spring Security 5.2 should have fixed the issue according to this pull request.
I developped spring remoting amqp rpc applications.
That's works well for methods that don't use bean with Scope SESSION.
For the other methods, the client can't use spring session, and I get this exception
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.userSession': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:362) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at io.kzreactive.akwtype.akwtypeback.common.service.UserSession$$EnhancerBySpringCGLIB$$55d53e95.setUser(<generated>) ~[classes/:na]
at io.kzreactive.akwtype.akwtypeback.engine.service.AppService.login(AppService.kt:30) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
at org.springframework.remoting.support.RemoteInvocation.invoke(RemoteInvocation.java:215) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.remoting.support.DefaultRemoteInvocationExecutor.invoke(DefaultRemoteInvocationExecutor.java:39) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at io.kzreactive.akwtype.akwtypeback.gateway.rabbitmq.SessionDefaultRemoteInvocationExecutor.invoke(RabbitMQSession.kt:48) ~[classes/:na]
at org.springframework.remoting.support.RemoteInvocationBasedExporter.invoke(RemoteInvocationBasedExporter.java:78) [spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.remoting.support.RemoteInvocationBasedExporter.invokeAndCreateResult(RemoteInvocationBasedExporter.java:114) [spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.amqp.remoting.service.AmqpInvokerServiceExporter.onMessage(AmqpInvokerServiceExporter.java:80) [spring-amqp-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1457) [spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1348) [spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1324) [spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1303) [spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:785) ~[spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:769) ~[spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$700(SimpleMessageListenerContainer.java:77) ~[spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1010) ~[spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
at java.base/java.lang.Thread.run(Thread.java:844) ~[na:na]
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.context.request.SessionScope.get(SessionScope.java:55) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:350) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
... 24 common frames omitted
So I would create and use a spring session over rabbit mq
First, I managed to pass the sessionId in RPC call
on the server I add an attribute
class SessionDefaultRemoteInvocationFactory : DefaultRemoteInvocationFactory() {
override fun createRemoteInvocation(methodInvocation: MethodInvocation?): RemoteInvocation {
return super.createRemoteInvocation(methodInvocation)
.apply { addAttribute("sessionId", RequestContextHolder.currentRequestAttributes().sessionId) }
}
}
on the client I can read it
class SessionDefaultRemoteInvocationExecutor : DefaultRemoteInvocationExecutor() {
override fun invoke(invocation: RemoteInvocation?, targetObject: Any?): Any {
if (invocation is RemoteInvocation) {
invocation.getAttribute("sessionId")?.let {
val sessionId = it.toString()
SecurityContextHolder.getContext().authentication = AnonymousAuthenticationToken(sessionId,
"anonymousUser", listOf(SimpleGrantedAuthority("ROLE_ANONYMOUS")))
val attr = RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes // <= ERROR here
attr.request.getSession(true)
}
}
return super.invoke(invocation, targetObject)
}
}
but I don't manage to use it to create a spring session
How can I create a spring session in this NON http context
I tried to create a request context listener
#Configuration
#WebListener
class MyRequestContextListener : RequestContextListener()
but same error
For the moment I bypass the spring session and I inject a bean with the same behavior
#Component
#Scope("singleton")
class EngineThread(var engineSession: ThreadLocal<EngineSession> = ThreadLocal()) : IEngineSession by engineSession.getOrSet( { EngineSession() })
#Component
#Profile("engine & !test")
class SessionDefaultRemoteInvocationExecutor(val engineThread: EngineThread) : DefaultRemoteInvocationExecutor() {
val engineSessionStore : MutableMap<String, EngineSession> = mutableMapOf()
override fun invoke(invocation: RemoteInvocation?, targetObject: Any?): Any {
if (invocation is RemoteInvocation) {
invocation.getAttribute(SESSION_ID)?.let {
val sessionId = it.toString()
SecurityContextHolder.getContext().authentication = AnonymousAuthenticationToken(sessionId,
"anonymousUser", listOf(SimpleGrantedAuthority("ROLE_ANONYMOUS")))
if (! engineSessionStore.contains(sessionId)) {
engineSessionStore.put(sessionId, EngineSession())
}
engineThread.engineSession.set(engineSessionStore.getValue(sessionId))
}
}
return super.invoke(invocation, targetObject)
}
}
The job is done but I'm not very well with this solution
(I will add clean by softreference… but all this work is reinvent session instead of use spring session)
I have built a microservice using Java 8 and SpringBoot 2. From this microservice, I'm trying to consume another REST API service. However, I'm getting the following error on Chrome. I have already disabled the windows firewall and McAfee antivirus firewall but still getting the same error. I can call the REST API directly using the Postman tool but not through my microservice.
Error:-
java.lang.IllegalStateException: The underlying HTTP client completed
without emitting a response.
2018-06-12 15:21:29.300 ERROR 17996 --- [ctor-http-nio-3]
.a.w.r.e.DefaultErrorWebExceptionHandler : Failed to handle request
[GET http://localhost:8080/category/search]
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection
timed out: no further information: test.usdemo.xyz.com/92.54.41.24:443
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
~[na:1.8.0_171] at
sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)
~[na:1.8.0_171] at
io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:325)
~[netty-transport-4.1.24.Final.jar:4.1.24.Final] at
io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:340)
~[netty-transport-4.1.24.Final.jar:4.1.24.Final] at
io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:633)
~[netty-transport-4.1.24.Final.jar:4.1.24.Final] at
io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580)
~[netty-transport-4.1.24.Final.jar:4.1.24.Final] at
io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497)
~[netty-transport-4.1.24.Final.jar:4.1.24.Final] at
io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:459)
~[netty-transport-4.1.24.Final.jar:4.1.24.Final] at
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
~[netty-common-4.1.24.Final.jar:4.1.24.Final] at
java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_171] Caused by:
java.net.ConnectException: Connection timed out: no further
information ... 10 common frames omitted
Controller class:-
#RestController
public class CategorySearchController {
private final CategorySearchService categorySearchService;
#Autowired
public CategorySearchController(CategorySearchService categorySearchService) {
this.categorySearchService = categorySearchService;
}
#GetMapping(path = "/search-category")
public Mono<CategoryResponse> searchCategories(SearchRequest categorySearchRequest){
return categorySearchService
.searchCategories()
.switchIfEmpty(Mono.error(
new EntityNotFoundException("No category matching " + categorySearchRequest.getSearchTerm() + " was found")));
}
}
Service class:-
#Service
public class CategorySearchServiceImpl implements CategorySearchService {
private String baseUrl = "https://demo0954903.mockable.io";
#Override
public Mono<CategoryResponse> searchCategories() {
WebClient webClient = WebClient.create(baseUrl);
return webClient.
get()
.uri("/category-search")
.retrieve()
.bodyToMono(CategoryResponse.class);
}
}
I found the solution for this issue. I need to add the proxy in the webclient as follows:-
private final WebClient webClient;
ReactorClientHttpConnector connector = new ReactorClientHttpConnector(
options -> options.httpProxy(addressSpec -> {
return addressSpec.host(PROXY_HOST).port(PROXY_PORT);
}));
this.webClient = webClientBuilder.baseUrl(BASE_URL).clientConnector(connector).build();
I am evaluating resilience4j to include it in our reactive APIs, so far I am using mock Fluxes.
The service below always fails as I want to test if the circuit OPENs on multiple errors:
#Service
class GamesRepositoryImpl : GamesRepository {
override fun findAll(): Flux<Game> {
return if (Math.random() <= 1.0) {
Flux.error(RuntimeException("fail"))
} else {
Flux.just(
Game("The Secret of Monkey Island"),
Game("Loom"),
Game("Maniac Mansion"),
Game("Day of the Tentacle")).log()
}
}
}
This is the handler that uses the repository, printing the state of the circuit:
#Component
class ApiHandlers(private val gamesRepository: GamesRepository) {
var circuitBreaker : CircuitBreaker = CircuitBreaker.ofDefaults("gamesCircuitBreaker")
fun getGames(serverRequest: ServerRequest) : Mono<ServerResponse> {
println("*********${circuitBreaker.state}")
return ok().body(gamesRepository.findAll().transform(CircuitBreakerOperator.of(circuitBreaker)), Game::class.java)
}
}
I invoke the API endpoint many times, always getting this stacktrace:
*********CLOSED
2018-03-14 12:02:28.153 ERROR 1658 --- [ctor-http-nio-3] .a.w.r.e.DefaultErrorWebExceptionHandler : Failed to handle request [GET http://localhost:8081/api/v1/games]
java.lang.RuntimeException: FAIL
at com.codependent.reactivegames.repository.GamesRepositoryImpl.findAll(GamesRepositoryImpl.kt:12) ~[classes/:na]
at com.codependent.reactivegames.web.handler.ApiHandlers.getGames(ApiHandlers.kt:20) ~[classes/:na]
...
2018-03-14 12:05:48.973 DEBUG 1671 --- [ctor-http-nio-2] i.g.r.c.i.CircuitBreakerStateMachine : No Consumers: Event ERROR not published
2018-03-14 12:05:48.975 ERROR 1671 --- [ctor-http-nio-2] .a.w.r.e.DefaultErrorWebExceptionHandler : Failed to handle request [GET http://localhost:8081/api/v1/games]
java.lang.RuntimeException: fail
at com.codependent.reactivegames.repository.GamesRepositoryImpl.findAll(GamesRepositoryImpl.kt:12) ~[classes/:na]
at com.codependent.reactivegames.web.handler.ApiHandlers.getGames(ApiHandlers.kt:20) ~[classes/:na]
at com.codependent.reactivegames.web.route.ApiRoutes$apiRouter$1$1$1.invoke(ApiRoutes.kt:14) ~[classes/:na]
As you see the circuit is always CLOSED. I don't know if it has anything to do but notice this message No Consumers: Event ERROR not published.
Why isn't this working?
The problem was the default ringBufferSizeInClosedState which is 100 requests and I never made so many manual requests.
I setup my own CircuitBreakerConfig for my tests and now the circuit opens right away:
val circuitBreakerConfig : CircuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50f)
.waitDurationInOpenState(Duration.ofMillis(10000))
.ringBufferSizeInHalfOpenState(5)
.ringBufferSizeInClosedState(5)
.build()
var circuitBreaker: CircuitBreaker = CircuitBreaker.of("gamesCircuitBreaker", circuitBreakerConfig)