org.red5.server.net.servlet.RTMPTServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.red5.server.net.servlet.RTMPTServlet.java

Source

package org.red5.server.net.servlet;

/*
 * RED5 Open Source Flash Server - http://www.osflash.org/red5
 *
 * Copyright (c) 2006-2007 by respective authors (see below). All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 *
 * This library 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

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.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.mina.common.ByteBuffer;
import org.red5.server.net.rtmpt.RTMPTConnection;
import org.red5.server.net.rtmpt.RTMPTHandler;

/**
 * Servlet that handles all RTMPT requests.
 *
 * @author The Red5 Project (red5@osflash.org)
 * @author Joachim Bauch (jojo@struktur.de)
 */
public class RTMPTServlet extends HttpServlet {

    private static final long serialVersionUID = -3097083526127047079L;

    protected static Logger log = Logger.getLogger(RTMPTServlet.class);

    /**
     * HTTP request method to use for RTMPT calls.
     */
    private static final String REQUEST_METHOD = "POST";

    /**
     * Content-Type to use for RTMPT requests / responses.
     */
    private static final String CONTENT_TYPE = "application/x-fcs";

    /**
     * URL that is called to start a new RTMPT session.
     */
    @SuppressWarnings("unused")
    private static final String OPEN_REQUEST = "/open";

    /**
     * URL that is called to close a RTMPT session.
     */
    @SuppressWarnings("unused")
    private static final String CLOSE_REQUEST = "/close";

    /**
     * URL that is called to send data through a RTMPT connection.
     */
    @SuppressWarnings("unused")
    private static final String SEND_REQUEST = "/send";

    /**
     * URL that is called to poll RTMPT connection for new events / data.
     */
    @SuppressWarnings("unused")
    private static final String IDLE_REQUEST = "/idle";

    /**
     * Try to generate responses that contain at least 32768 bytes data.
     * Increasing this value results in better stream performance, but
     * also increases the latency.
     */
    private static final int RESPONSE_TARGET_SIZE = 32768;

    /**
     * Holds a map of client id -> client object.
     */
    protected HashMap rtmptClients = new HashMap();

    /**
     * Return an error message to the client.
     *
     * @param message
     * @param resp
     * @throws IOException
     */
    protected void handleBadRequest(String message, HttpServletResponse resp) throws IOException {
        resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        resp.setContentType("text/plain");
        resp.setContentLength(message.length());
        resp.getWriter().write(message);
        resp.flushBuffer();
    }

    /**
     * Return a single byte to the client.
     *
     * @param message
     * @param resp
     * @throws IOException
     */
    protected void returnMessage(byte message, HttpServletResponse resp) throws IOException {
        resp.setStatus(HttpServletResponse.SC_OK);
        resp.setHeader("Connection", "Keep-Alive");
        resp.setHeader("Cache-Control", "no-cache");
        resp.setContentType(CONTENT_TYPE);
        resp.setContentLength(1);
        resp.getWriter().write(message);
        resp.flushBuffer();
    }

    /**
     * Return a message to the client.
     *
     * @param message
     * @param resp
     * @throws IOException
     */
    protected void returnMessage(String message, HttpServletResponse resp) throws IOException {
        resp.setStatus(HttpServletResponse.SC_OK);
        resp.setHeader("Connection", "Keep-Alive");
        resp.setHeader("Cache-Control", "no-cache");
        resp.setContentType(CONTENT_TYPE);
        resp.setContentLength(message.length());
        resp.getWriter().write(message);
        resp.flushBuffer();
    }

    /**
     * Return raw data to the client.
     *
     * @param client
     * @param buffer
     * @param resp
     * @throws IOException
     */
    protected void returnMessage(RTMPTConnection client, ByteBuffer buffer, HttpServletResponse resp)
            throws IOException {
        resp.setStatus(HttpServletResponse.SC_OK);
        resp.setHeader("Connection", "Keep-Alive");
        resp.setHeader("Cache-Control", "no-cache");
        resp.setContentType(CONTENT_TYPE);
        //this will prevent stringbuffers from being created when not in debug mode
        if (log.isDebugEnabled()) {
            log.debug("Sending " + buffer.limit() + " bytes.");
        }
        resp.setContentLength(buffer.limit() + 1);
        ServletOutputStream output = resp.getOutputStream();
        output.write(client.getPollingDelay());
        ServletUtils.copy(buffer.asInputStream(), output);
        buffer = null;
    }

    /**
     * Return the client id from a url like /send/123456/12 -> 123456
     */
    protected String getClientId(HttpServletRequest req) {
        String path = req.getPathInfo();
        if (path.equals("")) {
            return "";
        }

        if (path.charAt(0) == '/') {
            path = path.substring(1);
        }

        int endPos = path.indexOf('/');
        if (endPos != -1) {
            path = path.substring(0, endPos);
        }

        return path;
    }

