Getting "reactor.netty.http.client.PrematureCloseException: Connection prematurely closed BEFORE response" while mocking a third part API - spring

My Spring boot API is consuming a third part API using WebClient
DemoAPIController
#RestController
public class DemoAPIController
{
#Autowired
DemoService demoService;
#GetMapping("/mytest")
public Mono<UserType> getUserType()
{
return demoService.getUserType();
}
}
DemoService.java
#Component
public class DemoService
{
#Value("${base.url}")
private String baseUrl;
public Mono<UserType> getUserType()
{
System.out.println("baseUrl:" + baseUrl);
WebClient webClient = WebClient.builder().baseUrl(baseUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader("apikey", "test").build();
Mono<UserType> testResult = webClient.get()
.uri("/valic/valic-security-data-api/v1/utility/users/ID873366777/usertype?appName=EAO").retrieve()
.bodyToMono(UserType.class);
testResult.subscribe(value -> System.out.println(value.getUserType()));
return testResult;
}
}
I have mocked the third part API using MockWebServer. When I try to test the API using mockMvc.perform(...). My assertions are working as expected. But while shutting down the MockWebServer, I am getting the following exception
2019-11-29 16:02:43.308 INFO 13048 --- [127.0.0.1:53970] okhttp3.mockwebserver.MockWebServer : MockWebServer[443] received request: GET /valic/valic-security-data-api/v1/utility/users/ID873366777/usertype?appName=EAO HTTP/1.1 and responded: HTTP/1.1 200 OK
2019-11-29 16:03:18.362 INFO 13048 --- [ckWebServer 443] okhttp3.mockwebserver.MockWebServer : MockWebServer[443] done accepting connections: socket closed
2019-11-29 16:03:18.385 WARN 13048 --- [ctor-http-nio-1] r.netty.http.client.HttpClientConnect : [id: 0x186c45c1, L:0.0.0.0/0.0.0.0:53970 ! R:localhost/127.0.0.1:443] The connection observed an error
reactor.netty.http.client.PrematureCloseException: Connection prematurely closed BEFORE response
2019-11-29 16:03:18.389 INFO 13048 --- [127.0.0.1:53970] okhttp3.mockwebserver.MockWebServer : MockWebServer[443] connection from /127.0.0.1 failed: java.net.SocketException: Socket closed
2019-11-29 16:03:20.509 INFO 13048 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
My Test case
#ExtendWith(SpringExtension.class)
#WebAppConfiguration()
#ContextConfiguration(classes = WebclientApplication.class)
#SpringBootTest
public class EmployeeServiceMockWebServerTest
{
public static MockWebServer mockWebServer;
private ObjectMapper MAPPER = new ObjectMapper();
public MockMvc mockMvc;
public MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
public MediaType contentTypeURLEncoded = new MediaType(MediaType.APPLICATION_FORM_URLENCODED.getType(),
MediaType.APPLICATION_FORM_URLENCODED.getSubtype(), Charset.forName("utf8"));
#Autowired
private WebApplicationContext webApplicationContext;
#BeforeAll
static void setUp() throws IOException
{
mockWebServer = new MockWebServer();
mockWebServer.start(443);
}
#AfterAll
static void tearDown() throws IOException
{
mockWebServer.shutdown();
}
#BeforeEach
void initialize()
{
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
System.out.println("Hostname:" + mockWebServer.getHostName());
System.out.println("Port:" + mockWebServer.getPort());
}
#Test
void getEmployeeById() throws Exception
{
UserType userType = new UserType();
userType.setUserType("PAR-Mock");
ServiceMessage serviceMessage = new ServiceMessage();
serviceMessage.setCode("200");
serviceMessage.setType("OK");
serviceMessage.setDescription("From Mock");
userType.setServiceMessage(serviceMessage);
mockWebServer.enqueue(
new MockResponse().setBody(MAPPER.writeValueAsString(userType)).addHeader("Content-Type", "application/json"));
mockMvc.perform(get("/mytest").contentType(contentType)).andExpect(status().isOk());
RecordedRequest recordedRequest = mockWebServer.takeRequest();
assertEquals("GET", recordedRequest.getMethod());
assertEquals("/logic/logic-security-data-api/v1/utility/users/ID873366777/usertype?appName=EAO",
recordedRequest.getPath());
}
}
I tried all the solution mentioned in other start overflow posts but still I am getting the same error. Any help is appreciated.

I fixed the issue by commenting the below line.
testResult.subscribe(value -> System.out.println(value.getUserType()));
I created WebClient object using the below code to get additional logging
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.newConnection().compress(true).wiretap(true)))
.baseUrl(baseUrl).defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader("apikey", "somekey").build();
I was able to see two requests.Then after commenting, everything worked as expected.

