001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright notice. All rights reserved.
003: */
004: package com.tc.net.protocol;
005:
006: import com.tc.async.api.Sink;
007: import com.tc.bytes.TCByteBuffer;
008: import com.tc.bytes.TCByteBufferFactory;
009: import com.tc.net.core.TCConnection;
010: import com.tc.util.Assert;
011:
012: import java.io.IOException;
013: import java.net.Socket;
014: import java.util.Arrays;
015: import java.util.HashSet;
016: import java.util.Set;
017:
018: /**
019: * Inspects the first few bytes from a socket to decide if it should stay in the TC comms stack, or if it should be
020: * handed off to Jetty. Evolving this switch behaviour to more protocols should probably involve refactoring the generic
021: * comms code, not just this class
022: */
023: public class ProtocolSwitch implements TCProtocolAdaptor {
024:
025: private static final String[] HTTP_METHODS = new String[] { "GET",
026: "POST", "HEAD", "PUT", "OPTIONS", "DELETE", "TRACE",
027: "CONNECT" };
028:
029: private static final Set METHODS = new HashSet(Arrays
030: .asList(HTTP_METHODS));
031:
032: // The longest HTTP method is 7 chars, +1 for the space
033: private static final int INSPECT = 8;
034:
035: private static final int PROTOCOL_UNKNOWN = 0;
036: private static final int PROTOCOL_NOT_HTTP = 1;
037: private static final int PROTOCOL_HTTP = 2;
038:
039: private volatile int protocol = PROTOCOL_UNKNOWN;
040: private final TCByteBuffer[] buffer = new TCByteBuffer[] { TCByteBufferFactory
041: .wrap(new byte[INSPECT]) };
042: private final TCProtocolAdaptor delegate;
043: private final Sink httpSink;
044:
045: public ProtocolSwitch(TCProtocolAdaptor delegate, Sink httpSink) {
046: this .delegate = delegate;
047: this .httpSink = httpSink;
048: }
049:
050: public void addReadData(TCConnection source, TCByteBuffer[] data,
051: int length) throws TCProtocolException {
052: switch (protocol) {
053: case PROTOCOL_NOT_HTTP: {
054: delegate.addReadData(source, data, length);
055: return;
056: }
057: case PROTOCOL_UNKNOWN: {
058: Assert.assertEquals(1, data.length);
059: TCByteBuffer buf = data[0];
060: if (buf.hasRemaining()) {
061: // didn't get enough bytes yet to make a decision
062: return;
063: }
064:
065: buf.flip();
066: boolean isHttp = isHttp(buf);
067: buf.rewind();
068:
069: if (isHttp) {
070: protocol = PROTOCOL_HTTP;
071: final Socket socket;
072: try {
073: socket = source.detach();
074: } catch (IOException e) {
075: throw new TCProtocolException(e);
076: }
077: httpSink.add(new HttpConnectionContext(socket, buf));
078: return;
079: } else {
080: protocol = PROTOCOL_NOT_HTTP;
081: feedDataToDelegate(source, buf);
082: return;
083: }
084: }
085: default:
086: throw new AssertionError("Protocol is " + protocol);
087: }
088:
089: // unreachable
090: }
091:
092: private void feedDataToDelegate(TCConnection source,
093: TCByteBuffer src) throws TCProtocolException {
094: while (src.hasRemaining()) {
095: int count = 0;
096:
097: TCByteBuffer[] readBuffers = delegate.getReadBuffers();
098: for (int i = 0; i < readBuffers.length; i++) {
099: TCByteBuffer dest = readBuffers[i];
100: int len = Math.min(src.remaining(), dest.remaining());
101: count += len;
102: for (int j = 0; j < len; j++) {
103: dest.put(src.get());
104: }
105: if (!src.hasRemaining()) {
106: break;
107: }
108: }
109:
110: delegate.addReadData(source, readBuffers, count);
111: }
112: }
113:
114: private static boolean isHttp(TCByteBuffer buf) {
115: Assert.assertEquals(INSPECT, buf.limit());
116: byte[] bytes = new byte[buf.limit()];
117: buf.get(bytes);
118:
119: final String s;
120: try {
121: s = new String(bytes);
122: } catch (Exception e) {
123: return false;
124: }
125:
126: int spaceIndex = s.indexOf(' ');
127: if (spaceIndex < 0) {
128: return false;
129: }
130:
131: String token = s.substring(0, spaceIndex);
132:
133: // best I can tell, HTTP methods are case sensitive, so I'm not uppercase'ing the token here
134: return METHODS.contains(token);
135: }
136:
137: public TCByteBuffer[] getReadBuffers() {
138: switch (protocol) {
139: case PROTOCOL_NOT_HTTP: {
140: return delegate.getReadBuffers();
141: }
142: case PROTOCOL_UNKNOWN: {
143: return buffer;
144: }
145: default:
146: throw new AssertionError("Protocol is " + protocol);
147: }
148:
149: // unreachable
150: }
151: }
|