I'm trying to send emails using spring-cloud-aws. Bellow the relevant snippets:
application.properties:
cloud.aws.credentials.accessKey=XXXXXXXXXXXXXXXXXXXX
cloud.aws.credentials.secretKey=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
cloud.aws.region.static=us-east-1
MailSendingService:
#Service
public class MailSendingService {
#Autowired
private MailSender mailSender;
public void sendMailMessage() {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setFrom("noreply#xxx.com");
simpleMailMessage.setTo("johndoe#gmail.com");
simpleMailMessage.setSubject("test subject");
simpleMailMessage.setText("test content");
this.mailSender.send(simpleMailMessage);
}
}
Application:
#SpringBootApplication
public class Application {
#Autowired
MailSendingService mailService;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
CommandLineRunner init() {
return (evt) -> {
mailService.sendMailMessage();
};
}
}
In my POM I'm using:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-aws</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
</dependency>
When I run this code, I am getting the following error:
Caused by: org.springframework.mail.MailSendException: Failed
messages: com.amazonaws.AmazonServiceException: The request signature
we calculated does not match the signature you provided. Check your
AWS Secret Access Key and signing method. Consult the service
documentation for details.
The Canonical String for this request should have been 'POST /
amz-sdk-invocation-id:3a35628a-ca4d-5d3f-666a-1bd596b25a0
amz-sdk-retry:3/230/485 host:email.us-east-1.amazonaws.com
user-agent:aws-sdk-java/1.11.18 Windows_10/10.0
Java_HotSpot(TM)_64-Bit_Server_VM/25.92-b14/1.8.0_92
x-amz-date:20170201T113844Z
amz-sdk-invocation-id;amz-sdk-retry;host;user-agent;x-amz-date
c87e0a9aed59cebfbf123b9a248c1bece98e17c59ab38486fef0220d1f86da'
The String-to-Sign should have been 'AWS4-HMAC-SHA256 20170201T113844Z
20170201/us-east-1/ses/aws4_request
daa69ea00e5c19ce5123fdbfe0c335d2678516925dc2042f7627d9660520ef'
(Service: AmazonSimpleEmailService; Status Code: 403; Error Code:
SignatureDoesNotMatch; Request ID:
fd73a86-e872-11e6-a68d-7b50079b6d0)
I have triple checked the keys. Any ideas?
Related
I need to retry feign call for certain http status code and after 3 second for maximum 4 time.
Is there any properties that i can define on my application.yml or i need to write my custom Retryer that implement Retry interface
Feign has a build in Retryer however you can not configure the Retryer via application.yml. I guess the Spring Boot Team assumed that people would use the deprecated Hystrix project for this matter.
Instead of configuring Feign by config you could write a bit of code:
https://cloud.spring.io/spring-cloud-openfeign/reference/html/index.html#creating-feign-clients-manually
In addition you have to map the corresponding status code to RetryableException using a custom ErrorDecoder.
public class CustomErrorDecoder implements ErrorDecoder {
private final ErrorDecoder errorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
Exception exception = defaultErrorDecoder.decode(s, response);
if(exception instanceof RetryableException){
return exception;
}
if(response.status() == 499){
return new RetryableException("499 blub", response.request().httpMethod(), null );
}
return exception;
}
}
public class Example {
public static void main(String[] args) {
MyApi myApi = Feign.builder()
.errorDecoder(new CustomErrorDecoder())
.target(MyApi.class, "https://api.hostname.com");
}
}
You can use retryable annotation.
Ex: You can throw custom exception when http status code is equal to 404
#Service
public interface MyService {
#Retryable(value = CustomException.class, maxAttempts = 2, backoff = #Backoff(delay = 100))
void retry(String str) throws CustomException;
}
I created a new sample and slipted the codes into client and server side.
The complete codes can be found here.
There are 3 version of server side.
server None Spring Boot app, using Spring Integration RSocket InboundGateway.
server-boot Reuse Spring RSocket autconfiguration, and created ServerRSocketConnecter through ServerRSocketMessageHanlder.
server-boot-messsagemapping Not use Spring Integration, just use Spring Boot RSocket autconfiguration, and #Controller and #MessageMapping.
There are 2 versions of client.
client, Sending messages using Spring Integration Rocket OutboundGateway.
client-requester Send messages using RSocketRequester, not use Spring Integration at all.
The client and server interaction mode is REQUEST_CHANNEL, and connect server via TCP/localhost:7000.
server
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-rsocket</artifactId>
</dependency>
The application class:
#Configuration
#ComponentScan
#IntegrationComponentScan
#EnableIntegration
public class DemoApplication {
public static void main(String[] args) throws IOException {
try (ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(DemoApplication.class)) {
System.out.println("Press any key to exit.");
System.in.read();
} finally {
System.out.println("Exited.");
}
}
#Bean
public ServerRSocketConnector serverRSocketConnector() {
return new ServerRSocketConnector("localhost", 7000);
}
#Bean
public IntegrationFlow rsocketUpperCaseFlow(ServerRSocketConnector serverRSocketConnector) {
return IntegrationFlows
.from(RSockets.inboundGateway("/uppercase")
.interactionModels(RSocketInteractionModel.requestChannel)
.rsocketConnector(serverRSocketConnector)
)
.<Flux<String>, Flux<String>>transform((flux) -> flux.map(String::toUpperCase))
.get();
}
}
server-boot
Dependencies in pom.xml.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-rsocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-rsocket</artifactId>
</dependency>
application.properties
spring.rsocket.server.port=7000
spring.rsocket.server.transport=tcp
Application class.
#SpringBootApplication
#EnableIntegration
public class DemoApplication {
public static void main(String[] args) throws IOException {
SpringApplication.run(DemoApplication.class, args);
}
// see PR: https://github.com/spring-projects/spring-boot/pull/18834
#Bean
ServerRSocketMessageHandler serverRSocketMessageHandler(RSocketStrategies rSocketStrategies) {
var handler = new ServerRSocketMessageHandler(true);
handler.setRSocketStrategies(rSocketStrategies);
return handler;
}
#Bean
public ServerRSocketConnector serverRSocketConnector(ServerRSocketMessageHandler serverRSocketMessageHandler) {
return new ServerRSocketConnector(serverRSocketMessageHandler);
}
#Bean
public IntegrationFlow rsocketUpperCaseFlow(ServerRSocketConnector serverRSocketConnector) {
return IntegrationFlows
.from(RSockets.inboundGateway("/uppercase")
.interactionModels(RSocketInteractionModel.requestChannel)
.rsocketConnector(serverRSocketConnector)
)
.<Flux<String>, Flux<String>>transform((flux) -> flux.map(String::toUpperCase))
.get();
}
}
server-boot-messagemapping
Dependencies in pom.xml.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-rsocket</artifactId>
</dependency>
The application.properties.
spring.rsocket.server.port=7000
spring.rsocket.server.transport=tcp
The applcition class.
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
#Controller
class UpperCaseHandler {
#MessageMapping("/uppercase")
public Flux<String> uppercase(Flux<String> input) {
return input.map(String::toUpperCase);
}
}
client
In the client, the dependencies in the pom.xml is like.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-rsocket</artifactId>
</dependency>
The application class:
#SpringBootApplication
#EnableIntegration
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public ClientRSocketConnector clientRSocketConnector() {
ClientRSocketConnector clientRSocketConnector = new ClientRSocketConnector("localhost", 7000);
clientRSocketConnector.setAutoStartup(false);
return clientRSocketConnector;
}
#Bean
public IntegrationFlow rsocketUpperCaseRequestFlow(ClientRSocketConnector clientRSocketConnector) {
return IntegrationFlows
.from(Function.class)
.handle(RSockets.outboundGateway("/uppercase")
.interactionModel((message) -> RSocketInteractionModel.requestChannel)
.expectedResponseType("T(java.lang.String)")
.clientRSocketConnector(clientRSocketConnector))
.get();
}
}
#RestController
class HelloController {
#Autowired()
#Lazy
#Qualifier("rsocketUpperCaseRequestFlow.gateway")
private Function<Flux<String>, Flux<String>> rsocketUpperCaseFlowFunction;
#GetMapping(value = "hello", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> uppercase() {
return rsocketUpperCaseFlowFunction.apply(Flux.just("a", "b", "c", "d"));
}
}
When running the client and server application, and try to access the http://localhost:8080/hello by curl.
When using server and server-boot which uses InboundGateway to handle messages, the output looks like this.
curl http://localhost:8080/hello
data:ABCD
When using server-boot-messagemapping, the output is woking as I expected:
data:A
data:B
data:C
data:D
client-requester
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-rsocket</artifactId>
</dependency>
The application class:
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
#RestController
class HelloController {
Mono<RSocketRequester> requesterMono;
public HelloController(RSocketRequester.Builder builder) {
this.requesterMono = builder.connectTcp("localhost", 7000);
}
#GetMapping(value = "hello", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> uppercase() {
return requesterMono.flatMapMany(
rSocketRequester -> rSocketRequester.route("/uppercase")
.data(Flux.just("a", "b", "c", "d"))
.retrieveFlux(String.class)
);
}
}
When running this client and the 3 servers, and try to access the http://localhost:8080/hello by curl.
When using server and server-boot which uses InboundGateway to handle messages, it throws a class cast exception.
When using server-boot-messagemapping, the output is woking as I expected:
data:A
data:B
data:C
data:D
I do not know where is the problem of the configuration of InboundGateway and OutboundGateway?
Thank you for such a detailed sample!
So, what I see. Both clients (plain RSocketRequester and Spring Integration) work well with plain RSocket server.
To make them working with Spring Integration server you have to do this changes:
The server side:
Add .requestElementType(ResolvableType.forClass(String.class)) into an RSockets.inboundGateway() definition, so it will know to what to convert an incoming payloads.
The client side:
.data(Flux.just("a\n", "b\n", "c\n", "d\n")).
Currently the server side of Spring Integration doesn't treat an incoming Flux as a stream of independent payloads. So, we try to connect all of them into a single value.
The new line delimiter is an indicator that we expect independent values. Spring Messaging on its side does exactly opposite: it checks for multi-value expected type and decode every element in the incoming Flux in its map() instead of an attempt for the whole Publisher decoding.
It's going to be kinda breaking change, but possibly need to consider to fix RSocketInboundGateway logic to be consistent with regular #MessageMapping for RSocket support. Feel free to raise a GH issue!
I am following the spring guide to create the client of a web service. Firstly, I create the server and I don't have problems. I generated a jar and executed it. It returns the response.
These are my clases:
InventoryClient.java
public class InventoryClient extends WebServiceGatewaySupport {
public CatalogResponse getCatalog(Catalog cataog) {
Catalog request = new Catalog();
CatalogResponse response = (CatalogResponse) getWebServiceTemplate()
.marshalSendAndReceive("http://localhost:8080/ws/", request,
new SoapActionCallback(
"http://com.uciext.ws.hw5/Catalog"));
return response;
}
}
InventoryConfiguration.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
#Configuration
public class InventoryConfiguration {
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
// this package must match the package in the <generatePackage> specified in
// pom.xml
marshaller.setContextPath("inventory.wsdl");
return marshaller;
}
#Bean
public InventoryClient getCatalog(Jaxb2Marshaller marshaller) {
InventoryClient client = new InventoryClient();
client.setDefaultUri("http://localhost:8080/ws");
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}
}
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
Util.log("---- Application: args[1] "+args[0]);
}
#Bean
void lookup(InventoryClient inventoryClient) {
Catalog catalog = new Catalog();
CatalogResponse response = inventoryClient.getCatalog(catalog);
Util.log("---- Application: date"+response.getReturn().getLastModifiedDate());
}
}
When I execute the jar, I get the following error
Unknown JAXB exception; nested exception is javax.xml.bind.JAXBException: Provider com.sun.xml.internal.bind.v2.ContextFactory could not be instantiated: javax.xml.bind.JAXBException: "inventory.wsdl" doesnt contain ObjectFactory.class or jaxb.index
- with linked exception:
[javax.xml.bind.JAXBException: "inventory.wsdl" doesnt contain ObjectFactory.class or jaxb.index]
When I created the server, I generated the classes from inventory.xsd
Try to make the wsdl package created by jaxb under JAVA Package directly
I've set a couple of retry configurations in my application.properties file. However, none of them is working when I ran the ribbon application.
//this is my service
#RestController
#SpringBootApplication
public class HelloApplication {
#Value("${server.port}")
private int port;
public static void main(String[] args) {
SpringApplication.run(HelloApplication .class, args);
}
#GetMapping(value="/app")
public String notification() {
return "This Is HelloService running on port:"+ port;
}
}
Here is my RibbonAppApplication class:
#SpringBootApplication(scanBasePackages={"com.netflix.client.config.IClientConfig"})
#RestController
#RibbonClient(name= "hello", configuration=RibbonConfig.class )
public class RibbonAppApplication {
#Autowired
private RestTemplate restTemplate;
public static void main(String[] args) {
SpringApplication.run(RibbonAppApplication.class, args);
}
#GetMapping
public String getService() {
return restTemplate.getForObject("http://hello/app",String.class);
}
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
This is the application.properties for the RibbonAppApplication:
ribbon.eureka.enabled=false
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
hello.ribbon.listOfServers=http://localhost:1111, http://localhost:2222
hello.ribbon.OkToRetryOnAllOperations=false
hello.ribbon.MaxAutoRetries=0
hello.ribbon.MaxAutoRetriesNextServer=1
Thank you guys so much for helping!
Missing dependency of Sprint Retry is almost always the reason for Ribbon not able to retry. Spring Retry a dependency for retry functionality for Zuul/Ribbon.
When a request fails, you may want to have the request be retried automatically. To do so when using Sping Cloud Netflix, you need to include Spring Retry on your application’s classpath. When Spring Retry is present, load-balanced RestTemplates, Feign, and Zuul automatically retry any failed requests (assuming your configuration allows doing so)
Adding Spring Retry to pom.xml should fix this.
Related docs: https://cloud.spring.io/spring-cloud-netflix/multi/multi_retrying-failed-requests.html
You have to add the spring-retry dependency to your pom.xml file:
<!-- https://mvnrepository.com/artifact/org.springframework.retry/spring-retry -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.4.RELEASE</version>
</dependency>
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