Logging request and response in one place with JAX-RS - java

I have a RESTEasy web server with lot of methods. I want implement logback to track all requests and responses, but I don't want add log.info() to every methods.
Maybe there's way to catch requests and responses in one place and log it. Maybe something like a filter on HTTP request process chain on RESTEasy.
#Path("/rest")
#Produces("application/json")
public class CounterRestService {
//Don't want use log in controler every method to track requests and responces
static final Logger log = LoggerFactory.getLogger(CounterRestService.class);
#POST
#Path("/create")
public CounterResponce create(#QueryParam("name") String name) {
log.info("create "+name)
try {
CounterService.getInstance().put(name);
log.info("responce data"); // <- :((
return new CounterResponce();
} catch (Exception e){
log.info("responce error data"); // <- :((
return new CounterResponce("error", e.getMessage());
}
}
#POST
#Path("/insert")
public CounterResponce create(Counter counter) {
try {
CounterService.getInstance().put(counter);
return new CounterResponce();
} catch (Exception e){
return new CounterResponce("error", e.getMessage());
}
}
...
}

You can create filters and easily bind them to the endpoints you need to log, keeping your endpoints lean and focused on the business logic.
Defining a name binding annotation
To bind filters to your REST endpoints, JAX-RS provides the meta-annotation #NameBinding and it can be used as following:
#NameBinding
#Retention(RUNTIME)
#Target({TYPE, METHOD})
public #interface Logged { }
Logging the HTTP request
The #Logged annotation will be used to decorate a filter class, which implements ContainerRequestFilter, allowing you to handle the request:
#Logged
#Provider
public class RequestLoggingFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Use the ContainerRequestContext to extract information from the HTTP request
// Information such as the URI, headers and HTTP entity are available
}
}
The #Provider annotation marks an implementation of an extension interface that should be discoverable by JAX-RS runtime during a provider scanning phase.
The ContainerRequestContext helps you to extract information from the HTTP request.
Here are methods from the ContainerRequestContext API to get information from the HTTP request that can be useful for your logs:
ContainerRequestContext#getMethod(): Get the HTTP method from the request.
ContainerRequestContext#getUriInfo(): Get URI information from the HTTP request.
ContainerRequestContext#getHeaders(): Get the headers from the HTTP request.
ContainerRequestContext#getMediaType(): Get the media type of the entity.
ContainerRequestContext#getEntityStream(): Get the entity input stream.
Logging the HTTP response
For logging the response, consider implementing a ContainerResponseFilter:
#Logged
#Provider
public class ResponseLoggingFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
// Use the ContainerRequestContext to extract information from the HTTP request
// Use the ContainerResponseContext to extract information from the HTTP response
}
}
The ContainerResponseContext helps you to extract information from the HTTP response.
Here are some methods from the ContainerResponseContext API to get information from the HTTP response that can be useful for your logs:
ContainerResponseContext#getStatus(): Get the status code from the HTTP response.
ContainerResponseContext#getHeaders(): Get the headers from the HTTP response.
ContainerResponseContext#getEntityStream(): Get the entity output stream.
Binding the filters to your endpoints
To bind the filter to your endpoints methods or classes, annotate them with the #Logged annotation defined above. For the methods and/or classes which are annotated, the filters will be executed:
#Path("/")
public class MyEndpoint {
#GET
#Path("{id}")
#Produces("application/json")
public Response myMethod(#PathParam("id") Long id) {
// This method is not annotated with #Logged
// The logging filters won't be executed when invoking this method
...
}
#DELETE
#Logged
#Path("{id}")
#Produces("application/json")
public Response myLoggedMethod(#PathParam("id") Long id) {
// This method is annotated with #Logged
// The request logging filter will be executed before invoking this method
// The response logging filter will be executed before invoking this method
...
}
}
In the example above, the logging filters will be executed only for myLoggedMethod(Long) because it's annotated with #Logged.
Additional information
Besides the methods available in ContainerRequestContext and ContainerResponseFilter interfaces, you can inject ResourceInfo in your filters using #Context:
#Context
ResourceInfo resourceInfo;
It can be used to get the Method and the Class which match with the requested URL:
Class<?> resourceClass = resourceInfo.getResourceClass();
Method resourceMethod = resourceInfo.getResourceMethod();
HttpServletRequest and HttpServletResponse are also available for injection:
#Context
HttpServletRequest httpServletRequest;
#Context
HttpServletResponse httpServletResponse;
Refer to this answer for the types that can be injected with #Context.

Try Interceptors(not just vanilla EJB interceptors you can use CDI with that).
They are there for implementing Cross Cutting Concerns(aspects).

For others using Jersey and trying to solve same problem, there is org.glassfish.jersey.logging.LoggingFeature that can be used both on client or server. It logs request and response to java.util.logging.Logger.
Output can be bridged to slf4j with org.slf4j.bridge.SLF4JBridgeHandler if needed.

