Spring cloud contract stub jar as a http rest endpoint - docker

I'm trying to setup Spring contract stub jar as either a fat jar with consumer or a http REST endpoint that my services can send request to and eventually receive the evaluated response.
Ideally I'd prefer the latter where consumer could run the stub to manage the interactions. My tests from consumer to local producer stubs work as expected. My compilation fails when I add the required annotations to the main class for stub runner. I think I'm missing some config or setup needed for the consumer to run the stub either as http REST endpoint or recognize within its m2.
The app compilation fails with #EnableStubRunnerServer complaining the below:
ConsumerApplication.java:[8,60] package org.springframework.cloud.contract.stubrunner.server does not exist
ConsumerApplication.java:[15,2] cannot find symbol
[ERROR] symbol: class EnableStubRunnerServer
ConsumerApplication.java:
#SpringBootApplication
#EnableWebMvc
#EnableStubRunnerServer
#Slf4j
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
application.properties (Consumer):
stubrunner.ids=com.somecompany.somegroup:producer:0.0.1-SNAPSHOT:stubs:8081
stubrunner.stubsMode=REMOTE
server.ssl.key-store-password=password
server.ssl.key-password=password
server.ssl.trust-store-password=password
server.port=8081
Test.java: - This test works
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
#AutoConfigureMockMvc
#AutoConfigureJsonTesters
#AutoConfigureStubRunner( ids = "com.somecompany.somegroup:producer:+:stubs:8081",
stubsMode = StubRunnerProperties.StubsMode.LOCAL)
#DirtiesContext
public class ContractControllerTest extends AbstractTest {
}
pom:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>

Since you're trying to add test code to your main, production code, you have to change
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
to
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
</dependency>

Related

Open Liberty and Jersey servlet - 415 Unsupported Media Type

