I’m setting up a Spring Boot microservice infrastructure with a Eureka Service Registry.
I’m using RestTemplate to call another service (resolution done via Eureka) locally it works perfect! But on Cloud Foundry I always get a “301 Moved permanently” errorcode when calling the service.
Anyone knows if there is a specific configuration necessary for RestTemplate to work with Eureka on Cloud Foundry?
#Bean
#LoadBalanced
RestTemplate getRestTemplate() {
return new RestTemplate();
}
public UserMapping getUserMappingFromRemoteServer(String name_id){
UserMapping userMappingResponse = mappingTemplate.getForObject("http://user-mapping/user?id=" + name_id, UserMapping.class);
}
My response is always
Setting request Accept header to [application/json, application/*+json]
Created GET request for "http://user-mapping/user?id=1"
GET request for "http://user-mapping/user?id=1" resulted in 301 (MOVED_PERMANENTLY)
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.user.SmartCharging.UserMapping] and content type [text/html]]
eureka:
instance:
non-secure-port-enabled: false
secure-port-enabled: true
did the job
Related
Hi I am trying to implement a pass through SOAP proxy via #RestController in spring. For this purpose I have mapped a rest controller in following way:
#RestController
class MyProxy {
#PostMapping(value = "/**")
public ResponseEntity<String> proxyPost(#RequestBody(required = false) String body, HttpServletRequest request) {}
}
The regular SOAP requests are going OK. The problem comes when a MTOM type of SOAP request is send via the proxy. Then spring failes with unrecognized content type. Here is the exception:
Caused by: org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is javax.servlet.ServletException: Unsupported Content-Type [multipart/related; type="application/xop+xml"; boundary="uuid:dacf4733-80b4-41bc-b2e1-db69b6beadf6"; start="<root.message#cxf.apache.org>"; start-info="text/xml"], expected [multipart/form-data]
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.handleParseFailure(StandardMultipartHttpServletRequest.java:124)
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:115)
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.<init>(StandardMultipartHttpServletRequest.java:88)
at org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:122)
at org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1205)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
... 60 common frames omitted
Caused by: javax.servlet.ServletException: Unsupported Content-Type [multipart/related; type="application/xop+xml"; boundary="uuid:dacf4733-80b4-41bc-b2e1-db69b6beadf6"; start="<root.message#cxf.apache.org>"; start-info="text/xml"], expected [multipart/form-data]
at org.eclipse.jetty.server.Request.getParts(Request.java:2407)
at javax.servlet.http.HttpServletRequestWrapper.getParts(HttpServletRequestWrapper.java:317)
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:95)
... 66 common frames omitted
When receiving a multipart/* request Spring delegates this to the configured Multipart handler. This is enabled by default and for this case should be disabled.
spring.servlet.multipart.enabled=false
Adding the above to your properties should disable it and prevent the parsing, so you can handle it in your controller.
I am trying to use WebClient to consume an endpoint which provides a token.
Using Postman it works as expected. Exported curl from postman is:
curl --location --request POST 'https://mycomp.url/api/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=xxx' \
--data-urlencode 'client_secret=yyy' \
--data-urlencode 'grant_type=client_credentials'
I am configuring webclient call based on same curl above.
Here is my WebClient config:
#Configuration
class ClientConfiguration {
#Bean
fun webClient(): WebClient = WebClient.builder()
.clientConnector(
ReactorClientHttpConnector(
HttpClient.from(
TcpClient
.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.doOnConnected { connection: Connection ->
connection.addHandlerLast(ReadTimeoutHandler(10000, TimeUnit.MILLISECONDS))
connection.addHandlerLast(WriteTimeoutHandler(10000, TimeUnit.MILLISECONDS))
}))
)
.build()
}
Here is the webclient post in order to recieve a token:
#Service
class TokenService(private val webClient: WebClient) {
fun postAsynchronous(): Mono<TokenResponse> = webClient
.post()
.uri(UriComponentsBuilder
.fromHttpUrl("https://mycomp.url")
.path("/api/oauth/token")
.build()
.toUri())
.header("grant_type","client_credentials")
.header("client_id","xxx")
.header("client_secret","yyy")
.header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
.retrieve()
.onStatus(HttpStatus::is4xxClientError) { Mono.error(RuntimeException("4XX Error ${it.statusCode()}")) }
.onStatus(HttpStatus::is5xxServerError) { Mono.error(RuntimeException("5XX Error ${it.statusCode()}")) }
.bodyToMono(TokenResponse::class.java)
}
Here is my build.gradle.kts (the relevant part):
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.jetbrains.kotlin.jvm") version "1.4.10"
id("org.jetbrains.kotlin.kapt") version "1.4.10"
kotlin("plugin.spring") version "1.5.20"
id("org.springframework.boot") version "2.4.7"
//kotlin("jvm") version "1.5.30"
id("io.spring.dependency-management") version "1.0.10.RELEASE"
}
val kotlinVersion: String by project
val springVersion: String by project
val projectGroupId: String by project
val projectVersion: String by project
group = projectGroupId
version = projectVersion
repositories {
mavenLocal()
... some internal artifactories
mavenCentral()
}
// add dependencies
dependencies {
kapt(kotlin("stdlib", kotlinVersion))
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect", kotlinVersion))
implementation("org.springframework.boot:spring-boot-dependencies:2.4.7")
implementation("org.springframework.boot:spring-boot-starter:2.4.7")
implementation("org.springframework.boot:spring-boot-starter-web:2.4.7")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.cloud:spring-cloud-starter-openfeign:3.0.3")
implementation("io.github.openfeign:feign-okhttp:10.2.0")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.11.2")
}
The whole exception is:
2021/09/23 17:33:53.123 [http-nio-8080-exec-2] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2021/09/23 17:33:53.123 [http-nio-8080-exec-2] INFO o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
2021/09/23 17:33:53.124 [http-nio-8080-exec-2] INFO o.s.web.servlet.DispatcherServlet - Completed initialization in 1 ms
2021/09/23 17:33:54.396 [http-nio-8080-exec-2] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: 4XX Error 401 UNAUTHORIZED] with root cause
java.lang.RuntimeException: 4XX Error 401 UNAUTHORIZED
at com.mycomp.security.TokenService$postAsynchronous$2.apply(TokenService.kt:32)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ 401 from POST https://mycomp-url/api/oauth/token [DefaultWebClient]
Stack trace:
at com.mycomp.security.TokenService$postAsynchronous$2.apply(TokenService.kt:32)
at com.mycomp.security.TokenService$postAsynchronous$2.apply(TokenService.kt:15)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec$StatusHandler.apply(DefaultWebClient.java:693)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.applyStatusHandlers(DefaultWebClient.java:652)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.handleBodyMono(DefaultWebClient.java:621)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.lambda$bodyToMono$2(DefaultWebClient.java:541)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125)
I tried also other approach just in case.
I keep webclient as it is and I just change how I send the credentials.
Firstly I created a simple class containing all three parameters:
data class TokenRequest(
var grantType: String,
var clientId: String,
var clientSecret: String
)
And then I modified the webclient.post to
fun postAsynchronous(): Mono<TokenResponse> = webClient
.post()
.uri(UriComponentsBuilder
.fromHttpUrl("https://mycomp-url")
.path("/api/oauth/token")
.build()
.toUri())
.body(BodyInserters.fromValue(TokenRequest("client_credentials","xxx", "yyy")))
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.retrieve()
.onStatus(HttpStatus::is4xxClientError) { Mono.error(RuntimeException("4XX Error ${it.statusCode()}")) }
.onStatus(HttpStatus::is5xxServerError) { Mono.error(RuntimeException("5XX Error ${it.statusCode()}")) }
.bodyToMono(TokenResponse::class.java)
And I got exact same issue:
2021/09/23 18:01:55.994 [http-nio-8080-exec-1] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: 4XX Error 401 UNAUTHORIZED] with root cause
java.lang.RuntimeException: 4XX Error 401 UNAUTHORIZED
at com.mycomp.security.TokenService$postAsynchronous$2.apply(TokenService.kt:32)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ 401 from POST https://mycomp.url/api/oauth/token [DefaultWebClient]
Stack trace:
at com.mycomp.security.TokenService$postAsynchronous$2.apply(TokenService.kt:32)
at com.mycomp.security.TokenService$postAsynchronous$2.apply(TokenService.kt:15)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec$StatusHandler.apply(DefaultWebClient.java:693)
*** Edited in Oct 7th 2021
With Aniket Singla proposal I reached this new issue:
[reactor-tcp-nio-2] WARN r.n.http.client.HttpClientConnect - [id:9270e5dc-1, L:/10.92.12.165:58268 - R:mycomp-url/x.x.x.x:443] The connection observed an error
org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/x-www-form-urlencoded' not supported for bodyType=com.mycomp.application.models.token.TokenRequest
at org.springframework.web.reactive.function.BodyInserters.unsupportedError(BodyInserters.java:391)
...
[http-nio-8080-exec-1] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.reactive.function.client.WebClientRequestException: Content type 'application/x-www-form-urlencoded' not supported for bodyType=com.mycomp.application.models.token.TokenRequest; nested exception is org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/x-www-form-urlencoded' not supported for bodyType=com.mycomp.application.models.token.TokenRequest] with root cause
org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/x-www-form-urlencoded' not supported for bodyType=com.mycomp.application.models.token.TokenRequest
With Maciej Dobrowolski proposal I got this new exception:
2021/10/07 17:36:29.098 [http-nio-8080-exec-2] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.core.codec.DecodingException: JSON decoding error: Instantiation of [simple type, class com.mycomp.application.models.token.TokenResponse] value failed for JSON property result due to missing (therefore NULL) value for creator parameter result which is a non-nullable type; nested exception is com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.mycomp.application.models.token.TokenResponse] value failed for JSON property result due to missing (therefore NULL) value for creator parameter result which is a non-nullable type
at [Source: (io.netty.buffer.ByteBufInputStream); line: 8, column: 1] (through reference chain: com.mycomp.application.models.token.TokenResponse["result"])] with root cause
com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.mycomp.application.models.token.TokenResponse] value failed for JSON property result due to missing (therefore NULL) value for creator parameter result which is a non-nullable type
at [Source: (io.netty.buffer.ByteBufInputStream); line: 8, column: 1] (through reference chain: com.mycomp.application.models.token.TokenResponse["result"])
at com.fasterxml.jackson.module.kotlin.KotlinValueInstantiator.createFromObjectWith(KotlinValueInstantiator.kt:112)
*** Edited
data class TokenResponse (
val result: String
)
Using --data-urlencode curl option, you are adding a parameter to the request's body. In your Kotlin code, you are not passing the same data in the request's body, but in the headers.
What you should do (to mimic postman behavior) is to pass grant_type, client_id, client_secret in the request body by using BodyInserters, like this:
webClient
.post()
.uri(UriComponentsBuilder
.fromHttpUrl("https://mycomp.url")
.path("/api/oauth/token")
.build()
.toUri())
.body(BodyInserters.fromFormData("grant_type", "client_credentials")
.with("client_id", "xxx")
.with("client_secret", "yyy"))
.header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
.retrieve()
// ...
Supplying url encoded data in headers wont work , you just need to tell in headers that you are going to use "application/x-www-form-urlencoded" as content type, else will be taken care by webclient to convert the body into url encoded form. Made some changes to your postAsynchronous method, should solve your problem.
fun postAsynchronous(): Mono<TokenResponse> = webClient
.post()
.uri(UriComponentsBuilder
.fromHttpUrl("https://des-sts-int.mbi.cloud.ihf")
.path("/api/oauth/token")
.build()
.toUri())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.body(BodyInserters.fromFormData("grant_type", "client_credentials")
.with("client_id", "xxx")
.with("client_secret", "yyy")) )
.retrieve()
.onStatus(HttpStatus::is4xxClientError) { Mono.error(RuntimeException("4XX Error ${it.statusCode()}")) }
.onStatus(HttpStatus::is5xxServerError) { Mono.error(RuntimeException("5XX Error ${it.statusCode()}")) }
.bodyToMono(TokenResponse::class.java)
Spring boot-2.3.10, Spring Cloud Gcp: 1.2.8
I'm trying to access specific image pattern **(/resources/images/specific_folder/****) from GC Storage. For that I wrote the resource handler as shown below:
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("Setting the resource location {}", gcStorageLocation);
registry.addResourceHandler("/resources/images/specific_folder/**").addResourceLocations("gs:bucket_name/storage/images/specific_folder/").setCachePeriod(3600).resourceChain(true)
.addResolver(new GcStorageResolver());
}
GcStorageResolver.java extends AbstractResourceResolver.java
#Override
protected Resource resolveResourceInternal(#Nullable HttpServletRequest request, String requestPath, List<? extends Resource> locations,
ResourceResolverChain chain) {
log.info("resolveResourceInternal called for request: {}, requestPath: {}", request.getRequestURL(), requestPath);
return getResource(requestPath, request, locations);
}
I verified that a valid GoogleStorageResource is being returned along with credential. But somewhere in the spring chain, I'm getting the below error:
2021-06-25 15:40:23.366 ERROR 4676 --- [nio-8080-exec-1]
o.a.c.c.C.[.[.[.[dispatcherServlet] 175 : Servlet.service() for
servlet [dispatcherServlet] in context with path [] threw exception
[Request processing failed; nested exception is
com.google.cloud.storage.StorageException: Error parsing token refresh
response. Expected value access_token not found.] with root cause
java.io.IOException: Error parsing token refresh response. Expected
value access_token not found. at
com.google.auth.oauth2.OAuth2Utils.validateString(OAuth2Utils.java:113)
~[google-auth-library-oauth2-http-0.22.1.jar:?] at
com.google.auth.oauth2.ServiceAccountCredentials.refreshAccessToken(ServiceAccountCredentials.java:449)
~[google-auth-library-oauth2-http-0.22.1.jar:?] at
com.google.auth.oauth2.OAuth2Credentials.refresh(OAuth2Credentials.java:157)
~[google-auth-library-oauth2-http-0.22.1.jar:?] at
com.google.auth.oauth2.OAuth2Credentials.getRequestMetadata(OAuth2Credentials.java:145)
~[google-auth-library-oauth2-http-0.22.1.jar:?] at
com.google.auth.oauth2.ServiceAccountCredentials.getRequestMetadata(ServiceAccountCredentials.java:603)
~[google-auth-library-oauth2-http-0.22.1.jar:?] at
com.google.auth.http.HttpCredentialsAdapter.initialize(HttpCredentialsAdapter.java:91)
~[google-auth-library-oauth2-http-0.22.1.jar:?] at
com.google.cloud.http.HttpTransportOptions$1.initialize(HttpTransportOptions.java:159)
~[google-cloud-core-http-1.94.0.jar:1.94.0] at
com.google.cloud.http.CensusHttpModule$CensusHttpRequestInitializer.initialize(CensusHttpModule.java:109)
~[google-cloud-core-http-1.94.0.jar:1.94.0] at
com.google.api.client.http.HttpRequestFactory.buildRequest(HttpRequestFactory.java:88)
~[google-http-client-1.38.0.jar:1.38.0]
Not sure what's going on here. Any pointers?
I published endpoints using Spring Boot WS-Server
When I use SoapUI I see:
HTTP/1.1 200
Accept: text/xml, text/html, image/gif, image/jpeg, *; q=.2, /; q=.2
SOAPAction: ""
Content-Type: text/xml;charset=utf-8
Content-Length: 828
Date: Thu, 29 Apr 2021 14:04:54 GMT
Keep-Alive: timeout=60
Connection: keep-alive
I would like to set custom HTTP Status in response (I know that it may be against the standard but it is an external requirement). I also read following topic:
Spring WS (DefaultWsdl11Definition) HTTP status code with void
But this solution failed
Spring Boot version: 2.2.7
Problem was solved
As I said I wanted to set custom HTTP status in SOAP response.
I found this post:
Spring WS (DefaultWsdl11Definition) HTTP status code with void
Author used EndpointInterceptor with TransportContext to get HttpServletResponse, then he changed status. The difference between my and his case is the fact, that he returned void from WebService method whereas I wanted to return some response.
In my situation following code in Spring WebServiceMessageReceiverObjectSupport class (method handleConnection) overrode servlet status previously set in interceptor:
if (response instanceof FaultAwareWebServiceMessage && connection instanceof FaultAwareWebServiceConnection) {
FaultAwareWebServiceMessage faultResponse = (FaultAwareWebServiceMessage)response;
FaultAwareWebServiceConnection faultConnection = (FaultAwareWebServiceConnection)connection;
faultConnection.setFaultCode(faultResponse.getFaultCode());
}
In order to bypass this fragment of code I needed to define class with my own implementation of handleConnection method, which extended class WebServiceMessageReceiverHandlerAdapter
In my implementation I excluded change of status. Important thing is to pass WebMessageFactory bean in autowired constructor of this class, otherwise exception is raised during app's startup.
This class has to be marked with Spring stereotype (eg. #Component) and name of this bean has to be configured in Configuration class when configuring ServletRegistrationBean:
#Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext){
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
servlet.setMessageFactoryBeanName("webServiceMessageFactory");
servlet.setMessageReceiverHandlerAdapterBeanName("myOwnMessageReceiverHandlerAdapter");
return new ServletRegistrationBean<>(servlet,"/ws/*");
}
I am currently having a connection issue trying to connect to an AWS SQS Queue using Spring Cloud and Spring Boot. I believe I have everything configured fine but am getting:
2015-07-01 18:12:11,926 [WARN][-]
org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext[487]
- Exception encountered during context initialization - cancelling refresh attempt
org.springframework.context.ApplicationContextException: Failed to
start bean 'simpleMessageListenerContainer'; nested exception is
com.amazonaws.AmazonServiceException: Access to the resource
https://sqs.us-west-2.amazonaws.com/{Number}/{Queue Name} is denied.
(Service: AmazonSQS; Status Code: 403; Error Code: AccessDenied;
Request ID: 87312428-ec0f-5990-9f69-6a269a041b4d)
#Configuration
#EnableSqs
public class CloudConfiguration {
private static final Logger log = Logger.getLogger(CloudConfiguration.class);
#MessageMapping("QUEUE")
public void retrieveProvisionMessages(User user) {
log.warn(user.firstName);
}
}
YML
cloud:
aws:
credentials.accessKey: AccessKey
credentials.secretKey: SecretKey
region.static: us-west-2
credentials.instanceProfile: true
When it attempts to connect I see that a header value of:
AWS4-HMAC-SHA256 Credential=accesskey/20150701/us-west-2/sqs/aws4_request, SignedHeaders=host;user-agent;x-amz-date, Signature=signature
After the request is sent:
HTTP/1.1 403 Forbidden [Server: Server, Date: Wed, 01 Jul 2015 22:51:25 GMT, Content-Type: text/xml, Content-Length: 349, Connection: keep-alive, x-amzn-RequestId: Request Id] org.apache.http.conn.BasicManagedEntity#37e55df6
I have checked all AIM policies and they are correct.
Using:
private AmazonSQS establishQueue(){
AmazonSQS sqs = new AmazonSQSClient(new BasicAWSCredentials(accessKey, secretKey));
sqs.setRegion(RegionUtils.getRegion(region));
return sqs;
}
AmazonSQS sqs = establishQueue();
return sqs.receiveMessage(sqs.getQueueUrl(userProductPurchase).getQueueUrl());
with the same credentials works fine. Any help is greatly appreciated.
Thanks
Do you have GetQueueAttributes calls allowed for your IAM user?
I think it's using also few more operations. Not only ReceiveMessage and GetQueueUrl.
In my case, using Spring Cloud, I had to set the following permissions up:
sqs:DeleteMessage
sqs:GetQueueUrl
sqs:ReceiveMessage
sqs:SendMessage
sqs:GetQueueAttributes