Odd response from actuator/metrics/http.server.requests - spring

I have this weird response from /actuator/metrics/http.server.requests
{"name":"http.server.requests","baseUnit":"seconds","measurements":
[{"statistic":"COUNT","value":2.0},{"statistic":"TOTAL_TIME","value":0.325170155},
{"statistic":"MAX","value":0.250113973}],"availableTags":
[{"tag":"requiredMetricName","values":["http.server.requests"]}]}
And all tags don't work
For example
/actuator/metrics/http.server.requests?tag=status:200
gets 404
I use Spring boot 2
implementation 'org.springframework.boot:spring-boot-starter-actuator'
I have
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
Properties properties = new Properties();
properties.put("management.metrics.web.server.auto-time-requests", true);
properties.put("management.endpoints.web.exposure.include", "*");
application.setDefaultProperties(properties);
application.run(args);
}

Looks like I had extra bean. It was the reason
#Bean
public WebMvcTagsProvider webMvcTagsProvider() {
return new WebMvcTagsProvider() {
#Override
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response, Object handler, Throwable exception) {
return ((Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE))
.entrySet()
.stream()
.map(entry -> new ImmutableTag(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
}
#Override
public Iterable<Tag> getLongRequestTags(HttpServletRequest request, Object handler) {
return new ArrayList<>();
}
};
}

Related

Spring boot application filter response body

I am working on a spring boot application. I want to modify the response of the request by request body field "Id".
I have implemented below, but still getting just the name in the output while implementing.Any suggestions on implementing below would be helpful:
Below is the requestBody:
{
"id" : "123"
}
In response, I want to append that field to response id(fieldname from request body).
responseBody:
{
"name" : "foo123" //name + id from request
}
MyCustomFilter:
public class TestFilter implements Filter {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
final PrintStream ps = new PrintStream(baos);
MultiReadHttpServletRequest wrapper = new MultiReadHttpServletRequest((HttpServletRequest) request);
MyRequestWrapper req = new MyRequestWrapper(wrapper);
String userId = req.getId();
chain.doFilter(wrapper, new HttpServletResponseWrapper(res) {
#Override
public ServletOutputStream getOutputStream() throws IOException {
return new DelegatingServletOutputStream(new TeeOutputStream(super.getOutputStream(), ps)
);
}
#Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(new DelegatingServletOutputStream(new TeeOutputStream(super.getOutputStream(), ps))
);
}
});
String responseBody = baos.toString();
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(responseBody);
String name = node.get("name").astext();
((ObjectNode) node1).put("name", name + userId);
chain.doFilter(wrapper, res);
}
MyRequestWrapper:
public class MyRequestWrapper extends HttpServletRequestWrapper {
private ServletInputStream input;
public MyRequestWrapper(ServletRequest request) {
super((HttpServletRequest)request);
}
public String getId() throws IOException {
if (input == null) {
try {
JSONObject jsonObject = new JSONObject(IOUtils.toString(super.getInputStream()));
String userId = jsonObject.getString("id");
userId = userId.replaceAll("\\D+","");
return userId;
} catch (JSONException e) {
e.printStackTrace();
}
}
return null;
}
}
MultiReadHttpServletRequest.java
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private byte[] body;
public MultiReadHttpServletRequest(HttpServletRequest request) {
super(request);
try {
body = IOUtils.toByteArray(request.getInputStream());
} catch (IOException ex) {
body = new byte[0];
}
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
ByteArrayInputStream wrapperStream = new ByteArrayInputStream(body);
#Override
public boolean isFinished() {
return false;
}
#Override
public boolean isReady() {
return false;
}
#Override
public void setReadListener(ReadListener readListener) {
}
#Override
public int read() throws IOException {
return wrapperStream.read();
}
};
}
}
Any suggestions are appreciated. TIA.
Nte: After update i am not able to see the updated response as output. I am still seeing just the name but not id appended to it.
The one issue I see with your own implementation of ServletRequest is that you call super.getInputStream() instead of request.getInputStream(). Your own request is empty by default, that's why you're getting time out exception. You have to delegate a call to the actual request:
public class MyRequestWrapper extends HttpServletRequestWrapper {
private ServletInputStream input;
public MyRequestWrapper(ServletRequest request) {
super((HttpServletRequest)request);
}
public String getId() throws IOException {
if (input == null) {
try {
JSONObject jsonObject = new JSONObject(IOUtils.toString(/*DELETEGATE TO ACTUAL REQUEST*/request.getInputStream()));
String userId = jsonObject.getString("id");
userId = userId.replaceAll("\\D+","");
return userId;
} catch (JSONException e) {
e.printStackTrace();
}
}
return null;
}
}

