/* * ============================================================================ * The Apache Software License, Version 1.1 * ============================================================================ * * Copyright (C) 2002 The Apache Software Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without modifica- * tion, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The end-user documentation included with the redistribution, if any, must * include the following acknowledgment: "This product includes software * developed by SuperBonBon Industries (http://www.sbbi.net/)." * Alternately, this acknowledgment may appear in the software itself, if * and wherever such third-party acknowledgments normally appear. * * 4. The names "UPNPLib" and "SuperBonBon Industries" must not be * used to endorse or promote products derived from this software without * prior written permission. For written permission, please contact * info@sbbi.net. * * 5. Products derived from this software may not be called * "SuperBonBon Industries", nor may "SBBI" appear in their name, * without prior written permission of SuperBonBon Industries. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT,INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * on behalf of SuperBonBon Industries. For more information on * SuperBonBon Industries, please see <http://www.sbbi.net/>. */ package net.sbbi.upnp.jmx; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Class to handle HTTP UPNP requests on UPNPMBeanDevices * @author <a href="mailto:superbonbon@sbbi.net">SuperBonBon</a> * @version 1.0 */ public class UPNPMBeanDevicesRequestsHandler implements Runnable { private final static Log log = LogFactory.getLog( UPNPMBeanDevicesRequestsHandler.class ); private final static int MAX_HTTP_WORKERS = 10; private final static Map instances = new HashMap(); private Set handledDevices = new HashSet(); private Set httpWorkers = new HashSet(); private ServerSocket srv; private boolean isRunning = false; private InetSocketAddress bindAddress; public final static UPNPMBeanDevicesRequestsHandler getInstance( InetSocketAddress bindAddress ) { String key = bindAddress.toString(); synchronized( instances ) { UPNPMBeanDevicesRequestsHandler handler = (UPNPMBeanDevicesRequestsHandler)instances.get( key ); if ( handler == null ) { handler = new UPNPMBeanDevicesRequestsHandler( bindAddress ); instances.put( key, handler ); } return handler; } } private UPNPMBeanDevicesRequestsHandler( InetSocketAddress bindAddress ) { this.bindAddress = bindAddress; } protected void addUPNPMBeanDevice( UPNPMBeanDevice rootDevice ) { synchronized( handledDevices ) { for ( Iterator i = handledDevices.iterator(); i.hasNext(); ) { UPNPMBeanDevice registred = (UPNPMBeanDevice)i.next(); if ( registred.getDeviceType().equals( rootDevice.getDeviceType() ) && registred.getUuid().equals( rootDevice.getUuid() ) ) { // API Use error throw new RuntimeException( "An UPNPMBeanDevice object of type " + rootDevice.getDeviceType() + " with uuid " + rootDevice.getUuid() + " is already registred within this class, use a different UPNPMBeanDevice internalId" ); } } if ( handledDevices.size() == 0 ) { Thread runner = new Thread( this, "UPNPMBeanDevicesRequestsHandler " + bindAddress.toString() ); runner.setDaemon( true ); runner.start(); } handledDevices.add( rootDevice ); // adding the child devices for ( Iterator i = rootDevice.getUPNPMBeanChildrens().iterator(); i.hasNext(); ) { handledDevices.add( i.next() ); } } } protected void removeUPNPMBeanDevice( UPNPMBeanDevice rootDevice ) { synchronized( handledDevices ) { if ( handledDevices.contains( rootDevice ) ) { handledDevices.remove( rootDevice ); // removing the child devices for ( Iterator i = rootDevice.getUPNPMBeanChildrens().iterator(); i.hasNext(); ) { handledDevices.remove( i.next() ); } if ( handledDevices.size() == 0 ) { try { isRunning = false; srv.close(); } catch ( IOException ex ) { // do not care } } } } } protected void notifyWorkerThreadEnd( HttpWorker worker ) { synchronized( httpWorkers ) { httpWorkers.remove( worker ); } } public void run() { try { srv = new ServerSocket( bindAddress.getPort(), 200, bindAddress.getAddress() ); } catch ( IOException ex ) { log.error( "Error during server socket creation, thread cannot start", ex ); return; } isRunning = true; while ( isRunning ) { try { Socket skt = srv.accept(); skt.setSoTimeout( 30000 ); HttpWorker worker = new HttpWorker(skt, log, handledDevices, this ); while ( httpWorkers.size() >= MAX_HTTP_WORKERS ) { try { Thread.sleep( 100 ); } catch ( InterruptedException ex ) { // ignore } } Thread workerThread = new Thread( worker, "UPNPMBeanDevicesRequestsHandler Http Worker " + httpWorkers.size() ); workerThread.start(); synchronized( httpWorkers ) { httpWorkers.add( worker ); } } catch ( IOException ex ) { if ( isRunning ) { log.error( "Error during client socket creation", ex ); } } } } private class HttpWorker implements Runnable { private Socket client; private Log logger; private Set devices; private UPNPMBeanDevicesRequestsHandler handler; public HttpWorker( Socket client, Log log, Set handledDevices, UPNPMBeanDevicesRequestsHandler handler ) { this.client = client; this.logger = log; this.devices = handledDevices; this.handler = handler; } public void run() { try { byte[] buffer = new byte[256]; InputStream in = client.getInputStream(); StringBuffer request = new StringBuffer(); int readen = 0; String firstReadenData = null; while ( readen != -1 ) { readen = in.read( buffer ); String data = new String( buffer, 0, readen ); if ( firstReadenData == null ) { firstReadenData = data.toUpperCase(); } request.append( data ); // either a simple get request String rawRequest = request.toString(); if ( rawRequest.endsWith( "\r\n\r\n" ) && firstReadenData.startsWith( "GET" ) ) { readen = -1; } else if ( rawRequest.indexOf( "</s:Envelope>" ) != -1 ) { // or a post request with content that should end with </s:Envelope> readen = -1; } } OutputStream out = client.getOutputStream(); String req = request.toString().trim(); if ( logger.isDebugEnabled() ) logger.debug( "Received message:\n" + req ); String toWrite = null; if ( req.length() > 0 ) { HttpRequest httpReq = new HttpRequest( req ); if ( httpReq.getHttpCommand() != null && httpReq.getHttpCommandArg() != null && httpReq.getHttpCommandArg().trim().length() > 0 ) { String cmd = httpReq.getHttpCommand(); HttpRequestHandler handler = null; if ( cmd.equals( "GET" ) ) { handler = HttpGetRequest.getInstance(); // implement M-POST } else if ( cmd.equals( "POST" ) ) { handler = HttpPostRequest.getInstance(); } else if ( cmd.equals( "SUBSCRIBE" ) ) { handler = HttpSubscriptionRequest.getInstance(); } else if ( cmd.equals( "UNSUBSCRIBE" ) ) { handler = HttpSubscriptionRequest.getInstance(); } if ( handler != null ) { toWrite = handler.service( devices, httpReq ); } } } if ( toWrite == null ) { String content = "<html><head><title>Not found</title></head><body>The requested ressource cannot be found</body></html>"; StringBuffer rtr = new StringBuffer(); rtr.append( "HTTP/1.1 404 Not Found\r\n" ); rtr.append( "CONTENT-LENGTH: " ).append( content.length() ).append( "\r\n" ); rtr.append( "CONTENT-TYPE: text/html\r\n\r\n" ); rtr.append( content ); toWrite = rtr.toString(); } if ( logger.isDebugEnabled() ) logger.debug( "Sending response :\n" + toWrite ); out.write( toWrite.getBytes() ); out.flush(); out.close(); in.close(); client.close(); } catch ( IOException ex ) { logger.error( "IO Exception occured during client serving", ex ); } catch ( Throwable t ) { logger.error( "Unexpected Exception occured during client serving", t ); } finally { handler.notifyWorkerThreadEnd( this ); } } } }