Related

How to add a custom interceptor to FeignClient in SpringBoot

In RestTemplate I have a custom interceptor which will log some request response details and saves to database.
my custom Interceptor:
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
#Component
public class LogServices implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
final String uri = request.getURI().toString();
final ClientHttpResponse response = execution.execute(request, body);
//log request response details and save to database
return response;
RestTemplate bean configuration in springboot:
#Bean
public RestTemplate restTemplate(final RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.setConnectTimeout(Duration.ofMillis(connectTimeout))
.setReadTimeout(Duration.ofMillis(readTimeout))
.build();
Add the interceptor to restTemplate bean:
#Configuration
public class LogInterceptorConfiguration {
#Autowired
public void configureLogger(final RestTemplate restTemplate, final LogServices logServices) {
final var interceptors = restTemplate.getInterceptors();
interceptors.add(logServices);
restTemplate.setInterceptors(interceptors);
}
How can I add this interceptor to FeignClient?
In application.yml:
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
request-interceptors[0]: com.api.restclient.InterceptorOne
request-interceptors[1]: com.api.log.LogServices
InterceptorOne which adds a header to every request in feign client:
#Configuration
public class InterceptorOne implements RequestInterceptor {
#Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("some-header", "value");
}
But I cannot add the LogServices interceptor since it does not work due to the error cannot be cast to class feign.RequestInterceptor
My guess is that the interceptor I am trying to add is a generic interceptor and not specifically request interceptor. So I want to know how do I add a generic interceptor to FeignClient similar to RestTemplate
You can add multiple interceptors as follows
feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.InterceptorOne
- com.example.LogServices
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract

How to disable SpringSecurity in Junit Test class using spring boot

I have created simple Rest Api service using spring boot 2.2.5.RELEASE, I have just enabled Spring Security in the application. The JUnits are not working. I tried some of the ways to solve this issue but its not working.
Looking at references in books and online (including questions answered in Stack Overflow) I learned about two methods to disable security in tests:
#WebMvcTest(value =MyController.class, secure=false)
#AutoConfigureMockMvc(secure = false)
#EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class})
All these annotation i tried one by one on Test class but its not working.
1.#WebMvcTest(value =MyController.class, secure=false)
2.#AutoConfigureMockMvc(secure = false)
Both of these settings were identified in various Stack Overflow answers as being deprecated, but I tried them anyway.
Unfortunately, they didn't work. Apparently, in Version 2.2.1 of Spring Boot (the version I am using) secure isn't just deprecated, it is gone. Tests with the annotations using the "secure = false" parameter do not compile.
The code snippet looks like this:
Code Snippet
package com.akarsh.controller;
import static org.junit.Assert.*;
#RunWith(SpringRunner.class)
#AutoConfigureMockMvc
#EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class})
#SpringBootTest(classes = SpringBootProj2Application.class,webEnvironment =SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SurveyControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private SurveyService surveyService;
#Test
public void retrieveDetailsForQuestion_Test() throws Exception {
Question mockQuestion = new Question("Question1",
"Largest Country in the World", "Russia", Arrays.asList(
"India", "Russia", "United States", "China"));
Mockito.when(
surveyService.retrieveQuestion(Mockito.anyString(), Mockito
.anyString())).thenReturn(mockQuestion);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/surveys/Survey1/questions/Question1").accept(
MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
String expected = "{\"id\":\"Question1\",\"description\":\"Largest Country in the World\",\"correctAnswer\":\"Russia\",\"options\":[\"India\",\"Russia\",\"United States\",\"China\"]}";
String actual=result.getResponse().getContentAsString();
JSONAssert.assertEquals(expected,actual , false);
}
\\
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Authentication : User-->Roles
// Authorization : Role->Access
#Autowired
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("{noop}secret").roles("USER")
.and()
.withUser("akarsh").password("{noop}ankit").roles("ADMIN","USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and().authorizeRequests()
.antMatchers("/surveys/**").hasRole("USER")
.antMatchers("/users/**").hasRole("USER")
.antMatchers("/**").hasRole("ADMIN")
.and().csrf().disable()
.headers().frameOptions().disable();
}
}
\
Getting following exception
Description:
A component required a bean of type 'org.springframework.security.config.annotation.ObjectPostProcessor' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.config.annotation.ObjectPostProcessor' in your configuration.
2020-04-13 14:51:15.659 ERROR 5128 --- [ main] o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener#36902638] to prepare test instance [com.akarsh.controller.SurveyControllerTest#3eb8057c]
\\
#RestController
public class SurveyController {
#Autowired
SurveyService surveyService;
#GetMapping("/surveys/{surveyId}/questions")
public List<Question> retrieveQuestionForSrvey(#PathVariable String surveyId)
{
if(surveyId!=null)
{
return surveyService.retrieveQuestions(surveyId);
}
return null;
}
#GetMapping("/surveys/{surveyId}/questions/{questionId}")
public Question retrieveQuestion(#PathVariable String surveyId,#PathVariable String questionId)
{
if(surveyId!=null)
{
return surveyService.retrieveQuestion(surveyId, questionId);
}
return null;
}
#PostMapping("/surveys/{surveyId}/questions")
public ResponseEntity<?> addQuestionForSrvey(#PathVariable String surveyId, #RequestBody Question question) {
Question createdTodo = surveyService.addQuestion(surveyId, question);
if (createdTodo == null) {
return ResponseEntity.noContent().build();
}
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(createdTodo.getId()).toUri();
return ResponseEntity.created(location).build();
}

Spring Boot 2, Spring Security 5 and #WithMockUser

Since I migrated to Spring Boot 2.0.5 from 1.x, with no mean to disable security, I can't get test roles to work on mock MVC tests :
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class ApplicationsControllerShould {
...
#Autowired
private MockMvc mockMvc;
private ObjectMapper mapper = new ObjectMapper();
#Test
#WithMockUser(roles = "ADMIN")
public void handle_CRUD_for_applications() throws Exception {
Application app = Application.builder()
.code(APP_CODE).name(APP_NAME)
.build();
mockMvc.perform(post("/applications")
.accept(MediaType.APPLICATION_JSON_UTF8)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(mapper.writeValueAsString(app)))
.andExpect(authenticated())
.andExpect(status().isOk()); // failure 403!
...
My controller endpoint isn't even protected!
#RestController
#RequestMapping("/applications")
public class ApplicationsController {
...
#PostMapping
public Application addApplication(#RequestBody Application application) {
Assert.isTrue(!applicationsDao.existsById(application.getCode()), "Application code already exists: " + application.getCode());
return applicationsDao.save(application);
}
}
So I have in the test a session (#authenticated fails when #WithMockUser is commented out) and a role by the way (ROLE_ADMIN is visible in traces) but my request is being rejected and I don't understand what I did wrong.
Thx for any idea!
Ok... the good old CSRF stuff, then...
logging.level.org.springframework.security=DEBUG
2018-10-02 10:11:41.285 DEBUG 12992 --- [ main] o.s.security.web.csrf.CsrfFilter : Invalid CSRF token found for http://localhost/applications/foo
Application app = Application.builder()
.code(APP_CODE).name(APP_NAME)
.build();
mockMvc.perform(post("/applications").with(csrf()) // oups...
.accept(MediaType.APPLICATION_JSON_UTF8)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(mapper.writeValueAsString(app)))
.andExpect(authenticated())
.andExpect(status().isOk()); // there we go!

Testing Spring Boot Eureka Server 404

I'm trying to test authentication in my Spring Boot Eureka Server. To do so, I perform a GET on /eureka/apps. I get a 404 instead of 200.
#RunWith(SpringRunner.class)
#WebAppConfiguration
#SpringBootTest(classes = Application.class)
public class GlobalSecurityTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private FilterChainProxy springSecurityFilterChain;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilter(springSecurityFilterChain).build();
}
#Test
public void givenRoleDiscoveryClient_whenGetEureka_then200() throws Exception {
mockMvc.perform(get("/eureka/apps").header(HttpHeaders.AUTHORIZATION, TOKEN_DISCOVERY_CLIENT)
.andExpect(status().isOk());
}
}
Eureka starts correctly as the logs prove:
2018-04-12 23:07:39.308 INFO 80833 --- [ Thread-12] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
2018-04-12 23:07:39.315 INFO 80833 --- [ main] GlobalSecurityTest : Started GlobalSecurityTest in 7.255 seconds (JVM running for 8.007)
...
2018-04-12 23:07:39.822 DEBUG 80833 --- [ main] o.s.security.web.FilterChainProxy : /eureka/apps/REGISTRY reached end of additional filter chain; proceeding with original chain
2018-04-12 23:07:39.831 DEBUG 80833 --- [ main] w.c.HttpSessionSecurityContextRepository : SecurityContext 'org.springframework.security.core.context.SecurityContextImpl#0: Authentication: StateTokenAuthentication{principalTokenState=be.charliebravo.ibpt.qos3.commons.security.models.ClientState#50b624da, tokenStates={}}' stored to HttpSession: 'org.springframework.mock.web.MockHttpSession#50b4e7b2
2018-04-12 23:07:39.833 DEBUG 80833 --- [ main] o.s.s.w.a.ExceptionTranslationFilter : Chain processed normally
2018-04-12 23:07:39.833 DEBUG 80833 --- [ main] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
java.lang.AssertionError: Status
Expected :200
Actual :404
My security config:
#Configuration
public class WebSecurityConfig {
#Configuration
#Order(3)
public static class DiscoveryClientSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private StateTokenHttpSecurityConfigurer stateTokenHttpSecurityConfigurer;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/eureka/**").authorizeRequests()
.anyRequest().hasRole(Role.DISCOVERY_CLIENT.toString())
.and().exceptionHandling().authenticationEntryPoint(new Http401UnauthorizedEntryPoint());
stateTokenHttpSecurityConfigurer.configure(http);
}
}
}
The Eureka server works fine when I run the application instead of the test.
Don't use MockMvc, because it is limited to testing the web layer, but Eureka mappings aren't registered there. Instead, use TestRestTemplate.
Remove #WebAppConfiguration and add weEnvironment setting in #SpringBootTest
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
Autowire TestRestTemplate and local server port
#Autowired
private TestRestTemplate restTemplate;
#LocalServerPort
private int localServerPort;
Perform the request
#Test
public void givenRoleDiscoveryClient_whenGetEurekaPage_then200() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, TOKEN_DISCOVERY_CLIENT);
HttpEntity entity = new HttpEntity<>(null, headers);
String endpoint = "https://localhost:" + localServerPort + "/eureka/apps";
ResponseEntity responseEntity = restTemplate.exchange(endpoint, HttpMethod.GET, entity, String.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
And off you go.

404 while using Spring cloud FeignClients

This is my setup:
First service(FlightIntegrationApplication) which invoke second service(BaggageServiceApplication) using FeignClients API and Eureka.
Project on github: https://github.com/IdanFridman/BootNetflixExample
First service:
#SpringBootApplication
#EnableCircuitBreaker
#EnableDiscoveryClient
#ComponentScan("com.bootnetflix")
public class FlightIntegrationApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(FlightIntegrationApplication.class).run(args);
}
}
in one of the controllers:
#RequestMapping("/flights/baggage/list/{id}")
public String getBaggageListByFlightId(#PathVariable("id") String id) {
return flightIntegrationService.getBaggageListById(id);
}
FlightIntegrationService:
public String getBaggageListById(String id) {
URI uri = registryService.getServiceUrl("baggage-service", "http://localhost:8081/baggage-service");
String url = uri.toString() + "/baggage/list/" + id;
LOG.info("GetBaggageList from URL: {}", url);
ResponseEntity<String> resultStr = restTemplate.getForEntity(url, String.class);
LOG.info("GetProduct http-status: {}", resultStr.getStatusCode());
LOG.info("GetProduct body: {}", resultStr.getBody());
return resultStr.getBody();
}
RegistryService:
#Named
public class RegistryService {
private static final Logger LOG = LoggerFactory.getLogger(RegistryService.class);
#Autowired
LoadBalancerClient loadBalancer;
public URI getServiceUrl(String serviceId, String fallbackUri) {
URI uri;
try {
ServiceInstance instance = loadBalancer.choose(serviceId);
uri = instance.getUri();
LOG.debug("Resolved serviceId '{}' to URL '{}'.", serviceId, uri);
} catch (RuntimeException e) {
// Eureka not available, use fallback
uri = URI.create(fallbackUri);
LOG.error("Failed to resolve serviceId '{}'. Fallback to URL '{}'.", serviceId, uri);
}
return uri;
}
}
And this is the second service (baggage-service):
BaggageServiceApplication:
#Configuration
#ComponentScan("com.bootnetflix")
#EnableAutoConfiguration
#EnableEurekaClient
#EnableFeignClients
public class BaggageServiceApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(BaggageServiceApplication.class).run(args);
}
}
BaggageService:
#FeignClient("baggage-service")
public interface BaggageService {
#RequestMapping(method = RequestMethod.GET, value = "/baggage/list/{flight_id}")
List<String> getBaggageListByFlightId(#PathVariable("flight_id") String flightId);
}
BaggageServiceImpl:
#Named
public class BaggageServiceImpl implements BaggageService{
....
#Override
public List<String> getBaggageListByFlightId(String flightId) {
return Arrays.asList("2,3,4");
}
}
When invoking the rest controller of flight integration service I get:
2015-07-22 17:25:40.682 INFO 11308 --- [ XNIO-2 task-3] c.b.f.service.FlightIntegrationService : GetBaggageList from URL: http://X230-Ext_IdanF:62007/baggage/list/4
2015-07-22 17:25:43.953 ERROR 11308 --- [ XNIO-2 task-3] io.undertow.request : UT005023: Exception handling request to /flights/baggage/list/4
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException: 404 Not Found
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
Any idea ?
Thanks,
ray.
Your code looks backwards to me.
The feign client for the baggage service should be declared in the flight service and the baggage service should have a controller that responds on the URL you map in your baggage service client, you should not implement the interface annotated with #FeignClient.
The setup you have now will not have any controller listening on /baggage/list/{flightId} in the baggage service and no Feign client in flight service - the whole point of Feign is to call methods on an interface instead of manually handling URLs, Spring Cloud takes care of auto-instantiating the interface implementation and will use Eureka for discovery.
Try this (or modify so it fits your real world app):
Flight Service:
FlightIntegrationService.java:
#Component
public class FlightIntegrationService {
#Autowired
BaggageService baggageService;
public String getBaggageListById(String id) {
return baggageService.getBaggageListByFlightId(id);
}
}
BaggageService.java:
#FeignClient("baggage-service")
public interface BaggageService {
#RequestMapping(method = RequestMethod.GET, value = "/baggage/list/{flight_id}")
List<String> getBaggageListByFlightId(#PathVariable("flight_id") String flightId);
}
Baggage Service:
BaggageController.java:
#RestController
public class BaggageController {
#RequestMapping("/baggage/list/{flightId}")
public List<String> getBaggageListByFlightId(#PathVariable String flightId) {
return Arrays.asList("2,3,4");
}
}
Remove BaggageService.java and BaggageServiceImpl.java from the Baggage Service
registryService.getServiceUrl("baggage-service", ... replace with
registryService.getServiceUrl("baggage-service")
make sure that matches the right name
remove the localhost part
or only use the http://local part
It only worked for us if you have just the name of the service listed in eureka dashboard, not both

Resources