/** * */ package ecologylab.oodss.distributed.server.clientsessionmanager; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.util.HashMap; import java.util.HashSet; import java.util.Queue; import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; import ecologylab.collections.Scope; import ecologylab.generic.StringTools; import ecologylab.oodss.distributed.common.NetworkingConstants; import ecologylab.oodss.distributed.common.ServerConstants; import ecologylab.oodss.distributed.common.SessionObjects; import ecologylab.oodss.distributed.impl.MessageWithMetadata; import ecologylab.oodss.distributed.impl.MessageWithMetadataPool; import ecologylab.oodss.distributed.impl.NIOServerIOThread; import ecologylab.oodss.distributed.server.NIOServerProcessor; import ecologylab.oodss.exceptions.BadClientException; import ecologylab.oodss.messages.RequestMessage; import ecologylab.oodss.messages.ResponseMessage; import ecologylab.oodss.messages.UpdateMessage; import ecologylab.serialization.SIMPLTranslationException; import ecologylab.serialization.SimplTypesScope; import ecologylab.serialization.formatenums.StringFormat; /** * The base class for all ContextManagers, objects that track the state and respond to clients on a * server. There is a one-to-one correspondence between connected clients and ContextManager * instances. * * AbstractContextManager handles all encoding and decoding of messages, as well as translating * them. Hook methods provide places where subclasses may modify behavior for specific purposes. * * Typical usage is to have the context manager's request queue be filled by a network thread, while * it is emptied by a working thread. * * The normal cycle for filling the queue is to call acquireIncomingSequenceBuf() to clear and get * the incomingCharBuffer, then fill it externally (normally passing it as an argument to a * CharsetDecoder.decode call), then calling processIncomingSequenceBufToQueue() to release it and * let the ContextManager store the characters, converting messages into objects as they become * available. * * For a complete, basic implementation (which is suitable for most uses), see * {@link ecologylab.oodss.distributed.server.clientsessionmanager.ClientSessionManager * ContextManager}. * * @author Zachary O. Toups (zach@ecologylab.net) * */ public abstract class TCPClientSessionManager<S extends Scope, PARENT extends Scope> extends BaseSessionManager<S, PARENT> implements ServerConstants { /** * Stores the key-value pairings from a parsed HTTP-like header on an incoming message. */ protected final HashMap<String, String> headerMap = new HashMap<String, String>(); protected int startReadIndex = 0; /** Stores outgoing header character data. */ protected final StringBuilder headerBufOutgoing = new StringBuilder( MAX_HTTP_HEADER_LENGTH); protected final StringBuilder startLine = new StringBuilder( MAX_HTTP_HEADER_LENGTH); /** * The network communicator that will handle all the reading and writing for the socket associated * with this ContextManager */ protected NIOServerIOThread server; /** * The maximum message length allowed for clients that connect to this session manager. Note that * most of the buffers used by AbstractClientManager are mutable in size, and will dynamically * reallocate as necessary if they were initialized to be too small. */ protected int maxMessageSize; /** Used to translate incoming message XML strings into RequestMessages. */ protected SimplTypesScope translationScope; /** * stores the sequence of characters read from the header of an incoming message, may need to * persist across read calls, as the entire header may not be sent at once. */ private final StringBuilder currentHeaderSequence = new StringBuilder(); /** * stores the sequence of characters read from the header of an incoming message and identified as * being a key for a header entry; may need to persist across read calls. */ private final StringBuilder currentKeyHeaderSequence = new StringBuilder(); /** * Tracks the number of bad transmissions from the client; used for determining if a client is * bad. */ private int badTransmissionCount; private int endOfFirstHeader = -1; /** * Counts how many characters still need to be extracted from the incomingMessageBuffer before * they can be turned into a message (based upon the HTTP header). A value of -1 means that there * is not yet a complete header, so no length has been determined (yet). */ private int contentLengthRemaining = -1; /** * Specifies whether or not the current message uses compression. */ private String contentEncoding = "identity"; /** * Set of encoding schemes that the client supports */ private Set<String> availableEncodings = new HashSet<String>(); /** * Stores the first XML message from the incomingMessageBuffer, or parts of it (if it is being * read over several invocations). */ private StringBuilder persistentMessageBuffer = null; private long contentUid = -1; private Inflater inflater = new Inflater(); private Deflater deflater = new Deflater(); /** * A queue of the requests to be performed by this ContextManager. Subclasses may override * functionality and not use requestQueue. */ protected final Queue<MessageWithMetadata<RequestMessage, Object>> requestQueue = new LinkedBlockingQueue<MessageWithMetadata<RequestMessage, Object>>(); protected final MessageWithMetadataPool<RequestMessage, Object> reqPool = new MessageWithMetadataPool<RequestMessage, Object>( 2, 4); protected CharsetDecoder decoder = CHARSET .newDecoder(); protected CharsetEncoder encoder = CHARSET .newEncoder(); private static final String POST_PREFIX = "POST "; private static final String GET_PREFIX = "GET "; /** * Creates a new ContextManager. * * @param sessionId * @param clientSessionScope * TODO * @param maxMessageSizeIn * @param server * @param frontend * @param socket * @param translationScope * @param registry */ public TCPClientSessionManager(String sessionId, int maxMessageSizeIn, NIOServerIOThread server, NIOServerProcessor frontend, SelectionKey socket, SimplTypesScope translationScope, PARENT baseScope) { super(sessionId, frontend, socket, baseScope); this.server = server; this.translationScope = translationScope; // set up session id this.sessionId = sessionId; this.maxMessageSize = maxMessageSizeIn; this.handle = new SessionHandle(this); this.localScope.put(SessionObjects.SESSION_HANDLE, this.handle); this.prepareBuffers(headerBufOutgoing); } /** * Extracts messages from the given CharBuffer, using HTTP-like headers, converting them into * RequestMessage instances, then enqueues those instances. * * enqueueStringMessage will normally be called repeatedly, as new data comes in from a client. It * will automatically parse messages that are split up over multiple reads, and will handle * multiple messages in one read, if necessary. * * @param message * the CharBuffer containing one or more messages, or pieces of messages. */ public synchronized final void processIncomingSequenceBufToQueue(CharBuffer incomingSequenceBuf) throws CharacterCodingException, BadClientException { // debug("incoming: " + incomingSequenceBuf); StringBuilder msgBufIncoming = this.frontend.getSharedStringBuilderPool().acquire(); msgBufIncoming.append(incomingSequenceBuf); try { // look for HTTP header while (msgBufIncoming.length() > 0) { if (endOfFirstHeader == -1) { endOfFirstHeader = this.parseHeader(startReadIndex, msgBufIncoming); } if (endOfFirstHeader == -1) { /* * no header yet; if it's too large, bad client; if it's not too large yet, just exit, * it'll get checked again when more data comes down the pipe */ if (msgBufIncoming.length() > NetworkingConstants.MAX_HTTP_HEADER_LENGTH) { // clear the buffer BadClientException e = new BadClientException( ((SocketChannel) this.socketKey.channel()).socket().getInetAddress() .getHostAddress(), "Maximum HTTP header length exceeded. Read " + msgBufIncoming.length() + "/" + MAX_HTTP_HEADER_LENGTH); msgBufIncoming.setLength(0); throw e; } // next time around, start reading from where we left off this // time startReadIndex = msgBufIncoming.length(); break; } else { // we've read all of the header, and have it loaded into // the map; // now we can use it if (contentLengthRemaining == -1) { try { // handle all header information here; delete it when // done // here String contentLengthString = this.headerMap.get(CONTENT_LENGTH_STRING); contentLengthRemaining = (contentLengthString != null) ? Integer .parseInt(contentLengthString) : 0; String uidString = this.headerMap.get(UNIQUE_IDENTIFIER_STRING); contentUid = (uidString != null) ? Long.parseLong(uidString) : 0; this.contentEncoding = this.headerMap.get(HTTP_CONTENT_CODING); String encodings = this.headerMap.get(HTTP_ACCEPT_ENCODING); if (encodings != null) { String[] encodingList = encodings.split(","); for (String encoding : encodingList) { this.availableEncodings.add(encoding); } } // done with the header text; delete it; header values // will // be retained for later processing by subclasses msgBufIncoming.delete(0, endOfFirstHeader); } catch (NumberFormatException e) { e.printStackTrace(); contentLengthRemaining = -1; } // next time we read the header (the next message), we need // to // start from the beginning startReadIndex = 0; } } /* * we have the end of the header (otherwise we would have broken out earlier). If we don't * have the content length, something bad happened, because it should have been read. */ if (contentLengthRemaining == -1) { /* * if we still don't have the remaining length, then there was a problem */ break; } else if (contentLengthRemaining > maxMessageSize) { throw new BadClientException(((SocketChannel) this.socketKey.channel()).socket() .getInetAddress().getHostAddress(), "Specified content length too large: " + contentLengthRemaining); } try { // see if the incoming buffer has enough characters to // include the specified content length if (persistentMessageBuffer == null) { persistentMessageBuffer = this.frontend.getSharedStringBuilderPool().acquire(); } if (msgBufIncoming.length() >= contentLengthRemaining) { persistentMessageBuffer.append(msgBufIncoming.substring(0, contentLengthRemaining)); msgBufIncoming.delete(0, contentLengthRemaining); // reset to do a new read on the next invocation contentLengthRemaining = -1; endOfFirstHeader = -1; } else { persistentMessageBuffer.append(msgBufIncoming); // indicate that we need to get more from the buffer in // the next invocation contentLengthRemaining -= msgBufIncoming.length(); msgBufIncoming.setLength(0); } } catch (NullPointerException e) { e.printStackTrace(); } if ((contentLengthRemaining == -1)) { /* * if we've read a complete message, then contentLengthRemaining will be reset to -1 */ try { if (this.contentEncoding == null || this.contentEncoding.equals("identity")) { processString(persistentMessageBuffer, contentUid); } else if (contentEncoding.equals(HTTP_DEFLATE_ENCODING)) { try { processString(this.unCompress(persistentMessageBuffer), contentUid); } catch (DataFormatException e) { throw new BadClientException(((SocketChannel) this.socketKey.channel()).socket() .getInetAddress().getHostAddress(), "Content was not encoded properly: " + e.getMessage()); } } else { throw new BadClientException(((SocketChannel) this.socketKey.channel()).socket() .getInetAddress().getHostAddress(), "Content encoding: " + contentEncoding + " not supported!"); } } finally { // clean up: clear the message buffer and the header values this.frontend.getSharedStringBuilderPool().release(persistentMessageBuffer); persistentMessageBuffer = null; this.headerMap.clear(); StringTools.clear(this.startLine); } } } } finally { this.frontend.getSharedStringBuilderPool().release(msgBufIncoming); } } private CharSequence unCompress(StringBuilder firstMessageBuffer) throws CharacterCodingException, DataFormatException { CharBuffer zippingChars = this.frontend.getSharedCharBufferPool().acquire(); ByteBuffer zippingInBytes = this.frontend.getSharedByteBufferPool().acquire(); zippingChars.clear(); firstMessageBuffer.getChars(0, firstMessageBuffer.length(), zippingChars.array(), 0); zippingChars.position(0); zippingChars.limit(firstMessageBuffer.length()); zippingInBytes.clear(); encoder.reset(); encoder.encode(zippingChars, zippingInBytes, true); encoder.flush(zippingInBytes); zippingInBytes.flip(); inflater.reset(); inflater.setInput(zippingInBytes.array(), zippingInBytes.position(), zippingInBytes.limit()); ByteBuffer zippingOutBytes = this.frontend.getSharedByteBufferPool().acquire(); zippingOutBytes.clear(); inflater.inflate(zippingOutBytes.array(), zippingOutBytes.position(), zippingOutBytes.limit()); zippingOutBytes.position(0); zippingOutBytes.limit(inflater.getTotalOut()); this.frontend.getSharedByteBufferPool().release(zippingInBytes); zippingChars.clear(); decoder.reset(); decoder.decode(zippingOutBytes, zippingChars, true); decoder.flush(zippingChars); this.frontend.getSharedByteBufferPool().release(zippingOutBytes); zippingChars.flip(); firstMessageBuffer.setLength(0); firstMessageBuffer.append(zippingChars.array(), 0, zippingChars.limit()); this.frontend.getSharedCharBufferPool().release(zippingChars); return firstMessageBuffer; } /** * Calls processRequest(RequestMessage) on each queued message as they are acquired through * getNextRequest() and finishing when isMessageWaiting() returns false. * * The functionality of processAllMessagesAndSendResponses() may be overridden by overridding the * following methods: isMessageWaiting(), processRequest(RequestMessage), getNextRequest(). * * @throws BadClientException */ public final void processAllMessagesAndSendResponses() throws BadClientException { while (isMessageWaiting()) { this.processNextMessageAndSendResponse(); } } /** * Sets the SelectionKey, and sets the new SelectionKey to have the same attachment (session id) * as the old one. * * @param socket * the socket to set */ public void setSocket(SelectionKey socket) { String sessionId = (String) this.socketKey.attachment(); this.socketKey = socket; this.socketKey.attach(sessionId); } protected abstract void clearOutgoingMessageBuffer(StringBuilder outgoingMessageBuf); protected abstract void clearOutgoingMessageHeaderBuffer(StringBuilder outgoingMessageHeaderBuf); protected abstract void createHeader(int messageSize, StringBuilder outgoingMessageHeaderBuf, RequestMessage incomingRequest, ResponseMessage outgoingResponse, long uid); protected abstract void makeUpdateHeader(int messageSize, StringBuilder headerBufOutgoing, UpdateMessage<?> update); /** * Parses the header of an incoming set of characters (i.e. a message from a client to a server), * loading all of the HTTP-like headers into the given headerMap. * * If headerMap is null, this method will throw a null pointer exception. * * @param allIncomingChars * - the characters read from an incoming stream. * @param headerMap * - the map into which all of the parsed headers will be placed. * @return the length of the parsed header, or -1 if it was not yet found. */ protected int parseHeader(int startChar, StringBuilder allIncomingChars) { // indicates that we might be at the end of the header boolean maybeEndSequence = false; // true if the start line has been found, or if a key has been found // instead boolean noMoreStartLine = false; char currentChar; synchronized (currentHeaderSequence) { StringTools.clear(currentHeaderSequence); StringTools.clear(currentKeyHeaderSequence); int length = allIncomingChars.length(); for (int i = 0; i < length; i++) { currentChar = allIncomingChars.charAt(i); switch (currentChar) { case (':'): /* * we have the end of a key; move the currentHeaderSequence into the * currentKeyHeaderSequence and clear it */ currentKeyHeaderSequence.append(currentHeaderSequence); StringTools.clear(currentHeaderSequence); noMoreStartLine = true; break; case ('\r'): /* * we have the end of a line; if there's a CRLF, then we have the end of the value * sequence or the end of the header. */ if (allIncomingChars.charAt(i + 1) == '\n') { if (!maybeEndSequence) { if (noMoreStartLine) { // load the key/value pair headerMap.put(currentKeyHeaderSequence.toString().toLowerCase(), currentHeaderSequence.toString().trim()); } else { // we potentially have data w/o a key-value pair; this // is the start-line of an HTTP header StringTools.clear(startLine); this.startLine.append(currentHeaderSequence); noMoreStartLine = true; } StringTools.clear(currentKeyHeaderSequence); StringTools.clear(currentHeaderSequence); i++; // so we don't re-read that last character } else { // end of the header return i + 2; } maybeEndSequence = true; } break; default: currentHeaderSequence.append(currentChar); maybeEndSequence = false; break; } } // if we got here, we didn't finish the header return -1; } } protected abstract void prepareBuffers(StringBuilder outgoingMessageHeaderBuf); protected abstract void translateResponseMessageToStringBufferContents( RequestMessage requestMessage, ResponseMessage responseMessage, StringBuilder messageBuffer) throws SIMPLTranslationException; /** * Translates the given XML String into a RequestMessage object. * * translateStringToRequestMessage(String) may be overridden to provide specific functionality, * such as a ContextManager that does not use XML Strings. * * @param messageCharSequence * - an XML String representing a RequestMessage object. * @return the RequestMessage created by translating messageString into an object. * @throws SIMPLTranslationException * if an error occurs when translating from XML into a RequestMessage. * @throws UnsupportedEncodingException * if the String is not encoded properly. */ protected RequestMessage translateStringToRequestMessage(CharSequence messageCharSequence) throws SIMPLTranslationException, UnsupportedEncodingException { String startLineString = null; // debug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); // debug(this.startLine); if (this.startLine == null || (startLineString = startLine.toString()).equals("")) { // normal case return translateOODSSRequest(messageCharSequence, startLineString); } else if (startLineString.startsWith(GET_PREFIX)) { // get case // debug("GET case!"); return this.translateGetRequest(messageCharSequence, startLineString); } else if (startLineString.startsWith(POST_PREFIX)) { // post case return translatePostRequest(messageCharSequence, startLineString); } else { // made of fail case return translateOtherRequest(messageCharSequence, startLineString); } } /** * Translates an incoming character sequence identified to be an OODSS request message (not a GET * or POST request). * * @param messageCharSequence * @param startLineString * TODO * @return The request message contained in the message. * @throws SIMPLTranslationException */ protected RequestMessage translateOODSSRequest(CharSequence messageCharSequence, String startLineString) throws SIMPLTranslationException { return (RequestMessage) translationScope.deserialize(messageCharSequence, StringFormat.XML); } /** * Translates an incoming character sequence identified to be a GET request. * * This implementation returns null. * * @param messageCharSequence * @param startLineString * TODO * @return null. */ protected RequestMessage translateGetRequest(CharSequence messageCharSequence, String startLineString) throws SIMPLTranslationException { return null; } /** * Translates an incoming character sequence identified to be a POST request. * * This implementation expects the POST request to contain a nested OODSS request. * * @param messageCharSequence * @param startLineString * TODO * @return * @throws SIMPLTranslationException */ protected RequestMessage translatePostRequest(CharSequence messageCharSequence, String startLineString) throws SIMPLTranslationException { String messageString = messageCharSequence.toString(); if (!messageString.startsWith("<")) messageString = messageString.substring(messageString.indexOf('=') + 1); return this.translateOODSSRequest(messageString, startLineString); } /** * Translates an incoming character sequence that cannot be identified. Called when the first line * of the request is not empty, not GET, and not POST. * * This implementation returns null. * * @param startLineString * TODO * * @return null. */ protected RequestMessage translateOtherRequest(CharSequence messageCharSequence, String startLineString) throws SIMPLTranslationException { return null; } /** * Adds the given request to this's request queue. * * enqueueRequest(RequestMessage) is a hook method for ContextManagers that need to implement * other functionality, such as prioritizing messages. * * If enqueueRequest(RequestMessage) is overridden, the following methods should also be * overridden: isMessageWaiting(), getNextRequest(). * * @param request */ protected void enqueueRequest(MessageWithMetadata<RequestMessage, Object> request) { messageWaiting = this.requestQueue.offer(request); } /** * Returns the next message in the request queue. * * getNextRequest() may be overridden to provide specific functionality, such as a priority queue. * In this case, it is important to override the following methods: isMessageWaiting(), * enqueueRequest(). * * @return the next message in the requestQueue. */ protected MessageWithMetadata<RequestMessage, Object> getNextRequest() { synchronized (requestQueue) { int queueSize = requestQueue.size(); if (queueSize == 1) { messageWaiting = false; } // return null if none left, or the next Request otherwise return requestQueue.poll(); } } /** * Calls processRequest(RequestMessage) on the result of getNextRequest(). * * In order to override functionality processRequest(RequestMessage) and/or getNextRequest() * should be overridden. * */ private final void processNextMessageAndSendResponse() { this.processRequest(this.getNextRequest()); } /** * Calls performService(requestMessage), then converts the resulting ResponseMessage into a * String, adds the HTTP-like headers, and passes the completed String to the server backend for * sending to the client. * * @param request * - the request message to process. */ protected final ResponseMessage processRequest( MessageWithMetadata<RequestMessage, Object> requestWithMetadata) { RequestMessage request = requestWithMetadata.getMessage(); ResponseMessage response = super.processRequest(request, ((SocketChannel) this.socketKey .channel()).socket().getInetAddress()); if (response != null) { // if the response is null, then we do // nothing else sendResponseToClient(requestWithMetadata, response, request); } else { debug("context manager did not produce a response message."); } requestWithMetadata = reqPool.release(requestWithMetadata); return response; } private synchronized void sendResponseToClient( MessageWithMetadata<RequestMessage, Object> requestWithMetadata, ResponseMessage response, RequestMessage request) { StringBuilder msgBufOutgoing = this.frontend.getSharedStringBuilderPool().acquire(); try { // setup outgoingMessageBuffer this.translateResponseMessageToStringBufferContents(request, response, msgBufOutgoing); } catch (SIMPLTranslationException e1) { e1.printStackTrace(); } try { ByteBuffer compressedMessageBuffer = null; CharBuffer outgoingChars = this.frontend.getSharedCharBufferPool().acquire(); boolean usingCompression = this.availableEncodings.contains(HTTP_DEFLATE_ENCODING); /* * If Compressing must know the length of the data being sent so must compress here */ if (usingCompression) { compressedMessageBuffer = this.frontend.getSharedByteBufferPool().acquire(); compressedMessageBuffer.clear(); this.compress(msgBufOutgoing, compressedMessageBuffer); compressedMessageBuffer.flip(); this.clearOutgoingMessageBuffer(msgBufOutgoing); } this.clearOutgoingMessageHeaderBuffer(headerBufOutgoing); // setup outgoingMessageHeaderBuffer this.createHeader((usingCompression) ? compressedMessageBuffer.limit() : msgBufOutgoing .length(), headerBufOutgoing, request, response, requestWithMetadata.getUid()); if (usingCompression) { headerBufOutgoing.append(HTTP_HEADER_LINE_DELIMITER); headerBufOutgoing.append(HTTP_CONTENT_CODING); headerBufOutgoing.append(":"); headerBufOutgoing.append(HTTP_DEFLATE_ENCODING); } headerBufOutgoing.append(HTTP_HEADER_TERMINATOR); // move the characters from the outgoing buffers into // outgoingChars using bulk get and put methods outgoingChars.clear(); headerBufOutgoing.getChars(0, headerBufOutgoing.length(), outgoingChars.array(), 0); if (!usingCompression) { // assert that the size of the message is smaller than the buffer. // if not, increase the buffer. if (msgBufOutgoing.length() + headerBufOutgoing.length() > outgoingChars.capacity()) { int newCapacity = outgoingChars.capacity()*2; this.frontend.increaseSharedBufferPoolSize(newCapacity); outgoingChars = this.frontend.getSharedCharBufferPool().acquire(); } msgBufOutgoing.getChars(0, msgBufOutgoing.length(), outgoingChars.array(), headerBufOutgoing.length()); outgoingChars.limit(headerBufOutgoing.length() + msgBufOutgoing.length()); this.clearOutgoingMessageBuffer(msgBufOutgoing); } else { outgoingChars.limit(headerBufOutgoing.length()); } outgoingChars.position(0); ByteBuffer outgoingBuffer = this.server.acquireByteBufferFromPool(); synchronized (encoder) { encoder.reset(); encoder.encode(outgoingChars, outgoingBuffer, true); encoder.flush(outgoingBuffer); } this.frontend.getSharedCharBufferPool().release(outgoingChars); if (usingCompression) { outgoingBuffer.put(compressedMessageBuffer); this.frontend.getSharedByteBufferPool().release(compressedMessageBuffer); } server.enqueueBytesForWriting(this.socketKey, outgoingBuffer); } catch (DataFormatException e) { debug("Failed to compress response!"); e.printStackTrace(); } finally { this.frontend.getSharedStringBuilderPool().release(msgBufOutgoing); } // debug("...done ("+(System.currentTimeMillis()-currentTime)+"ms)"); } @Override public synchronized void sendUpdateToClient(UpdateMessage<?> update) { StringBuilder msgBufOutgoing = this.frontend.getSharedStringBuilderPool().acquire(); if (this.isInvalidating()) { return; } try { // setup outgoingMessageBuffer SimplTypesScope.serialize(update, msgBufOutgoing, StringFormat.XML); } catch (SIMPLTranslationException e1) { e1.printStackTrace(); } try { ByteBuffer compressedMessageBuffer = null; CharBuffer outgoingChars = this.frontend.getSharedCharBufferPool().acquire(); boolean usingCompression = this.availableEncodings.contains(HTTP_DEFLATE_ENCODING); /* * If Compressing must know the length of the data being sent so must compress here */ if (usingCompression) { compressedMessageBuffer = this.frontend.getSharedByteBufferPool().acquire(); compressedMessageBuffer.clear(); this.compress(msgBufOutgoing, compressedMessageBuffer); compressedMessageBuffer.flip(); this.clearOutgoingMessageBuffer(msgBufOutgoing); } this.clearOutgoingMessageHeaderBuffer(headerBufOutgoing); // setup outgoingMessageHeaderBuffer this.makeUpdateHeader((usingCompression) ? compressedMessageBuffer.limit() : msgBufOutgoing .length(), headerBufOutgoing, update); if (usingCompression) { headerBufOutgoing.append(HTTP_HEADER_LINE_DELIMITER); headerBufOutgoing.append(HTTP_CONTENT_CODING); headerBufOutgoing.append(":"); headerBufOutgoing.append(HTTP_DEFLATE_ENCODING); } headerBufOutgoing.append(HTTP_HEADER_TERMINATOR); // move the characters from the outgoing buffers into // outgoingChars using bulk get and put methods outgoingChars.clear(); headerBufOutgoing.getChars(0, headerBufOutgoing.length(), outgoingChars.array(), 0); if (!usingCompression) { msgBufOutgoing.getChars(0, msgBufOutgoing.length(), outgoingChars.array(), headerBufOutgoing.length()); outgoingChars.limit(headerBufOutgoing.length() + msgBufOutgoing.length()); this.clearOutgoingMessageBuffer(msgBufOutgoing); } else { outgoingChars.limit(headerBufOutgoing.length()); } outgoingChars.position(0); ByteBuffer outgoingBuffer = this.server.acquireByteBufferFromPool(); synchronized (encoder) { encoder.reset(); encoder.encode(outgoingChars, outgoingBuffer, true); encoder.flush(outgoingBuffer); } this.frontend.getSharedCharBufferPool().release(outgoingChars); if (usingCompression) { outgoingBuffer.put(compressedMessageBuffer); this.frontend.getSharedByteBufferPool().release(compressedMessageBuffer); } server.enqueueBytesForWriting(this.socketKey, outgoingBuffer); } catch (DataFormatException e) { debug("Failed to compress update!"); e.printStackTrace(); } finally { this.frontend.getSharedStringBuilderPool().release(msgBufOutgoing); } } private void compress(StringBuilder src, ByteBuffer dest) throws DataFormatException { CharBuffer zippingChars = this.frontend.getSharedCharBufferPool().acquire(); zippingChars.clear(); src.getChars(0, src.length(), zippingChars.array(), 0); zippingChars.position(0); zippingChars.limit(src.length()); ByteBuffer zippingInBytes = this.frontend.getSharedByteBufferPool().acquire(); zippingInBytes.clear(); encoder.reset(); encoder.encode(zippingChars, zippingInBytes, true); encoder.flush(zippingInBytes); this.frontend.getSharedCharBufferPool().release(zippingChars); zippingInBytes.flip(); deflater.reset(); deflater.setInput(zippingInBytes.array(), zippingInBytes.position(), zippingInBytes.limit()); deflater.finish(); this.frontend.getSharedByteBufferPool().release(zippingInBytes); dest.position(dest.position() + deflater.deflate(dest.array(), dest.position(), dest.remaining())); } /** * Takes an incoming message in the form of an XML String and converts it into a RequestMessage * using translateStringToRequestMessage(String). Then places the RequestMessage on the * requestQueue using enqueueRequest(). * * @param incomingMessage * @param headerMap2 * @throws BadClientException */ private final void processString(CharSequence incomingMessage, long incomingUid) throws BadClientException { Exception failReason = null; RequestMessage request = null; try { request = this.translateStringToRequestMessage(incomingMessage); } catch (SIMPLTranslationException e) { // drop down to request == null, below failReason = e; } catch (UnsupportedEncodingException e) { // drop down to request == null, below failReason = e; } if (request == null) { if (incomingMessage.length() > 100) { debug("ERROR; incoming message could not be translated: " + incomingMessage.toString()); debug("HEADERS:"); debug(headerMap.toString()); if (failReason != null) { debug("EXCEPTION: " + failReason.getMessage()); failReason.printStackTrace(); } } else { debug("ERROR; incoming message could not be translated: " + incomingMessage.toString()); debug("HEADERS:"); debug(headerMap.toString()); if (failReason != null) { debug("EXCEPTION: " + failReason.getMessage()); failReason.printStackTrace(); } } if (++badTransmissionCount >= MAXIMUM_TRANSMISSION_ERRORS) { throw new BadClientException(((SocketChannel) this.socketKey.channel()).socket() .getInetAddress().getHostAddress(), "Too many Bad Transmissions: " + badTransmissionCount); } // else error("translation failed: badTransmissionCount=" + badTransmissionCount); } else { badTransmissionCount = 0; MessageWithMetadata<RequestMessage, Object> pReq = this.reqPool.acquire(); pReq.setMessage(request); pReq.setUid(incomingUid); synchronized (requestQueue) { this.enqueueRequest(pReq); } } } @Override public InetSocketAddress getAddress() { return (InetSocketAddress) ((SocketChannel) getSocketKey().channel()).socket() .getRemoteSocketAddress(); } public SessionHandle getHandle() { return handle; } }