| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| package chat; |
| |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.apache.catalina.comet.CometEvent; |
| import org.apache.catalina.comet.CometProcessor; |
| |
| |
| /** |
| * Helper class to implement Comet functionality. |
| */ |
| public class ChatServlet |
| extends HttpServlet implements CometProcessor { |
| |
| private static final long serialVersionUID = 1L; |
| |
| private static final String CHARSET = "UTF-8"; |
| |
| protected final ArrayList<HttpServletResponse> connections = |
| new ArrayList<>(); |
| protected transient MessageSender messageSender = null; |
| |
| @Override |
| public void init() throws ServletException { |
| messageSender = new MessageSender(); |
| Thread messageSenderThread = |
| new Thread(messageSender, "MessageSender[" + getServletContext().getContextPath() + "]"); |
| messageSenderThread.setDaemon(true); |
| messageSenderThread.start(); |
| } |
| |
| @Override |
| public void destroy() { |
| connections.clear(); |
| messageSender.stop(); |
| messageSender = null; |
| } |
| |
| /** |
| * Process the given Comet event. |
| * |
| * @param event The Comet event that will be processed |
| * @throws IOException |
| * @throws ServletException |
| */ |
| @Override |
| public void event(CometEvent event) |
| throws IOException, ServletException { |
| |
| // Note: There should really be two servlets in this example, to avoid |
| // mixing Comet stuff with regular connection processing |
| HttpServletRequest request = event.getHttpServletRequest(); |
| HttpServletResponse response = event.getHttpServletResponse(); |
| |
| if (event.getEventType() == CometEvent.EventType.BEGIN) { |
| String action = request.getParameter("action"); |
| if (action != null) { |
| if ("login".equals(action)) { |
| String nickname = request.getParameter("nickname"); |
| request.getSession(true).setAttribute("nickname", nickname); |
| response.sendRedirect("index.jsp"); |
| event.close(); |
| return; |
| } |
| String nickname = (String) request.getSession(true).getAttribute("nickname"); |
| String message = request.getParameter("message"); |
| messageSender.send(nickname, message); |
| response.sendRedirect("post.jsp"); |
| event.close(); |
| return; |
| } |
| if (request.getSession(true).getAttribute("nickname") == null) { |
| // Redirect to "login" |
| log("Redirect to login for session: " + request.getSession(true).getId()); |
| response.sendRedirect("login.jsp"); |
| event.close(); |
| return; |
| } |
| begin(event, request, response); |
| } else if (event.getEventType() == CometEvent.EventType.ERROR) { |
| error(event, request, response); |
| } else if (event.getEventType() == CometEvent.EventType.END) { |
| end(event, request, response); |
| } else if (event.getEventType() == CometEvent.EventType.READ) { |
| read(event, request, response); |
| } |
| } |
| |
| protected void begin(@SuppressWarnings("unused") CometEvent event, |
| HttpServletRequest request, HttpServletResponse response) |
| throws IOException { |
| log("Begin for session: " + request.getSession(true).getId()); |
| |
| response.setContentType("text/html; charset=" + CHARSET); |
| |
| PrintWriter writer = response.getWriter(); |
| writer.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"); |
| writer.println("<html><head><title>JSP Chat</title></head><body bgcolor=\"#FFFFFF\">"); |
| writer.println("<div>Welcome to the chat. <a href='chat'>Click here to reload this window</a></div>"); |
| writer.flush(); |
| |
| synchronized(connections) { |
| connections.add(response); |
| } |
| |
| messageSender.send("Tomcat", request.getSession(true).getAttribute("nickname") + " joined the chat."); |
| } |
| |
| protected void end(CometEvent event, HttpServletRequest request, HttpServletResponse response) |
| throws IOException { |
| log("End for session: " + request.getSession(true).getId()); |
| synchronized(connections) { |
| connections.remove(response); |
| } |
| |
| PrintWriter writer = response.getWriter(); |
| writer.println("</body></html>"); |
| |
| event.close(); |
| } |
| |
| protected void error(CometEvent event, HttpServletRequest request, HttpServletResponse response) |
| throws IOException { |
| log("Error for session: " + request.getSession(true).getId()); |
| synchronized(connections) { |
| connections.remove(response); |
| } |
| event.close(); |
| } |
| |
| protected void read(CometEvent event, HttpServletRequest request, HttpServletResponse response) |
| throws IOException { |
| InputStream is = request.getInputStream(); |
| byte[] buf = new byte[512]; |
| while (is.available() > 0) { |
| log("Available: " + is.available()); |
| int n = is.read(buf); |
| if (n > 0) { |
| log("Read " + n + " bytes: " + new String(buf, 0, n) |
| + " for session: " + request.getSession(true).getId()); |
| } else if (n < 0) { |
| log("End of file: " + n); |
| end(event, request, response); |
| return; |
| } |
| } |
| } |
| |
| @Override |
| protected void service(HttpServletRequest request, HttpServletResponse response) |
| throws IOException, ServletException { |
| // Compatibility method: equivalent method using the regular connection model |
| response.setContentType("text/html; charset=" + CHARSET); |
| PrintWriter writer = response.getWriter(); |
| writer.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"); |
| writer.println("<html><head><title>JSP Chat</title></head><body bgcolor=\"#FFFFFF\">"); |
| writer.println("Chat example only supports Comet processing. "); |
| writer.println("Configure a connector that supports Comet and try again."); |
| writer.println("</body></html>"); |
| } |
| |
| |
| /** |
| * Poller class. |
| */ |
| public class MessageSender implements Runnable { |
| |
| protected boolean running = true; |
| protected final ArrayList<String> messages = new ArrayList<>(); |
| |
| public MessageSender() { |
| // Default contructor |
| } |
| |
| public void stop() { |
| running = false; |
| synchronized (messages) { |
| messages.notify(); |
| } |
| } |
| |
| public void send(String user, String message) { |
| synchronized (messages) { |
| messages.add("[" + user + "]: " + message); |
| messages.notify(); |
| } |
| } |
| |
| /** |
| * The background thread that listens for incoming TCP/IP connections and |
| * hands them off to an appropriate processor. |
| */ |
| @Override |
| public void run() { |
| |
| // Loop until we receive a shutdown command |
| while (running) { |
| String[] pendingMessages; |
| synchronized (messages) { |
| try { |
| if (running && messages.size() == 0) { |
| messages.wait(); |
| } |
| } catch (InterruptedException e) { |
| // Ignore |
| } |
| pendingMessages = messages.toArray(new String[0]); |
| messages.clear(); |
| } |
| |
| synchronized (connections) { |
| for (int i = 0; i < connections.size(); i++) { |
| try { |
| PrintWriter writer = connections.get(i).getWriter(); |
| for (int j = 0; j < pendingMessages.length; j++) { |
| writer.println("<div>"+filter(pendingMessages[j]) + "</div>"); |
| } |
| writer.flush(); |
| } catch (IOException e) { |
| log("IOException sending message", e); |
| } |
| } |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| /** |
| * Filter the specified message string for characters that are sensitive |
| * in HTML. |
| * |
| * @param message The message string to be filtered |
| * @author Copied from org.apache.catalina.util.RequestUtil#filter(String) |
| */ |
| protected static String filter(String message) { |
| if (message == null) |
| return (null); |
| |
| char content[] = new char[message.length()]; |
| message.getChars(0, message.length(), content, 0); |
| StringBuilder result = new StringBuilder(content.length + 50); |
| for (int i = 0; i < content.length; i++) { |
| switch (content[i]) { |
| case '<': |
| result.append("<"); |
| break; |
| case '>': |
| result.append(">"); |
| break; |
| case '&': |
| result.append("&"); |
| break; |
| case '"': |
| result.append("""); |
| break; |
| default: |
| result.append(content[i]); |
| } |
| } |
| return (result.toString()); |
| } |
| } |