View Javadoc
1 /* Reattore HTTP Server 2 3 Copyright (C) 2002 Michael Hope <michaelh@juju.net.nz> 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software 17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 19 $Id: HttpClientHandler.java,v 1.32 2003/02/22 04:29:52 michaelh Exp $ 20 */ 21 22 package juju.reattore.server.http; 23 24 import juju.reattore.core.reactor.*; 25 import juju.reattore.protocol.http.*; 26 import juju.reattore.protocol.http.impl.*; 27 import juju.reattore.server.intercept.impl.*; 28 import juju.reattore.server.intercept.Interceptor; 29 import juju.reattore.io.impl.*; 30 import juju.reattore.io.*; 31 import juju.reattore.util.*; 32 33 import java.nio.*; 34 import java.nio.channels.*; 35 import java.io.*; 36 import java.util.*; 37 38 import org.apache.commons.logging.*; 39 40 /*** Handler that knows about the HTTP protocol. 41 */ 42 public class HttpClientHandler 43 implements ClientSocketHandler { 44 45 private static Log log = LogFactory.getLog(HttpClientHandler.class); 46 47 private static final int DEFAULT_IN_CAPACITY = 2048; 48 private static final int DEFAULT_OUT_CAPACITY = 2048; 49 50 private static final int STATE_WRITE_START = 0; 51 private static final int STATE_WRITE_NEXT = 1; 52 53 private int writeState; 54 55 private HttpMediator mediator; 56 57 private SocketChannel ch; 58 private ByteBuffer in; 59 60 private ByteBuffer headers; 61 private ByteSource body; 62 private ByteBuffer bodyPart; 63 private boolean shouldClose; 64 65 /*** List of HttpRequests not yet processed */ 66 private List pending = new ArrayList(); 67 68 private HttpParser parser = new BasicHttpParser(); 69 70 private static DurationStat readStat = new DurationStat(HttpClientHandler.class, "Read"); 71 private static DurationStat parseStat = new DurationStat(HttpClientHandler.class, "Parse"); 72 private static DurationStat processStat = new DurationStat(HttpClientHandler.class, "Process"); 73 private static DurationStat writeStat = new DurationStat(HttpClientHandler.class, "Write"); 74 75 private static RateStat writeRateStat = new RateStat(HttpClientHandler.class, "WriteRate"); 76 private static RateStat serveRateStat = new RateStat(HttpClientHandler.class, "ServeRate"); 77 private static RateStat errorRateStat = new RateStat(HttpClientHandler.class, "ErrorRate"); 78 79 private static GaugeStat concurrentStat = new GaugeStat(HttpClientHandler.class, "Concurrent"); 80 81 private int reqsLeft = 100; 82 83 /*** Create a new HTTP handler. 84 85 @param ch The socket to read and write on 86 @param mediator The mediator to use for any side information. 87 */ 88 public HttpClientHandler(SocketChannel ch, HttpMediator mediator) { 89 concurrentStat.inc(); 90 91 assert ch != null; 92 assert mediator != null; 93 94 this.ch = ch; 95 this.mediator = mediator; 96 97 in = ByteBufferPool.allocate(DEFAULT_IN_CAPACITY); 98 headers = ByteBufferPool.allocate(DEFAULT_OUT_CAPACITY); 99 } 100 101 /*** @see Handler 102 */ 103 public int getInterestOps() { 104 /* HTTP 1.1, so we're always interested in new requests and 105 are often interested in writing responses. 106 */ 107 int ret = SelectionKey.OP_READ; 108 109 if (body != null || pending.size() > 0) { 110 ret |= SelectionKey.OP_WRITE; 111 } 112 113 return ret; 114 } 115 116 private void done() { 117 ByteBufferPool.release(in); 118 in = null; 119 ByteBufferPool.release(headers); 120 headers = null; 121 122 concurrentStat.dec(); 123 } 124 125 /*** @see ClientSocketHandler 126 */ 127 public boolean handleConnected() 128 throws IOException { 129 130 return false; 131 } 132 133 /*** @see ClientSocketHandler 134 */ 135 public boolean handleReadable() 136 throws IOException { 137 138 int got; 139 assert (getInterestOps() & SelectionKey.OP_READ) != 0; 140 141 long ref = readStat.start(); 142 143 in.clear(); 144 got = ch.read(in); 145 146 readStat.end(ref); 147 148 if (got >= 0) { 149 ref = parseStat.start(); 150 in.flip(); 151 pending.addAll(parser.add(new ByteBufferSource(in))); 152 parseStat.end(ref); 153 } 154 else { 155 /* Socket was closed */ 156 ch.close(); 157 done(); 158 return true; 159 } 160 161 return false; 162 } 163 164 private byte[] getBytes(StringBuffer in) { 165 final int len = in.length(); 166 167 byte[] ret = new byte[len]; 168 char[] tmp = new char[len]; 169 170 in.getChars(0, len, tmp, 0); 171 172 for (int i = 0; i < len; i++) { 173 ret[i] = (byte)tmp[i]; 174 } 175 176 return ret; 177 } 178 179 private boolean processNext() 180 throws IOException { 181 182 long ref = processStat.start(); 183 184 assert pending.size() > 0; 185 186 HttpRequest current = (HttpRequest)pending.remove(0); 187 188 String uri = current.getPath(); 189 190 BaseHttpResponse resp = new BaseHttpResponse(); 191 192 if (mediator.getInterceptor().process(current, resp) == false) { 193 ch.close(); 194 done(); 195 return true; 196 } 197 198 processStat.end(ref); 199 200 StringBuffer header = new StringBuffer(384); 201 202 if (resp.getStatus() != HttpResponse.SC_OK) { 203 /* Missed */ 204 log.info("Error " + resp.getStatus() + " on " + uri); 205 206 header.append("HTTP/1.1 " + resp.getStatus() + " Error"); 207 } 208 else { 209 header.append("HTTP/1.1 200 OK"); 210 } 211 212 /* Main logic */ 213 boolean httpVer09 = false; 214 boolean httpVer10 = false; 215 boolean httpVer11 = false; 216 217 shouldClose = false; 218 boolean shouldKeepAlive = false; 219 220 /* Erk. String processing. */ 221 if (current.getVersion().equals("HTTP/1.1")) { 222 httpVer11 = true; 223 /* 1.1 assumes keep alive */ 224 shouldKeepAlive = true; 225 226 /* Scan all of the headers for close messages */ 227 int max = current.getNumHeaders(); 228 229 String val = current.getHeader(HttpConstants.CONNECTION); 230 231 if (val != null && val.equals(HttpConstants.CLOSE)) { 232 shouldClose = true; 233 } 234 } 235 else if (current.getVersion().equals("HTTP/1.0")) { 236 httpVer10 = true; 237 /* By default no keep-alive */ 238 shouldClose = true; 239 240 /* Scan all of the headers for keep-alive messages */ 241 int max = current.getNumHeaders(); 242 243 String val = current.getHeader(HttpConstants.CONNECTION); 244 245 if (val != null && val.equals(HttpConstants.KEEP_ALIVE)) { 246 shouldKeepAlive = true; 247 shouldClose = false; 248 } 249 } 250 else { 251 /* Assume 0.9 */ 252 httpVer09 = true; 253 shouldClose = true; 254 } 255 256 reqsLeft--; 257 258 if (reqsLeft <= 0) { 259 shouldClose = true; 260 } 261 262 body = resp.getBody(); 263 264 header.append("\r\nServer: Reattore/0.9\r\n"); 265 if (shouldClose) { 266 header.append("Connection: close\r\n"); 267 } 268 if (shouldKeepAlive && !shouldClose) { 269 header.append("Keep-Alive: timeout=15, max="); 270 header.append(reqsLeft); 271 header.append("\r\n"); 272 } 273 header.append("Content-Length: "); 274 header.append(body.remaining()); 275 header.append("\r\n"); 276 277 for (Iterator i = resp.getHeaders().iterator(); 278 i.hasNext();) { 279 280 Map.Entry h = (Map.Entry)i.next(); 281 header.append(h.getKey()); 282 header.append(": "); 283 header.append(h.getValue()); 284 header.append("\r\n"); 285 } 286 287 header.append("\r\n"); 288 289 headers.clear(); 290 headers.put(getBytes(header)); 291 headers.flip(); 292 293 writeRateStat.add(body.remaining() + headers.remaining()); 294 serveRateStat.inc(); 295 296 return write(); 297 } 298 299 private boolean write() 300 throws IOException { 301 302 /* Prepare the body part to write */ 303 if (bodyPart != null && bodyPart.remaining() > 0) { 304 /* Have pending data */ 305 } 306 else if (body != null && body.remaining() > 0) { 307 bodyPart = SourceHelper.asByteBuffer(body); 308 } 309 else { 310 /* Nothing remaining */ 311 bodyPart = null; 312 } 313 314 if (headers.remaining() > 0) { 315 if (bodyPart != null) { 316 ch.write(new ByteBuffer[] { headers, bodyPart }); 317 } 318 else { 319 ch.write(headers); 320 } 321 } 322 else { 323 if (bodyPart != null) { 324 ch.write(bodyPart); 325 } 326 else { 327 log.info("Hit empty write"); 328 /* Nothing to do */ 329 } 330 } 331 332 if (bodyPart != null && bodyPart.remaining() == 0) { 333 bodyPart = null; 334 } 335 if (bodyPart == null && body != null && body.remaining() == 0) { 336 body.dispose(); 337 body = null; 338 } 339 340 if (headers.remaining() == 0 && body == null) { 341 /* Done writing */ 342 if (shouldClose) { 343 ch.close(); 344 done(); 345 return true; 346 } 347 } 348 349 return false; 350 } 351 352 private boolean writeBody() 353 throws IOException { 354 355 return false; 356 } 357 358 /*** @see ClientSocketHandler 359 @todo Maximum number of requests in a keep alive session is 360 not configurable. 361 @todo Could run out of space to write the headers. 362 */ 363 public boolean handleWritable() 364 throws IOException { 365 366 if (body != null) { 367 return write(); 368 } 369 else if (pending.size() > 0) { 370 return processNext(); 371 } 372 else { 373 /* Nothing to do - must be a phantom */ 374 return false; 375 } 376 } 377 378 /*** @see ClientSocketHandler */ 379 public void handleError() { 380 errorRateStat.inc(); 381 382 done(); 383 } 384 }

This page was automatically generated by Maven