Im trying to create microservices but zuul api gateway service maped wrong url. Seems it is related to encoding problem.
Zuul application.properties
spring.application.name=zuul-api-gateway
server.port=8765
eureka.client.service-url.default-zone=http://localhost:8761/eureka
Service application.yml
spring:
application:
name: car-service
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3307/car_service_db?useSSL=false
username: root
password: admin
profiles:
active: local
server:
port: 8080
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka
management:
security:
enabled: false
Other service application.yml
spring:
application:
name: company-service
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3307/company_service_db?useSSL=false
username: root
password: admin
server:
port: 8081
eureka:
client:
service-url:
default-zone: http://localhost:8761/eureka
management:
security:
enabled: false
Eureka logs:
2018-02-11 00:02:41.204 INFO 22672 --- [nio-8761-exec-2] c.n.e.registry.AbstractInstanceRegistry : Registered instance CAR-SERVICE/localhost:car-service:8080 with status UP (replication=false)
2018-02-11 00:02:58.469 INFO 22672 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry : Running the evict task with compensationTime 0ms
2018-02-11 00:03:06.468 INFO 22672 --- [nio-8761-exec-4] c.n.e.registry.AbstractInstanceRegistry : Registered instance COMPANY-SERVICE/localhost:company-service:8081 with status UP (replication=false)
2018-02-11 00:03:26.139 INFO 22672 --- [nio-8761-exec-9] c.n.e.registry.AbstractInstanceRegistry : Registered instance ZUUL-API-GATEWAY/localhost:zuul-api-gateway:8765 with status UP (replication=false)
And the zuul logs
2018-02-11 00:03:35.037 INFO [zuul-api-gateway,6944cfba5180a640,6944cfba5180a640,false] 2132 --- [nio-8765-exec-1] o.s.c.n.zuul.web.ZuulHandlerMapping : Mapped URL path [/car-servıce/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
2018-02-11 00:05:06.872 INFO [zuul-api-gateway,773aa5fc779af5e6,773aa5fc779af5e6,false] 2132 --- [nio-8765-exec-3] o.s.c.n.zuul.web.ZuulHandlerMapping : Mapped URL path [/company-servıce/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
2018-02-11 00:05:06.873 INFO [zuul-api-gateway,773aa5fc779af5e6,773aa5fc779af5e6,false] 2132 --- [nio-8765-exec-3] o.s.c.n.zuul.web.ZuulHandlerMapping : Mapped URL path [/zuul-apı-gateway/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
In the zuul logs, all services mapped by special letter with 'ı' which is the turkish character instead of 'i' and i can not reach my services via zuul api gateway.
I have also tried override Zuul configuration in this example : Spring-Cloud Zuul breaks UTF-8 symbols in forwarded multipart request filename
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
FormBodyWrapperFilter formBodyWrapperFilter() {
return new FormBodyWrapperFilter(new MyFormHttpMessageConverter());
}
private class MyFormHttpMessageConverter extends FormHttpMessageConverter {
private byte[] getAsciiBytes(String name) {
try {
// THIS IS THE ONLY MODIFICATION:
return name.getBytes("UTF-8");
} catch (UnsupportedEncodingException ex) {
// Should not happen - US-ASCII is always supported.
throw new IllegalStateException(ex);
}
}
}
}
So when i try call service via Zuul api gateway, it returns 404 not found.
Im using spring-boot 2.0.0.M3, spring-cloud Finchley.M2 versions
As you are using Spring boot, I will suggest you to use annotation for zuul gateway and create custom filter for zuul that will take care for each and every thing.
Please find the code below and see whether it works for you or not :
Main Class
#EnableZuulProxy
#SpringBootApplication
#EnableScheduling
#EnableFeignClients(basePackages = { Constants.FEIGN_BASE_PACKAGE })
#ComponentScan(basePackages = { Constants.BASE_PACKAGE, Constants.LOGGING_PACKAGE })
#EntityScan(basePackages = { Constants.ENTITY_BASE_PACKAGE })
#EnableDiscoveryClient
public class GatewayInitializer {
/**
* The main method.
*
* #param args the arguments
*/
public static void main(String[] args) {
Security.setProperty("networkaddress.cache.ttl", "30");
ConfigurableApplicationContext context = SpringApplication.run(Initializer.class, args);
}
ZUUL Filter
package com.sxm.aota.gateway.filters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.util.ReflectionUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
/**
* This CustomErrorFilter involve to handle ZuulException.
*/
public class CustomErrorFilter extends ZuulFilter {
/** The logger. */
private static final Logger logger = LoggerFactory.getLogger(CustomErrorFilter.class);
/** The message source. */
#Autowired
private MessageSource messageSource;
/** The properties. */
#Autowired
private Properties properties;
/*
* (non-Javadoc)
*
* #see com.netflix.zuul.ZuulFilter#filterType()
*/
#Override
public String filterType() {
return "post";
}
/*
* (non-Javadoc)
*
* #see com.netflix.zuul.ZuulFilter#filterOrder()
*/
#Override
public int filterOrder() {
return -1; // Needs to run before SendErrorFilter which has filterOrder == 0
}
/*
* (non-Javadoc)
*
* #see com.netflix.zuul.IZuulFilter#shouldFilter()
*/
#Override
public boolean shouldFilter() {
// only forward to errorPath if it hasn't been forwarded to already
return RequestContext.getCurrentContext().containsKey("error.status_code");
}
/*
* (non-Javadoc)
*
* #see com.netflix.zuul.IZuulFilter#run()
*/
#Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
Object e = ctx.get("error.exception");
if (e != null && e instanceof ZuulException) {
ZuulException zuulException = (ZuulException) e;
logger.error("Zuul failure detected: " + zuulException.getMessage(), zuulException);
// Remove error code to prevent further error handling in follow up filters
ctx.remove("error.status_code");
} else {
error.setMessage(messageSource.getMessage(Constants.REQUESTED_SERVICE_UNAVAILABLE, new Object[] { zuulException.getCause() }, properties.getCurrentLocale()));
ctx.setResponseBody(mapper.writeValueAsString(error));
ctx.getResponse().setContentType("application/json");
ctx.setResponseStatusCode(500); // Can set any error code as excepted
}
}
} catch (Exception ex) {
logger.error("Exception filtering in custom error filter", ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
}
I solved the problem by create extended EurekaDiscoveryClient just for toLowerCase(Locale.ROOT) option.
import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient;
import org.springframework.stereotype.Component;
import java.util.*;
#Component
public class CustomEurekaDiscoveryClient extends EurekaDiscoveryClient implements DiscoveryClient {
private final EurekaInstanceConfig config;
private final EurekaClient eurekaClient;
public CustomEurekaDiscoveryClient(EurekaInstanceConfig config, #Qualifier("eurekaClient") EurekaClient eurekaClient) {
super(config, eurekaClient);
this.config = config;
this.eurekaClient = eurekaClient;
}
#Override
public List<String> getServices() {
Applications applications = this.eurekaClient.getApplications();
if (applications == null) {
return Collections.emptyList();
}
List<Application> registered = applications.getRegisteredApplications();
List<String> names = new ArrayList<>();
for (Application app : registered) {
if (app.getInstances().isEmpty()) {
continue;
}
names.add(app.getName().toLowerCase(Locale.ROOT));
}
return names;
}
}
Upper case 'I' transform to lower case 'ı' not 'i' in Turkish OS and jvm that's why i need to override the public List getServices() method.
If the toLowerCase(Locale.ROOT) method calls without Locale.ROOT java transform character to 'ı' but use the Locale.ROOT option, method transform to 'i' and project works fine.
you can add Locale.setDefault for english in main method of Zuul application.
public static void main(String[] args) {
Locale.setDefault(new Locale("en", "US"));
SpringApplication.run(ServerApplication.class, args);
}
it solves your problem too.
Related
The code "works", in that it returns the expected information (a list of DemoPOJO objects). However, as demonstrated by the console output shown below, two calls are being made to the REST service at localhost:8080/v2/DemoPOJO. I have a feeling that the second call is a result of a lack of understanding of reactive programming, but I do not see where the second call is being made on this REST API, and would like to eliminate it as the redundancy is likely to be a performance issue when "something real" is deployed.
In the code provided, a call is made on localhost:8080/v3/DemoClient (implemented in DemoClientHandler), which then uses a WebClient object to access a corresponding REST service at localhost:8080/v2/DemoPOJO (implemented in DemoPOJOHandler).
(I have stripped the code down to only those statements associated with the REST endpoint /v3/DemoClient)
2019-09-26 12:30:23.389 INFO 4260 --- [ main] com.test.demo.DemoApplication : Starting DemoApplication on M7730-LFR with PID 4260 (D:\sandbox\DemoReactive\build\classes\java\main started by LesR in D:\sandbox\DemoReactive)
2019-09-26 12:30:23.391 INFO 4260 --- [ main] com.test.demo.DemoApplication : No active profile set, falling back to default profiles: default
2019-09-26 12:30:24.570 INFO 4260 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080
2019-09-26 12:30:24.573 INFO 4260 --- [ main] com.test.demo.DemoApplication : Started DemoApplication in 1.41 seconds (JVM running for 1.975)
2019-09-26 12:30:28.796 INFO 4260 --- [ctor-http-nio-3] m.n.d.accesslogger.ServiceRequestLogger :
***** Begin, Access Request Log
Service request -> GET # http://localhost:8080/v3/DemoClient
Service handled by -> com.test.demo.democlient.DemoClientHandler.getAll()
***** End, Access Request Log
2019-09-26 12:30:28.823 INFO 4260 --- [ctor-http-nio-8] m.n.d.accesslogger.ServiceRequestLogger :
***** Begin, Access Request Log
Service request -> GET # http://localhost:8080/v2/DemoPOJO
Service handled by -> com.test.demo.demopojo.DemoPOJOHandler.getAll()
***** End, Access Request Log
2019-09-26 12:30:28.911 INFO 4260 --- [ctor-http-nio-9] m.n.d.accesslogger.ServiceRequestLogger :
***** Begin, Access Request Log
Service request -> GET # http://localhost:8080/v2/DemoPOJO
Service handled by -> com.test.demo.demopojo.DemoPOJOHandler.getAll()
***** End, Access Request Log
"Second-level" handler, access "first-level" REST API via WebClient (DemoClient)
#Component
public class DemoClientHandler {
public static final String PATH_VAR_ID = "id";
#Autowired
ServiceRequestLogger svcRequestLogger;
#Autowired
DemoClient demoClient;
public Mono<ServerResponse> getAll(ServerRequest request) {
Flux<DemoPOJO> fluxDemoPOJO = demoClient.getAll();
svcRequestLogger.logServiceRequest(this.getClass(), "getAll()", request);
return fluxDemoPOJO.hasElements().flatMap(hasElement -> {
return hasElement ? ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(fluxDemoPOJO, DemoPOJO.class)
: ServerResponse.noContent().build();
});
}
}
Uses WebClient to access "first-level" REST API...
#Component
public class DemoClient {
private final WebClient client;
public DemoClient() {
client = WebClient.create();
}
public Flux<DemoPOJO> getAll() {
return client.get().uri("http://localhost:8080/v2/DemoPOJO")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMapMany(response -> response.bodyToFlux(DemoPOJO.class));
}
)
"First-level" handler...
#Component
public class DemoPOJOHandler {
#Autowired
private ServiceRequestLogger svcRequestLogger;
#Autowired
private DemoPOJOService service;
public Mono<ServerResponse> getAll(ServerRequest request) {
Flux<DemoPOJO> fluxDemoPOJO = service.getAll();
svcRequestLogger.logServiceRequest(this.getClass(), "getAll()", request);
return fluxDemoPOJO.hasElements().flatMap(hasElement -> {
return hasElement ? ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(fluxDemoPOJO, DemoPOJO.class)
: ServerResponse.noContent().build();
});
}
}
Router for second-level (WebClient) REST API...
#Configuration
public class DemoClientRouter {
#Bean
public RouterFunction<ServerResponse> clientRoutes(DemoClientHandler requestHandler) {
return nest(path("/v3"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.GET("/DemoClient"), requestHandler::getAll)));
}
}
Router for first-level REST API...
#Configuration
public class DemoPOJORouter {
#Bean
public RouterFunction<ServerResponse> demoPOJORoute(DemoPOJOHandler requestHandler) {
return nest(path("/v2"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.GET("/DemoPOJO"), requestHandler::getAll)));
}
}
Following code is added for completeness of the example, but I doubt is involved in the behavior I want to isolate-and-remove.
Service layer, supports DemoPOJO operations...
#Component
public class DemoPOJOService {
#Autowired
private DemoPOJORepo demoPOJORepo;
public Flux<DemoPOJO> getAll() {
return Flux.fromArray(demoPOJORepo.getAll());
}
}
Simple mock-up of POJO/data to support exploration...
#Component
public class DemoPOJORepo {
private static final int NUM_OBJS = 5;
private static DemoPOJORepo demoRepo = null;
private Map<Integer, DemoPOJO> demoPOJOMap;
private DemoPOJORepo() {
initMap();
}
public static DemoPOJORepo getInstance() {
if (demoRepo == null) {
demoRepo = new DemoPOJORepo();
}
return demoRepo;
}
public DemoPOJO[] getAll() {
return demoPOJOMap.values().toArray(new DemoPOJO[demoPOJOMap.size()]);
}
private void initMap() {
demoPOJOMap = new TreeMap<Integer, DemoPOJO>();
for (int ndx = 1; ndx < (NUM_OBJS + 1); ndx++) {
demoPOJOMap.put(ndx, new DemoPOJO(ndx, "foo_" + ndx, ndx + 100));
}
}
}
Logs (SLF4J/(Logback*) client access of REST services to application's log...
#Component
public class ServiceRequestLogger {
Logger logger = LoggerFactory.getLogger(this.getClass());
public void logServiceRequest(Class serviceHandler, String serviceMethod, ServerRequest request) {
logger.info(buildLogMessage(serviceHandler, serviceMethod, request));
}
private String buildLogMessage(Class serviceHandler, String serviceMethod, ServerRequest request) {
StringBuilder logMessage = new StringBuilder();
/* <housekeeping code to build message to log */
return logMessage.toString();
}
}
Probably violating some rule, but I've tried rephrasing the question and tightening up the code example at Why is the handler for a REST endpoint being accessed twice, when accessed from a WebClient?.
For the mods, I'm trying to avoid excessive editing of the original question, which can make comments appear non-topical.
I try to integrate CXF WS to jhipster stack, so avoid xml configuration.
The first class to config service
#EnableWs
#Configuration
#AutoConfigureAfter(WebConfigurer.class)
public class WebServiceConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean dispatcherServlet() {
CXFServlet cxfServlet = new CXFServlet();
return new ServletRegistrationBean(cxfServlet, "/soap/*");
}
#Bean(name = "cxf")
public SpringBus springBus() {
return new SpringBus();
}
#Bean
public Hello hello() {
return new HelloPortImpl();
}
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), hello());
endpoint.publish("/hello");
return endpoint;
}
}
The second file :
#WebService(targetNamespace = "http://service.ws.sample/", name = "Hello")
public interface Hello {
#WebResult(name = "return", targetNamespace = "")
#RequestWrapper(localName = "sayHello", targetNamespace = "http://service.ws.sample/", className = "com.orange.api.rfid.tacites.proxyauth.web.restWS.SayHello")
#WebMethod(action = "urn:SayHello")
#ResponseWrapper(localName = "sayHelloResponse", targetNamespace = "http://service.ws.sample/", className = "com.orange.api.rfid.tacites.proxyauth.web.restWS.SayHelloResponse")
public java.lang.String sayHello(
#WebParam(name = "myname", targetNamespace = "")
java.lang.String myname
);
}
The third file
#javax.jws.WebService(
serviceName = "HelloService",
portName = "HelloPort",
targetNamespace = "http://service.ws.sample/",
endpointInterface = "com.orange.api.rfid.tacites.proxyauth.web.restWS.Hello")
public class HelloPortImpl implements Hello {
private static final Logger LOG = Logger.getLogger(HelloPortImpl.class.getName());
public java.lang.String sayHello(java.lang.String myname) {
LOG.info("Executing operation sayHello" + myname);
try {
return "Welcome to CXF Spring boot " + myname + "!!!";
} catch (java.lang.Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
}
In my logs when start spring boot, i have this line:
[DEBUG] com.sun.xml.bind.v2.schemagen.XmlSchemaGenerator - Wrigin XML Schema for com.sun.xml.bind.v2.schemagen.XmlSchemaGenerator#6a08fd54[http://service.ws.sample/=com.sun.xml.bind.v2.schemagen.XmlSchemaGenerator$Namespace#76617add]
com.sun.xml.bind.v2.util.StackRecorder: null
at com.sun.xml.bind.v2.schemagen.XmlSchemaGenerator.write(XmlSchemaGenerator.java:441) [jaxb-impl-2.2.jar:2.2]
The problem is Jhipster index.html is not found and in http://localhost:8080/soap/hello i have No binding operation info while invoking unknown method with params unknown
I think the cxf servlet kill first one servlet, how to configure to coexist both?
Regards
Try renaming your WebServiceConfig.dispatcherServlet method to something else as there is probably a bean with this name defined by Spring Boot that you're overriding.
To solve the issue, i add to WebConfigurer.java:
/**
* Initialize cxf - ws
*/
private void initCxf(ServletContext servletContext) {
log.debug("Initialize cxf - ws");
ServletRegistration.Dynamic cxfServlet = servletContext.addServlet("CxfWS", new CXFServlet());
cxfServlet.addMapping("/soap/*");
cxfServlet.setLoadOnStartup(3);
}
I've got this error when not specifying the proper wsdl name. Verify that the Soap Service instance uses a proper path to wsdl.
I have been migrating an existing application over to Spring Cloud's service discovery, Ribbon load balancing, and circuit breakers. The application already makes extensive use of the RestTemplate and I have been able to successfully use the load balanced version of the template. However, I have been testing the situation where there are two instances of a service and I drop one of those instances out of operation. I would like the RestTemplate to failover to the next server. From the research I have done, it appears that the fail-over logic exists in the Feign client and when using Zuul. It appears that the LoadBalancedRest template does not have logic for fail-over. In diving into the code, it looks like the RibbonClientHttpRequestFactory is using the netflix RestClient (which appears to have logic for doing retries).
So where do I go from here to get this working?
I would prefer to not use the Feign client because I would have to sweep A LOT of code.
I had found this link that suggested using the #Retryable annotation along with #HystrixCommand but this seems like something that should be a part of the load balanced rest template.
I did some digging into the code for RibbonClientHttpRequestFactory.RibbonHttpRequest:
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
try {
addHeaders(headers);
if (outputStream != null) {
outputStream.close();
builder.entity(outputStream.toByteArray());
}
HttpRequest request = builder.build();
HttpResponse response = client.execute(request, config);
return new RibbonHttpResponse(response);
}
catch (Exception e) {
throw new IOException(e);
}
}
It appears that if I override this method and change it to use "client.executeWithLoadBalancer()" that I might be able to leverage the retry logic that is built into the RestClient? I guess I could create my own version of the RibbonClientHttpRequestFactory to do this?
Just looking for guidance on the best approach.
Thanks
To answer my own question:
Before I get into the details, a cautionary tale:
Eureka's self preservation mode sent me down a rabbit hole while testing the fail-over on my local machine. I recommend turning self preservation mode off while doing your testing. Because I was dropping nodes at a regular rate and then restarting (with a different instance ID using a random value), I tripped Eureka's self preservation mode. I ended up with many instances in Eureka that pointed to the same machine, same port. The fail-over was actually working but the next node that was chosen happened to be another dead instance. Very confusing at first!
I was able to get fail-over working with a modified version of RibbonClientHttpRequestFactory. Because RibbonAutoConfiguration creates a load balanced RestTemplate with this factory, rather then injecting this rest template, I create a new one with my modified version of the request factory:
protected RestTemplate restTemplate;
#Autowired
public void customizeRestTemplate(SpringClientFactory springClientFactory, LoadBalancerClient loadBalancerClient) {
restTemplate = new RestTemplate();
// Use a modified version of the http request factory that leverages the load balacing in netflix's RestClient.
RibbonRetryHttpRequestFactory lFactory = new RibbonRetryHttpRequestFactory(springClientFactory, loadBalancerClient);
restTemplate.setRequestFactory(lFactory);
}
The modified Request Factory is just a copy of RibbonClientHttpRequestFactory with two minor changes:
1) In createRequest, I removed the code that was selecting a server from the load balancer because the RestClient will do that for us.
2) In the inner class, RibbonHttpRequest, I changed executeInternal to call "executeWithLoadBalancer".
The full class:
#SuppressWarnings("deprecation")
public class RibbonRetryHttpRequestFactory implements ClientHttpRequestFactory {
private final SpringClientFactory clientFactory;
private LoadBalancerClient loadBalancer;
public RibbonRetryHttpRequestFactory(SpringClientFactory clientFactory, LoadBalancerClient loadBalancer) {
this.clientFactory = clientFactory;
this.loadBalancer = loadBalancer;
}
#Override
public ClientHttpRequest createRequest(URI originalUri, HttpMethod httpMethod) throws IOException {
String serviceId = originalUri.getHost();
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
RestClient client = clientFactory.getClient(serviceId, RestClient.class);
HttpRequest.Verb verb = HttpRequest.Verb.valueOf(httpMethod.name());
return new RibbonHttpRequest(originalUri, verb, client, clientConfig);
}
public class RibbonHttpRequest extends AbstractClientHttpRequest {
private HttpRequest.Builder builder;
private URI uri;
private HttpRequest.Verb verb;
private RestClient client;
private IClientConfig config;
private ByteArrayOutputStream outputStream = null;
public RibbonHttpRequest(URI uri, HttpRequest.Verb verb, RestClient client, IClientConfig config) {
this.uri = uri;
this.verb = verb;
this.client = client;
this.config = config;
this.builder = HttpRequest.newBuilder().uri(uri).verb(verb);
}
#Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(verb.name());
}
#Override
public URI getURI() {
return uri;
}
#Override
protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException {
if (outputStream == null) {
outputStream = new ByteArrayOutputStream();
}
return outputStream;
}
#Override
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
try {
addHeaders(headers);
if (outputStream != null) {
outputStream.close();
builder.entity(outputStream.toByteArray());
}
HttpRequest request = builder.build();
HttpResponse response = client.executeWithLoadBalancer(request, config);
return new RibbonHttpResponse(response);
}
catch (Exception e) {
throw new IOException(e);
}
//TODO: fix stats, now that execute is not called
// use execute here so stats are collected
/*
return loadBalancer.execute(this.config.getClientName(), new LoadBalancerRequest<ClientHttpResponse>() {
#Override
public ClientHttpResponse apply(ServiceInstance instance) throws Exception {}
});
*/
}
private void addHeaders(HttpHeaders headers) {
for (String name : headers.keySet()) {
// apache http RequestContent pukes if there is a body and
// the dynamic headers are already present
if (!isDynamic(name) || outputStream == null) {
List<String> values = headers.get(name);
for (String value : values) {
builder.header(name, value);
}
}
}
}
private boolean isDynamic(String name) {
return name.equals("Content-Length") || name.equals("Transfer-Encoding");
}
}
public class RibbonHttpResponse extends AbstractClientHttpResponse {
private HttpResponse response;
private HttpHeaders httpHeaders;
public RibbonHttpResponse(HttpResponse response) {
this.response = response;
this.httpHeaders = new HttpHeaders();
List<Map.Entry<String, String>> headers = response.getHttpHeaders().getAllHeaders();
for (Map.Entry<String, String> header : headers) {
this.httpHeaders.add(header.getKey(), header.getValue());
}
}
#Override
public InputStream getBody() throws IOException {
return response.getInputStream();
}
#Override
public HttpHeaders getHeaders() {
return this.httpHeaders;
}
#Override
public int getRawStatusCode() throws IOException {
return response.getStatus();
}
#Override
public String getStatusText() throws IOException {
return HttpStatus.valueOf(response.getStatus()).name();
}
#Override
public void close() {
response.close();
}
}
}
I had the same problem but then, out of the box, everything was working (using a #LoadBalanced RestTemplate). I am using Finchley version of Spring Cloud, and I think my problem was that I was not explicity adding spring-retry in my pom configuration. I'll leave here my spring-retry related yml configuration (remember this only works with #LoadBalanced RestTemplate, Zuul of Feign):
spring:
# Ribbon retries on
cloud:
loadbalancer:
retry:
enabled: true
# Ribbon service config
my-service:
ribbon:
MaxAutoRetries: 3
MaxAutoRetriesNextServer: 1
OkToRetryOnAllOperations: true
retryableStatusCodes: 500, 502
I am trying to autowire an object of a service class in mule flow. The code is:
public class SignatureValidator implements Callable
{
#Autowired
private TriggerHostServiceImpl triggerHostServiceImpl;
#Override
public Object onCall(MuleEventContext eventContext) throws Exception
{
MuleMessage message = eventContext.getMessage();
message = fetchPropertiesAndValidateMessageSignature(message);
return message.getPayload();
}
private MuleMessage fetchPropertiesAndValidateMessageSignature(MuleMessage message) throws GeneralSecurityException, IOException
{
String muleWSTriggerLabel = message.getInboundProperty("triggerLabel");
String muleWSSignature = message.getInboundProperty("signature");
String muleWSExpiresOn = message.getInboundProperty("expiresOn");
String xmlData = message.getInboundProperty("xmlData");
String appHostName = InitConfigurationLoader.getConfigSetting("applicationHostingName");
Trigger triggerJaxbObject = (Trigger) message.getPayload();
String applicationIdentifier = triggerJaxbObject.getApplicationIdentifier();
TriggerMapper triggerMapper = FetchConfigurationEntities.getTriggerMapper(applicationIdentifier, muleWSTriggerLabel);
String reportEmail = FetchConfigurationEntities.getReportEmail(triggerMapper);
ImportDetails importInstance = FetchConfigurationEntities.getImport(triggerMapper);
String importInstanceURL = importInstance.getWebserviceURL();
message.setInvocationProperty("triggerJaxbObject", triggerJaxbObject);
message.setInvocationProperty("importInstance", importInstance);
message.setInvocationProperty("reportEmail", reportEmail);
message.setInvocationProperty("appIdentifier", applicationIdentifier);
message.setInvocationProperty("importHost", importInstanceURL.substring(importInstanceURL.lastIndexOf('/')+1, importInstanceURL.length()));
setPayloadAfterValidation(message, muleWSTriggerLabel, xmlData, muleWSSignature, appHostName, muleWSExpiresOn);
return message;
}
My service class is:
package com.catalystone.csi.service;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.catalystone.csi.core.cache.UpdateCacheable;
import com.catalystone.csi.dao.TriggerHostDao;
import com.catalystone.csi.model.TriggerHost;
#Service
public class TriggerHostServiceImpl implements TriggerHostService
{
#Autowired
private TriggerHostDao triggerHostDao;
#Autowired
private UpdateCacheable updateCacheable;
/**
* Method to save mule configurations i.e. TriggerHosts
*/
#Override
#Transactional
public boolean saveTriggerHost(TriggerHost triggerHost)
{
if(triggerHostDao.saveTriggerHost(triggerHost))
{
Map<String, TriggerHost> allTriggerHosts = getAllTriggerHosts();
allTriggerHosts.put(triggerHost.getTriggerIdentifier(), triggerHost);
updateCacheable.updateAllTriggerHostCache(allTriggerHosts);
return true;
}
else
return false;
}
/**
* Method to fetch all the configurations
*/
#Override
#Transactional//this annotation is used to maintain transaction
public Map<String, TriggerHost> getAllTriggerHosts()
{
return triggerHostDao.getAllTriggerHosts();
}
/**
* Method to delete mule configuration for triggerHost
* #return - true if delete configuration is successfully done
*/
#Override
#Transactional//this annotation is used to maintain transaction
public Boolean deleteConfiguration(TriggerHost triggerHost, boolean isMultipleImportOccurrence)
{
Boolean isDeleteSuccessful = triggerHostDao.deleteConfiguration(triggerHost, isMultipleImportOccurrence);
//Getting all the configurations from cache
Map<String, TriggerHost> allTriggerHosts = getAllTriggerHosts();
//check if delete configuration successful then remove that configuration from cache
if(isDeleteSuccessful)
{
for(Entry<String, TriggerHost> triggerHostEntry : allTriggerHosts.entrySet())
{
if(triggerHostEntry.getValue().getTriggerIdentifier().equals(triggerHost.getTriggerIdentifier()))
{
allTriggerHosts.remove(triggerHostEntry.getKey());
break;
}
}
//update cache
updateCacheable.updateAllTriggerHostCache(allTriggerHosts);
return true;
}
return false;
}
#Override
#Transactional
public Boolean updateConfiguration(TriggerHost triggerHost)
{
if(triggerHostDao.updateConfiguration(triggerHost))
{
Map<String, TriggerHost> allTriggerHosts = getAllTriggerHosts();
allTriggerHosts.put(triggerHost.getTriggerIdentifier(), triggerHost);
updateCacheable.updateAllTriggerHostCache(allTriggerHosts);
return true;
}
return false;
}
#Override
#Transactional
public Boolean deleteConfiguration(String existingImportIdentifier)
{
return triggerHostDao.deleteConfiguration(existingImportIdentifier);
}
}
when I run this code then value of triggerHostServiceImpl is always null. How to autowire? I have also tried a link Dependency Injection is working at Mule application startup. Objects are getting null, when a request received and Failing by throwing NullEx
and
Spring3 Dependency Injection not working with mule
but then it is giving me so many exception that I couldn't get.
you have to Autowire the Interface not the Implementation
#Autowired
private TriggerHostService triggerHostService;
and add the setter and getter of triggerHostService
I am trying to figure out how many connections are currently opened and I can't seem to find an obvious way to do that with Hikari.
HikariPool exposes that information (getActiveConnections) but I don't see an easy way to access that from HikariDataSource.
If you are using spring boot:
new HikariDataSourcePoolMetadata(dataSource).getActive();
You'll have to get it via JMX programmatic access. First, enable MBean registration through the registerMbeans property or by calling setRegisterMeans(). Then consult this page for how to perform programmatic access:
https://github.com/brettwooldridge/HikariCP/wiki/JMX-Monitoring
This can be done very directly.
dataSource.hikariPoolMXBean.activeConnections
You can use below class for better monitoring:
import javax.sql.DataSource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.pool.HikariPool;
import lombok.extern.slf4j.Slf4j;
#Aspect
#Component
#Slf4j
public class DataSourceAspectLogger {
private HikariPool pool;
#Autowired
private HikariDataSource ds;
#Before("execution(* com.x.common.sql.repo.*.*(..))")
public void logBeforeConnection(JoinPoint jp) throws Throwable {
logDataSourceInfos("Before", jp);
}
#After("execution(* com.x.common.sql.repo.*.*(..)) ")
public void logAfterConnection(JoinPoint jp) throws Throwable {
logDataSourceInfos("After", jp);
}
#Autowired
public void getPool() {
try {
java.lang.reflect.Field field = ds.getClass().getDeclaredField("pool");
field.setAccessible(true);
this.pool = (HikariPool) field.get(ds);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void logDataSourceInfos(final String time, final JoinPoint jp) {
final String method = String.format("%s:%s", jp.getTarget().getClass().getName(), jp.getSignature().getName());
int totalConnections = pool.getTotalConnections();
int activeConnections = pool.getActiveConnections();
int freeConnections = totalConnections - activeConnections;
int connectionWaiting = pool.getThreadsAwaitingConnection();
log.info(String.format("%s %s: number of connections in use by the application (active): %d.", time, method, activeConnections));
log.info(String.format("%s %s: the number of established but idle connections: %d.", time, method, freeConnections));
log.info(String.format("%s %s: number of threads waiting for a connection: %d.", time, method, connectionWaiting));
log.info(String.format("%s %s: max pool size: %d.", time, method, ds.getMaximumPoolSize()));
}
}