com.sun.enterprise.web.connector.grizzly.DefaultReadTask Maven / Gradle / Ivy
The newest version!
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.enterprise.web.connector.grizzly;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Read available data on a non blocking SocketChannel
.
* StreamAlgorithm
stategy will decide if more bytes are required
* or not. Once the StreamAlgorithm
is ready, the
* ProcessorTask
attached to this class will be executed.
*
* @author Scott Oaks
* @author Jean-Francois Arcand
*/
public class DefaultReadTask extends TaskBase implements ReadTask {
private static final Logger logger = Logger.getLogger(DefaultReadTask.class.getName());
/**
* The time in milliseconds before this Task
can stay idle.
* This is only used when the SelectionKey.attach() is used.
*/
private long idleTime = 30 * 1000; // 30 seconds
/**
* The TaskContext
instance associated with this object.
* The TaskContext
is initialized at startup and then recycled.
*/
protected TaskContext taskContext;
/**
* The TaskEvent
instance used by this object to notify its
* listeners
*/
protected TaskEvent taskEvent;
/**
* The ByteBuffer
used by this task to buffer the request
* stream.
*/
protected ByteBuffer byteBuffer;
/**
* The ProcessorTask
used by this class.
*/
protected ProcessorTask processorTask;
/**
* Max post size.
*/
protected int maxPostSize = 25 * 1024 * 1024;
/**
* The recycled OutputStream
used by this buffer.
*/
protected ByteBufferInputStream inputStream;
/**
* The Algorithm
used to parse the request and determine
* of the bytes has been all read from the SocketChannel
*/
protected StreamAlgorithm algorithm;
/**
* true
only when another object has already read bytes
* from the channel.
*/
protected boolean bytesAvailable = false;
/**
* Is the ByteBuffer
used by the ReadTask
use
* direct ByteBuffer
or not.
*/
protected boolean useDirectByteBuffer = true;
/**
* Create view ByteBuffer
from another ByteBuffer
*/
protected boolean useByteBufferView = false;
// ----------------------------------------------------- Constructor ----/
public DefaultReadTask(){
;//
}
public void initialize(StreamAlgorithm algorithm,
boolean useDirectByteBuffer, boolean useByteBufferView){
type = READ_TASK;
this.algorithm = algorithm;
inputStream = new ByteBufferInputStream();
this.useDirectByteBuffer = useDirectByteBuffer;
this.useByteBufferView = useByteBufferView;
}
/**
* Force this task to always use the same ProcessorTask
instance.
*/
public void attachProcessor(ProcessorTask processorTask){
this.processorTask = processorTask;
configureProcessorTask();
}
/**
* Set appropriate attribute on the ProcessorTask
.
*/
protected void configureProcessorTask(){
// Disable blocking keep-alive mechanism. Keep-Alive mechanism
// will be managed by this class instead.
processorTask.setSelectionKey(key);
processorTask.setSocket(((SocketChannel)key.channel()).socket());
processorTask.setHandler(algorithm.getHandler());
processorTask.setDropConnection(
selectorThread.getKeepAlivePipeline().dropConnection(key));
}
/**
* Return the ProcessorTask
to the pool.
*/
public void detachProcessor(){
if (processorTask != null){
processorTask.recycle();
}
// Notify listeners
if ( listeners != null ) {
for (int i=listeners.size()-1; i > -1; i--){
if ( taskEvent == null ) {
taskEvent = new TaskEvent();
}
taskEvent.attach(this);
taskEvent.setStatus(TaskEvent.COMPLETED);
listeners.get(i).taskEvent(taskEvent);
}
clearTaskListeners();
}
if (recycle && processorTask != null){
selectorThread.returnTask(processorTask);
processorTask = null;
}
}
/**
* Read data from the socket and process it using this thread, and only if
* the StreamAlgorith
stategy determine no more bytes are
* are needed.
*/
public void doTask() throws IOException {
if ( byteBuffer == null) {
WorkerThread workerThread = (WorkerThread)Thread.currentThread();
byteBuffer = workerThread.getByteBuffer();
if ( workerThread.getByteBuffer() == null){
byteBuffer = algorithm.allocate(useDirectByteBuffer,
useByteBufferView,selectorThread.getBufferSize());
workerThread.setByteBuffer(byteBuffer);
}
}
doTask(byteBuffer);
}
/**
* Pull data from the socket and store it inside the ByteBuffer
.
* The StreamAlgorithM
implementation will take care of
* determining if we need to register again to the main Selector
* or execute the request using temporary Selector
*
* @param byteBuffer
*/
protected void doTask(ByteBuffer byteBuffer){
int count = 0;
Socket socket = null;
SocketChannel socketChannel = null;
boolean keepAlive = false;
Exception exception = null;
key.attach(null);
try {
socketChannel = (SocketChannel)key.channel();
socket = socketChannel.socket();
algorithm.setSocketChannel(socketChannel);
int loop = 0;
int bufferSize = 0;
while ( socketChannel.isOpen() && (bytesAvailable ||
(count = socketChannel.read(byteBuffer)) > -1)) {
// Avoid calling the Selector.
if ( count == 0 && !bytesAvailable){
loop++;
if (loop > 2){
break;
}
continue;
}
if (bytesAvailable) {
count = byteBuffer.position();
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "DefaultReadTask.doTask [channel=" + socketChannel + ", bytesRead=" + count + ", bytesAvailable= " + bytesAvailable + "]");
if (count > 0) {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "DefaultReadTask.read-content [channel=" + socketChannel
+ " content=" + new String(byteBuffer.array(), byteBuffer.arrayOffset()
+ byteBuffer.position() - count, count) + "]");
}
}
}
bytesAvailable = false;
byteBuffer = algorithm.preParse(byteBuffer);
inputStream.setByteBuffer(byteBuffer);
inputStream.setSelectionKey(key);
// try to predict which HTTP method we are processing
if ( algorithm.parse(byteBuffer) ){
keepAlive = executeProcessorTask();
if (!keepAlive) {
break;
}
} else {
// We must call the Selector since we don't have all the
// bytes
keepAlive = true;
}
}
// Catch IO AND NIO exception
} catch (IOException ex) {
logger.log(Level.FINE, "DefaultReadTask.IOException", ex);
exception = ex;
} catch (RuntimeException ex) {
logger.log(Level.FINE, "DefaultReadTask.RuntimeException", ex);
exception = ex;
} finally {
manageKeepAlive(keepAlive,count,exception);
}
}
/**
* Evaluate if the SelectionKey
needs to be registered to
* the main Selector
*/
protected void manageKeepAlive(boolean keepAlive,int count,
Exception exception){
// The key is invalid when the Task has been cancelled.
if ( count == -1 || !key.isValid() || exception != null ){
keepAlive = false;
if ( exception != null){
// Make sure we have detached the processorTask
detachProcessor();
if (SelectorThread.logger().isLoggable(Level.FINE)){
SelectorThread.logger().log
(Level.FINE,
"SocketChannel Read Exception:",exception);
}
}
}
if (keepAlive) {
registerKey();
}
terminate(keepAlive);
}
/**
* Execute the ProcessorTask
only if the request has
* been fully read. Guest the size of the request by using the
* content-type HTTP headers.
* @return false if the request wasn't fully read by the channel.
* so we need to respin the key on the Selector.
*/
public boolean executeProcessorTask() throws IOException{
boolean registerKey = false;
if (SelectorThread.logger().isLoggable(Level.FINEST))
SelectorThread.logger().log(Level.FINEST,"executeProcessorTask");
if ( algorithm.getHandler() != null && algorithm.getHandler()
.handle(null, Handler.REQUEST_BUFFERED) == Handler.BREAK ){
return true;
}
// Get a processor task. If the processorTask != null, that means we
// failed to load all the bytes in a single channel.read().
if (processorTask == null){
attachProcessor(selectorThread.getProcessorTask());
}
try {
// The socket might not have been read entirely and the parsing
// will fail, so we need to respin another event.
registerKey = processorTask.process(inputStream,null);
} catch (Exception e) {
SelectorThread.logger()
.log(Level.SEVERE,"readTask.processException", e);
}
detachProcessor();
return registerKey;
}
/**
* Return this object to the pool
*/
protected void returnTask(){
if (recycle) {
recycle();
selectorThread.returnTask(this);
}
}
public void taskEvent(TaskEvent event){
if (event.getStatus() == TaskEvent.COMPLETED
|| event.getStatus() == TaskEvent.ERROR){
terminate(processorTask.isKeepAlive());
}
}
/**
* Complete the processing.
*/
public void terminate(boolean keepAlive){
if ( !keepAlive ){
finishConnection();
}
returnTask();
}
/**
* Clear the current state and make this object ready for another request.
*/
public void recycle(){
if (byteBuffer != null){
try{
final WorkerThread workerThread =
(WorkerThread)Thread.currentThread();
if (workerThread.getByteBuffer() == null){
workerThread.setByteBuffer(byteBuffer);
}
} catch (ClassCastException ex){
// Avoid failling if the Grizzly extension doesn't support
// the WorkerThread interface.
if (SelectorThread.logger().isLoggable(Level.FINEST))
SelectorThread.logger().log(Level.FINEST,"recycle",ex);
} finally{
byteBuffer = algorithm.postParse(byteBuffer);
byteBuffer.clear();
}
}
inputStream.recycle();
algorithm.recycle();
key = null;
inputStream.setSelectionKey(null);
byteBuffer = null;
}
// -------------------------------------------------------- TaskEvent ---//
/**
* Cancel the SelectionKey
and close its underlying
* SocketChannel
. Add this Task
to the Keep-Alive
* sub-system.
*/
protected void finishConnection(){
if (SelectorThread.logger().isLoggable(Level.FINEST))
SelectorThread.logger().log(Level.FINEST,"finishConnection");
try{
if (taskContext != null){
taskContext.recycle();
}
} catch (IOException ioe){
;
}
selectorThread.cancelKey(key);
}
/**
* Register the SelectionKey
with the Selector
*/
public void registerKey(){
if (key.isValid()){
if (SelectorThread.logger().isLoggable(Level.FINEST))
SelectorThread.logger().log(Level.FINEST,"registerKey");
selectorThread.registerKey(key);
}
}
// -------------------------------------------------------- getter/setter--/
/**
* Return the associated ProcessorTask
.
* @return the associated ProcessorTask
, null if not used.
*/
public ProcessorTask getProcessorTask(){
return processorTask;
}
/**
* Return the underlying ByteBuffer
used by this class.
*/
public ByteBuffer getByteBuffer(){
if ( byteBuffer == null) {
byteBuffer = algorithm.allocate(useDirectByteBuffer,
useByteBufferView,selectorThread.getBufferSize());
}
return byteBuffer;
}
/**
* Set the underlying ByteBuffer
used by this class.
*/
public void setByteBuffer(ByteBuffer byteBuffer){
this.byteBuffer = byteBuffer;
}
/**
* If the attached byteBuffer was already filled, tell the
* Algorithm to re-use the bytes.
*/
public void setBytesAvailable(boolean bytesAvailable){
this.bytesAvailable = bytesAvailable;
}
/**
* Set the time in milliseconds this Task
is allowed to be idle.
*/
public void setIdleTime(long idleTime){
this.idleTime = idleTime;
}
/**
* Return the time in milliseconds this Task
is allowed to be idle.
*/
public long getIdleTime(){
return idleTime;
}
}