/** * Copyright 2014 Comcast Cable Communications Management, LLC * * This file is part of CATS. * * CATS is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * CATS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CATS. If not, see <http://www.gnu.org/licenses/>. */ package com.comcast.cats.image.testutil; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.imageio.ImageIO; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.mortbay.jetty.Request; import org.mortbay.jetty.Server; import org.mortbay.jetty.handler.ContextHandlerCollection; import org.mortbay.jetty.servlet.Context; import org.mortbay.jetty.servlet.ServletHolder; /** * Wrapper class for starting and stopping Jetty Web Server. * This class is to be used for CoreImageCompareTest. */ public class JettyAxisCameraServer { private Server server; private int port; Hashtable<Integer, List<ImageStreamData>> cameras; private final Logger log = LoggerFactory.getLogger(JettyAxisCameraServer.class); private static final String MPEG = "/mjpg"; /** * Constructor. Sets the port and camera list. * @param port The port to set. * @param cameras the camera list. */ public JettyAxisCameraServer(int port, Hashtable<Integer, List<ImageStreamData>> cameras) { if (null == cameras || cameras.isEmpty()) { throw new IllegalArgumentException("cameras cannot be null or empty."); } if (port < 0 || port > 65535) { throw new IllegalArgumentException("port range must be between 0 - 65535"); } Iterator<Map.Entry<Integer, List<ImageStreamData>>> iter = cameras.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<Integer, List<ImageStreamData>> entry = iter.next(); List<ImageStreamData> images = entry.getValue(); if (null == images || images.isEmpty()) { throw new IllegalArgumentException("ImageStreamData List cannot be null or empty."); } for (ImageStreamData data : images) { if (null == data) { throw new IllegalArgumentException("ImageStreamData List cannot contain null values."); } } } this.cameras = cameras; this.port = port; } /** * Starts the jetty web server. * @return true on successful start. */ public boolean startServer() { if (server != null && server.isRunning()) { return true; } else { server = new Server(port); ContextHandlerCollection contexts = new ContextHandlerCollection(); server.setHandler(contexts); Context mjpg = new Context(contexts, MPEG, Context.SESSIONS); mjpg.addServlet(new ServletHolder(new AxisServlet()), "/*"); try { server.start(); return true; } catch (Exception e1) { log.error("Could not start server", e1); } } return false; } /** * Stops the jetty web server. * @return true on successful stop. */ public boolean stopServer() { if (server == null || !server.isRunning()) { return false; } try { server.stop(); return true; } catch (Exception e) { log.error("Could not stop server", e); } return false; } /** * Class used to parse out common URL parameters between Axis Server Servlets. */ private class AxisServlet extends HttpServlet { private static final long serialVersionUID = 6865895657001503625L; protected int camera; protected int fps; public AxisServlet() { fps = 4; } /** * Gets the camera and fps url parameters. * Camera parameters must exist otherwise this function returns false. * Response status will be set to HttpServletResponse.SC_NOT_FOUND if camera param is not found. * Fps is set to 4 if not specified. * * @param request The request. * @param response The response. * @return True on success. */ private boolean configureParams(HttpServletRequest request, HttpServletResponse response) { assert null != request : "request cannot be null"; assert null != response : "response cannot be null"; // Get the "camera". If its not specified then send back back request code. String cameraStr = request.getParameter("camera"); if (cameraStr == null) { log.warn("No camera paramater found in url request."); response.setStatus(HttpServletResponse.SC_NOT_FOUND); ((Request) request).setHandled(true); return false; } else { try { camera = Integer.valueOf(cameraStr); } catch (NumberFormatException nfe) { log.error("Invalid camera paramater found in url request. Integer value required.", nfe); response.setStatus(HttpServletResponse.SC_NOT_FOUND); ((Request) request).setHandled(true); return false; } } String fpsStr = request.getParameter("fps"); if (null != fpsStr) { try { fps = Integer.valueOf(fpsStr); } catch (NumberFormatException nfe) { log.error("Invalid fps paramater found in url request. Using default value " + fps, nfe); } } return true; } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { assert null != request : "request cannot be null"; assert null != response : "response cannot be null"; if (configureParams(request, response)) { // Stream the appropriate image. stream(response, cameras.get(camera)); } else { response.setStatus(HttpServletResponse.SC_NOT_FOUND); } ((Request) request).setHandled(true); } private void stream(HttpServletResponse response, List<ImageStreamData> images) throws IOException, ServletException { assert null != response : "response cannot be null"; assert null != images : "images cannot be null"; response.setContentType("multipart/x-mixed-replace; boundary=--myboundary"); response.setStatus(HttpServletResponse.SC_OK); ServletOutputStream stream = null; try { stream = response.getOutputStream(); int fpsDelay = 1000 / fps; Iterator<ImageStreamData> iter = images.iterator(); while (iter.hasNext()) { ImageStreamData imageData = iter.next(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); // Need to get the bytes from image so we know what the size of it is. ImageIO.write(imageData.getImage(), "JPG", bos); byte[] imageInBytes = bos.toByteArray(); String content_info = "--myboundary\nContent-Type: image/jpeg\nContent-Length: " + imageInBytes.length + "\n"; long currentStreamTime = 0L; // Stream the image for the specified amount of time. while (currentStreamTime < imageData.getTimeout()) { long start = System.currentTimeMillis(); // Send this as defined by Axis Camera HTTP API. stream.write(content_info.getBytes()); stream.write("\n".getBytes()); // Now send the image. stream.write(imageInBytes); stream.flush(); try { // Only sleep if we need to. long sleep = fpsDelay - (System.currentTimeMillis() - start); if (sleep > 0) { Thread.sleep(sleep); } } catch (InterruptedException e) { e = null; } currentStreamTime += System.currentTimeMillis() - start; } } } catch (EOFException eof) { // Don't log this as error, it will happen every time a client disconnects. log.trace("EOFException. This may just be because the client has disconnected", eof); } catch (IOException ioe) { log.warn("IOException. This may just be because the client has disconnected", ioe); } finally { if (stream != null) { stream.close(); } } } } }