Loading a custom ApplicationContextInitializer in AWS Lambda Spring boot

How to loada custom ApplicationContextInitializer to in spring boot AWS Lambda?
I have an aws lambda application using spring boot, I would like to write an ApplicationContextInitializer for decrypting database passwords. I have the following code that works while running it as a spring boot application locally, but when I deploy it to the AWS console as a lambda it doesn't work.
Here is my code
1. applications.properties
spring.datasource.url=url
spring.datasource.username=testuser
CIPHER.spring.datasource.password=encryptedpassword
The following code is the ApplicationContextInitializer, assuming password is Base64 encoded for testing only (In the actual case it will be encrypted by AWM KMS). The idea here is if the key is starting with 'CIPHER.' (as in CIPHER.spring.datasource.password)I assume it's value needs to be decrypted and another key value pair with actual, key (here spring.datasource.password) and its decrypted value will be added at context initialization.
will be like spring.datasource.password=decrypted password
#Component
public class DecryptedPropertyContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final String CIPHER = "CIPHER.";
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySource<?> propertySource : environment.getPropertySources()) {
Map<String, Object> propertyOverrides = new LinkedHashMap<>();
decodePasswords(propertySource, propertyOverrides);
if (!propertyOverrides.isEmpty()) {
PropertySource<?> decodedProperties = new MapPropertySource("decoded "+ propertySource.getName(), propertyOverrides);
environment.getPropertySources().addBefore(propertySource.getName(), decodedProperties);
}
}
}
private void decodePasswords(PropertySource<?> source, Map<String, Object> propertyOverrides) {
if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerablePropertySource = (EnumerablePropertySource<?>) source;
for (String key : enumerablePropertySource.getPropertyNames()) {
Object rawValue = source.getProperty(key);
if (rawValue instanceof String && key.startsWith(CIPHER)) {
String cipherRemovedKey = key.substring(CIPHER.length());
String decodedValue = decode((String) rawValue);
propertyOverrides.put(cipherRemovedKey, decodedValue);
}
}
}
}
public String decode(String encodedString) {
byte[] valueDecoded = org.apache.commons.codec.binary.Base64.decodeBase64(encodedString);
return new String(valueDecoded);
}
Here is the Spring boot initializer
#SpringBootApplication
#ComponentScan(basePackages = "com.amazonaws.serverless.sample.springboot.controller")
public class Application extends SpringBootServletInitializer {
#Bean
public HandlerMapping handlerMapping() {
return new RequestMappingHandlerMapping();
}
#Bean
public HandlerAdapter handlerAdapter() {
return new RequestMappingHandlerAdapter();
}
#Bean
public HandlerExceptionResolver handlerExceptionResolver() {
return new HandlerExceptionResolver() {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
return null;
}
};
}
//loading the initializer here
public static void main(String[] args) {
SpringApplication application=new SpringApplication(Application.class);
application.addInitializers(new DecryptedPropertyContextInitializer());
application.run(args);
}
This is working when run as a spring boot appliaction, But when it deployed as a lambda into AWS the main() method in my SpringBootServletInitializer will never be called by lambda. Here is my Lambda handler.
public class StreamLambdaHandler implements RequestStreamHandler {
private static Logger LOGGER = LoggerFactory.getLogger(StreamLambdaHandler.class);
private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
static {
try {
handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class);
handler.onStartup(servletContext -> {
FilterRegistration.Dynamic registration = servletContext.addFilter("CognitoIdentityFilter", CognitoIdentityFilter.class);
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
});
} catch (ContainerInitializationException e) {
e.printStackTrace();
throw new RuntimeException("Could not initialize Spring Boot application", e);
}
}
#Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
throws IOException {
handler.proxyStream(inputStream, outputStream, context);
outputStream.close();
}
}
What change is to be made in the code to load the ApplicationContextInitializer by Lambda? Any help will be highly appreciated.
I was able to nail it in the following way.
First changed the property value with place holder with a prefix, where the prefix denotes the values need to be decrypted, ex.
spring.datasource.password=${MY_PREFIX_placeHolder}
aws lambda environment variable name should match to the placeholder
('MY_PREFIX_placeHolder') and it value is encrypted using AWS KMS (This sample is base64 decoding).
create an ApplicationContextInitializer which will decrypt the property value
public class DecryptedPropertyContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final String CIPHER = "MY_PREFIX_";
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySource<?> propertySource : environment.getPropertySources()) {
Map<String, Object> propertyOverrides = new LinkedHashMap<>();
decodePasswords(propertySource, propertyOverrides);
if (!propertyOverrides.isEmpty()) {
PropertySource<?> decodedProperties = new MapPropertySource("decoded "+ propertySource.getName(), propertyOverrides);
environment.getPropertySources().addBefore(propertySource.getName(), decodedProperties);
}
}
}
private void decodePasswords(PropertySource<?> source, Map<String, Object> propertyOverrides) {
if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerablePropertySource = (EnumerablePropertySource<?>) source;
for (String key : enumerablePropertySource.getPropertyNames()) {
Object rawValue = source.getProperty(key);
if (rawValue instanceof String && key.startsWith(CIPHER)) {
String decodedValue = decode((String) rawValue);
propertyOverrides.put(key, decodedValue);
}
}
}
}
public String decode(String encodedString) {
byte[] valueDecoded = org.apache.commons.codec.binary.Base64.decodeBase64(encodedString);
return new String(valueDecoded);
}
}
The above code will decrypt all the values with prefix MY_PREFIX_ and add them at the top of the property source.
As the spring boot is deployed into aws lambda, lambda will not invoke the main() function, so if the ApplicationContextInitializer is initialized in main() it is not going to work. In order to make it work need to override createSpringApplicationBuilder() method of SpringBootServletInitializer, so SpringBootServletInitializer will be like
#SpringBootApplication
#ComponentScan(basePackages = "com.amazonaws.serverless.sample.springboot.controller")
public class Application extends SpringBootServletInitializer {
#Bean
public HandlerMapping handlerMapping() {
return new RequestMappingHandlerMapping();
}
#Bean
public HandlerAdapter handlerAdapter() {
return new RequestMappingHandlerAdapter();
}
#Bean
public HandlerExceptionResolver handlerExceptionResolver() {
return new HandlerExceptionResolver() {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
return null;
}
};
}
#Override
protected SpringApplicationBuilder createSpringApplicationBuilder() {
SpringApplicationBuilder builder = new SpringApplicationBuilder();
builder.initializers(new DecryptedPropertyContextInitializer());
return builder;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
No need to make any changes for the lambdahandler.

Autowired RestTemplate returns incorrect ResponseErrorHandler

everyone. I am now working in a Spring Boot project that one of the function is to send some HTTP requests to third party API and get the responses.
I decided to use RestTemplate as the http client and I created a bean for RestTemplate in a java file with #Configuration
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new RequestLoggingInterceptor());
restTemplate.setInterceptors(interceptors);
restTemplate.setErrorHandler(new RestResponseErrorHandler());
return restTemplate;
}
Custom error handler and logging interceptor are set in restTemplate. Then, I injected the RestTemplate into my service class by
#Autowired
RestTemplate restTemplate;
However, I found that my custom error handler is not working properly and I tried to check the error handler by
ResponseErrorHandler customErrorHandler = restTemplate.getErrorHandler();
I found that theErrorHandler is still DefaultResponseErrorHandler but not my custom errorHandler. Why? Is there anything wrong?
Updated
Below are my custom error handler and logging interceptor
public class RestResponseErrorHandler implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
if (!response.getStatusCode().equals(HttpStatus.ACCEPTED)) {
return true;
}
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
if (!response.getStatusCode().equals(HttpStatus.ACCEPTED)) {
throw new RestResponseErrorException(response.getStatusCode() + "" + response.getStatusText());
}
}
}
and
public class RequestLoggingInterceptor implements ClientHttpRequestInterceptor {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
logRequest(request, body);
ClientHttpResponse response = execution.execute(request, body);
logResponse(response);
return response;
}
private void logRequest(HttpRequest request, byte[] body) throws IOException {
if (log.isDebugEnabled()) {
log.debug("Request begin -----------------------------");
log.debug("URI:{}", request.getURI());
log.debug("Method:{}", request.getMethod());
log.debug("Headers:{}", request.getHeaders().toString());
log.debug("Request body:{}", new String(body, "UTF-8"));
log.debug("Request end -----------------------------");
}
}
private void logResponse(ClientHttpResponse response) throws IOException {
if (log.isDebugEnabled()) {
log.debug("Response begin -----------------------------");
log.debug("Status code:{}", response.getStatusCode());
log.debug("Status text:{}", response.getStatusText());
log.debug("Headers:{}", response.getHeaders().toString());
log.debug("Response body:{}", StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()));
log.debug("Response end -----------------------------");
}
}
}

