org.cytoscape.app.internal.net.server.CyHttpdFactoryImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.cytoscape.app.internal.net.server.CyHttpdFactoryImpl.java

Source

package org.cytoscape.app.internal.net.server;

/*
 * #%L
 * Cytoscape App Impl (app-impl)
 * $Id:$
 * $HeadURL:$
 * %%
 * Copyright (C) 2008 - 2013 The Cytoscape Consortium
 * %%
 * This program 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 program 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 Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
 * #L%
 */

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.Collection;
import java.util.regex.Matcher;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import java.io.IOException;

import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.http.protocol.HttpService;
import org.apache.http.params.HttpParams;
import org.apache.http.params.SyncBasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.ImmutableHttpProcessor;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.HttpServerConnection;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.HttpException;
import org.apache.http.ConnectionClosedException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.HttpStatus;

public class CyHttpdFactoryImpl implements CyHttpdFactory {
    public CyHttpd createHttpd(final ServerSocketFactory serverSocketFactory) {
        return new CyHttpdImpl(serverSocketFactory);
    }
}

class CyHttpdImpl implements CyHttpd {
    static final Logger logger = LoggerFactory.getLogger(CyHttpdImpl.class);

    /**
     * If the server port can't be bound on the first try, wait this long
     * between subsequent attempts to rebind.
     */
    static final int PORT_REBIND_INTERVAL = 5000;

    final ServerSocketFactory serverSocketFactory;
    final List<CyHttpResponder> responders = new ArrayList<CyHttpResponder>();
    final List<CyHttpBeforeResponse> beforeResponses = new ArrayList<CyHttpBeforeResponse>();
    final List<CyHttpAfterResponse> afterResponses = new ArrayList<CyHttpAfterResponse>();

    boolean running = false;
    boolean aborted = false;

    ExecutorService executor = null;

    final HttpParams params;
    final HttpService service;

    private final Object lock = new Object();

    public CyHttpdImpl(final ServerSocketFactory serverSocketFactory) {
        if (serverSocketFactory == null)
            throw new IllegalArgumentException("serverSocketFactory == null");
        this.serverSocketFactory = serverSocketFactory;

        // Setup params

        params = (new SyncBasicHttpParams()).setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
                .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
                .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
                .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
                .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "HttpComponents/1.1");

        // Setup service

        final HttpProcessor proc = new ImmutableHttpProcessor(new HttpResponseInterceptor[] { new ResponseDate(),
                new ResponseServer(), new ResponseContent(), new ResponseConnControl() });

        final HttpRequestHandlerRegistry registry = new HttpRequestHandlerRegistry();
        registry.register("*", new RequestHandlerDispatcher());

