I need to be able to log http request & response with header & body.
Currently I have an Interceptor and a #AroundInvoke advise which logs the body.
But is it possible to also get hold of the http headers? In some way?
I have included some code snippets below
The project is using Resteasy, ie. having quarkus-rest-client dependency in pom
//#LoggedClient not included in example
#Priority(0)
#Interceptor
public class LoggingInterceptor {
#AroundInvoke
Object logInvocation(InvocationContext ctx) throws Exception {
logRequest(ctx);
try {
Object response = ctx.proceed();
logResponse(response);
return response;
} catch (WebApplicationException e) {
logError(e);
return e;
}
}
private void logError(WebApplicationException e) {/* impl */}
private void logResponse(Object response) {/* impl */}
private void logRequest(InvocationContext ctx) {/* impl which logs httpheader & body(class,method & request) */}
}
#Produces(APPLICATION_JSON)
#Consumes(APPLICATION_JSON)
#Path("/offer")
//#LoggedClient not included in example
public class MyResource {
#Inject
#RestClient
MyClient client;
#POST
#Produces(APPLICATION_JSON)
public Response create(MyRequest req) {
MyResponse response = client.create(req);
return Response.ok().entity(response).build();
}
}
#Path("/somepath")
#RegisterRestClient
public interface MyClient {
#POST
#Produces(MediaType.APPLICATION_JSON)
#Path("/create")
MyResponse create(MyRequest req);
}
Related
I am implementing a filter to find out any json mapping or parsing issues using ContainerRequestFilter interface. It logs error for all json parsing or mapping issues but for request which doesn't have any json validation issues it throws 400 Bad request. If I remove the filter from the code it works fine for proper json request. Below is the code for the same ..
#Provider
public class AppFilter implements ContainerRequestFilter {
private static final Logger LOG = Logger.getLogger(AppFilter.class);
#Inject
ObjectMapper objectMapper;
#Override
public void filter(ContainerRequestContext context) throws IOException {
try(InputStream stream = new CloseShieldInputStream(context.getEntityStream())) {
objectMapper.readValue(stream,AppRequest.class);
}catch(Exception ex) {
LOG.error(ExceptionUtils.getRootCauseMessage(ex));
}
}
}
#Path("/app")
public class MatchResource {
#POST
#Produces(MediaType.APPLICATION_JSON)
public Uni<Response> processRequest(AppRequest request,
#Context SecurityContext securityContext) {
String tranId = UUID.randomUUID().toString();
ZonedDateTime zonedDateTime = ZonedDateTime.now(TIME_ZONE_ID);
return Uni.createFrom().item(request)
.onItem().invoke(() -> {
Set<ConstraintViolation<AppRequest>> violations = validator.validate(request);
if (!violations.isEmpty()) {
throw new RequestViolationsException(violations,
request.header.applicationReferenceId);
}
}).onFailure()
.transform(t -> LOG.error(ExceptionUtils.getRootCauseMessage(t)));
}
Let me know if I am missing anything in this ..
I've created a custom web service client by extending WebServiceGatewaySupport and also implement custom ClientInterceptor to log some request/response data.
I have to create new interceptor for every call because it has to store some data about the request.
The problem occurs when I make two or more calls to my client. The first request applies its own interceptor with its clientId. The second should do the same. But since both requests use the same WebServicetemplate in my client, the second request replaces the interceptor with its own, with its clientId there.
As a result, I should get the following output to the console:
Request: clientId-1
Request: clientId-2
Response: clientId-1
Response: clientId-2
But I got this:
Request: clientId-1
Request: clientId-2
Response: clientId-2
Response: clientId-2
Here is come code examples (just for understanding how it should work):
#Data
class Response {
private final String result;
public Response(String result) {
this.result = result;
}
}
#Data
class Request {
private final String firstName;
private final String lastName;
}
#Data
class Context {
private final String clientId;
}
#Data
class Client {
private final String clientId;
private final String firstName;
private final String lastName;
}
class CustomInterceptor extends ClientInterceptorAdapter {
private final String clientId;
public CustomInterceptor(String clientId) {
this.clientId = clientId;
}
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
System.out.println("Request: " + clientId);
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
System.out.println("Response: " + clientId);
return true;
}
#Override
public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
System.out.println("Error: " + clientId);
return true;
}
}
#Component
class CustomClient extends WebServiceGatewaySupport {
public Response sendRequest(Request request, Context context) {
CustomInterceptor[] interceptors = {new CustomInterceptor(context.getClientId())};
setInterceptors(interceptors);
return (Response) getWebServiceTemplate().marshalSendAndReceive(request);
}
}
#Service
#RequiredArgsConstructor
class CustomService {
private final CustomClient customClient;
public String call(Request request, Context context) {
Response response = customClient.sendRequest(request, context);
return response.getResult();
}
}
#RestController
#RequestMapping("/test")
#RequiredArgsConstructor
class CustomController {
private final CustomService service;
public CustomController(CustomService service) {
this.service = service;
}
#PostMapping
public String test(#RequestBody Client client) {
Request request = new Request(client.getFirstName(), client.getLastName());
Context context = new Context(client.getClientId());
return service.call(request, context);
}
}
Is it possible to implement custom interceptors with some state for each call? Preferably without any locks on WebServicetemplate to avoid performance degradation.
Okay. I've found the solution for my case.
I've created an implementation of WebServiceMessageCallback and using it I'm saving data of each request not in interceptor but in WebServiceMessage's mime header.
#Data
class CustomMessageCallback implements WebServiceMessageCallback {
private final String clientId;
#Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
MimeHeaders headers = ((SaajSoapMessage) message).getSaajMessage().getMimeHeaders();
headers.addHeader("X-Client-Id", clientId);
}
}
And pass this callback in my client implementation:
#Component
class CustomClient extends WebServiceGatewaySupport {
public Response sendRequest(Request request, Context context) {
CustomInterceptor[] interceptors = {new CustomInterceptor()};
setInterceptors(interceptors);
return (Response) getWebServiceTemplate()
.marshalSendAndReceive(request, new CustomMessageCallback(context.getClientId()));
}
}
So now I can get this data while processing request/response/error via interceptor.
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
String clientId = ((SaajSoapMessage) messageContext.getRequest())
.getSaajMessage()
.getMimeHeaders()
.getHeader("X-Client-Id")[0];
System.out.println("Request: " + clientId);
return true;
}
I have to test Jersey 1.19 with jersey-test-framework-grizzly2. There is configuration class with registered REST endpoint and exception mapper class:
public class ConfiguredMyServiceTest extends JerseyTest {
#Override
protected int getPort(int defaultPort) {
return 8080;
}
public static class AppConfig extends DefaultResourceConfig {
public AppConfig() {
getSingletons().add(new ExceptionMapperProvider());
getSingletons().add(new MyService());
}
}
#Override
public WebAppDescriptor configure() {
return new WebAppDescriptor.Builder()
.initParam(WebComponent.RESOURCE_CONFIG_CLASS,
AppConfig.class.getName())
.build();
}
}
When I execute/test REST endpoint which returns HTTP status 200 it works well.
If exception is thrown, exception mapper handles it well and forms return object javax.ws.rs.core.Response with error code:
#Provider
#Singleton
public class ExceptionMapperProvider implements ExceptionMapper<Exception>{
#Override
public Response toResponse(final Exception exception){
return Response.status(HttpStatusCodes.STATUS_CODE_SERVER_ERROR).entity(new BasicResponse(InternalStatus.UNHANDLED_EXCEPTION, exception.getMessage())).type(MediaType.APPLICATION_JSON).build();
}
}
However, I get
com.sun.jersey.api.client.UniformInterfaceException: POST http://localhost:8080/v1/my-service/ returned a response status of 401 Unauthorized
when I try to assert Response in my JUnit tests. How to get well formed response instead of UniformInterfaceException?
Changed expected class type to com.sun.jersey.api.client.ClientResponse
protected ClientResponse executeGet(String path){
WebResource resource = resource().path(path);
Builder builder = resource.header("Content-Type", "application/json;charset=UTF-8");
return builder.get(ClientResponse.class);
}
Now it is able to handle various HTTP statuses and parse underlying response:
ClientResponse clientResponse = executeGet("/info");
if (clientResponse.getStatus() == 200)
CustomResponseType customResponse = clientResponse.getEntity(CustomResponseType.class);
I have a REST api that responds with some additional non JSON data in the body content. This breaks the use of RestTemplate and jackson. Can I intercept the http response body prior to the parsing?
I am using RestTemplate.getForObject.
I've taken a look at the RestTemplate and couldn't see an appropriate method.
You can try to implement ClientHttpRequestInterceptor and assign it to restTemplate. Implement intercept method:
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
ClientHttpResponse response=clientHttpRequestExecution.execute(httpRequest, bytes);
//...do magic with response body from getBody method
return response;
}
You might have to extend AbstractClientHttpResponse with your own implementation to do that.
Another option could be to treat the response from the REST API as String, then format the string as needed and explicitly map it to object using ObjectMapper.
Then in your restTemplate you would have:
String result = restTemplate.getForObject(url, String.class, host);
//..trim the extra stuff
MyClass object=objectMapper.readValue(result, MyClass.class);
Yet another option would be to implement your own HttpMessageConverter which extends AbstractJackson2HttpMessageConverter and register it with restTemplate. In my opinion that would be the cleaneast from the Spring point of view
Another way would be to unwrap the response by implementing a ClientHttpRequestInterceptor along with a ClientHttpResponse.
#Component
public class MyInterceptor implements ClientHttpRequestInterceptor {
#Autowired
Function<ClientHttpResponse, MyResponseWrapper> responseWrapperBeanFactory;
#Autowired
MyRequestAdvice requestAdvice;
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
byte[] wrappedBody = requestAdvice.wrapRequest(bytes);
ClientHttpResponse res = clientHttpRequestExecution.execute(httpRequest, wrappedBody);
return responseWrapperBeanFactory.apply(res);
}
}
Here's the bean config for the MyResponseWrapper:
#Bean
Function<ClientHttpResponse, MyResponseWrapper> responseWrapperBeanFactory() {
return this::getMyResponseWrapper;
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyResponseWrapper getMyResponseWrapper(ClientHttpResponse originalResponse) {
return new MyResponseWrapper(originalResponse);
}
#Bean
public RestTemplate restTemplate(#Autowired MyInterceptor interceptor) {
RestTemplate t = new RestTemplate();
t.setInterceptors(Arrays.asList(interceptor));
// other setup code
return t;
}
And here's the ClientHttpResponse implementation:
public class MyResponseWrapper implements ClientHttpResponse {
private byte[] filteredContent;
private ByteArrayInputStream responseInputStream;
private ClientHttpResponse originalResponse;
public MyResponseWrapper(ClientHttpResponse originalResponse) {
this.originalResponse = originalResponse;
try {
filteredContent = MyContentUnwrapper.unwrapResponse(originalResponse.getBody().readAllBytes());
} catch (Exception e) {
throw new RuntimeException("There was a problem reading/decoding the response coming from the service ", e);
}
}
#Override
public HttpStatus getStatusCode() throws IOException {
return originalResponse.getStatusCode();
}
#Override
public int getRawStatusCode() throws IOException {
return originalResponse.getRawStatusCode();
}
#Override
public String getStatusText() throws IOException {
return originalResponse.getStatusText();
}
#Override
public void close() {
if (responseInputStream != null) {
try {
responseInputStream.close();
} catch (IOException e) { /* so long */}
}
}
#Override
public InputStream getBody() throws IOException {
if (responseInputStream == null) {
responseInputStream = new ByteArrayInputStream(filteredContent);
}
return responseInputStream;
}
#Override
public HttpHeaders getHeaders() {
return originalResponse.getHeaders();
}
}
From your Controller you can try to return a ResponseEntity and manipulate the entity object manually
If you don't need these extra properties you may add:
#JsonIgnoreProperties(ignoreUnknown = true)
to your mapping class.
From docs:
Property that defines whether it is ok to just ignore any unrecognized
properties during deserialization. If true, all properties that are
unrecognized -- that is, there are no setters or creators that accept them
-- are ignored without warnings (although handlers for unknown properties,
if any, will still be called) without exception.
Does not have any effect on serialization.
I have a problem with my jersey rest service when method is PUT or POST. The problem is that parameter is null. I test with angularjs and jersey-test-framework with same result, the parameter is null.
I thought the problem was due to the header "Content-Type" but I add this in my test with the same result.
Jersey version: 2.14
Jersey modules: jersey-container-servlet, jersey-spring3, jersey-media-json-jackson
My application config and object mapper:
public class CustomObjectMapper implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public CustomObjectMapper() {
defaultObjectMapper = createDefaultMapper();
}
#Override
public ObjectMapper getContext(final Class<?> type) {
return defaultObjectMapper;
}
private static ObjectMapper createDefaultMapper() {
final ObjectMapper result = new ObjectMapper();
return result;
}
}
#ApplicationPath("/")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
packages("es.fjtorres.cpFacturas.server.api.impl",
"es.fjtorres.cpFacturas.server.config",
"es.fjtorres.cpFacturas.server.filter",
"com.fasterxml.jackson.jaxrs");
property(
"jersey.config.beanValidation.enableOutputValidationErrorEntity.server",
true);
register(CustomObjectMapper.class);
register(JacksonFeature.class);
}
}
My resource partial code:
#Named
#Path(CUSTOMERS_PATH)
#Produces(MediaType.APPLICATION_JSON)
public class CustomerResourceImpl extends AbstractResource implements
ICustomerResource
...
#Override
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response add(final CustomerDto pDto) {
try {
getService().add(pDto);
} catch (final ValidationException ve) {
badRequest(ve.getErrors());
}
return Response.ok().build();
}
}
My unit test:
#Test
public void addTest() {
final Response response = target(NameTokens.CUSTOMERS_PATH).request()
.header("Content-Type", MediaType.APPLICATION_JSON)
.post(Entity.entity(new CustomerDto(), MediaType.APPLICATION_JSON));
Assert.assertNotNull(response, "Response cannot be null");
Assert.assertEquals(response.getStatus(), Status.OK.getStatusCode(),
"Response status code isn't ok");
}
The problem are the CORS filters, I review the filters code to solve the problem with POST and PUT requests. I have two CORS filter (one to request and other for response) with the following code.
#Provider
public class CorsRequestFilter implements ContainerRequestFilter {
#Override
public void filter(final ContainerRequestContext requestContext)
throws IOException {
if (requestContext.getRequest().getMethod().equals(HttpMethod.OPTIONS)) {
requestContext.abortWith(Response.status(Response.Status.OK).build());
}
}
}
#Provider
public class CorsResponseFilter implements ContainerResponseFilter {
#Override
public void filter(final ContainerRequestContext requestContext,
final ContainerResponseContext responseContext) throws IOException {
final MultivaluedMap<String, Object> headers = responseContext.getHeaders();
headers.add("Access-Control-Allow-Origin", "*");
headers.add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS");
headers.add("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, X-Auth-Token");
}
}