    /**
     * Get the RTMPT client for a session.
     *
     * @param req
     * @return
     */
    protected RTMPTConnection getClient(HttpServletRequest req) {
        String id = getClientId(req);
        if (StringUtils.isEmpty(id) || !rtmptClients.containsKey(id)) {
            //this will prevent stringbuffers from being created when not in debug mode
            if (log.isDebugEnabled()) {
                log.debug("Unknown client id: " + id);
            }
            return null;
        }

        return (RTMPTConnection) rtmptClients.get(id);
    }

    /**
     * Skip data sent by the client.
     *
     * @param req
     * @throws IOException
     */
    protected void skipData(HttpServletRequest req) throws IOException {
        ByteBuffer data = ByteBuffer.allocate(req.getContentLength());
        ServletUtils.copy(req.getInputStream(), data.asOutputStream());
        data.flip();
        data = null;
    }

    /**
     * Send pending messages to client.
     *
     * @param client
     * @param resp
     * @throws IOException
     */
    protected void returnPendingMessages(RTMPTConnection client, HttpServletResponse resp) throws IOException {

        ByteBuffer data = client.getPendingMessages(RESPONSE_TARGET_SIZE);
        if (data == null) {
            // no more messages to send...
            returnMessage(client.getPollingDelay(), resp);
            return;
        }

        returnMessage(client, data, resp);
    }

    /**
     * Start a new RTMPT session.
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void handleOpen(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        // Skip sent data
        skipData(req);

        // TODO: should we evaluate the pathinfo?

        //      RTMPTHandler handler = (RTMPTHandler) getServletContext().getAttribute(RTMPTHandler.HANDLER_ATTRIBUTE);
        //      RTMPTConnection client = new RTMPTConnection(handler);
        //      synchronized (rtmptClients) {
        //         rtmptClients.put(client.getId(), client);
        //      }
        //
        //      // Return connection id to client
        //      returnMessage(client.getId() + "\n", resp);
    }

    /**
     * Close a RTMPT session.
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void handleClose(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        // Skip sent data
        skipData(req);

        RTMPTConnection client = getClient(req);
        if (client == null) {
            handleBadRequest("Unknown client.", resp);
            return;
        }

        synchronized (rtmptClients) {
            rtmptClients.remove(client.getId());
        }

        RTMPTHandler handler = (RTMPTHandler) getServletContext().getAttribute(RTMPTHandler.HANDLER_ATTRIBUTE);
        client.setServletRequest(req);
        handler.connectionClosed(client, client.getState());

        returnMessage((byte) 0, resp);
    }

    /**
     * Add data for an established session.
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void handleSend(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        RTMPTConnection client = getClient(req);
        if (client == null) {
            handleBadRequest("Unknown client.", resp);
            return;
        }

        client.setServletRequest(req);

        // Put the received data in a ByteBuffer
        int length = req.getContentLength();
        ByteBuffer data = ByteBuffer.allocate(length);
        ServletUtils.copy(req.getInputStream(), data.asOutputStream());
        data.flip();

        // Decode the objects in the data
        List messages = client.decode(data);
        data = null;
        if (messages == null || messages.isEmpty()) {
            returnMessage(client.getPollingDelay(), resp);
            return;
        }

        // Execute the received RTMP messages
        RTMPTHandler handler = (RTMPTHandler) getServletContext().getAttribute(RTMPTHandler.HANDLER_ATTRIBUTE);
        Iterator it = messages.iterator();
        while (it.hasNext()) {
            try {
                handler.messageReceived(client, client.getState(), it.next());
            } catch (Exception e) {
                log.error("Could not process message.", e);
            }
        }

        // Send results to client
        returnPendingMessages(client, resp);
    }

    /**
     * Poll RTMPT session for updates.
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void handleIdle(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        // Skip sent data
        skipData(req);

        RTMPTConnection client = getClient(req);
        if (client == null) {
            handleBadRequest("Unknown client.", resp);
            return;
        }

        client.setServletRequest(req);
        returnPendingMessages(client, resp);
    }

    /**
     * Main entry point for the servlet.
     */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        log.info("RTMPT service");

        if (!req.getMethod().equals(REQUEST_METHOD) || req.getContentLength() == 0 || req.getContentType() == null
                || !req.getContentType().equals(CONTENT_TYPE)) {
            // Bad request - return simple error page
            handleBadRequest("Bad request, only RTMPT supported.", resp);
            return;
        }

        // XXX Paul: since the only current difference in the type of request
        // that we are interested in is the 'second' character, we can double
        // the speed of this entry point by using a switch on the second charater.
        char p = req.getServletPath().charAt(1);
        switch (p) {
        case 'o': //OPEN_REQUEST
            handleOpen(req, resp);
            break;
        case 'c': //CLOSE_REQUEST
            handleClose(req, resp);
            break;
        case 's': //SEND_REQUEST
            handleSend(req, resp);
            break;
        case 'i': //IDLE_REQUEST
            handleIdle(req, resp);
            break;
        default:
            handleBadRequest("RTMPT command " + p + " is not supported.", resp);
        }

    }

}