I'm working with OpenLiberty version 22.0.0.2 (no Jakarta Restful Web Services enabled).
I'm trying to implement a document upload API using the Multipart Feature Provided by Jersey.
I have tried numerous suggestions, but cannot get it to work.
API
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON)
public Response upload(
#FormDataParam("file") InputStream uploadedInputStream,
#FormDataParam("file") FormDataContentDisposition fileDetail)
Application
#ApplicationPath("/")
public class RestApplication extends ResourceConfig {
public RestApplication() {
register(MultiPartFeature.class);
packages("com.packages.to.scan");
}
pom.xml
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.28</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.28</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>2.28</version>
</dependency>
The error I'm getting in OL when uploading a file via Postman:
[INFO] [ERROR] No message body reader has been found for class org.glassfish.jersey.media.multipart.FormDataContentDisposition, ContentType: multipart/form-data;boundary=--------------------------814467839830640328856820
Postman outputs
What am I missing in the setup?

spring-cloud-starter-openfeign: Invalid HTTP method: PATCH executing PATCH

Context
I have a spring boot (version 2.2.6.RELEASE) web project.
From this web application (I call "APP1") I want to call another URI using the PATCH method from another web application (Let's call it "APP2").
In my pom.xml, I have the following dependency:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Here is how I call the PATCH method of the other web application.
#FeignClient(name = "clientName", url = "base-uri")
public interface MyInterface{
#PatchMapping(value = "/target-uri")
void callClientMethod(Map<String, Object> args);
Problem
The APP2's PATCH method is effectively being called
But then APP1 throws the following error:
feign.RetryableException: Invalid HTTP method: PATCH executing PATCH
I looked on the Internet for a solution, and added the following snipet to my pom.xml
<dependency>
<groupId>com.netflix.feign</groupId> <!-- Also tried io.github.openfeign -->
<artifactId>feign-httpclient</artifactId>
<version>8.18.0</version>
</dependency>
After that, APP2's PATCH method is stille properly called but in APP1 I got the following error :
java.lang.NoSuchMethodError: feign.Response.create(ILjava/lang/String;Ljava/util/Map;Lfeign/Response$Body;)Lfeign/Response;
Question
Does anyone know how to solve this error ?
Thanks in advance for your help !
I had the same problem and spent a lot of time for understand and resolve this problem.
First what you need to understand that is the Feign doesn't support PATCH http method for call from the box!
And if you can change methods in both services use PUT for update instead PATCH...
But if you integrate with third party implementation you should add some configurations:
1. Add dependency which support PATCH http method:
// https://mvnrepository.com/artifact/io.github.openfeign/feign-okhttp
compile group: 'io.github.openfeign', name: 'feign-okhttp', version:
'10.2.0'
Add configuration:
#Configuration
public class FeignConfiguration {
#Bean
public OkHttpClient client() {
return new OkHttpClient();
}
}
And example for PATCH request with Feign:
#FeignClient(name = "someapi", url = "${client.someapi.url}")
#Component
#RequestMapping("/users")
public interface SomeClient {
#RequestMapping(value = "/{id}",
method = RequestMethod.PATCH,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
FeignUser update(#PathVariable("id") Long id, #RequestBody Map<String, Object> fields);
}
Hope it helps someone.
Just Add:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
If you are adding Feign with the following dependency:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId> <!-- has dependecy on spring-cloud-openfeign-core inside, which already maintains version of feign-okhttp artifact -->
</dependency>
you can add okhttp client (without hardcoding artifact version) to fix the issue with PATCH request:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId> <!-- Required to use PATCH -->
</dependency>
No other steps needed. The okhttp client will be applied automatically by auto configuration.
Also, this way you don't need to manage feign-okhttp artifact version. Spring Cloud will manage version for you.
Tested with Spring Boot 2.7.6
The following config works for me:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>${feign.version}</version>
</dependency>
Where:
feign.version - 11.0
Spring Boot - 2.3.0.RELEASE
Spring-cloud.version - 2.2.3.RELEASE

can't set up swagger with jax-rs

I can't understand, why swagger doesn't work with my spring boot jax-rs app.
I add this dependencies to pom.xml:
<!-- Swagger -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-service-description-swagger</artifactId>
<version>3.3.4</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>swagger-ui</artifactId>
<version>3.1.5</version>
</dependency>
also I set up -> cxf.jaxrs.component-scan=true in application properties,my rest requests:
#Path("/")
#Api("/")
#Service
public interface IService {
#GET
#Path("/health")
#ApiOperation("/health")
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Status getStatus();
#GET
#Path("/info")
#ApiOperation("/info")
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Info getInfo();
}
work as I expected, but I can't get documentation, when I call:
http://localhost:8000/swagger-ui.html
get -> Whitelabel Error Page.
Maybe someone can help me?
just in case here is my properties file:
#cxf url mapping
cxf.path=/api
cxf.jaxrs.component-scan=true
cxf.jaxrs.classes-scan-packages=com,org.apache.cxf.jaxrs.swagger.Swagger2Feature,org.codehaus.jackson.jaxrs,org.apache.cxf.jaxrs.swagger.ui.SwaggerUiResourceLocator
#port
server.port=8000
server.servlet.context-path=/
Can you please share the complete project?
You'll find working samples in https://github.com/apache/cxf/tree/cxf-3.3.4/distribution/src/main/release/samples/jax_rs, if you start a new project I suggest to go with OpenAPI v3 instead of Swagger.

SqsListner not listening to messages

I have below application.properties for connection:
cloud.aws.credentials.instanceProfile=true
cloud.aws.credentials.useDefaultAwsCredentialsChain=true
cloud.aws.region.static=us-east-2
cloud.aws.stack.auto=false
I have written listener with SqsListner and running my application with below dependencies
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-aws-messaging</artifactId>
<version>1.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>1.4.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-aws-messaging</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
But somehow my listener is not listening to messages (I am doing this in the existing code base).
I tried creating a separate spring boot application with only the listener in it, which is working fine. But the same thing is not working when added to existing code
and Listener code is as below:
#SqsListener(value = "test-queue",deletionPolicy = ON_SUCCESS)
public void receiveMessage(String message,
#Header(X_REQUEST_ID) String xRequestId,
#Header(X_SESSION_ID) String xSessionId) {
LOGGER.info("message received is: {}", message);
}
}
By mistake I had overriden AmazonSQSAsync bean
public AmazonSQSAsync amazonSQSClient() {
return new AmazonSQSAsyncClient();
}
which caused ignoring the aws properties I added. And hence SqsListner was not working as there is no way to explicitly specify aws properties including credentials to SqsListner. I removed the manual bean creation part and it started working smoothly as Bean is automatically created now and picking up the correct properties.

Ribbon Retry properties not respected

I have a zuul gateway application, that receives requests from a client app and forwards the requests using load balanced rest template to a micro service with 2 endpoints, say endpoint1 and endpoint2 (load balancing between the two end points in round robbin which is okay for now, although I want it to be availability based).
Here are the issues I am facing -
I brought down one of the end points, say endpoint2 and tried calling the zuul route and I see that when the request is going to the endpoint2 - zuul takes 2 mins or so before failing with HTTP 503 and does not retry on the next request. the error is just cascaded back to the caller.
Also, even after setting the read time out and connect timeout configurations, I don't see ribbon respecting the configuration and still takes 2 mins to throw the error from the server.
I tried enabling logs at the netflix package level, but I am unable to see logs unless I pass a custom http client to rest template.
I am new to netflix stack of components... please advise if I am missing something obvious. Thanks
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycomp</groupId>
<artifactId>zuul-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>zuul-gateway</name>
<description>Spring Boot Zuul</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Edgware.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-lambda</artifactId>
<version>1.11.242</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.3.10</version>
</dependency>
<dependency>
<groupId>com.netflix.netflix-commons</groupId>
<artifactId>netflix-commons-util</artifactId>
<version>0.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
and my application.yml looks like below -
eureka:
client:
healthcheck:
enabled: true
lease:
duration: 5
service-url:
defaultZone: http://localhost:8761/eureka/
ingestWithOutEureka:
ribbon:
eureka:
enabled: false
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
listOfServers: http://demo-nlb-6a67d59c901ecd128.elb.us-west-2.amazonaws.com,http://demo-nlb-124321w2a123ecd128.elb.us-west-2.amazonaws.com
okToRetryOnAllOperations: true
ConnectTimeout: 500
ReadTimeout: 1000
MaxAutoRetries: 5
MaxAutoRetriesNextServer: 5
MaxTotalHttpConnections: 500
MaxConnectionsPerHost: 100
retryableStatusCodes: 404,503
okhttp:
enabled: true
zuul:
debug:
request: true
parameter: true
ignored-services: '*'
routes:
ingestServiceELB:
path: /ingestWithoutEureka/ingest/**
retryable: true
url: http://dummyURL
management.security.enabled : false
spring:
application:
name: zuul-gateway
cloud:
loadbalancer:
retry:
enabled: true
logging:
level:
org:
apache:
http: DEBUG
com:
netflix: DEBUG
hystrix:
command:
default:
execution:
isolation:
strategy: THREAD
thread:
timeoutInMilliseconds: 60000
and my application class looks like below
#SpringBootApplication
#EnableZuulProxy
#EnableDiscoveryClient
public class ZuulGatewayApplication {
#Bean
public InterceptionFilter addInterceptionFilter() {
return new InterceptionFilter();
}
public static void main(String[] args) {
SpringApplication.run(ZuulGatewayApplication.class, args);
}
}
and lastly my zuul filter looks like below -
package com.zuulgateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.stream.Collectors;
public class InterceptionFilter extends ZuulFilter{
private static final String REQUEST_PATH = "/ingestWithoutEureka";
#LoadBalanced
#Bean
RestTemplate loadBalanced() {
//RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
#Autowired
#LoadBalanced
private RestTemplate loadBalancedRestTemplate;
#Override
public String filterType() {
return "route";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String method = request.getMethod();
String requestURI = request.getRequestURI();
return requestURI.startsWith(REQUEST_PATH);
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
try {
String requestPayload = ctx.getRequest().getReader().lines().collect(Collectors.joining(System.lineSeparator()));
String response = loadBalancedRestTemplate.postForObject("http://ingestWithOutEureka/ingest", requestPayload, String.class);
ctx.setResponseStatusCode(200);
ctx.setResponseBody(response);
} catch (IOException e) {
ctx.setResponseStatusCode(500);
ctx.setResponseBody("{ \"error\" : " + e.getMessage() + " }");
System.out.println("Exception during feign call - " + e.getMessage());
e.printStackTrace();
} finally {
ctx.setSendZuulResponse(false);
ctx.getResponse().setContentType("application/json");
}
return null;
}
}
So, here are the solutions that worked for me -
Issue 1 - Retry was not working in spite of configuring ribbon.<client>.OkToRetryOnAllOperations: true. Ribbon was clearly ignoring my configuration.
Solution: - It's strange, but after some debugging I had noticed that Ribbon was picking up client level configuration only if a global configuration was present in the first place.
Once I set the global "OkToRetryOnAllOperations" as either "true" or "false" as shown below, ribbon started picking up the ribbon.<client>.OkToRetryOnAllOperations as expected and I could see the retries happening.
ribbon:
OkToRetryOnAllOperations: false
Issue 2 - Also, even after setting the read time out and connect timeout configurations, I don't see ribbon respecting the configuration and still takes 2 mins to throw the error from the server
Solution 2 - Though ribbon started retrying requests after the changes suggested in solution 1 above, I did not see ribbon honoring <client>.ribbon.ReadTimeout and <client>.ribbon.ConnectTimeout.
After spending some time, I figure that this is because of using RestTemplate.
While spring documentation mentions that you could use load balanced RestTemplate for achieving retries, it does not mention that the timeouts wont work with it. Based on this SO answer from 2014, it looks like while ribbon has been added as a interceptor when using load balanced RestTemplate to achieve serviceId to URI resolution, ribbon does not use the underlying HTTP client and uses the http client provided by the RestTemplate. Thus, the ribbon specific <client>.ribbon.ReadTimeout and <client>.ribbon.ConnectTimeout are NOT honored. After I added timeouts to RestTemplate, requests have started timing out at expected intervals.
Lastly,
Issue 3 - I enabled logs by passing a custom http client to rest template.
#LoadBalanced
#Bean
RestTemplate loadBalanced() {
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
System.out.println("returning load balanced rest client");
((HttpComponentsClientHttpRequestFactory)restTemplate.getRequestFactory()).setReadTimeout(1000*30);
((HttpComponentsClientHttpRequestFactory)restTemplate.getRequestFactory()).setConnectTimeout(1000*3);
((HttpComponentsClientHttpRequestFactory)restTemplate.getRequestFactory()).setConnectionRequestTimeout(1000*3);
return restTemplate;
}
#Bean
LoadBalancedBackOffPolicyFactory backOffPolicyFactory() {
return new LoadBalancedBackOffPolicyFactory() {
#Override
public BackOffPolicy createBackOffPolicy(String service) {
return new ExponentialBackOffPolicy();
}
};
}
With all the changes I see that the request retries are happening and with the timeouts and with exponential backoff and with request / responses logs visible as expected. Good luck!

Resources