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 }