001: /*
002: * Copyright (c) 2001 by Matt Welsh and The Regents of the University of
003: * California. All rights reserved.
004: *
005: * Permission to use, copy, modify, and distribute this software and its
006: * documentation for any purpose, without fee, and without written agreement is
007: * hereby granted, provided that the above copyright notice and the following
008: * two paragraphs appear in all copies of this software.
009: *
010: * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
011: * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
012: * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
013: * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
014: *
015: * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
016: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
017: * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
018: * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
019: * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
020: *
021: * Author: Matt Welsh <mdw@cs.berkeley.edu>
022: *
023: */
024:
025: package seda.apps.Haboob.http;
026:
027: import seda.sandStorm.api.*;
028: import seda.sandStorm.core.*;
029: import seda.sandStorm.lib.http.*;
030: import seda.apps.Haboob.*;
031: import com.rimfaxe.webserver.seda.*;
032: import seda.util.*;
033: import java.util.Enumeration;
034:
035: /**
036: * This stage is responsible for accepting new HTTP requests and forwarding
037: * them to the page cache (or else, responding with a dynamically-generated
038: * page for statistics gathering).
039: *
040: */
041: public class HttpRecv implements EventHandlerIF, HaboobConst {
042:
043: private static final boolean DEBUG = false;
044: private static final boolean VERBOSE = false;
045:
046: // If true, enable ATLS support
047: private static final boolean USE_ATLS = false;
048:
049: private static final long TIMER_DELAY = 2000;
050: private int HTTP_PORT, HTTP_SECURE_PORT;
051: private int ServerPort;
052: private httpServer server, secureServer;
053: private ManagerIF mgr;
054: private SinkIF mysink, cacheSink, bottleneckSink, sendSink,
055: dynSink;
056: private int maxConns, maxSimReqs, numConns = 0, numSimReqs = 0;
057: private int lastNumConns = 0;
058: private String SPECIAL_URL;
059: private String BOTTLENECK_URL;
060: private ssTimer timer;
061:
062: // Empty class representing timer event
063: class timerEvent implements QueueElementIF {
064: }
065:
066: public HttpRecv() {
067:
068: }
069:
070: public void init(ConfigDataIF config) throws Exception {
071: mysink = config.getStage().getSink();
072: this .mgr = config.getManager();
073: cacheSink = mgr.getStage(CACHE_STAGE).getSink();
074: sendSink = mgr.getStage(HTTP_SEND_STAGE).getSink();
075:
076: // These are optional
077: try {
078: dynSink = mgr.getStage(DYNAMIC_HTTP_STAGE).getSink();
079: } catch (NoSuchStageException nsse) {
080: dynSink = null;
081: }
082:
083: try {
084: bottleneckSink = mgr.getStage(BOTTLENECK_STAGE).getSink();
085: } catch (NoSuchStageException nsse) {
086: bottleneckSink = null;
087: }
088:
089: timer = new ssTimer();
090: timer.registerEvent(TIMER_DELAY, new timerEvent(), mysink);
091:
092: SPECIAL_URL = config.getString("specialURL");
093: if (SPECIAL_URL == null)
094: throw new IllegalArgumentException(
095: "Must specify specialURL");
096: BOTTLENECK_URL = config.getString("bottleneckURL");
097:
098: String serverName = config.getString("serverName");
099: if (serverName != null)
100: httpResponse.setDefaultHeader("Server: " + serverName
101: + httpConst.CRLF);
102: HTTP_PORT = config.getInt("httpPort");
103:
104: ServerPort = config.getInt("ServerPort");
105: if (ServerPort == -1) {
106: throw new IllegalArgumentException(
107: "Must specify ServerPort");
108: } else {
109: HaboobStats.addReceiver("" + ServerPort, this );
110: }
111:
112: if (USE_ATLS == false) {
113: if (HTTP_PORT == -1) {
114: throw new IllegalArgumentException(
115: "Must specify httpPort");
116: }
117: }
118: HTTP_SECURE_PORT = config.getInt("httpSecurePort");
119: if (USE_ATLS == true) {
120: if ((HTTP_PORT == -1) && (HTTP_SECURE_PORT == -1)) {
121: throw new IllegalArgumentException(
122: "Must specify either httpPort or httpSecurePort");
123: }
124: }
125:
126: maxConns = config.getInt("maxConnections");
127: maxSimReqs = config.getInt("maxSimultaneousRequests");
128: System.err.println("HttpRecv: Starting, maxConns=" + maxConns
129: + ", maxSimReqs=" + maxSimReqs);
130:
131: if (HTTP_PORT != -1) {
132: server = new httpServer(mgr, mysink, HTTP_PORT);
133: }
134:
135: /* Uncomment the following lines if you want to enable SSL/TLS support */
136: /* if (USE_ATLS && (HTTP_SECURE_PORT != -1)) {
137: * secureServer = new seda.sandStorm.lib.aTLS.http.httpSecureServer(mgr, mysink, HTTP_SECURE_PORT);
138: *
139: * }
140: */
141:
142: }
143:
144: public void destroy() {
145: }
146:
147: public void handleEvent(QueueElementIF item) {
148: if (DEBUG)
149: System.err.println("HttpRecv: GOT QEL: " + item);
150:
151: if (item instanceof httpConnection) {
152: HaboobStats.numConnectionsEstablished++;
153: numConns++;
154: if (VERBOSE)
155: System.err
156: .println("HttpRecv: Got connection "
157: + (HaboobStats.numConnectionsEstablished - HaboobStats.numConnectionsClosed));
158:
159: if ((maxConns != -1) && (numConns == maxConns)) {
160: System.err.println("Suspending accept() after "
161: + numConns + " connections");
162: server.suspendAccept();
163: }
164:
165: } else if (item instanceof httpRequest) {
166: if (DEBUG)
167: System.err.println("HttpRecv: Got request " + item);
168:
169: httpRequest req = (httpRequest) item;
170:
171: req.setServerPort(ServerPort);
172:
173: // Record time for controller
174: req.timestamp = System.currentTimeMillis();
175:
176: // Check for special URL
177: if (DEBUG)
178: System.err.println("HttpRecv: URL is [" + req.getURL()
179: + "]");
180: if (req.getURL().startsWith(SPECIAL_URL)) {
181: if (DEBUG)
182: System.err.println("HttpRecv: Doing special");
183: doSpecial(req);
184: return;
185: }
186:
187: // Check for bottleneck URL
188: if ((bottleneckSink != null) && (BOTTLENECK_URL != null)
189: && (req.getURL().startsWith(BOTTLENECK_URL))) {
190: if (!bottleneckSink.enqueue_lossy(req)) {
191: //System.err.println("HttpRecv: Warning: Could not enqueue_lossy to bottleneck stage: "+item);
192: // Send not available response
193: HttpSend.sendResponse(new httpResponder(
194: new httpServiceUnavailableResponse(req,
195: "Bottleneck stage is busy!"), req,
196: true));
197: }
198: return;
199: }
200:
201: // Check for dynamic URLs
202: if (dynSink != null) {
203: try {
204: if (DynamicHttp.handleRequest(req))
205: return;
206: } catch (Exception e) {
207: // Send not available response
208: //System.err.println("*************** Haboob: Could not enqueue request to HDAPI: "+e);
209: HttpSend
210: .sendResponse(new httpResponder(
211: new httpServiceUnavailableResponse(
212: req,
213: "Could not enqueue request to HDAPI ["
214: + req.getURL()
215: + "]: " + e), req,
216: true));
217: return;
218: }
219: }
220:
221: // Threshold maximum number of in-flight requests
222: if (maxSimReqs != -1) {
223: synchronized (this ) {
224: numSimReqs++;
225: while (numSimReqs >= maxSimReqs) {
226: try {
227: this .wait();
228: } catch (InterruptedException ie) {
229: // Ignore
230: }
231: }
232: }
233: }
234:
235: if (DEBUG)
236: System.err.println("HttpRecv: Sending to cacheSink");
237: if (!cacheSink.enqueue_lossy(item)) {
238: System.err
239: .println("HttpRecv: Warning: Could not enqueue_lossy "
240: + item);
241: }
242:
243: } else if (item instanceof SinkClosedEvent) {
244: // Connection closed by remote peer
245: if (DEBUG)
246: System.err.println("HttpRecv: Closed connection "
247: + item);
248: HaboobStats.numConnectionsClosed++;
249:
250: numConns--;
251: if ((maxConns != -1) && (numConns == maxConns - 1)) {
252: System.err.println("Resuming accept() for " + numConns
253: + " connections");
254: server.resumeAccept();
255: }
256: cacheSink.enqueue_lossy(item);
257:
258: if (VERBOSE)
259: System.err
260: .println("HttpRecv: Closed connection "
261: + (HaboobStats.numConnectionsEstablished - HaboobStats.numConnectionsClosed));
262:
263: } else if (item instanceof timerEvent) {
264:
265: int nc = (HaboobStats.numConnectionsEstablished - HaboobStats.numConnectionsClosed);
266: if (nc != lastNumConns) {
267: if (DEBUG)
268: System.err
269: .println("Haboob: "
270: + (HaboobStats.numConnectionsEstablished - HaboobStats.numConnectionsClosed)
271: + " active connections");
272: }
273: lastNumConns = nc;
274: timer.registerEvent(TIMER_DELAY, item, mysink);
275:
276: } else {
277: if (DEBUG)
278: System.err.println("HttpRecv: Got unknown event type: "
279: + item);
280: }
281:
282: }
283:
284: public void handleEvents(QueueElementIF items[]) {
285: for (int i = 0; i < items.length; i++) {
286: handleEvent(items[i]);
287: }
288: }
289:
290: // Indicate that we are done with a request; used by HttpSend
291: void doneWithReq() {
292: if (maxSimReqs == -1)
293: return;
294:
295: synchronized (this ) {
296: numSimReqs--;
297: if (numSimReqs < maxSimReqs)
298: this .notify();
299: }
300: }
301:
302: // Close the given connection; used by HttpSend
303: void closeConnection(httpConnection conn) {
304: HaboobStats.numConnectionsClosed++;
305: numConns--;
306: try {
307: conn.close(cacheSink);
308: } catch (SinkClosedException sce) {
309: if (DEBUG)
310: System.err
311: .println("Warning: Tried to close connection "
312: + conn + " multiple times");
313: }
314: if ((maxConns != -1) && (numConns == maxConns - 1)) {
315: System.err.println("Resuming accept() for " + numConns
316: + " connections");
317: server.resumeAccept();
318: }
319: }
320:
321: private void doSpecial(httpRequest req)
322: {
323: double pct;
324:
325: if (req.getURL().endsWith("?graph")) {
326: mgr.getProfiler().getGraphProfiler().dumpGraph();
327: }
328:
329: String repl = "<html><head><title>RWS - Haboob Statistics Page</title></head>"+
330: "<body bgcolor=white><font face=helvetica><h2>RWS - Haboob Statistics Page</h2>\n";
331:
332: if (req.getURL().endsWith("?graph")) {
333: mgr.getProfiler().getGraphProfiler().dumpGraph();
334: repl += "<p><b><font color=red>Graph dumped.</font></b>";
335: }
336:
337: repl += "<p><b>Virtual Host Statistics</b>\n";
338: com.rimfaxe.webserver.Configuration conf = com.rimfaxe.webserver.ObjectStore.getConfiguration();
339: Enumeration enum = conf.getVHostList();
340: while (enum.hasMoreElements())
341: {
342: com.rimfaxe.webserver.VirtualHost vh = (com.rimfaxe.webserver.VirtualHost) enum.nextElement();
343: repl += "<br>Virtual host ["+vh.getName()+"]\n";
344: repl += "<br>Size of session store = "+vh.getSessionStore().getSize()+"\n<br>\n";
345: }
346:
347: repl += "<p><b>Server Statistics</b>\n";
348: Runtime r = Runtime.getRuntime();
349: double totalmemkb = r.totalMemory() / 1024.0;
350: double freememkb = r.freeMemory() / 1024.0;
351: repl += "<br>Total memory in use: "+MDWUtil.format(totalmemkb)+" KBytes\n";
352: repl += "<br>Free memory: "+MDWUtil.format(freememkb)+" KBytes\n";
353:
354: repl += "<p><b>HTTP Request Statistics</b>\n";
355: repl += "<br>Total requests: "+HaboobStats.numRequests+"\n";
356: if (HaboobStats.numRequests>0)
357: pct = (HaboobStats.numErrors * 100.0 / HaboobStats.numRequests);
358: else
359: pct = 0.0;
360: repl += "<br>Errors: "+HaboobStats.numErrors+" ("+MDWUtil.format(pct)+"%)\n";
361:
362: repl += "\n<p><b>Cache Statistics</b>\n";
363:
364: int cacheSizeKb = HaboobStats.cacheSizeBytes/1024;
365: repl += "<br>Current size of page cache: "+HaboobStats.cacheSizeEntries+" files, "+cacheSizeKb+" KBytes\n";
366:
367: if (HaboobStats.numRequests>0)
368: pct = (HaboobStats.numCacheHits * 100.0 / HaboobStats.numRequests);
369: else
370: pct = 0.0;
371: repl += "<br>Cache hits: "+HaboobStats.numCacheHits+" ("+MDWUtil.format(pct)+"%)\n";
372:
373: if (HaboobStats.numRequests>0)
374: pct = (HaboobStats.numCacheMisses * 100.0 / HaboobStats.numRequests);
375: else
376: pct = 0.0;
377: repl += "<br>Cache misses: "+HaboobStats.numCacheMisses+" ("+MDWUtil.format(pct)+"%)\n";
378:
379: repl += "\n<p><b>Connection Statistics</b>\n";
380: int numconns = HaboobStats.numConnectionsEstablished - HaboobStats.numConnectionsClosed;
381: repl += "<br>Number of connections: "+numconns+"\n";
382: repl += "<br>Total connections: "+HaboobStats.numConnectionsEstablished+"\n";
383:
384: repl += "\n<p><b>Profiling Information</b>\n";
385: double cacheLookupTime = 0, cacheAllocateTime = 0, cacheRejectTime = 0,
386: fileReadTime = 0;
387: if (HaboobStats.numCacheLookup != 0)
388: cacheLookupTime = (HaboobStats.timeCacheLookup * 1.0) / HaboobStats.numCacheLookup;
389: if (HaboobStats.numCacheAllocate != 0)
390: cacheAllocateTime = (HaboobStats.timeCacheAllocate * 1.0) / HaboobStats.numCacheAllocate;
391: if (HaboobStats.numCacheReject != 0)
392: cacheRejectTime = (HaboobStats.timeCacheReject * 1.0) / HaboobStats.numCacheReject;
393: if (HaboobStats.numFileRead != 0)
394: fileReadTime = (HaboobStats.timeFileRead * 1.0) / HaboobStats.numFileRead;
395: repl += "<br>Cache lookup time: "+MDWUtil.format(cacheLookupTime)+" ms avg ("+HaboobStats.timeCacheLookup+" total, "+HaboobStats.numCacheLookup+" times)\n";
396: repl += "<br>Cache allocate time: "+MDWUtil.format(cacheAllocateTime)+" ms avg ("+HaboobStats.timeCacheAllocate+" total, "+HaboobStats.numCacheAllocate+" times)\n";
397: repl += "<br>Cache reject time: "+MDWUtil.format(cacheRejectTime)+" ms avg ("+HaboobStats.timeCacheReject+" total, "+HaboobStats.numCacheReject+" times)\n";
398: repl += "<br>File read time: "+MDWUtil.format(fileReadTime)+" ms avg ("+HaboobStats.timeFileRead+" total, "+HaboobStats.numFileRead+" times)\n";
399:
400: repl += "<p></font></body></html>"+httpConst.CRLF;
401:
402: httpOKResponse resp = new httpOKResponse("text/html", new BufferElement(repl.getBytes()));
403: HttpSend.sendResponse(new httpResponder(resp, req, true));
404:
405: }
406: }
|