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 }