Related

How to check HTTP request header for certain endpoints in Spring Framework

I have a simple Spring Boot REST service for the IFTTT platform. Each authorized request will contain a header IFTTT-Service-Key with my account's service key and I will use that to either process the request or return a 401 (Unauthorized). However, I only want to do this for select endpoints -- and specifically not for ANY of the Spring actuator endpoints.
I have looked into Spring Security, using filters, using HandlerInterceptors, but none seem to fit what I am trying to do exactly. Spring security seems to come with a lot of extra stuff (especially the default user login), filters don't really seem to match the use case, and the handler interceptor works fine but I would have to code logic in to watch specific URLs and ignore others.
What is the best way to achieve what I am trying to do?
For reference, this is the code I have now:
public class ServiceKeyValidator implements HandlerInterceptor {
private final String myIftttServiceKey;
public ServiceKeyValidator(#Value("${ifttt.service-key}") String myIftttServiceKey) {
this.myIftttServiceKey = myIftttServiceKey;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO will have to put logic in to skip this when actuator endpoints are added
String serviceKeyHeader = request.getHeader("IFTTT-Service-Key");
if (!myIftttServiceKey.equals(serviceKeyHeader)) {
var error = new Error("Incorrect value for IFTTT-Service-Key");
var errorResponse = new ErrorResponse(Collections.singletonList(error));
throw new UnauthorizedException(errorResponse);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
You need to add filtering for the required endpoints in the place where you register your HandlerInterceptor.
For example:
#EnableWebMvc
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new ServiceKeyValidator())
.addPathPatterns("/ifttt/**")
.excludePathPatterns("/actuator/**");
}
}
You can use different URLs path matchers to filter which URL endpoints must be handled by your interceptor and which are not. As the method addPathPatterns returns InterceptorRegistration object that configures this.

How to get user ip via rest service in java [duplicate]

Is it possible to access the Request object in a REST method under JAX-RS?
I just found out
#Context Request request;
On JAX-RS you must annotate a Request parameter with #Context:
#GET
public Response foo(#Context Request request) {
}
Optionally you can also inject:
UriInfo
HttpHeaders
SecurityContext
HttpServletRequest
To elaborate on #dfa's answer for alternatives, I find this to be simpler than specifying the variable on each resource method signature:
public class MyResource {
#Context
private HttpServletRequest httpRequest;
#GET
public Response foo() {
httpRequest.getContentType(); //or whatever else you want to do with it
}
}

Equivalent of Spring MVC #ResponseStatus(HttpStatus.CREATED) in Jersey?

What's the Jersey equivalent of this Spring MVC code? I need the response to return 201 along with the resource URL, following successful POST:
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
Widget create(#RequestBody #Valid Widget wid) {
return service.create(wid);
}
This is the shortest example I found in Jersey. Is it required to build the response manually for successful POST/201?
#POST #Path("widget")
Response create(#RequestBody #Valid Widget wid) {
return Response
.status(Response.Status.CREATED)
.entity("new widget created")
.header("Location","http://localhost:7001/widget"+wid)
.build();
}
Example of comment, per request of OP:
I don't think there is an equivalent, but personally, I like creating my own response. I have more control. Also there is a Response.created(...), this will automatically set the status. It accepts the URI or String as an argument, and sets the location header with that argument. Also You can use UriInfo to getAbsolutePathBuilder() then just append the created id. That's generally the way I go about it.
#Path("/widgets")
public class WidgetResource {
#Inject
WidgetService widgetService;
#POST
#Consumes(...)
public Response createWidget(#Context UriInfo uriInfo, Widget widget) {
Widget created = widgetService.createWidget(widget);
UriBuilder builder = uriInfo.getAbsolutePathBuilder();
URI uri = builder.path(created.getId()).build();
return Response.created(uri).build();
}
}
This is the general pattern I use for my create methods. The collection path will be the absolute path obtained from uriInfo.getAbsolutePath(Builder), then you just append the created id to the path. So if the collection path is http://blah.com/widgets, and the id is someId, then the location header will be Location: http://blah.com/widgets/someId (which is the location of the new resource), and the status will get set to 201 Created
Response.created(..) returns Response.ResponseBuilder, just like Response.status, so you can do the usual method chaining. There are a number of static method on Response that have default settings, like ok, noContent. Just do through the API. Their names pretty much match up with the status name.
I don't think there is an annotation like that in Jersey. You could create one using Name Binding.
Basically, you create an annotation and add the #NameBinding meta-annotation:
#NameBinding
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ResponseStatusCreated {}
Next you create an filter which will override the status.
#ResponseStatusCreated
#Provider
class StatusCreatedFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
responseContext.setStatusInfo(Response.Status.CREATED)
String location = "..."; // set based on responseContext.getEntity()
// or any other properties
responseContext.getHeaders().putSingle("Location", location);
}
}
Then use the same annotation on your resource methods.
#POST
#Path("widget")
#ResponseStatusCreated
Object create(#RequestBody #Valid Widget wid) {
return ... // return whatever you need to build the
// correct header fields in the filter
}
You could also make it more generic by creating an annotation that will accept the status as an argument, i.e. #ResponseStatus(Status.CREATED) and get the status in the filter using responseContext.getAnnotations().

