Adobe AEM 6.5 Cloud: wildcard in SlingServletPaths?

295 Views Asked by At

We want to build a single proxy servlet which will serve a number of different paths, such as

/api/register

/api/player/1234/

/api/player/12312/transactions

Basically, any and every path below /api/

Currently we are using this pattern, which only allows a single or list of fixed paths:

@SlingServletPaths(value="/bin/somepath")
private class MyServlet extends SlingAllMethodsServlet

Any suggestions on how to handle variable paths?

I found a potential solution here using a custom ResourceResolver, but it wont compile unfortunately (the annotations and imports etc are no longer available in AEM 6.5):

https://stackoverflow.com/a/41990177/1072187

1

There are 1 best solutions below

3
Alexander Berndt On

You cannot do this with Sling-Servlets. BUT you could have an OSGi Whiteboard-Pattern Servlet next to the Sling MainServlet. The simplest for your case is to use the same context-name as Sling (because Sling registered already the entire URL-space)

For details refer to: https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.http.whiteboard.html

I also like to use a proxy servlets for some external services (e.g. Solr, Commerce-System, ...). For development it is much simpler, than setting up extra infrastructure on every developer machine.

BUT:

  1. Use this approach only for local development (or maybe bug-analysis on PROD)
  2. Use a type of Semaphore to limit the number of requests. If your proxy is slow, or doesn't work - it can easily eat up all of the 100 parallel requests, until Sling blocks incoming requests. This would completely kill your AEM instance. For me a limit to 4/5 parallel proxy-requests and a wait time of 200-500ms was good enough.

PS: scope=PROTOTYPE is important for the HttpWhiteboard service.

@Component(service = Servlet.class, scope = ServiceScope.PROTOTYPE)
@HttpWhiteboardServletPattern("/api/*")
@HttpWhiteboardContextSelect("(" + HTTP_WHITEBOARD_CONTEXT_NAME + "=org.apache.sling)")
public class OsgiProxyServlet extends HttpServlet {

    public static final Logger LOGGER = LoggerFactory.getLogger(OsgiProxyServlet.class);

    private final Semaphore semaphore;

    @Activate
    public OsgiProxyServlet() {
        this.semaphore = new Semaphore(/* max. parallel requests */ 5, true);
        LOGGER.info("Proxy Servlet instantiated");
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {

        LOGGER.info("Got API Request: {}", request.getRequestURL());

        boolean permitAcquired = false;
        try {
            permitAcquired = semaphore.tryAcquire(/* timeout */ 500, TimeUnit.MILLISECONDS);
            if (permitAcquired) {
                doProxyGet(request, response);
            } else {
                respond(response, SC_REQUEST_TIMEOUT, "Failed to acquire a proxy connection");
            }
        } catch (InterruptedException e) {
            // in the Servlet.doGet()-method we cannot propagate the InterruptedException,
            // so we have to set the flag back to true
            Thread.currentThread().interrupt();
        } finally {
            if (permitAcquired) {
                semaphore.release();
            }
        }
    }


    private void doProxyGet(HttpServletRequest request, HttpServletResponse response) {
        // put your proxy code here
        LOGGER.info("Proxy request {}", request.getRequestURI());
        respond(response, SC_OK, "Proxy request for path " + request.getPathInfo());
    }

    private static void respond(HttpServletResponse response, int status, String message) {
        response.setContentType("text/plain");
        response.setCharacterEncoding("utf-8");
        response.setStatus(status);
        try {
            response.getWriter().print(message);
        } catch (IOException e) {
            LOGGER.error("Failed to write output", e);
        }
    }
}

Here is a working ServletFilter, what you asked for (for more information refer to https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.http.whiteboard.html):

@Component(scope = ServiceScope.PROTOTYPE)
@HttpWhiteboardFilterPattern("/api/*")
@HttpWhiteboardContextSelect("(" + HTTP_WHITEBOARD_CONTEXT_NAME + "=org.apache.sling)")
public class OsgiFilter implements Filter {

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response,
                         final FilterChain filterChain) throws IOException, ServletException {

        filterChain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) {
        // empty
    }

    @Override
    public void destroy() {
        // empty
    }
}