contract accept header with api resource and feign client - spring-cloud-contract

feign client works with contracts but not actual api resource
I have a feign client with integration tests against a contract. everything works fine but when moving to end-to-end tests, the feign client keeps getting 406 status code. How could it happen?
Api resource (very standard API in controller):
#RestController
#RequestMapping(value = "/")
public class DateController {
#GetMapping(path = "/somepath", produces = "text/plain")
public ResponseEntity<String> getDateAsString(){
return ResponseEntity.status(HttpStatus.OK).body("20190909");
}
Contract:
request {
method GET()
urlPath ($(consumer('/somepath'),
producer('/somepath'))) {
}
# is below normal based on the controller API definition?
headers {accept(textPlain())}
}
response {
status 200
headers {contentType(textPlain())}
body("20190909")
}
feign client (should "produces = ..." in the feign client?):
public interface DateClient {
#GetMapping(path = "/somepath", produces = "text/plain")
String getDateAsString();
}
here is the feign client integration test
public class DateClientTest {
#Autowired
private DateClient dateClient;
#Test
public void getDate() throws Exception {
String date = dateClient.getDateAsString();
assertThat(date).isEqualTo("20190909");
}
Since feign client works fine with the contract, I expect the same behavior with actual API resource. unfortunately, in real environment, I got 406 status code from dependent micro service.
Can anyone help if there is anything wrong with the contract or feign client?
As an experiment, I removed "produces = ..." in feign client as below (should this be correct feign client?). Now the actual environment works fine but this fails with the contract.
#GetMapping(path = "/somepath")
String getDateAsString();
here version info:
springboot version: 1.5.3.RELEASE
spring cloud version: Dalston.RELEASE
spring-cloud-contract version: 1.2.1.RELEASE
this is a simple Get request if using curl command to my microservice:
curl -v "http://localhost:8090/somepath"
and below is the error logging from feign client:
message="Feign client received an unsuccessful response.", status="406", reason="Not Acceptable"
path="/somepath"
there is no consumer (I omitted one query parameter "?id={id}"

Related

How to filter out headers during HTTP redirect in Camel?

In my Camel SpringBoot app I send a request (which includes a number of specific HTTP headers) and receive the 302 from the remote service. Camel automatically follows the redirect but includes all headers previously set, which causes some problems on the other side. Is there any way to control this behavior?
The most obvious is to remove specific headers in the Camel route with
.removeHeaders("Camel*")
This of course must be done for every header pattern and also multiple times if you have multiple outbound connections.
But Camel also has the concept of a HeaderFilterStrategy. Most components have a default implementation that is used by default.
In the case of HTTP this is HttpHeaderFilterStrategy. It is applied for all HTTP connections and filters the following headers:
content-length
content-type
host
cache-control
connection
date
pragma
trailer
transfer-encoding
upgrade
via
warning
Camel*
org.apache.camel*
You are free to implement your own custom HeaderFilterStrategy or extend one of Camel. You can then register it as a Spring bean and configure your endpoints to use it where needed.
.to("http:myEndpoint?&headerFilterStrategy=#myHeaderFilter") // myHeaderFilter = name of the Spring bean
I was able to solve this by creating a custom HTTP configurer which adds an interceptor to the request, e.g.
public class RedirectHttpClientConfigurer implements HttpClientConfigurer {
#Override
public void configureHttpClient(HttpClientBuilder clientBuilder) {
clientBuilder.addInterceptorFirst(new RedirectInterceptor());
}
public class RedirectInterceptor implements HttpRequestInterceptor {
#Override
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
Object httpResponseContextAttr = context.getAttribute("http.response");
if (httpResponseContextAttr != null) {
HttpResponse httpResponse = (HttpResponse) httpResponseContextAttr;
int statusCode = httpResponse.getStatusLine().getStatusCode();
if (statusCode > 299 && statusCode < 399) {
request.removeHeaders("Authorization");
}
}
}
}
}
Then I configured it as a bean:
<spring:bean id="redirectConfigurer" class="com.foo.bar.RedirectHttpClientConfigurer"/>
and referenced it in the endpoint:
<to uri="http://com.foo.bar?httpClientConfigurer=#redirectConfigurer"/>

Using service in Pact consumer test with Java EE

I'd like to implement a Pact consumer test in our Java EE application. This test shall invoke a consumer service method which would trigger the actual REST call.
Here's the Pact test so far:
#ExtendWith(PactConsumerTestExt.class)
#PactTestFor(providerName = "my-service")
public class MyServiceConsumerTest {
#Inject
private MyService myService;
#Pact(consumer = "myConsumer")
public RequestResponsePact mail(PactDslWithProvider builder) {
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", ContentType.getJSON().asString());
PactDslJsonBody jsonBody = new PactDslJsonBody()
.stringValue("emailAddress", "foo#bar.com")
.stringValue("subject", "Test subject")
.stringValue("content", "Test content")
.asBody();
return builder
.given("DEFAULT_STATE")
.uponReceiving("request for sending a mail")
.path("/mail")
.method("POST")
.headers(headers)
.body(jsonBody)
.willRespondWith()
.status(Response.Status.OK.getStatusCode())
.toPact();
}
#Test
#PactTestFor(pactMethod = "mail")
public void sendMail() {
MailNotification mailNotification = MailNotification.builder()
.emailAddress("foo#bar.com")
.subject("Test subject")
.content("Test content")
.build();
myService.sendNotification(mailNotification);
}
}
The interesting part is this line:
myService.sendNotification(mailNotification);
As I'm running a consumer unit test, the injection of MyService does not work, i.e. results in myService being null. Moreover I think it would be necessary to tell the service to send its request against the Pact mock serveR?
Of course I could just fire the final REST request in the test but that would ignore the service logic.
I guess I'm missing something here?
Yes, you should hit the mock server in the #PactVerification test. Don't fire without the actual application code, it makes a few sense in case of future changes. Tests should fail if you change an HTTP property of that request

Spring Boot - MockMVC forwardedUrl using Thymeleaf

I have a basic SpringBoot app. using Spring Initializer, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
I have this controller:
#Controller
#RequestMapping("/deviceevent")
public class DeviceEventController {
#RequestMapping(value={ "/list"}, method = { RequestMethod.GET})
public String deviceeventList() {
return "tdk/deviceEvent/DeviceEventList";
}
}
and this other test class. Tests using Spring's MockMVC framework. This drives an MVC application in a test, as if it was running in a container,
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#WebMvcTest
public class MockMvcTests {
// Pull in the application context created by #ContextConfiguration
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
// Setup MockMVC to use our Spring Configuration
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getDeviceEventsTest() throws Exception {
this.mockMvc
.perform(get("/deviceevent/list") //
.accept(MediaType.parseMediaType("text/html;charset=UTF-8")))
.andExpect(status().isOk()) //
.andExpect(model().size(1)) //
.andExpect(forwardedUrl("tdk/deviceEvent/DeviceEventList"));
}
But I got this error in the forwarded URL. I always used this method in JSP, never in Thymeleaf, but I guess that It is the same:
java.lang.AssertionError: Forwarded URL expected:</tdk/deviceEvent/DeviceEventList> but was:<null>
Assuming a standard Thymeleaf/Spring setup, it looks like there is a misunderstanding with what the controller is doing - when the controller returns that string "tdk/deviceEvent/DeviceEventList" it is not forwarding the HTTP request somewhere, but returning a view name.
With a normal Spring-thymeleaf setup, that string corresponds to the name of a thymeleaf view that will be rendered on hitting that endpoint (I assume the controller is just serving a normal webpage - so that path probably corresponds to some file path most likely in src/main/resources - but again, this depends a lot on your spring config) - at this point the HTTP request has not been returned to the user, and Spring is still processing it - and will attempt to render the HTML view before returning to the user.
The forwarded URL is used if Spring is not rendering anything but instead returning a HTTP response to the user to forward them to another URL (which will start a different Spring request-response process) using a 301/302 mechanism.
Note the difference in the following methods:
#RequestMapping( value="/document", method=RequestMethod.GET )
public String newDocumentSettings( Model model ){
model.addAllAttributes( contentManagementService.documentToJson() );
return "pages/document-settings";
}
#RequestMapping( value="/document", method=RequestMethod.POST )
public String createNewDocument( #RequestParam String title, #RequestParam String overview, #RequestParam String tags ){
Document doc = documentService.createDocument( title, overview, tags );
return "redirect:/document/${doc.url}/1?getting-started";
}
The first renders the template at the given filepath, the second returns a redirect command to the browser to make another HTTP request to the given URL.
In any case, the forwardedUrl in your test case is because hte HTTP Response doesn't have a URL to forward to (because its returning the HTML). If you do want forwarding behaviour (e.g. you actually want to complete the response and the browser to make a second HTTP request) then you would likely need to update the controller as per example, however, if you are happy with the rendered html page, then the test is invalid (look at the Thymeleaf testing framework to see how to test templating).
Caveat: This is based on the assumption of default Spring-Boot config - if you have other config whereby that string does result in a forwarded HTTP request then this doesnt apply!
Taking a guess here, but the URL tdk/deviceEvent/DeviceEventList is probably not defined. Try replacing it with the URL associated with your context (edit as necessary):
#Test
public void getDeviceEventsTest() throws Exception {
this.mockMvc
.perform(get("/deviceevent/list")
.accept(MediaType.parseMediaType("text/html;charset=UTF-8")))
.andExpect(status().isOk())
.andExpect(model().size(1))
.andExpect(forwardedUrl("/WEB-INF/tdk/deviceEvent/DeviceEventList.html"));
}
Aside, instead of:
#RequestMapping(value={ "/list"}, method = { RequestMethod.GET})
you can use the shorthand:
#GetMapping("/list")

Spring Cloud Netflix : Passing host request parameter via RequestInterceptor to FeignClient

I am building a Spring Cloud project (Brixton.M4 with Spring Boot 1.3.1) with Eureka, Zuul and FeignClient where I am trying to add multi tenancy support (Tenants are identified by subdomain : tenant1.myservice.com). To do so, I would like to somehow pass the original subdomain along requests that are forwarded from a service to the other via Feign but I can't seem to be able to find the right way to do it.
What I have is a client that exposes a #RestController which calls a #FeignClient to communicate with my backend which exposes server operations to the client through its own #RestController.
The #FeignClient using same interface as my #RestController on the server :
#FeignClient(name = "product")
public interface ProductService extends IProductService {
}
What I am currently trying to do is set a header in a RequestInterceptor :
#Component
public class MultiTenancyRequestInterceptor implements RequestInterceptor {
private CurrentTenantProvider currentTenantProvider;
#Autowired
public MultiTenancyRequestInterceptor(CurrentTenantProvider currentTenantProvider) {
this.currentTenantProvider = currentTenantProvider;
}
#Override
public void apply(RequestTemplate template) {
try {
template.header("TENANT", currentTenantProvider.getTenant());
} catch (Exception e) {
// "oops"
}
}
}
My provider class is a simple component where I'm trying to inject a request / session scope bean :
#Component
public class CurrentTenantProvider {
#Autowired
private CurrentTenant currentTenant;
//...
}
The bean (I tried both session and request scope) :
#Bean
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CurrentTenant currentTenant() {
return new CurrentTenant();
}
On the server, I use Hibernate multitenant provider that is supposed to catch the header value and use it to define which DB to connect to :
#Autowired
private HttpServletRequest httpRequest;
#Override
public String resolveCurrentTenantIdentifier() {
return httpRequest.getHeader("TENANT");
}
It seems the Feign call to the server is done in another thread and out of the incoming request scope, so i'm not sure how to pass that value along.
It all works fine when I hardcode the tenant value in the RequestInterceptor so I know the rest is working properly.
I have also looked at many other posts about Zuul "X-Forwaded-For" header and cannot find it in the request received on the server. I have also tried adding a ZuulFilter to pass host name to next request but what I see is that original request to the Client is picked up by the ZuulFilter and I can add but not when the Feign request is sent to the backend service even if I map it in zuul (i guess that is intended ?).
I am not really sure what's the next step and would appreciate some suggestions.
Hope that it's of any use for you but we're doing sth similar in Spring-Cloud-Sleuth but we're using a ThreadLocal to pass span between different libraries and approaches (including Feign + Hystrix).
Here is an example with the highlighted line where we retrieve the Span from the thread local: https://github.com/spring-cloud/spring-cloud-sleuth/blob/master/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceFeignClientAutoConfiguration.java#L123

how to manage multiple http response status code while using spring message converters

In our api we are using spring Jackson http message converter to automatically convert java object to json. I am enjoying that feature,but what I personally feel is that I've lost control over the response http status code.if I want to return the response with different status codes ,I have the choice of using #responsestatus(httpstatus),but I cannot specify the status dynamically,as annotation is expecting a enum const expression. The other choice is http server response.set status(),but I don't like that.spring's responseentity(jsonstring,statuscode) is a great thing to solve but if I want to use Jackson httpmessageconverter is any way to configure the response status code dynamically.
You can return ResponseEntity<MyObject> from your controller method and it will still use the configured message converters, example:
#RestController
#RequestMapping("/foo")
public class FooController {
#RequestMapping
public ResponseEntity<MyObject> foo() {
MyObject myObject = new MyObject();
// You can dynamically set the status based on your needs
HttpStatus status = HttpStatus.OK;
return new ResponseEntity<>(myObject, status);
}
}

Resources