        service = new HttpService(proc, new DefaultConnectionReuseStrategy(), new DefaultHttpResponseFactory(),
                registry, params);
    }

    public void start() {
        synchronized (lock) {
            if (running)
                throw new IllegalStateException("server is running");
            executor = Executors.newCachedThreadPool();
            aborted = false;
            executor.execute(new ServerThread());
            while (!running) {
                try {
                    lock.wait(500);
                    if (aborted) {
                        return;
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public void stop() {
        if (!running)
            throw new IllegalStateException("server is not running");
        running = false;
        executor.shutdown();
        executor = null;
    }

    public boolean isRunning() {
        return running && !aborted;
    }

    public void addResponder(CyHttpResponder responder) {
        responders.add(responder);
    }

    public void removeResponder(CyHttpResponder responder) {
        responders.remove(responder);
    }

    public Collection<CyHttpResponder> getResponders() {
        return responders;
    }

    public void addBeforeResponse(CyHttpBeforeResponse beforeResponse) {
        beforeResponses.add(beforeResponse);
    }

    public void removeBeforeResponse(CyHttpBeforeResponse beforeResponse) {
        beforeResponses.remove(beforeResponse);
    }

    public Collection<CyHttpBeforeResponse> getBeforeResponses() {
        return beforeResponses;
    }

    public void addAfterResponse(CyHttpAfterResponse afterResponse) {
        afterResponses.add(afterResponse);
    }

    public void removeAfterResponse(CyHttpAfterResponse afterResponse) {
        afterResponses.remove(afterResponse);
    }

    public Collection<CyHttpAfterResponse> getAfterResponses() {
        return afterResponses;
    }

    class ServerThread implements Runnable {
        public void run() {
            // Create a server socket
            ServerSocket serverSocket = null;
            while (true) {
                try {
                    serverSocket = serverSocketFactory.createServerSocket();
                    break;
                } catch (BindException e) {
                    // We can't bind the port.  Wait a bit before trying again.
                    logger.error("Port already in use", e);
                    aborted = true;
                    try {
                        Thread.sleep(PORT_REBIND_INTERVAL);
                    } catch (InterruptedException e1) {
                        logger.error("Unexpected interruption", e1);
                    }
                } catch (IOException e) {
                    logger.error("Failed to create server socket", e);
                    aborted = true;
                    return;
                }
            }

            logger.info("Server socket started on {}", String.format("%s:%d",
                    serverSocket.getInetAddress().getHostAddress(), serverSocket.getLocalPort()));

            running = true;
            synchronized (lock) {
                lock.notifyAll();
            }

            // Keep servicing incoming connections until we're told to stop
            while (running) {

                // Create a new http server connection from the incoming socket
                DefaultHttpServerConnection connection = null;
                try {
                    final Socket socket = serverSocket.accept();
                    logger.info("Server socket received connection from {}",
                            socket.getInetAddress().getHostAddress());
                    connection = new DefaultHttpServerConnection();
                    connection.bind(socket, params);
                } catch (IOException e) {
                    logger.error("Failed to initiate connection with client", e);
                    continue;
                }

                // Dispatch an incoming connection to ConnectionHandler
                final ConnectionHandler connectionHandler = new ConnectionHandler(service, connection);
                executor.execute(connectionHandler);
            }

            logger.info("Server socket stopped");
        }
    }

    class ConnectionHandler implements Runnable {
        final HttpService service;
        final HttpServerConnection connection;

        public ConnectionHandler(final HttpService service, final HttpServerConnection connection) {
            this.service = service;
            this.connection = connection;
        }

        public void run() {
            final HttpContext context = new BasicHttpContext(null);
            try {
                while (running && connection.isOpen())
                    service.handleRequest(connection, context);
            } catch (SocketTimeoutException e) {
                // ignore, this happens normally
            } catch (ConnectionClosedException e) {
                // ignore, this happens normally
            } catch (IOException e) {
                logger.warn("Failed to complete communication with client; connection closing", e);
            } catch (HttpException e) {
                logger.warn("Client violated http; connection closing", e);
            } finally {
                try {
                    connection.shutdown();
                } catch (IOException e) {
                    // We don't care if an exception happened during shutdown
                }
            }
        }
    }

    class RequestHandlerDispatcher implements HttpRequestHandler {
        public void handle(final HttpRequest httpRequest, final HttpResponse httpResponse,
                final HttpContext httpContext) throws HttpException {
            for (org.apache.http.Header header : httpRequest.getAllHeaders())
                logger.info("{}: {}", header.getName(), header.getValue());
            final CyHttpRequest request = new CyHttpRequestImpl(httpRequest);
            final CyHttpResponse response = handle(request);
            if (response == null) {
                setHttpResponse(HttpStatus.SC_NOT_FOUND, "<html><body><h1>404 Not Found</h1></body></html>",
                        "text/html", httpResponse);
            } else {
                setHttpResponse(response, httpResponse);
            }
        }

        private CyHttpResponse handle(final CyHttpRequest request) {
            CyHttpResponse response = null;

            for (final CyHttpBeforeResponse beforeResponse : beforeResponses) {
                response = beforeResponse.intercept(request);
                if (response != null)
                    return response;
            }

            final String uri = request.getURI();
            logger.info("Received request: {}", uri);
            for (final CyHttpResponder responder : responders) {
                final Matcher matcher = responder.getURIPattern().matcher(uri);
                if (matcher.matches()) {
                    response = responder.respond(request, matcher);
                    break;
                }
            }
            if (response == null)
                return null;

            for (final CyHttpAfterResponse afterResponse : afterResponses)
                response = afterResponse.intercept(request, response);

            return response;
        }
    }

    static final void setHttpResponse(final CyHttpResponse response, final HttpResponse httpResponse) {
        setHttpResponse(response.getStatusCode(), response.getContent(), response.getContentType(), httpResponse);
        for (final Map.Entry<String, String> entry : response.getHeaders().entrySet())
            httpResponse.addHeader(entry.getKey(), entry.getValue());
    }

    static final void setHttpResponse(final int statusCode, final String content, final String contentType,
            final HttpResponse httpResponse) {
        httpResponse.setStatusCode(statusCode);
        if (content != null && contentType != null)
            httpResponse.setEntity(new StringEntity(content, ContentType.create(contentType, "UTF-8")));
    }
}