4
\$\begingroup\$

I have a single page application where the resources sit on the classpath. Any request for a resource that doesn't exist should redirect to the index.html page. Resources should be mapped to memory on first request and then all subsequent requests should load from memory.

Have I made any mistakes with how I should be using direct ByteBuffers, it feels overly laborious, but that could just be the API?

class MemoryMappedClasspathHttpServlet extends HttpServlet {

    private final ConcurrentMap<String, ByteBuffer> mappedResources = new ConcurrentHashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String requestPath = req.getPathInfo();

        if (requestPath.equals("/")) {
            requestPath = "https://waybackassets.bk21.net/index.html";
        } else {
            requestPath = "/web" + requestPath;

            if (MemoryMappedClasspathHttpServlet.class.getResource(requestPath) == null) {
                requestPath = "https://waybackassets.bk21.net/index.html";
            }
        }

        ByteBuffer buffer = mappedResources.computeIfAbsent(requestPath, MemoryMappedClasspathHttpServlet::loadResourceFromClasspath);

        Channels.newChannel(resp.getOutputStream())
            .write(buffer.duplicate());
    }

    private static ByteBuffer loadResourceFromClasspath(String classpathItem) {
        try (ByteArrayOutputStream os = new ByteArrayOutputStream();
             InputStream is = MemoryMappedClasspathHttpServlet.class.getResourceAsStream(classpathItem)) {

            byte[] buffer = new byte[8092];

            for (int len; (len = is.read(buffer)) != -1; ) {
                os.write(buffer, 0, len);
            }

            os.flush();

            byte[] bytes = os.toByteArray();

            ByteBuffer directBuffer = ByteBuffer.allocateDirect(bytes.length);
            directBuffer.put(bytes);
            directBuffer.flip();

            return directBuffer;
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Possible improvement:

return directBuffer;

with:

return directBuffer.asReadOnlyBuffer();

Wanted to find a one-liner to help https://code.google.com/p/guava-libraries/wiki/IOExplained but ByteBuffer, streams nontrivial. Notice defining mutable ByteBuffer references in the method, placing each into an instance collection, one never flushed or refreshed...Servlet web container would need a restart in order to reflect changes to the files or other resources.

\$\endgroup\$
2
  • \$\begingroup\$ Since the resources are on the classpath (actually within a JAR), they aren't going to change. Another Servlet gets registered instead when developing locally. \$\endgroup\$ Commented Jan 12, 2016 at 8:34
  • \$\begingroup\$ Thank you for the additional information. Was concerned about an anti-pattern with unlimited, permanent cache approach as opposed to slower Servlet which reloads resource from JAR on occasion. Also curious if slightly more efficient to store arrays of bytes instead of ByteBuffer instances, instead creating, discarding the ByteBuffer upon retrieval from cache. \$\endgroup\$ Commented Jan 12, 2016 at 14:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.