Spring-Integration-DSL & Soap Service Soap header is not getting invoked

I am trying to call a soap service with spring integration DSL, have some custom header that needs to be added.
Constructed marshellingoutboundgateway. Trying to override DefaultSoapHeaderMapper but none of the overridden methods are getting called.
Trying to construct some thing like this.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<ObjectType34 >
</ObjectType34>
</soapenv:Header>
<soapenv:Body>
<ObjectType12 >
</ObjectType12>
</soapenv:Body>
</soapenv:Envelope>
There is a sample in : https://github.com/spring-projects/spring-integration/blob/master/src/reference/asciidoc/ws.adoc
looks like only available in 5.0
posted detail code.
Any insights will be helpful.
#Configuration
#SpringBootApplication
#IntegrationComponentScan
#EnableIntegration
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(Application.class, args);
Info Info = ctx.getBean(Info.class);
//Constructing request Payload
ObjectType12 getInfoType = new ObjectFactory().ObjectType12();
JAXBElement<GetInfoType> getInfoTypeJAXBElement = new ObjectFactory().createGetInfo(getInfoType);
JAXBElement<GetInfoResponseType> getInfoResponseType = Info.getInfo(getInfoTypeJAXBElement);
System.out.println(getInfoResponseType.getName());
ctx.close();
}
#MessagingGateway
public interface Info {
#Gateway(requestChannel = "convert.input")
JAXBElement<GetInfoResponseType> getInfo(JAXBElement<GetInfoType> InfoType);
}
#Bean
public IntegrationFlow convert() {
StringResult result = new StringResult();
return flow -> flow
.wireTap(f -> f.<JAXBElement, String>transform(ele -> {
jaxb2Marshaller().marshal(ele, result);
return result.toString();
}).log())
.handle(endpoint());
}
#Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("org.abc", "com.abc");
return marshaller;
}
#Bean
public MarshallingWebServiceOutboundGateway endpoint() {
MarshallingWebServiceOutboundGateway gateway = new MarshallingWebServiceOutboundGateway("https://example.com/v1", jaxb2Marshaller(), jaxb2Marshaller());
gateway.setHeaderMapper(new DefaultSoapHeaderMapper() {
#Override
protected void populateUserDefinedHeader(String headerName, Object headerValue, SoapMessage target) {
super.populateUserDefinedHeader("Content-Type", "application/soap+xml", target);
}
#Override
protected void populateStandardHeaders(Map<String, Object> headers, SoapMessage target) {
headers.put(WebServiceHeaders.SOAP_ACTION,
"http://www.example.com/SOAUI/ServiceHeader/V4");
super.populateStandardHeaders(headers, target);
}
#Override
public void fromHeadersToRequest(MessageHeaders headers, SoapMessage target) {
SaajSoapMessage targetMessage = (SaajSoapMessage) target;
SoapHeader header = targetMessage.getEnvelope().getHeader();
//Constructing SOAP Header
JAXBElement<ObjectType34> trackingHdrTypeJAXBElement = ObjectFactory().createHdr(ObjectType34);
jaxb2Marshaller().marshal(trackingHdrTypeJAXBElement, header.getResult());
System.out.println(header.getResult());
}
#Override
public void setRequestHeaderNames(String... requestHeaderNames) {
super.setRequestHeaderNames("*");
}
});
gateway.setMessageFactory(new SaajSoapMessageFactory() {
#Override
public void setSoapVersion(SoapVersion version) {
super.setSoapVersion(SoapVersion.SOAP_11);
}
});
return gateway;
}
}
UPDATE: Modified the code
#Bean
public DefaultSoapHeaderMapper headerMapper11() {
return new DefaultSoapHeaderMapper() {
#Override
public void fromHeadersToRequest(MessageHeaders headers, SoapMessage target) {
SaajSoapMessage targetMessage = (SaajSoapMessage) target;
SoapHeader header = targetMessage.getEnvelope().getHeader();
//Constructing SOAP Header
JAXBElement<ObjectType34> trackingHdrTypeJAXBElement = ObjectFactory().createHdr(ObjectType34);
jaxb2Marshaller().marshal(trackingHdrTypeJAXBElement, header.getResult());
super.fromHeadersToRequest(headers, target);
}
};
}
and set the header using the method call:
gateway.setHeaderMapper(headerMapper11());
Now the overridden method is getting called and having the header as well.
Code is working as expected now.

