Spring/Eureka/Feign - FeignClient setting Content-Type header to application/x-www-form-urlencoded - spring-boot

When I use a FeignClient it is setting the Content-Type to application/x-www-form-urlencoded instead of application/json;charset=UTF-8.
If I use a RestTemplate to send the same message the message header Content-Type is correctly set to application/json;charset=UTF-8.
Both the FeignClient and RestTemplate are using Eureka for service discovery, and I discovered this problem by debugging the HTTP message received by the server.
The controller on the server side looks like this:
#RestController
#RequestMapping("/site/alarm")
public class SiteAlarmController {
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<RaiseAlarmResponseDto> raiseAlarm(#RequestBody RaiseSiteAlarmRequestDto requestDto) {
...
}
My FeignClient interface in the service that calls the alarm looks like this:
#FeignClient("alarm-service")
public interface AlarmFeignService {
#RequestMapping(method = RequestMethod.POST, value = "/site/alarm")
RaiseAlarmResponseDto raiseAlarm(#RequestBody RaiseSiteAlarmRequestDto requestDto);
}
The HTTP message headers from the FeignClient are:
Accept: */*
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.7.0_60
Host: smit005s-MacBook-Pro.local:9120
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 323
The alarm service doesn't like the Content-Type and throws the following exception:
2015-04-22 12:12:28.580 thread="qtp1774842986-25" class="org.eclipse.jetty.servlet.ServletHandler" level="WARN"
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is feign.FeignException: status 415 reading AlarmFeignService#raiseAlarm(RaiseSiteAlarmRequestDto); content:
{"timestamp":1429701148576,"status":415,"error":"Unsupported Media Type","exception":"org.springframework.web.HttpMediaTypeNotSupportedException","message":"Unsupported Media Type","path":"/site/alarm"}
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978) ~[spring-webmvc-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857) ~[spring-webmvc-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:618) ~[tomcat-embed-core-8.0.20.jar:8.0.20]
...
... /* commented rest of stack out */
...
If I change the client side code to use a RestTemplate as follows:
#Service
public class AlarmService {
#Autowired
private RestTemplate restTemplate;
...
public void send(RaiseSiteAlarmRequestDto alarm) {
RaiseAlarmResponseDto result = restTemplate.postForObject("http://alarm-service/site/alarm",
raiseSiteAlarmRequestDto, RaiseAlarmResponseDto.class);
}
}
It works with the RestTemplate, the alarm-service receives the message and processes it successfully. The message headers sent by the RestTemplate are:
Accept: application/json, application/*+json
Content-Type: application/json;charset=UTF-8
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.7.0_60
Host: smit005s-MacBook-Pro.local:9120
Connection: keep-alive
Content-Length: 323

The answer was to do as #spencergibb suggests; use the consumes directive in the #RequestMapping annotation on the FeignClient interface. This Spring/Netflix documentaition also has an example.
So for example the #FeignClient interface declaration in the client is now:
#FeignClient("alarm-service")
public interface AlarmFeignService {
#RequestMapping(method = RequestMethod.POST, value = "/site/alarm", consumes = "application/json"))
RaiseAlarmResponseDto raiseAlarm(RaiseSiteAlarmRequestDto requestDto);
}
Note this is only necessary on the client side and the server side controller does not need to have this change.
Would be nice if this was done by default on the #FeignClient and then it would be the consistent with RestTemplate and the server side controller #RequestMapping annotation. Maybe that can be done in a future release of spring-cloud.

Related

XtextServices Servlet returns CORS-Error with HTTP-Code 404 but not other REST-Controllers

I currently try to set up a Xtext web editor with "Context Mapper" as DSL.
The backend is a basic Spring Boot backend with the Context Mapper DSL and Context Mapper LSP (Language Server).
The frontend is a VueJS frontend made with the help of this guide. It utilizes everything Eclipse Xtext generates and ports it to a VueJS website.
My only issue now is that the "/xtext-service/*" API return CORS errors with HTTP code 404 while the console of the backend prints absolutely nothing.
The Servlet looks like every other XtextServlet:
#WebServlet(name = "XtextServices", urlPatterns = "/xtext-service/*")
#SuppressWarnings("all")
public class ContextMappingDSLServlet extends XtextServlet {
private DisposableRegistry disposableRegistry;
#Override
public void init() {
try {
super.init();
final Injector injector = new ContextMappingDSLWebSetup().createInjectorAndDoEMFRegistration();
this.disposableRegistry = injector.<DisposableRegistry>getInstance(DisposableRegistry.class);
} catch (Throwable _e) {
throw Exceptions.sneakyThrow(_e);
}
}
#Override
public void destroy() {
if ((this.disposableRegistry != null)) {
this.disposableRegistry.dispose();
this.disposableRegistry = null;
}
super.destroy();
}
}
The requests are also the standard requests the Xtext generated web editor invokes and the occurrences request goes through it seems:
Response:
HTTP/1.1 404
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:8081
Access-Control-Allow-Credentials: true
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 21 Oct 2022 16:03:19 GMT
Keep-Alive: timeout=60
Connection: keep-alive
Request:
GET /xtext-service/occurrences?resource=1e26589b.cml&caretOffset=1 HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,de;q=0.8
Cache-Control: no-cache
Connection: keep-alive
Cookie: JSESSIONID=CDE6CC29EE123451F3DF3FF174337EE3
Host: localhost:8080
Origin: http://localhost:8081
Pragma: no-cache
Referer: http://localhost:8081/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
sec-ch-ua: "Chromium";v="106", "Google Chrome";v="106", "Not;A=Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
The requests to http://localhost:8080/xtext-service/occurrences?resource=1e26589b.cml&caretOffset=1 returns 404 while update throws
Access to XMLHttpRequest at 'http://localhost:8080/xtext-service/update?resource=1e26589b.cml' from origin 'http://localhost:8081' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
CORS however is enabled:
#Configuration
#EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedMethods(CorsConfiguration.ALL)
.allowedHeaders(CorsConfiguration.ALL)
.allowedOriginPatterns(CorsConfiguration.ALL)
.allowCredentials(true);
}
}
The non-servlet APIs do work without any issues, but the Xtext-service servlet doesn't, and I don't know why.
#ServletComponentScan is enabled.

Spring webflux: ServerResponse redirection

This is my related code:
#RestController
public class GicarController {
#PostMapping("/login")
public Mono<ServerResponse> gicar(#RequestHeader("GICAR_ID") String gicarId) {
return ServerResponse.temporaryRedirect(URI.create("/me")).build();
}
}
Issue arises when I'm calling to _/login endpoint:
$ curl -i -X POST localhost:8080/login -H "GICAR_ID: tre"
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream;charset=UTF-8
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
Referrer-Policy: no-referrer
curl: (18) transfer closed with outstanding read data remaining
Why am I getting an 200 http code response?
On spring boot logging I'm getting this exception:
022-06-27 13:11:19.931 ERROR 79654 --- [or-http-epoll-2] r.n.http.server.HttpServerOperations : [9750a9d8-1, L:/127.0.0.1:8080 - R:/127.0.0.1:33150] Error finishing response. Closing connection
org.springframework.core.codec.CodecException: Type definition error: [simple type, class org.springframework.web.reactive.function.server.DefaultServerResponseBuilder$WriterFunctionResponse]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.web.reactive.function.server.DefaultServerResponseBuilder$WriterFunctionResponse and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
Why above exception is reaised?
Any ideas?
According to Spring documentation ServerResponse
Represents a typed server-side HTTP response, as returned by a handler function or filter function.
and it supposed to be used in Functional Endpoints
#Configuration
public class GicarConfiguration {
#Bean
public RouterFunction<ServerResponse> route() {
return RouterFunctions
.route(POST("/login"), this::loginHandler);
}
private Mono<ServerResponse> loginHandler(ServerRequest request) {
var gicarId = request.headers().firstHeader("GICAR_ID");
return ServerResponse.temporaryRedirect(URI.create("/me")).build();
}
}
If you still want to use Annotated Controllers, use ResponseEntity instead
#RestController
public class GicarController {
#PostMapping("/login")
public Mono<ResponseEntity<Void>> gicar() {
return Mono.just(ResponseEntity
.status(HttpStatus.TEMPORARY_REDIRECT)
.header(HttpHeaders.LOCATION, "/me")
.build()
);
}
}

Spring WebServiceTemplate add value to Cookie header

I have to add an information in the cookie of a request that my application sends to another application,
but it doesn't seem to be added correctly.
When I check the request with WireShark, I see two Cookie headers in the headers :
POST /service HTTP/1.1
Accept-Encoding: gzip
Cookie: iam=**************************
Accept: text/xml, text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
SOAPAction: ""
Content-Type: text/xml; charset=utf-8
Content-Length: 128393
Host: host-dev:9999
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.10 (Java/1.8.0_271)
Cookie: JSESSIONID=***********************
Authorization: Basic **************************
(I've changed some of the info)
In my code I have this :
#Service
public class ESignatureSoapConnector extends WebServiceGatewaySupport {
private ObjectFactory objectFactory;
#Autowired
public ESignatureSoapConnector(ESignatureMarshaller marshaller, ConfigurationProperties configurationProperties) throws Exception {
this.setMarshaller(marshaller);
this.setUnmarshaller(marshaller);
this.setDefaultUri(configurationProperties.getBaseUrl());
this.setMessageSender(buildMessageSender(configurationProperties.getUsername(), configurationProperties.getPassword()));
this.objectFactory = new ObjectFactory();
}
public ESignatureResponse signDocument(MTOMFile file, String iamCookieValue) {
ESignature request = new ESignature();
request.setInputDocument(file);
JAXBElement<ESignatureResponse> response = (JAXBElement<ESignatureResponse>) getWebServiceTemplate()
.marshalSendAndReceive(objectFactory.createESignature(request), new WebServiceMessageCallback() {
#Override
public void doWithMessage(WebServiceMessage webServiceMessage) throws IOException, TransformerException {
TransportContext context = TransportContextHolder.getTransportContext();
HttpComponentsConnection connection = (HttpComponentsConnection) context.getConnection();
HttpPost post = connection.getHttpPost();
post.addHeader("Cookie", "iam=" + iamCookieValue);
}
});
return response.getValue();
}
private WebServiceMessageSender buildMessageSender(String username, String password) throws Exception {
...
}
}
I'm assuming the way I set the cookie isn't correct but I can't find the proper way to do it.
The value for the cookie is different for each request, it's a soap request and I work in Spring
The solution we've found :
JAXBElement<ESignatureResponse> response = (JAXBElement<ESignatureResponse>) getWebServiceTemplate()
.marshalSendAndReceive(objectFactory.createESignature(request), new WebServiceMessageCallback() {
#Override
public void doWithMessage(WebServiceMessage webServiceMessage) throws IOException, TransformerException {
HttpClient httpClient = ((HttpComponentsMessageSender) getWebServiceTemplate().getMessageSenders()[0]).getHttpClient();
BasicClientCookie iamCookie = new BasicClientCookie(iamConfigurationProperties.getCookieName(), iamCookieValue);
iamCookie.setDomain(iamConfigurationProperties.getCookieDomain());
iamCookie.setPath(iamConfigurationProperties.getCookiePath());
((DefaultHttpClient) httpClient).getCookieStore().addCookie(iamCookie);
}
});

Wiremock returning wrong content-type for Spring Cloud Contract stub runner

I'm trying out Spring-Cloud-Contract for the first time. I'm trying to have my client auto-discover the contract stubs, but even though my contract specifies a content-type of 'application/json' on the response, what I get from WireMock has a content-type of 'application/octet'. What am I doing wrong?
I have a simple method in my service which returns a model like this from a /status endpoint:
{
"name": string,
"status": string
}
My contract looks like this:
import org.springframework.cloud.contract.spec.Contract
Contract.make {
request {
method('GET')
headers {
contentType(applicationJson())
}
url("/status")
}
response {
status OK()
body(
name: "Demo",
status: "RUNNING"
)
headers {
contentType(applicationJson())
}
}
}
In my client, I have a class which uses a Spring RestTemplate to query this endpoint:
#Component
public class StatusClient {
private final RestTemplate restTemplate;
public StatusClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public Status getStatus() {
return this.restTemplate
.exchange("http://localhost:8080/status", HttpMethod.GET, null, Status.class)
.getBody();
}
}
#Data
class Status implements Serializable {
private String name;
private String status;
}
My unit test uses #AutoConfigureStubRunner to pull the latest version of the contract from the local repository and assert against the response from the contract (eg name = Demo, status = RUNNING).
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureStubRunner(ids = {"com.example:contract-demo:+:8080"}, stubsMode = StubRunnerProperties.StubsMode.LOCAL)
public class StatusClientTests {
#Autowired
private StatusClient client;
#Test
public void testThatStatusReturnsSuccessfully() {
Status result = this.client.getStatus();
assertEquals("Demo", result.getName());
assertEquals("RUNNING", result.getStatus());
}
}
When I run the test, WireMock reports the contract it received as expected:
2018-05-31 11:36:49.919 INFO 14212 --- [tp1255723887-26] WireMock : Request received:
127.0.0.1 - GET /status
User-Agent: [Java/1.8.0_161]
Connection: [keep-alive]
Host: [localhost:8080]
Accept: [application/json, application/*+json]
Matched response definition:
{
"status" : 200,
"body" : "{\"name\":\"Demo\",\"status\":\"RUNNING\"}",
"headers" : {
"contentType" : "application/json"
},
"transformers" : [ "response-template" ]
}
Response:
HTTP/1.1 200
contentType: [application/json]
But when the RestTemplate tries to deserialize it, it throws an exception because the response content type is actually "application/octet" once it hits the methods to extract the data:
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.example.contractclientdemo.Status] and content type [application/octet-stream]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:119)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:991)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:974)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:725)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:680)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:600)
at com.example.contractclientdemo.StatusClient.getStatus(StatusClient.java:18)
I'm using Finchley.RC2 for the Spring cloud version, and spring-cloud-starter-contract-stub-runner is my only test dependency other than spring-boot-starter-test.
I know WireMock is returning the wrong content type because I debugged deep into the HttpMessageConverterExtractor class in Spring and that's what the getContentType method returned when queried.
Why is WireMock returning the wrong content type, though it reports the correct one in the log? And how can I get it to properly return an application/json so I can deserialize my simple message?
I had the exact same problem than you. I solved it by adding
headers {
header 'Content-Type': 'application/json;charset=UTF-8'
}
to the response.
You seem to have in your response, although written in another way, but that did solve the problem in my case. So this have something to do with it.
Before making the change, curl showed no Content-Type response header :
curl -v -H "Accept: application/json" localhost:6565/products/ABC
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 6565 (#0)
GET /products/ABC HTTP/1.1
Host: localhost:6565
User-Agent: curl/7.58.0
Accept:application/json
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Server: Jetty(9.2.z-SNAPSHOT)
Connection #0 to host localhost left intact
{
"price": {
"currencyCode": "EUR",
"value": "100.50"
},
"name": "Fake product"
}
After doing the change, curl returned this and the RestTemplate managed to deserialize it.
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Server: Jetty(9.2.z-SNAPSHOT)
Here's my working contract :
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description "should return product information"
request{
method GET()
url("/products/ABC")
}
response {
status 200
headers {
header 'Content-Type': 'application/json;charset=UTF-8'
}
body([
name: 'Fake product',
price:[
currencyCode: 'EUR',
value: 100.50
]
])
}
}
Hope this helps
I think you should file it as an issue in WireMock. Also you're not setting the application / json content type header in the request explicit. Maybe that's a problem? Also shouldn't it be content-type as the header name in the response stub?

Spring Boot CORS headers

I am new to CORS headers and implementing with Spring boot. I am enabling CORS header on POST service which accept request body.
First time preflight request is made which runs fine and return 200 but when actual post request is invoked, it always return 403 forbidden with response body "Invalid CORS request". I have read almost all spring docs and all google/stackoverflow discussions but could not find out what am I missing..huh..
In Below snippet I have tested by adding crossOrigin at top of class and top of method but no luck.
#CrossOrigin(origins = "https://domain/", allowCredentials = "false")
#RequestMapping(value = ApplicationConstants.URI_PATH)
class MainController {
#RequestMapping(value = '/postMethod', method = RequestMethod.POST)
Map<String, Object> postMethod(HttpServletResponse servletResponse,
#RequestBody(required = false) AccessToken requestedConsumerInfo) {...}
For POST method - Preflight request is invoked and result is 200 but main POST call returns 403.
Call with OPTIONS: Status code 200
Response headers (616 B)
Access-Control-Allow-Credentials true
Access-Control-Allow-Headers content-type
Access-Control-Allow-Methods POST
Access-Control-Allow-Origin https://domain
Allow GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH
Cache-Control max-age=0, private, no-cache, …roxy-revalidate, no-transform
Connection close
Content-Length 0
Date Wed, 20 Dec 2017 17:57:14 GMT
Pragma no-cache
Server nginx/1.9.1
Strict-Transport-Security max-age=63072000; includeSubdomains;
Vary Origin,User-Agent
X-Frame-Options SAMEORIGIN
X-XSS-Protection 1; mode=block
Request headers (512 B)
Accept text/html,application/xhtml+xm…plication/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate, br
Accept-Language en-US,en;q=0.5
Access-Control-Request-Headers content-type
Access-Control-Request-Method POST
Connection keep-alive
Host domain
Origin https://domain
User-Agent Mozilla/5.0 (Windows NT 6.1; W…) Gecko/20100101 Firefox/57.0
Call with POST: Status code 403
Response headers (364 B)
Cache-Control max-age=0, private, no-cache, …roxy-revalidate, no-transform
Connection close
Content-Length 20
Date Wed, 20 Dec 2017 17:57:14 GMT
Pragma no-cache
Server nginx/1.9.1
Strict-Transport-Security max-age=63072000; includeSubdomains;
Vary User-Agent
X-Frame-Options SAMEORIGIN
X-XSS-Protection 1; mode=block
Request headers (2.507 KB)
Accept application/json, text/plain, */*
Accept-Encoding gzip, deflate, br
Accept-Language en-US,en;q=0.5
Connection keep-alive
Content-Length 102
Content-Type application/json
Cookie rxVisitor=1513720811976ARCUHEC…B4SL3K63V8|6952d9a33183e7bc|1
Host domain
Origin https://domain
Referer https://domain/home/account/register
User-Agent Mozilla/5.0 (Windows NT 6.1; W…) Gecko/20100101 Firefox/57.0
Since this was not working, I have also tested by adding global configurations alone and also along with above snippet but no luck.
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
void addCorsMappings(CorsRegistry registry) {
super.addCorsMappings(registry);
registry.addMapping(ApplicationConstants.MEMBER_URL_PATH)
.allowedOrigins("https://domain/")
.allowedMethods(HttpMethod.GET.toString(),
HttpMethod.POST.toString(), HttpMethod.PUT.toString());
}
}
}
On the preflight OPTIONS request, the server should respond with all the following (looks like you're doing this already):
Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials (if cookies are passed)
On the actual POST request, you'll need to return at least Access-Control-Allow-Origin and Access-Control-Allow-Credentials. You're not currently returning them for the POST response.
I had the same issue, then used the annotation #CrossOrigin and it works fine, but just for GET, when I tried to make a POST I still got Cross Origin error, then this fixed for me:
Create an interceptor and added the Access Controll headers to the response.
(You might not need all of them)
public class AuthInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse httpResponse, Object handler, ModelAndView modelAndView)
throws Exception {
httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
httpResponse.setHeader("Access-Control-Allow-Headers", "*");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Max-Age", "4800");
}
}
Then add the interceptor:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
System.out.println("++++++ WebConfig addInterceptors() ");
registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/**");
}
}
I hope this save you some time, it took me a while to get this working .

Resources