001    /******************************************************************************
002     * Copyright (C) MActor Developers. All rights reserved.                        *
003     * ---------------------------------------------------------------------------*
004     * This file is part of MActor.                                               *
005     *                                                                            *
006     * MActor is free software; you can redistribute it and/or modify             *
007     * it under the terms of the GNU General Public License as published by       *
008     * the Free Software Foundation; either version 2 of the License, or          *
009     * (at your option) any later version.                                        *
010     *                                                                            *
011     * MActor is distributed in the hope that it will be useful,                  *
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
014     * GNU General Public License for more details.                               *
015     *                                                                            *
016     * You should have received a copy of the GNU General Public License          *
017     * along with MActor; if not, write to the Free Software                      *
018     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA *
019     ******************************************************************************/
020    package org.mactor.brokers.http;
021    
022    import java.io.IOException;
023    import java.io.OutputStream;
024    import java.io.PrintWriter;
025    import java.net.ServerSocket;
026    import java.net.Socket;
027    import java.util.HashMap;
028    import java.util.HashSet;
029    import java.util.Map;
030    import java.util.Set;
031    import java.util.Map.Entry;
032    
033    import org.apache.log4j.Logger;
034    import org.mactor.framework.MactorException;
035    
036    /**
037     * Simple HTTP server
038     * 
039     * @author Lars Ivar Almli
040     */
041    public class HttpServer {
042            protected static Logger log = Logger.getLogger(HttpServer.class);
043            public HttpServer(int port) throws MactorException {
044                    Thread httpServer = new Thread(new HttpServerRunner(port));
045                    httpServer.start();
046            }
047            private class HttpServerRunner implements Runnable {
048                    ServerSocket ss;
049                    int port;
050                    public HttpServerRunner(int port) throws MactorException {
051                            this.port = port;
052                            try {
053                                    this.ss = new ServerSocket(port);
054                            } catch (IOException ioe) {
055                                    throw new MactorException("Failed to bind a HTTP listner on port '" + port + "'. Error: " + ioe.getMessage(), ioe);
056                            }
057                    }
058                    public void run() {
059                            try {
060                                    while (!ss.isClosed()) {
061                                            try {
062                                                    Socket client = ss.accept();
063                                                    new RequestWorker(client).start();
064                                            } catch (IOException ioe) {
065                                                    log.warn("Exception while accepting client HTTP connection at port '" + port + "'. Error: " + ioe.getMessage(), ioe);
066                                            }
067                                    }
068                            } finally {
069                                    if (ss != null && !ss.isClosed()) {
070                                            try {
071                                                    ss.close();
072                                            } catch (IOException ioe) {
073                                                    log.warn("Exception while closing HTTP server at port '" + port + "'. Error: " + ioe.getMessage(), ioe);
074                                            }
075                                    }
076                            }
077                    }
078            }
079            private class RequestWorker {
080                    Socket client;
081                    RequestWorker(Socket client) {
082                            this.client = client;
083                    }
084                    public void start() {
085                            new Thread() {
086                                    @Override
087                                    public void run() {
088                                            handleRequest(client);
089                                    }
090                            }.start();
091                    }
092                    boolean patientMode = true;
093                    private HttpRequestListener getListenerForUrl(String url) throws InterruptedException {
094                            HttpRequestListener listener = listeners.get(url);
095                            if (listener == null && patientMode) { // "buffering" to handle the
096                                    // frequent
097                                    // sub/unsub when running mock tests
098                                    if (recentPatterns.contains(url)) {
099                                            for (int i = 0; i < 20 && listener == null; i++) {
100                                                    Thread.sleep(200);
101                                                    listener = listeners.get(url);
102                                            }
103                                    }
104                            }
105                            return listener;
106                    }
107                    private void handleRequest(Socket client) {
108                            try {
109                                    if (log.isDebugEnabled()) {
110                                            log.debug("Handling request from " + client.getInetAddress());
111                                    }
112                                    HttpRequest req = new HttpRequest(client.getInputStream());
113                                    String url = req.getRequestedUrl().toLowerCase();
114                                    HttpRequestListener listener = getListenerForUrl(url);
115                                    if (listener == null) {
116                                            log.info("No listener for requested url '" + req.getRequestedUrl().toLowerCase());
117                                            sendErrorResponse(client, "404");
118                                    } else {
119                                            try {
120                                                    HttpResponse res = listener.onRequest(req);
121                                                    if (res == null && patientMode) {
122                                                            for (int i = 0; i < 20 && res == null; i++) {
123                                                                    Thread.sleep(200);
124                                                                    res = listener.onRequest(req);
125                                                            }
126                                                    }
127                                                    if (res != null)
128                                                            sendSuccessResponse(client, res);
129                                                    else {
130                                                            log.info("No response produced for requested url '" + req.getRequestedUrl().toLowerCase());
131                                                            sendErrorResponse(client, "204");// No content
132                                                    }
133                                            } catch (Exception e) {
134                                                    e.printStackTrace();
135                                                    sendErrorResponse(client, "500");
136                                            }
137                                    }
138                                    client.close();
139                            } catch (Exception e) {
140                                    e.printStackTrace();
141                            }
142                    }
143            }
144            private void sendErrorResponse(Socket client, String code) throws IOException {
145                    PrintWriter out = new PrintWriter(client.getOutputStream());
146                    out.println("HTTP/1.0 " + code);
147                    out.println("Content-Type: text/plain");
148                    out.println();
149                    out.flush();
150                    out.close();
151            }
152            private void sendSuccessResponse(Socket client, HttpResponse response) throws IOException {
153                    OutputStream stream = client.getOutputStream();
154                    PrintWriter out = new PrintWriter(stream);
155                    out.println("HTTP/1.0 200");
156                    for (Entry<String, String> e : response.getHeaders().entrySet()) {
157                            out.println(e.getKey() + ": " + e.getValue().trim());
158                    }
159                    out.println();
160                    out.flush();
161                    if (response.getData() != null) {
162                            stream.write(response.getData());
163                            stream.flush();
164                    }
165                    stream.close();
166            }
167            Set<String> recentPatterns = new HashSet<String>();
168            Map<String, HttpRequestListener> listeners = new HashMap<String, HttpRequestListener>();
169            public synchronized void addRequestListener(String url, HttpRequestListener listener) {
170                    url = url.toLowerCase();
171                    log.info("adding listener for:'" + url + "'");
172                    if (listeners.containsKey(url))
173                            throw new RuntimeException("Listener for url '" + url + "' already registered");
174                    listeners.put(url, listener);
175                    recentPatterns.add(url);
176            }
177            public synchronized void removeRequestListener(String url) {
178                    listeners.remove(url);
179            }
180    }