Type mismatch: cannot convert from String to ListenableFuture<String>

I'm trying to implementing non-blocking call. in spring 4, But unfortunately it's throwing the below error.
Type mismatch: cannot convert from String to ListenableFuture
and also same error can not able convert from Map to ListenableFuture>.
My Method call stack is as below.
ListenableFuture<Map<String,String>> unusedQuota = doLogin(userIdentity,request,"0");
doLogin login simply return Map
is there any converter required?
what changes would be required ?
Thanks.
public class MyController {
final DeferredResult<Map<String,String>> deferredResult = new DeferredResult<Map<String,String>>(5000l);
private final Logger log = LoggerFactory.getLogger(MyController.class);
#Inject
RestTemplate restTemplate;
#RequestMapping(value = "/loginservice", method = RequestMethod.GET)
#Timed
public DeferredResult<Map<String,String>> loginRequestService(#RequestParam String userIdentity,HttpServletRequest request) throws Exception {
deferredResult.onTimeout(new Runnable() {
#Override
public void run() { // Retry on timeout
deferredResult.setErrorResult(ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body("Request timeout occurred."));
}
});
#SuppressWarnings("unchecked")
ListenableFuture<Map<String,String>> unusedQuota = doLogin(userIdentity,request);
unusedQuota.addCallback(new ListenableFutureCallback<Map<String,String>>() {
#SuppressWarnings("unchecked")
#Override
public void onSuccess(Map<String, String> result) {
// TODO Auto-generated method stub
deferredResult.setResult((Map<String, String>) ResponseEntity.ok(result));
}
#Override
public void onFailure(Throwable t) {
// TODO Auto-generated method stub
deferredResult.setErrorResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(t));
}
});
return deferredResult;
}
private Map<String,String> doLogin(String userIdentity,HttpServletRequest request) throws Exception{
Map<String,String> unusedQuota=new HashMap<String,String>();
unusedQuota.put("quota", "100");
return unusedQuota;
}
}
}
You are NOT passing the Map object when there is an exception which is causing the issue, so your controller method needs to be changed as shown below, also move deferredResult object inside the Controller method as you should share the same instance of deferredResult for different user request.
public class MyController {
#Autowired
private TaskExecutor asyncTaskExecutor;
#RequestMapping(value = "/loginservice", method = RequestMethod.GET)
#Timed
public DeferredResult<Map<String,String>> loginRequestService(#RequestParam String userIdentity,HttpServletRequest request) throws Exception {
final DeferredResult<Map<String,String>> deferredResult = new DeferredResult<Map<String,String>>(5000l);
deferredResult.onTimeout(new Runnable() {
#Override
public void run() { // Retry on timeout
Map<String, String> map = new HashMap<>();
//Populate map object with error details with Request timeout occurred.
deferredResult.setErrorResult(new ResponseEntity
<Map<String, String>>(map, null,
HttpStatus.REQUEST_TIMEOUT));
}
});
ListenableFuture<String> task = asyncTaskExecutor.submitListenable(new Callable<String>(){
#Override
public Map<String,String> call() throws Exception {
return doLogin(userIdentity,request);
}
});
unusedQuota.addCallback(new ListenableFutureCallback<Map<String,String>>() {
#SuppressWarnings("unchecked")
#Override
public void onSuccess(Map<String, String> result) {
// TODO Auto-generated method stub
deferredResult.setResult((Map<String, String>) ResponseEntity.ok(result));
}
#Override
public void onFailure(Throwable t) {
Map<String, String> map = new HashMap<>();
//Populate map object with error details
deferredResult.setErrorResult(new ResponseEntity<Map<String, String>>(
map, null, HttpStatus.INTERNAL_SERVER_ERROR));
}
});
return deferredResult;
}
}
Also, you need to ensure that you are configuring the ThreadPoolTaskExecutor as explained in the example here.

Resources