/*
* Copyright (c) 2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.jetty.spdy;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.nio.AsyncConnection;
import org.eclipse.jetty.io.nio.DirectNIOBuffer;
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
import org.eclipse.jetty.io.nio.NIOBuffer;
import org.eclipse.jetty.spdy.api.Handler;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.parser.Parser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SPDYAsyncConnection extends AbstractConnection implements AsyncConnection, Controller<StandardSession.FrameBytes>, IdleListener
{
private static final Logger logger = LoggerFactory.getLogger(SPDYAsyncConnection.class);
private final ByteBufferPool bufferPool;
private final Parser parser;
private volatile Session session;
private ByteBuffer writeBuffer;
private Handler<StandardSession.FrameBytes> writeHandler;
private StandardSession.FrameBytes writeContext;
private volatile boolean writePending;
public SPDYAsyncConnection(AsyncEndPoint endPoint, ByteBufferPool bufferPool, Parser parser)
{
super(endPoint);
this.bufferPool = bufferPool;
this.parser = parser;
onIdle(true);
}
@Override
public Connection handle() throws IOException
{
AsyncEndPoint endPoint = getEndPoint();
boolean progress = true;
while (endPoint.isOpen() && progress)
{
int filled = fill();
progress = filled > 0;
int flushed = flush();
progress |= flushed > 0;
endPoint.flush();
progress |= endPoint.hasProgressed();
if (!progress && filled < 0)
{
onInputShutdown();
close(false);
}
}
return this;
}
public int fill() throws IOException
{
ByteBuffer buffer = bufferPool.acquire(8192, true);
NIOBuffer jettyBuffer = new DirectNIOBuffer(buffer, false);
jettyBuffer.setPutIndex(jettyBuffer.getIndex());
AsyncEndPoint endPoint = getEndPoint();
int filled = endPoint.fill(jettyBuffer);
logger.debug("Filled {} from {}", filled, endPoint);
if (filled <= 0)
return filled;
buffer.limit(jettyBuffer.putIndex());
buffer.position(jettyBuffer.getIndex());
parser.parse(buffer);
bufferPool.release(buffer);
return filled;
}
public int flush()
{
int result = 0;
// Volatile read to ensure visibility of buffer and handler
if (writePending)
result = write(writeBuffer, writeHandler, writeContext);
logger.debug("Flushed {} to {}", result, getEndPoint());
return result;
}
@Override
public int write(ByteBuffer buffer, Handler<StandardSession.FrameBytes> handler, StandardSession.FrameBytes context)
{
int remaining = buffer.remaining();
Buffer jettyBuffer = buffer.isDirect() ? new DirectNIOBuffer(buffer, false) : new IndirectNIOBuffer(buffer, false);
AsyncEndPoint endPoint = getEndPoint();
try
{
int written = endPoint.flush(jettyBuffer);
logger.debug("Written {} bytes, {} remaining", written, jettyBuffer.length());
}
catch (Exception x)
{
close(false);
handler.failed(x);
}
finally
{
buffer.limit(jettyBuffer.putIndex());
buffer.position(jettyBuffer.getIndex());
}
if (buffer.hasRemaining())
{
// Save buffer and handler in order to finish the write later in flush()
this.writeBuffer = buffer;
this.writeHandler = handler;
this.writeContext = context;
// Volatile write to ensure visibility of write fields
writePending = true;
endPoint.scheduleWrite();
}
else
{
if (writePending)
{
this.writeBuffer = null;
this.writeHandler = null;
this.writeContext = null;
// Volatile write to ensure visibility of write fields
writePending = false;
}
handler.completed(context);
}
return remaining - buffer.remaining();
}
@Override
public void close(boolean onlyOutput)
{
try
{
AsyncEndPoint endPoint = getEndPoint();
try
{
// We need to gently close first, to allow
// SSL close alerts to be sent by Jetty
logger.debug("Shutting down output {}", endPoint);
endPoint.shutdownOutput();
if (!onlyOutput)
{
logger.debug("Closing {}", endPoint);
endPoint.close();
}
}
catch (IOException x)
{
endPoint.close();
}
}
catch (IOException x)
{
logger.trace("", x);
}
}
@Override
public void onIdle(boolean idle)
{
getEndPoint().setCheckForIdle(idle);
}
@Override
public AsyncEndPoint getEndPoint()
{
return (AsyncEndPoint)super.getEndPoint();
}
@Override
public boolean isIdle()
{
return false;
}
@Override
public boolean isSuspended()
{
return false;
}
@Override
public void onClose()
{
}
@Override
public void onInputShutdown() throws IOException
{
}
@Override
public void onIdleExpired(long idleForMs)
{
session.goAway();
}
protected Session getSession()
{
return session;
}
protected void setSession(Session session)
{
this.session = session;
}
}