/*
* Created on 02-Jan-2005
* Created by Paul Gardner
* Copyright (C) 2004, 2005, 2006 Aelitis, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/
package org.gudy.azureus2.core3.tracker.server.impl.tcp.nonblocking;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import org.gudy.azureus2.core3.tracker.server.impl.TRTrackerServerImpl;
import org.gudy.azureus2.core3.tracker.server.impl.tcp.TRTrackerServerProcessorTCP;
import org.gudy.azureus2.core3.tracker.server.impl.tcp.TRTrackerServerTCP;
import org.gudy.azureus2.core3.util.AESemaphore;
import org.gudy.azureus2.core3.util.AsyncController;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.SystemTime;
import com.aelitis.azureus.core.networkmanager.VirtualChannelSelector;
/**
* @author parg
*
*/
public abstract class
TRNonBlockingServerProcessor
extends TRTrackerServerProcessorTCP
{
private static final int MAX_POST = 256*1024;
private static final int READ_BUFFER_INITIAL = 1024;
private static final int READ_BUFFER_INCREMENT = 1024;
private static final int READ_BUFFER_LIMIT = 32*1024; // needs to be reasonable size to handle scrapes with plugin generated per-hash content
private SocketChannel socket_channel;
private VirtualChannelSelector.VirtualSelectorListener read_listener;
private VirtualChannelSelector.VirtualSelectorListener write_listener;
private long start_time;
private ByteBuffer read_buffer;
private ByteBuffer post_data_buffer;
private String request_header;
private String lc_request_header;
private ByteBuffer write_buffer;
private boolean keep_alive;
protected
TRNonBlockingServerProcessor(
TRTrackerServerTCP _server,
SocketChannel _socket )
{
super( _server );
socket_channel = _socket;
start_time = SystemTime.getCurrentTime();
read_buffer = ByteBuffer.allocate( READ_BUFFER_INITIAL );
// System.out.println( "create: " + System.currentTimeMillis());
}
protected void
setReadListener(
VirtualChannelSelector.VirtualSelectorListener rl )
{
read_listener = rl;
}
protected VirtualChannelSelector.VirtualSelectorListener
getReadListener()
{
return( read_listener );
}
protected void
setWriteListener(
VirtualChannelSelector.VirtualSelectorListener wl )
{
write_listener = wl;
}
protected VirtualChannelSelector.VirtualSelectorListener
getWriteListener()
{
return( write_listener );
}
// 0 -> complete
// 1 -> more to do
// 2 -> no progress
// -1 -> error
protected int
processRead()
{
if ( post_data_buffer != null ){
try{
int len = socket_channel.read( post_data_buffer );
if ( len < 0 ){
return( -1 );
}
if ( post_data_buffer.remaining() == 0 ){
post_data_buffer.flip();
getServer().runProcessor( this );
return( 0 );
}else{
return( 1 );
}
}catch( IOException e ){
return( -1 );
}
}
if ( read_buffer.remaining() == 0 ){
int capacity = read_buffer.capacity();
if ( capacity == READ_BUFFER_LIMIT ){
return( -1 );
}else{
read_buffer.position(0);
byte[] data = new byte[capacity];
read_buffer.get( data );
read_buffer = ByteBuffer.allocate( capacity + READ_BUFFER_INCREMENT );
read_buffer.put( data );
}
}
try{
int len = socket_channel.read( read_buffer );
// System.out.println( "read op[" + len + "]: " + System.currentTimeMillis());
if ( len < 0 ){
return( -1 );
}else if ( len == 0 ){
return( 2 ); // no progress
}
byte[] data = read_buffer.array();
int array_offset = read_buffer.arrayOffset();
int array_position = array_offset + read_buffer.position();
for (int i=array_offset;i<=array_position-4;i++){
if ( data[i] == CR &&
data[i+1] == FF &&
data[i+2] == CR &&
data[i+3] == FF ){
int header_end = i + 4;
int header_length = header_end - array_offset;
request_header = new String( data, array_offset, header_length );
lc_request_header = request_header.toLowerCase();
int rem = array_position - header_end;
if ( rem == 0 ){
read_buffer = ByteBuffer.allocate( READ_BUFFER_INITIAL );
}else{
read_buffer = ByteBuffer.allocate( rem + READ_BUFFER_INCREMENT );
read_buffer.put( data, header_end, rem );
}
post_data_buffer = null;
int pos1 = lc_request_header.indexOf( "content-length" );
if ( pos1 == -1 ){
if ( lc_request_header.contains( "transfer-encoding" ) &&
lc_request_header.contains( "chunked" )){
Debug.out( "Chunked transfer-encoding not supported!!!!" );
}
}else{
int pos2 = lc_request_header.indexOf( NL, pos1 );
String entry;
if ( pos2 == -1 ){
entry = lc_request_header.substring( pos1 );
}else{
entry = lc_request_header.substring( pos1, pos2 );
}
int pos = entry.indexOf(':');
if ( pos != -1 ){
int content_length = Integer.parseInt( entry.substring( pos+1 ).trim());
if ( content_length > 0 ){
if ( content_length > MAX_POST ){
throw( new IOException( "content-length too large, max=" + MAX_POST ));
}
post_data_buffer = ByteBuffer.allocate( content_length );
int buffer_position = read_buffer.position();
if ( buffer_position > 0 ){
byte[] already_read = new byte[Math.min( buffer_position, content_length )];
read_buffer.flip();
read_buffer.get( already_read );
byte[] xrem = new byte[ read_buffer.remaining()];
read_buffer.get( xrem );
read_buffer = ByteBuffer.allocate( xrem.length + READ_BUFFER_INCREMENT );
read_buffer.put( xrem );
post_data_buffer.put( already_read );
if ( post_data_buffer.remaining() == 0 ){
getServer().runProcessor( this );
return( 0 );
}
}
}
}
}
if ( post_data_buffer == null ){
// System.out.println( "read done: " + System.currentTimeMillis());
getServer().runProcessor( this );
return( 0 );
}else{
return( 1 );
}
}
}
return( 1 );
}catch( IOException e ){
return( -1 );
}
}
// 0 -> complete
// 1 -> more to do
// 2 -> no progress made
// -1 -> error
protected int
processWrite()
{
if ( write_buffer == null ){
return( -1 );
}
if ( !write_buffer.hasRemaining()){
writeComplete();
return( 0 );
}
try{
int written = socket_channel.write( write_buffer );
if ( written == 0 ){
return( 2 );
}
if ( write_buffer.hasRemaining()){
return( 1 );
}
writeComplete();
return( 0 );
}catch( IOException e ){
return( -1 );
}
}
public void
runSupport()
{
boolean async = false;
try{
String url = request_header.substring(request_header.indexOf(' ')).trim();
int pos = url.indexOf( " " );
url = url.substring(0,pos);
final AESemaphore[] went_async = { null };
final ByteArrayOutputStream[] async_stream = { null };
AsyncController async_control =
new AsyncController()
{
public void
setAsyncStart()
{
went_async[0] = new AESemaphore( "async" );
}
public void
setAsyncComplete()
{
went_async[0].reserve();
asyncProcessComplete( async_stream[0] );
}
};
try{
ByteArrayOutputStream response =
process( request_header,
lc_request_header,
url,
(InetSocketAddress)socket_channel.socket().getRemoteSocketAddress(),
TRTrackerServerImpl.restrict_non_blocking_requests,
new ByteArrayInputStream(new byte[0]),
async_control );
// two ways of going async
// 1) return is null and something else will call asyncProcessComplete later
// 2) return is 'not-yet-filled' os and async controller is managing things
if ( response == null ){
async = true;
}else if ( went_async[0] != null ){
async_stream[0] = response;
async = true;
}else{
write_buffer = ByteBuffer.wrap( response.toByteArray());
}
}finally{
if ( went_async[0] != null ){
went_async[0].release();
}
}
}catch( Throwable e ){
}finally{
if ( !async ){
((TRNonBlockingServer)getServer()).readyToWrite( this );
}
}
}
protected abstract ByteArrayOutputStream
process(
String input_header,
String lowercase_input_header,
String url_path,
InetSocketAddress client_address,
boolean announce_and_scrape_only,
InputStream is,
AsyncController async )
throws IOException;
protected void
asyncProcessComplete(
ByteArrayOutputStream response )
{
write_buffer = ByteBuffer.wrap( response.toByteArray());
((TRNonBlockingServer)getServer()).readyToWrite( this );
}
protected SocketChannel
getSocketChannel()
{
return( socket_channel );
}
protected byte[]
getPostData()
{
ByteBuffer result = post_data_buffer;
if ( result == null ){
return( null );
}
post_data_buffer = null;
return( result.array());
}
protected long
getStartTime()
{
return( start_time );
}
protected boolean
getKeepAlive()
{
return( keep_alive );
}
protected void
setKeepAlive(
boolean k )
{
keep_alive = k;
}
public void
interruptTask()
{
}
// overridden if subclass is interested in failures, so don't remove!
protected void
failed()
{
}
protected void
writeComplete()
{
if ( keep_alive ){
// reset timer at end of current request ready for the next one
start_time = SystemTime.getCurrentTime();
}
}
protected void
completed()
{
//System.out.println( "complete: " + System.currentTimeMillis());
}
protected void
closed()
{
//System.out.println( "close: " + System.currentTimeMillis());
}
}