/** Receives does nothing - send will put the response in the same buffer */ public int receive(Msg msg, MsgContext ep) throws IOException { Msg sentResponse = (Msg) ep.getNote(receivedNote); ep.setNote(receivedNote, null); if (sentResponse == null) { if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) LoggerUtils.getLogger().log(Level.FINEST, "No send() prior to receive(), no data buffer"); // No sent() was done prior to receive. msg.reset(); msg.end(); sentResponse = msg; } sentResponse.processHeader(); if (LoggerUtils.getLogger().isLoggable(Level.FINE)) sentResponse.dump("received response "); if (msg != sentResponse) { LoggerUtils.getLogger() .severe( "Error, in JNI mode the msg used for receive() must be identical with the one used for send()"); } return 0; }
/** Send the packet. XXX This will modify msg !!! We could use 2 packets, or sendAndReceive(). */ public int send(Msg msg, MsgContext ep) throws IOException { ep.setNote(receivedNote, null); if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) LoggerUtils.getLogger().log(Level.FINEST, "ChannelJni.send: " + msg); int rc = super.nativeDispatch(msg, ep, JK_HANDLE_JNI_DISPATCH, 0); // nativeDispatch will put the response in the same buffer. // Next receive() will just get it from there. Very tricky to do // things in one thread instead of 2. ep.setNote(receivedNote, msg); return rc; }
public void init() throws IOException { super.initNative("channel.jni:jni"); if (apr == null) return; // We'll be called from C. This deals with that. apr.addJkHandler("channelJni", this); LoggerUtils.getLogger().info("JK: listening on channel.jni:jni"); if (next == null) { if (nextName != null) setNext(wEnv.getHandler(nextName)); if (next == null) next = wEnv.getHandler("dispatch"); if (next == null) next = wEnv.getHandler("request"); if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) LoggerUtils.getLogger() .log(Level.FINEST, "Setting default next " + next.getClass().getName()); } }
/** * Receive a packet from the C side. This is called from the C code using invocation, but only for * the first packet - to avoid recursivity and thread problems. * * <p>This may look strange, but seems the best solution for the problem ( the problem is that we * don't have 'continuation' ). * * <p>sendPacket will move the thread execution on the C side, and return when another packet is * available. For packets that are one way it'll return after it is processed too ( having 2 * threads is far more expensive ). * * <p>Again, the goal is to be efficient and behave like all other Channels ( so the rest of the * code can be shared ). Playing with java objects on C is extremely difficult to optimize and do * right ( IMHO ), so we'll try to keep it simple - byte[] passing, the conversion done in java ( * after we know the encoding and if anyone asks for it - same lazy behavior as in 3.3 ). */ public int invoke(Msg msg, MsgContext ep) throws IOException { if (apr == null) return -1; long xEnv = ep.getJniEnv(); long cEndpointP = ep.getJniContext(); int type = ep.getType(); if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) LoggerUtils.getLogger().log(Level.FINEST, "ChannelJni.invoke: " + ep + " " + type); switch (type) { case JkHandler.HANDLE_RECEIVE_PACKET: return receive(msg, ep); case JkHandler.HANDLE_SEND_PACKET: return send(msg, ep); case JkHandler.HANDLE_FLUSH: return flush(msg, ep); } // Reset receivedNote. It'll be visible only after a SEND and before a receive. ep.setNote(receivedNote, null); // Default is FORWARD - called from C try { // first, we need to get an endpoint. It should be // per/thread - and probably stored by the C side. if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) LoggerUtils.getLogger().log(Level.FINEST, "Received request " + xEnv); // The endpoint will store the message pt. msg.processHeader(); if (LoggerUtils.getLogger().isLoggable(Level.FINE)) msg.dump("Incoming msg "); int status = next.invoke(msg, ep); if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) LoggerUtils.getLogger().log(Level.FINEST, "after processCallbacks " + status); return status; } catch (Exception ex) { ex.printStackTrace(); } return 0; }
/** * Simple {@link Adapter} that map the {@link Request} URI to a local file. The file is send * synchronously using the NIO send file mechanism (@link File#transfertTo}. * * <p>This class doesn't not decode the {@link Request} uri and just do basic security check. If you * need more protection, use the {@link GrizzlyAdapter} class instead or extend the {@link * StaticResourcesAdapter#service} and use {@link HttpRequestURIDecoder} to protect against security * attack. * * @author Jeanfrancois Arcand */ public class StaticResourcesAdapter implements Adapter { private static final String USE_SEND_FILE = "com.sun.grizzly.useSendFile"; private final Queue<String> rootFolders = DataStructures.getCLQinstance(String.class); protected String resourcesContextPath = ""; protected final Queue<File> fileFolders = DataStructures.getCLQinstance(File.class); protected final ConcurrentHashMap<String, File> cache = new ConcurrentHashMap<String, File>(); protected Logger logger = LoggerUtils.getLogger(); private boolean useSendFile = true; /** Commit the 404 response automatically. */ protected boolean commitErrorResponse = true; private final ReentrantLock initializedLock = new ReentrantLock(); private String defaultContentType = "text/html"; public StaticResourcesAdapter() { this("."); } public StaticResourcesAdapter(String rootFolder) { addRootFolder(rootFolder); // Ugly workaround // See Issue 327 if ((System.getProperty("os.name").equalsIgnoreCase("linux") && !linuxSendFileSupported()) || System.getProperty("os.name").equalsIgnoreCase("HP-UX")) { useSendFile = false; } if (System.getProperty(USE_SEND_FILE) != null) { useSendFile = Boolean.valueOf(System.getProperty(USE_SEND_FILE)); logger.info("Send-file enabled:" + useSendFile); } } /** * Based on the {@link Request} URI, try to map the file from the rootFolder, and send it * synchronously using send file. * * @param req the {@link Request} * @param res the {@link Response} * @throws Exception */ public void service(Request req, final Response res) throws Exception { String uri = req.requestURI().toString(); if (uri.contains("..") || !uri.startsWith(resourcesContextPath)) { res.setStatus(404); if (commitErrorResponse) { customizedErrorPage(req, res); } return; } // We map only file that take the form of name.extension if (uri.contains(".")) { uri = uri.substring(resourcesContextPath.length()); } service(uri, req, res); } /** * Lookup a resource based on the request URI, and send it using send file. * * @param uri The request URI * @param req the {@link Request} * @param res the {@link Response} * @throws Exception */ protected void service(String uri, Request req, final Response res) throws Exception { FileInputStream fis = null; try { initWebDir(); boolean found = false; File resource = null; for (File webDir : fileFolders) { // local file resource = cache.get(uri); if (resource == null) { resource = new File(webDir, uri); if (resource.exists() && resource.isDirectory()) { final File f = new File(resource, "/index.html"); if (f.exists()) { resource = f; found = true; break; } } } if (resource.isDirectory() || !resource.exists()) { found = false; } else { found = true; break; } } cache.put(uri, resource); if (!found) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "File not found " + resource); } res.setStatus(404); if (commitErrorResponse) { customizedErrorPage(req, res); } return; } res.setStatus(200); String substr; int dot = uri.lastIndexOf("."); if (dot < 0) { substr = resource.toString(); dot = substr.lastIndexOf("."); } else { substr = uri; } if (dot > 0) { String ext = substr.substring(dot + 1); String ct = MimeType.get(ext, defaultContentType); if (ct != null) { res.setContentType(ct); } } else { res.setContentType(defaultContentType); } long length = resource.length(); res.setContentLengthLong(length); // Send the header, and flush the bytes as we will now move to use // send file. res.sendHeaders(); if (req.method().toString().equalsIgnoreCase("HEAD")) { return; } fis = new FileInputStream(resource); OutputBuffer outputBuffer = res.getOutputBuffer(); if (useSendFile && (outputBuffer instanceof FileOutputBuffer) && ((FileOutputBuffer) outputBuffer).isSupportFileSend()) { res.flush(); long nWrite = 0; while (nWrite < length) { nWrite += ((FileOutputBuffer) outputBuffer).sendFile(fis.getChannel(), nWrite, length - nWrite); } } else { byte b[] = new byte[8192]; ByteChunk chunk = new ByteChunk(); int rd; while ((rd = fis.read(b)) > 0) { chunk.setBytes(b, 0, rd); res.doWrite(chunk); } } } finally { if (fis != null) { try { fis.close(); } catch (IOException ignored) { } } } } /** * Customize the error pahe * * @param req The {@link Request} object * @param res The {@link Response} object * @throws Exception */ protected void customizedErrorPage(Request req, Response res) throws Exception { /** With Grizzly, we just return a 404 with a simple error message. */ res.setMessage("Not Found"); res.setStatus(404); ByteBuffer bb = HtmlHelper.getErrorPage("Not Found", "HTTP/1.1 404 Not Found\r\n", "Grizzly"); res.setContentLength(bb.limit()); res.setContentType("text/html"); res.flushHeaders(); if (res.getChannel() != null) { res.getChannel().write(bb); req.setNote(14, "SkipAfterService"); } else { byte b[] = new byte[bb.limit()]; bb.get(b); ByteChunk chunk = new ByteChunk(); chunk.setBytes(b, 0, b.length); res.doWrite(chunk); } } /** * Finish the {@link Response} and recycle the {@link Request} and the {@link Response}. If the * {@link StaticResourcesAdapter#commitErrorResponse} is set to false, this method does nothing. * * @param req {@link Request} * @param res {@link Response} * @throws Exception */ public void afterService(Request req, Response res) throws Exception { if (req.getNote(14) != null) { req.setNote(14, null); return; } if (res.getStatus() == 404 && !commitErrorResponse) { return; } try { req.action(ActionCode.ACTION_POST_REQUEST, null); } catch (Throwable t) { logger.log(Level.WARNING, "afterService unexpected exception: ", t); } res.finish(); } /** * Return the directory from where files will be serviced. * * @return the directory from where file will be serviced. * @deprecated - use {@link #getRootFolders} */ public String getRootFolder() { return rootFolders.peek(); } /** * Set the directory from where files will be serviced. * * <p>NOTE: For backward compatibility, invoking that method will clear all previous values added * using {@link #addRootFolder}. * * @param rootFolder the directory from where files will be serviced. * @deprecated - use {@link #addRootFolder} */ public void setRootFolder(String rootFolder) { rootFolders.clear(); addRootFolder(rootFolder); } /** * Return the list of folders the adapter can serve file from. * * @return a {@link Queue} of the folders this Adapter can serve file from. */ public Queue<String> getRootFolders() { return rootFolders; } /** * Add a folder to the list of folders this Adapter can serve file from. * * @param rootFolder * @return */ public boolean addRootFolder(String rootFolder) { return rootFolders.offer(rootFolder); } /** Initialize. */ protected void initWebDir() throws IOException { try { initializedLock.lock(); if (fileFolders.isEmpty()) { for (String s : rootFolders) { File webDir = new File(s); fileFolders.offer(webDir); } rootFolders.clear(); for (File f : fileFolders) { rootFolders.add(f.getCanonicalPath()); } } } finally { initializedLock.unlock(); } } @SuppressWarnings("UnusedDeclaration") public void setLogger(Logger logger) { this.logger = logger; } /** * @return <code>true</code> if {@link java.nio.channels.FileChannel#transferTo(long, long, * java.nio.channels.WritableByteChannel)} to send a static resources. */ @SuppressWarnings("UnusedDeclaration") public boolean isUseSendFile() { return useSendFile; } /** * <code>true</code> if {@link java.nio.channels.FileChannel#transferTo(long, long, * java.nio.channels.WritableByteChannel)} to send a static resources, false if the File needs to * be loaded in memory and flushed using {@link ByteBuffer}. * * @param useSendFile True if {@link java.nio.channels.FileChannel#transferTo(long, long, * java.nio.channels.WritableByteChannel)} to send a static resources, false if the File needs * to be loaded in memory and flushed using {@link ByteBuffer} */ public void setUseSendFile(boolean useSendFile) { this.useSendFile = useSendFile; } /** * Return the context path used for servicing resources. By default, "" is used so request taking * the form of http://host:port/index.html are serviced directly. If set, the resource will be * available under http://host:port/context-path/index.html * * @return the context path. */ @SuppressWarnings("UnusedDeclaration") public String getResourcesContextPath() { return resourcesContextPath; } /** * Set the context path used for servicing resource. By default, "" is used so request taking the * form of http://host:port/index.html are serviced directly. If set, the resource will be * available under http://host:port/context-path/index.html * * @param resourcesContextPath the context path */ public void setResourcesContextPath(String resourcesContextPath) { this.resourcesContextPath = resourcesContextPath; } /** * If the content-type of the request cannot be determined, used the default value. Current * default is text/html * * @return the defaultContentType */ @SuppressWarnings("UnusedDeclaration") public String getDefaultContentType() { return defaultContentType; } /** * Set the default content-type if we can't determine it. Default was text/html * * @param defaultContentType the defaultContentType to set */ public void setDefaultContentType(String defaultContentType) { this.defaultContentType = defaultContentType; } private static boolean linuxSendFileSupported(final String jdkVersion) { if (jdkVersion.startsWith("1.6")) { int idx = jdkVersion.indexOf('_'); if (idx == -1) { return false; } StringBuilder sb = new StringBuilder(3); final String substr = jdkVersion.substring(idx + 1); int len = Math.min(substr.length(), 3); for (int i = 0; i < len; i++) { final char c = substr.charAt(i); if (Character.isDigit(c)) { sb.append(c); continue; } break; } if (sb.length() == 0) { return false; } final int patchRev = Integer.parseInt(sb.toString()); return (patchRev >= 18); } else { return jdkVersion.startsWith("1.7") || jdkVersion.startsWith("1.8"); } } private static boolean linuxSendFileSupported() { return linuxSendFileSupported(System.getProperty("java.version")); } }