I would like to handle a variety of HTTP methods on the same URL. i.e
GET http://localhost:8081/test
POST http://localhost:8081/test
I was originally thinking I could create a separate HTTPHandler for each method, but what I'm not clear on is how to route the request to the correct handler. Is there some filter higher up that I'm missing? I'm currently adding multiple HTTPHandlers to the server configuration, but only the first handler is being called.
Or do I have the wrong idea? Should I be using a single HTTPHandler for all requests to the same path and just check the HTTP method and respond accordingly?
import java.net.URI;
import javax.ws.rs.client.ClientBuilder;
import org.glassfish.grizzly.http.Method;
import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.grizzly.http.server.Response;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
public class HttpHandlerTest
{
private static final URI BASE_URI = URI.create("http://localhost:8081");
private static final String MAPPING = "/test";
public static void main(String[] args)
{
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, (GrizzlyHttpContainer) null, false, null, true);
server.getServerConfiguration().addHttpHandler(new GetHandler(), MAPPING);
server.getServerConfiguration().addHttpHandler(new PostHandler(), MAPPING);
javax.ws.rs.core.Response getResponse = ClientBuilder.newClient().target(BASE_URI + MAPPING).request().get();
System.out.println(getResponse.readEntity(String.class));
javax.ws.rs.core.Response postResponse = ClientBuilder.newClient().target(BASE_URI + MAPPING).request().post(null);
System.out.println(postResponse.readEntity(String.class));
server.shutdown();
}
public static class GetHandler extends HttpHandler{
#Override
public void service(Request request, Response response) throws Exception{
if (request.getMethod() == Method.GET){
// handle GET only, move on if method is something else
response.getWriter().write("GET response");
}
else
{
// let another HTTPHandler handle this
response.getWriter().write("I wanted a different handler to handle this");
}
}
}
public static class PostHandler extends HttpHandler{
#Override
public void service(Request request, Response response) throws Exception{
if (request.getMethod() == Method.POST){
// handle GET only, move on if method is something else
response.getWriter().write("POST response");
}
}
}
}
Related
I have a controller in my Spring Boot application. In my controller, I have an endpoint where I need to timeout the call if too much time elapses. I do this by returning a Callable from this method and including the config spring.mvc.async.request-timeout in my application.yml. This seems to work well for our purposes.
I also have an Aspect class in this application that contains a method that is triggered whenever a method in my controller is called. The point of this method is to log details such as the amount of time taken for an endpoint, what the response code was, and etc. This works well when the response of the method is not a Callable (ie. a ResponseEntity) since I can get response information from the return type without issue. However, I cannot get this response information when the method returns a Callable without invoking ((Callable) ProceedingJoinPoint.proceed()).call() from the aspect class. This makes API calls longer, and I believe that's because it invokes call() twice. Is there any way that I can get the response information without having to use call() in the Aspect class?
Here is a simple example of what I have so far in my aspect class:
#Around("...")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
if (!(result instanceof Callable<?>)) {
// Do logging using result, which is a ResponseEntity...
} else {
Object callableResult = ((Callable<?>) result).call();
// Do logging using callableResult, which is a ResponseEntity...
}
return result;
}
Thank you.
I encountered the same situation at work a while ago and the following seems to solve it: Log response body after asynchronous Spring MVC controller method
Note: Try logging the async response in a class annotated with #ControllerAdvice and implements ResponseBodyAdvice instead. This should capture the real response instead of callable.
You could have a class annotated with #Aspects for logging request and #ControllerAdvice for logging response together.
e.g.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicLong;
#Aspect
#ControllerAdvice
public class LoggingAdvice implements ResponseBodyAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingAdvice.class);
private static final AtomicLong ID = new AtomicLong();
#Before("within(com.example.demo.controller..*)")
public void endpointBefore(JoinPoint p) {
LOGGER.info(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
Object[] signatureArgs = p.getArgs();
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
if (signatureArgs != null && signatureArgs.length > 0) {
LOGGER.info("\nRequest object: \n" + mapper.writeValueAsString(signatureArgs[0]));
} else {
LOGGER.info("request object is empty");
}
} catch (JsonProcessingException e) {
}
}
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
long id = ID.incrementAndGet();
ServletServerHttpResponse responseToUse = (ServletServerHttpResponse) response;
HttpMessageConverter httpMessageConverter;
LoggingHttpOutboundMessageWrapper httpOutputMessage = new LoggingHttpOutboundMessageWrapper();
try {
httpMessageConverter = (HttpMessageConverter) selectedConverterType.newInstance();
httpMessageConverter.write(body, selectedContentType, httpOutputMessage);
LOGGER.info("response {}, {}, {}, {}, {}", id, responseToUse.getServletResponse().getStatus(), responseToUse.getServletResponse().getContentType(),
responseToUse.getHeaders(), httpOutputMessage.getResponseBodyInString());
} catch (InstantiationException | IllegalAccessException | IOException e) {
e.printStackTrace();
}
return body;
}
private static final class LoggingHttpOutboundMessageWrapper implements HttpOutputMessage {
private HttpHeaders httpHeaders = new HttpHeaders();
private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
#Override
public OutputStream getBody() throws IOException {
return byteArrayOutputStream;
}
#Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
public String getResponseBodyInString() {
return new String(byteArrayOutputStream.toByteArray());
}
}
}
I have a problem with reading request body in interceptor. Both getReader() and getInputStream() causing problems.
My interceptor:
public class MyInterceptor extends HandlerInterceptorAdapter {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception)
throws Exception {
// TODO Auto-generated method stub
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
// TODO Auto-generated method stub
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestBody = httpRequest.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
//or
// String requestBody = new BufferedReader(new InputStreamReader(httpRequest.getInputStream()))
// .lines().collect(Collectors.joining("\n"));
//some logic...
return true;
}
Both approaches failing because probably spring uses such resource somewhere internally.
First causess java.lang.IllegalStateException: getReader() has already been called for this request and other Required request body is missing: org.springframework.http.ResponseEntity...
I have tried some workarounds with wrappers without an effect. I think its because I cannot pass wrapper down like in filters(i dont want to use filter cause I have common exception managager(#ControllerAdvice).
Is this a known issue? Is there any workaround for this?
Finally I have figured it out so I will leave here some simple but useful advice for others.
I have used a request wrapper but to make it work properly I have added a filter with highest order to wrap every request into wrapper at the beginning, before interceptor is executed. Now it works well ;)
Here's most important code - filter to wrap every request into multi read wrapper(interceptor looks almost the same as above and wrapper is not invented by me, found on stack and I found it as most clear and readable):
import lombok.SneakyThrows;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalWrapFilter implements Filter {
#Override
#SneakyThrows
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
MultiReadRequest wrapper = new MultiReadRequest((HttpServletRequest) request);
chain.doFilter(wrapper, response);
}
#Override
public void destroy() {
}
class MultiReadRequest extends HttpServletRequestWrapper {
private String requestBody;
#SneakyThrows
public MultiReadRequest(HttpServletRequest request) {
super(request);
requestBody = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody.getBytes());
return new ServletInputStream() {
#Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener readListener) {
}
#Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
#Override
#SneakyThrows
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8));
}
}
}
I'm trying to build a JAX-RS Rest API with Jersey. I'm following the most up-voted answer from this thread: Best practice for REST token-based authentication with JAX-RS and Jersey
I got to the Identifying the current user part. I'm trying to use CDI.
Here is my main app class:
public class Main {
// Base URI the Grizzly HTTP server will listen on
public static final String BASE_URI = "http://localhost:8080/myapp/";
/**
* Starts Grizzly HTTP server exposing JAX-RS resources defined in this application.
* #return Grizzly HTTP server.
*/
public static HttpServer startServer() {
// create a resource config that scans for JAX-RS resources and providers
// in appServer package
final ResourceConfig rc = new ResourceConfig().packages("appServer");
rc.register(new CORSFilter());
rc.register(new AuthenticationFilter());
// create and start a new instance of grizzly http server
// exposing the Jersey application at BASE_URI
return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc, false);
}
/**
* Main method.
* #param args
* #throws IOException
*/
public static void main(String[] args) throws IOException {
final Weld weld = new Weld();
weld.initialize();
final HttpServer server = startServer();
server.start();
new SessionUtil().buildSession(args);
System.out.println(String.format("Jersey app started with WADL available at "
+ "%sapplication.wadl\nHit enter to stop it...", BASE_URI));
System.in.read();
server.stop();
weld.shutdown();
}
}
And the relevant filter class:
import appServer.AuthenticatedUser;
import appServer.Secured;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import javax.annotation.Priority;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.event.Event;
import javax.enterprise.inject.Default;
import javax.inject.Inject;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
private static final String REALM = "myRealm";
private static final String AUTHENTICATION_SCHEME = "Bearer";
public AuthenticationFilter() {
super();
}
#Inject
#AuthenticatedUser
Event<String> userAuthenticatedEvent;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Get the Authorization header from the request
String authorizationHeader =
requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
// Validate the Authorization header
if (!isTokenBasedAuthentication(authorizationHeader)) {
abortWithUnauthorized(requestContext);
return;
}
// Extract the token from the Authorization header
String token = authorizationHeader
.substring(AUTHENTICATION_SCHEME.length()).trim();
try {
// Validate the token
validateToken(token);
// if successful, fire event with token
userAuthenticatedEvent.fire(token);
} catch (Exception e) {
abortWithUnauthorized(requestContext);
}
}
private boolean isTokenBasedAuthentication(String authorizationHeader) {
// Check if the Authorization header is valid
// It must not be null and must be prefixed with "Bearer" plus a whitespace
// The authentication scheme comparison must be case-insensitive
return authorizationHeader != null && authorizationHeader.toLowerCase()
.startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " ");
}
private void abortWithUnauthorized(ContainerRequestContext requestContext) {
// Abort the filter chain with a 401 status code response
// The WWW-Authenticate header is sent along with the response
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE,
AUTHENTICATION_SCHEME + " realm=\"" + REALM + "\"")
.build());
}
private void validateToken(String token) throws Exception {
// Check if the token was issued by the server and if it's not expired
// Throw an Exception if the token is invalid
}
When I run the application it crashes with this error:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no
object available for injection at
SystemInjecteeImpl(requiredType=Event,parent=AuthenticationFilter,qualifiers={#javax.enterprise.inject.Default(),#appServer.AuthenticatedUser()},position=-1,optional=false,self=false,unqualified=null,997918120)
I have came across this question: How to use CDI Events in Java Jersey? but there was no relevant answer.
I have tried other solutions posted for similar problems but none of them worked.
So, it is obviously some sort of injection issue here:
#AuthenticatedUser
#Inject
Event<String> userAuthenticatedEvent;
Or maybe I'm not registering the Filter properly.
Any suggestions ?
Is it possible remove header Referer when you execute redirect on java servlet?
kind of
response.setHeader("Referer", null);
response.sendRedirect(url)
Also I tried filter. It even doesnt call setHeader or addHeader methods on response. It looks like i cannot change existing filters. Found such article http://sandeepmore.com/blog/2010/06/12/modifying-http-headers-using-java/
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
#Component
public class HeaderFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
filterChain.doFilter(new HeaderHttpRequestWrapper(request), new HeaderHttpResponsetWrapper(response));
}
private static class HeaderHttpRequestWrapper extends HttpServletRequestWrapper {
public HeaderHttpRequestWrapper(HttpServletRequest request) {
super(request);
}
#Override
public String getHeader(String name) {
if ("Referer".equalsIgnoreCase(name))
return "";
return super.getHeader(name);
}
}
private static class HeaderHttpResponsetWrapper extends HttpServletResponseWrapper {
public HeaderHttpResponsetWrapper(HttpServletResponse response) {
super(response);
}
#Override
public void sendRedirect(String location) throws IOException {
// TODO Auto-generated method stub
super.sendRedirect(location);
}
#Override
public void addHeader(String name, String value) {
if ("Referer".equalsIgnoreCase(name))
return;
super.addHeader(name, value);
}
#Override
public void setHeader(String name, String value) {
if ("Referer".equalsIgnoreCase(name))
return;
super.setHeader(name, value);
}
}
}
The referer header is not set on the response at all. It's set on the request. With a redirect you're basically instructing the client to create a brand new request all on its own. That request is created on the client side, not on the server side.
The real technical problem is that you can't change the request headers from the server side at all. Response headers, however, are surely modifiable in server side as it's actually the server itself who creates them.
Your closest bet is to redirect to a proxy, which happens to be your own or the one you have full control over, and let the proxy in turn strip the request header. Or, just let the servlet itself act as proxy.
You can't delete response headers by the standard Servlet API. Your can prevent the header from being set by creating a Filter which replaces the ServletResponse with a custom HttpServletResponseWrapper implementation which skips the setHeader()'s job whenever the header name is Content-Disposition.
here is the similar question:
How do delete a HTTP response header?
Seems it's not possible to remove header
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<script>window.location.href='" + url + "';</script>");
out.close();
Since Referer is added by the browser, you can use js redirect.
I want to include a filter so i can check basic auth headers for each request
import java.io.IOException;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.container.PreMatching;
#Provider
#PreMatching
public class CheckRequestFilter implements ClientRequestFilter {
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
ServiceProvider sp = new ServiceProvider();
/*if (sp.authenticateSP(requestContext.getHeaderString("authorization")) == false){ */
requestContext.abortWith(
Response.status(Response.Status.BAD_REQUEST)
.entity("User cannot be authenticated")
.build());
}}
//}
So far that's what i've come up with following the documentation here https://jersey.java.net/documentation/latest/user-guide.html#filters-and-interceptors
My problems is that i don't know how to include a filter so i can test it (i.e copy pasting the first example is not working) and if my approach is correct.
I want to limit it for certain requests in the future but for now i just want to to work.
You should use ContainerRequestFilter
Here is the API: https://jax-rs-spec.java.net/nonav/2.0/apidocs/javax/ws/rs/container/ContainerRequestFilter.html
An here is an example:
public class AuthorizationRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
final SecurityContext securityContext =
requestContext.getSecurityContext();
if (securityContext == null ||
!securityContext.isUserInRole("privileged")) {
requestContext.abortWith(Response
.status(Response.Status.UNAUTHORIZED)
.entity("User cannot access the resource.")
.build());
}
}
}