is it possible to call one jax-rs method from another?

suppose i have some jax-rs resource class:
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class ResourceA {
#GET
public Something get(#Context UriInfo uriInfo) {
if (...) {
//how to get to ResourceB ?
}
}
}
and i want to conditionally redirect the call to some other jax-rs resource:
public class ResourceB {
#GET
#Path("{identifier}")
public Other get(#PathParam("identifier")String someArg) {
}
}
how do i do this?
note that i dont want this to be visible to the client (so no http redirects) and generally the resource methods i want to redirect to dont share the same signature (they may have path params etc as in the example i gave).
im running jersey 2.6 under apache tomcat (its a spring app, if thats any help)
EDIT - im looking for a jax-rs equivalent of servlet forward. i dont want to do an extra http hop or worry abour instantiating resource classes myself
You can get it using ResourceContext as follows:
#Context
ResourceContext resourceContext;
This will inject the ResourceContext into your Resource. You then get the resource you want using:
ResourceB b = resourceContext.getResource(ResourceB.class);
The Javadoc for ResourceContext is here. You can find a similar question here
I'm not aware of any possibility to do this from a resource method, but if it fits your use case, what you could do is implement your redirect logic in a pre matching request filter, for example like so:
#Provider
#PreMatching
public class RedirectFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) {
UriInfo uriInfo = requestContext.getUriInfo();
String prefix = "/redirect";
String path = uriInfo.getRequestUri().getPath();
if (path.startsWith(prefix)) {
String newPath = path.substring(prefix.length());
URI newRequestURI = uriInfo.getBaseUriBuilder().path(newPath).build();
requestContext.setRequestUri(newRequestURI);
}
}
}
This will redirect every request to /redirect/some/resource to /some/resource (or whatever you pass to requestContext.setRequestUri()) internally, before the resource method has been matched to the request and is executed and without http redirects or an additional internal http request.

JAX-RS 2.0 Filter parameters via #NameBinding annotation

I've created some JAX-RS 2.0 resources (using Jeresey 2.4 running in a Servlet container) and a filter that handles authentication and authorisation that can be selectively applied via a #NameBinding annotation. This all works great.
I would like to be able to define some parameters on this annotation (specifically, security permissions that are required to access each method/resource) that can be available to the filter at runtime to alter this behaviour.
I notice that interceptors can do this via javax.ws.rs.ext.InterceptorContext.getAnnotations() but there is no equivalent in javax.ws.rs.container.ContainerRequestContext for filters. Any ideas how this may be achieved? I would like to be able to do something like the following:
#Target({TYPE, METHOD})
#Retention(value = RetentionPolicy.RUNTIME)
#NameBinding
public #interface Secured {
String[] requiredPermissions() default {};
}
#Secured
#Priority(Priorities.AUTHENTICATION)
public class SecurityRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
// extract credentials, verify them and check that the user has required permissions, aborting if not
}
}
#Path("/userConfiguration/")
public class UserConfigurationResource {
#GET
#Produces(MediaType.APPLICATION_XML)
#Secured(requiredPermissions = {"configuration-permission"})
public Response getConfig(#Context HttpServletRequest httpServletRequest) {
// produce a response
}
}
For a non-vendor specific solution, since JAX-RS 2.0 you can use ResourceInfo:
#Secured
#Priority(Priorities.AUTHENTICATION)
public class SecurityRequestFilter implements ContainerRequestFilter {
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Method method = resourceInfo.getResourceMethod();
if (method != null) {
Secured secured = method.getAnnotation(Secured.class);
...
}
}
}
You can get this information from UriInfo, particularly it's (Jersey specific) ExtendedUriInfo subinterface. To obtain an instance either invoke ContainerRequestContext#getUriInfo() and cast it
final ExtendedUriInfo extendendUriInfo = (ExtendedUriInfo) containerRequestContext.getUriInfo();
or inject it into your filter:
#Inject
private ExtendedUriInfo extendendUriInfo;
then
extendedUriInfo
.getMatchedResourceMethod()
.getInvocable()
.getHandlingMethod().getAnnotation(Secured.class);
In the second approach you can implement DynamicFeature and assign your filter only to a particular resource methods (i.e. in case the configuration of the filter is more complex, filter applies only to a couple of methods and you want to reduce the overhead, ...). Take a look at the implementation of RolesAllowedDynamicFeature which adds support for security annotations over resource methods in Jersey.

Categories