package com.limegroup.gnutella.downloader;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.limegroup.gnutella.Assert;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.altlocs.AlternateLocation;
import com.limegroup.gnutella.altlocs.AlternateLocationCollection;
import com.limegroup.gnutella.altlocs.PushAltLoc;
import com.limegroup.gnutella.filters.IPFilter;
import com.limegroup.gnutella.http.HTTPHeaderName;
import com.limegroup.gnutella.http.HTTPUtils;
import com.limegroup.gnutella.settings.FilterSettings;
import com.limegroup.gnutella.util.AssertComparisons;
import com.limegroup.gnutella.util.BandwidthThrottle;
import com.limegroup.gnutella.util.IntPair;
import com.limegroup.gnutella.util.PrivilegedAccessor;
import com.limegroup.gnutella.util.RoundRobinQueue;
/**
* Callback for whenever this uploader starts or finishes to serve
* an http11 request.
*/
interface HTTP11Listener {
public void thexRequestStarted();
public void thexRequestHandled();
public void requestStarted(TestUploader uploader);
public void requestHandled();
}
public class TestUploader extends AssertComparisons {
private static final Log LOG = LogFactory.getLog(TestUploader.class);
/** My name, for debugging */
private final String name;
/** Number of bytes uploaded */
private volatile int fullRequestsUploaded;
/** The number of connections received */
private int connects=0;
/** The maximum number of connect attempts */
private int maxConnects = Integer.MAX_VALUE;
/** The last request sent, e.g., "GET /get/0/file.txt HTTP/1.0" */
private String request=null;
/** The throttle rate in kilobytes/sec */
private volatile float rate;
/**The number of bytes this uploader uploads before dying*/
private volatile int stopAfter;
/** This is stopped. */
private boolean stopped;
/** switch to send incorrect bytes to simulate a bad uploader*/
private boolean sendCorrupt;
/** the boundary between stop/start to send corrupt bytes */
private float corruptPercentage;
private AlternateLocationCollection storedGoodLocs,storedBadLocs;
public List incomingGoodAltLocs, incomingBadAltLocs;
private URN _sha1;
private boolean http11 = true;
private ServerSocket server;
private Socket socket;
private boolean busy = false;
private int retryAfter = -1;
private int timesBusy = Integer.MAX_VALUE;
//Note about queue testing: This is how the queuing simulation works: If
//queue is set, the uploader sends the X-Queue header etc in handleRequest
//method, and sets values for minPollTime and maxPollTime. In the loop
//method, if queue was set, when handleRequest returns, queue is set to
//false and handleRequest is called one more time. This time handleRequest
//will upload the file normally, but it will also check that the downloader
//1. Kept the socket open 2. Responsed within the given time range.
//3. completed the download after coming out of the queue.
private boolean queue = false;
private long minPollTime = -1;
private long maxPollTime = -1;
final int MIN_POLL = 45000;
private final int MAX_POLL = 120000;
private int partial = 0;
private Long creationTime = null;
boolean unqueue = true;
volatile int queuePos = 1;
boolean killedByDownloader = false;
int start,stop;
/**
* The offset for the low chunk.
*/
private int lowChunkOffset = 0;
/**
* The offset for the high chunk.
*/
private int highChunkOffset = 0;
/**
* The number of requests this uploader has received.
*/
private int requestsReceived = 0;
/**
* Whether or not this uploader should respond with HTTP/1.1
*/
private boolean respondWithHTTP11 = true;
/**
* Whether or not we'll include the THEX-Tree header in our response.
*/
private boolean sendThexTreeHeader = false;
/**
* Whether or not we'll include the THEX-Tree in our response.
*/
private boolean sendThexTree = false;
/**
* Whether or not thex was requested.
*/
private boolean thexWasRequested = false;
/**
* Whether or not to send the content length in the response.
*/
private boolean sendContentLength = true;
/**
* If we should queue when thex is requested.
*/
private boolean queueOnThex = false;
/**
* Whether or not we should use a bad THEX response header.
*/
private boolean useBadThexResponseHeader = false;
/**
* whether or not we are interested in receiving push locs
*/
private boolean interestedInFalts = false;
/**
* whether we are firewalled
*/
private boolean isFirewalled = false;
/**
* whether we stall while writing headers
*/
private boolean stallHeaders = false;
/**
* Use this to throttle sending our data
*/
private BandwidthThrottle throttle;
/**
* a callback to notify every time we start an http11 request
*/
HTTP11Listener _httpListener;
/**
* The sum of the number of bytes we need to upload across all requests. If
* this value is less than totalUploaded and the uploader encountered an
* IOException in handle request it means the downloader killed the
* connection.
*/
int totalAmountToUpload;
/**
* <tt>IPFilter</tt> for only allowing local connections.
*/
private final IPFilter IP_FILTER = IPFilter.instance();
/**
* String to send if we are writing the X-Push-Proxies header
*/
private String _proxiesString;
/**
* Creates a TestUploader listening on the given port. Will upload a
* special test file to any requesters via HTTP. Non-blocking; starts
* another thread to do the listening.
*/
public TestUploader(String name, final int port) {
super(name);
// ensure that only local machines can connect!!
FilterSettings.BLACK_LISTED_IP_ADDRESSES.setValue(
new String[] {"*.*.*.*"});
FilterSettings.WHITE_LISTED_IP_ADDRESSES.setValue(
new String[] {"127.*.*.*"});
this.name=name;
reset();
try {
server = new ServerSocket();
//Use Java 1.4's option to reuse a socket address. This is
//important because some client thread may be using the given port
//even though no threads are listening on the given socket.
server.setReuseAddress(true);
server.bind(new InetSocketAddress(port));
} catch (IOException e) {
LOG.debug("Couldn't bind socket to port "+port+"\n");
//System.out.println("Couldn't listen on port "+port);
ErrorService.error(e);
return;
}
//spawn loop();
Thread t = new Thread() {
public void run() {
try {
loop(port);
} catch(Throwable t) {
ErrorService.error(t);
}
}
};
t.setDaemon(true);
t.start();
}
public TestUploader(String name) throws IOException{
super(name);
this.name=name;
reset();
LOG.debug("starting to handle request with direct socket given");
Thread t = new Thread(name) {
public void run() {
synchronized(TestUploader.this) {
try{
while(socket==null) {LOG.debug("socket is null");
TestUploader.this.wait();
}
}catch(InterruptedException hmm) {
ErrorService.error(hmm);
}
}
Runnable r = new SocketHandler(socket);
r.run();
}
};
t.start();
}
public synchronized void setSocket(Socket s) {
LOG.debug("setting socket");
socket=s;
notify();
}
public void stopThread() {
LOG.debug("stopping thread");
try {
if ( server != null )
server.close();
} catch (IOException e) {}
}
/**
* Resets the rate, amount uploaded, stop byte, etc.
*/
public void reset() {
LOG.debug("resetting uploader "+name);
storedGoodLocs = null;//new AlternateLocationCollection();
storedBadLocs = null;
incomingGoodAltLocs = null;//new AlternateLocationCollection();
fullRequestsUploaded = 0;
stopAfter = -1;
rate = 10000;
stopped = false;
sendCorrupt = false;
corruptPercentage = 0;
busy = false;
retryAfter = -1;
timesBusy = Integer.MAX_VALUE;
queue = false;
partial = 0;
minPollTime = -1;
maxPollTime = -1;
unqueue = true;
queuePos=1;
killedByDownloader = false;
totalAmountToUpload = 0;
requestsReceived = 0;
connects = 0;
maxConnects = Integer.MAX_VALUE;
lowChunkOffset = 0;
highChunkOffset = 0;
respondWithHTTP11 = true;
sendThexTreeHeader = false;
sendThexTree = false;
thexWasRequested = false;
sendContentLength = true;
queueOnThex = false;
useBadThexResponseHeader = false;
_httpListener = null;
incomingBadAltLocs = new ArrayList();
incomingGoodAltLocs = new ArrayList();
}
public int fullRequestsUploaded() {
return fullRequestsUploaded;
}
public int getAmountUploaded() {
return fullRequestsUploaded+amountThisRequest;
}
/** Sets the upload throttle rate
* Note: Even if the rate is set to zero the send method will send atleast
* one byte per second, in order to detect socket closes.
* @param rate kilobytes/sec.
*/
public void setRate(float rate) {
this.rate=rate;
}
/**
* Determine how many times this test uploader should respond
* with a busy status.
*/
public void setTimesBusy(int nTimes) {
this.timesBusy = nTimes;
}
/**
* Determines what should be sent in the Retry-After header
*/
public void setRetryAfter(int seconds) {
this.retryAfter = seconds;
}
/**
* Sets this test uploader to be busy.
* Use setTimesBusy to set the number of times this
* uploader should respond as busy.
* By default it will respond Integer.MAX_VALUE times.
*/
public void setBusy(boolean busy) {
this.busy = busy;
}
public void setQueue(boolean q) {
this.queue = q;
}
public void setCreationTime(Long l) {
this.creationTime = l;
}
public void setPartial(boolean part) {
if(part)
this.partial = 1;
else
this.partial = 0;//reset
}
/** Sets whether this should send bad data. */
public void setCorruption(boolean corrupt) {
this.sendCorrupt = corrupt;
}
/**
* Sets the corrupt percentage of the corrupted bytes.
*/
public void setCorruptPercentage(float num) {
this.corruptPercentage = num;
}
/**
* Sets the number of bytes that this should send. This lets the user
* simulate a broken upload.
* @param n the number of bytes to send, or -1 if no limit
*/
public void stopAfter(int n) {
this.stopAfter = n;
}
/**
* Store the alternate locations that this uploader knows about.
* @param alts the alternate locations
*/
public void setGoodAlternateLocations(AlternateLocationCollection alts) {
storedGoodLocs = alts;
}
public void setBadAlternateLocations(AlternateLocationCollection alts) {
storedBadLocs = alts;
}
/**
* Sets the offset for the low chunk.
*/
public void setLowChunkOffset(int offset) {
lowChunkOffset = offset;
}
/**
* Sets the offset for the high chunk.
*/
public void setHighChunkOffset(int offset) {
highChunkOffset = offset;
}
/**
* Sets whether or not this uploader will respond with HTTP/1.1
*/
public void setHTTP11(boolean yes) {
respondWithHTTP11 = yes;
}
public void setHTTPListener(HTTP11Listener listener) {
_httpListener = listener;
}
/**
* Sets whether or not we'll send the thex tree header in our response.
*/
public void setSendThexTreeHeader(boolean yes) {
sendThexTreeHeader = yes;
}
/**
* Sets whether or not we'll send the thex tree in our response.
*/
public void setSendThexTree(boolean yes) {
sendThexTree = yes;
}
/**
* Determiens whether or not thex was requested this time.
*/
public boolean thexWasRequested() {
return thexWasRequested;
}
/**
* Sets whether or not to send the content length in the response.
*/
public void setSendContentLength(boolean yes) {
sendContentLength = yes;
}
/**
* Sets whether or not to queue on the thex request.
*/
public void setQueueOnThex(boolean yes) {
queueOnThex = yes;
}
/**
* Sets whether or not the uploader should receive falts
*/
public void setInterestedInFalts(boolean yes) {
interestedInFalts=yes;
}
/**
* sets whether the uploader is firewalled, which affects headers written
*/
public void setFirewalled(boolean yes) {
isFirewalled=yes;
}
/**
* sets which proxies we should write in the proxies header
*/
public void setProxiesString(String str) {
_proxiesString=str;
}
/**
* Sets whether or not we'll use a bad thex response header.
*/
public void setUseBadThexResponseHeader(boolean yes ) {
useBadThexResponseHeader = yes;
}
public void setStallHeaders(boolean yes) {
stallHeaders = yes;
}
/**
* Get the alternate locations that this uploader has read from headers
*/
public URN getReportedSHA1() {
return _sha1;
}
/** Returns the number of connections this accepted. */
public int getConnections() {
return connects;
}
/**
* Sets the maximum amount of connects allowed.
*/
public void setMaxConnects(int max) {
maxConnects = max;
}
/**
* Returns the number of requests this received.
*/
public int getRequestsReceived() {
return requestsReceived;
}
/** Returns the last request sent or null if none.
* @return a request like "GET /get/0/file.txt HTTP/1.1" */
public String getRequest() {
return request;
}
/**
* Repeatedly accepts connections and handles them.
*/
private void loop(int port) {
Socket socket = null;
while(true) {
try {
socket = server.accept();
connects++;
if(connects > maxConnects) {
LOG.debug("over-connected");
socket.close();
continue;
}
// make sure it's from us
InetAddress address = socket.getInetAddress();
if (isBannedIP(address.getHostAddress())) {
LOG.debug("Banned address -- closing");
server.close();
continue;
}
LOG.debug("Uploader accepted connection");
//spawn thread to handle request
final Socket mySocket = socket;
Thread runner=new Thread(new SocketHandler(mySocket),name);
runner.start();
} catch (IOException e) {
LOG.debug("exception in accept", e);
try { server.close(); } catch (IOException ignore) { }
return; //server socket closed.
}
//handling next request
}
}
/**
* Returns whether <tt>ip</tt> is a banned address.
* @param ip an address in resolved dotted-quad format, e.g., 18.239.0.144
* @return true iff ip is a banned address.
*/
public boolean isBannedIP(String ip) {
return !IP_FILTER.allow(ip);
}
private void handleRequest(Socket socket) throws IOException {
//Find the region of the file to upload. If a Range request is present,
//use that. Otherwise, send the whole file. Skip all other headers.
//TODO2: Later we should also check the validity of the requests
BufferedReader input =
new BufferedReader(
new InputStreamReader(socket.getInputStream()));
OutputStream output = new BufferedOutputStream(socket.getOutputStream());
if(rate > 0)
throttle = new BandwidthThrottle(rate*1024);
else
throttle = new BandwidthThrottle(Float.MAX_VALUE);
start = 0;
stop = TestFile.length();
boolean firstLine=true;
boolean thexReq = false;
while (true) {
String line=input.readLine();
//LOG.debug("read "+line);
if (firstLine) {
if(line != null && !line.equals("")) {
requestsReceived++;
}
request=line;
firstLine=false;
}
if (line==null)
throw new IOException("Unexpected close");
if (line.equals(""))
break;
if(HTTPHeaderName.GNUTELLA_CONTENT_URN.matchesStartOfString(line)) {
_sha1 = readContentUrn(line);
}
if(HTTPHeaderName.NALTS.matchesStartOfString(line) ||
HTTPHeaderName.BFALT_LOCATION.matchesStartOfString(line))
readAlternateLocations(line,false);
if(HTTPHeaderName.ALT_LOCATION.matchesStartOfString(line) ||
HTTPHeaderName.FALT_LOCATION.matchesStartOfString(line))
readAlternateLocations(line,true);
int i=line.indexOf("Range:");
Assert.that(i<=0, "Range should be at the beginning or not at all");
if (i==0) {
IntPair p = null;
try {
p=parseRange(line);
} catch (Exception e) {
Assert.that(false, "Bad Range request: \""+line+"\"");
}
start = Math.max(0, p.a + lowChunkOffset);
stop = Math.min(TestFile.length(),
Math.max(1, p.b + highChunkOffset));
}
i = line.indexOf("GET");
if(i==0) {
http11 = line.indexOf("1.1") > 0;
thexReq = line.indexOf(TestFile.tree().getThexURI()) > 0;
thexWasRequested |= thexReq;
}
}
if(thexReq && queueOnThex) {
queueOnThex = false;
queue = true;
sendThexTree = true;
}
if(thexReq && useBadThexResponseHeader) {
sendThexTree = true;
}
if(thexReq && sendThexTree && !queue) {
if (_httpListener != null)
_httpListener.thexRequestStarted();
LOG.debug("sending thex tree.");
sendThexTree = false;
sendThexTree(output);
output.flush();
LOG.debug("done sending thex tree.");
} else {
//Send the data.
if (_httpListener != null)
_httpListener.requestStarted(this);
send(output, start, stop);
}
if (_httpListener != null) {
if (thexReq)
_httpListener.thexRequestHandled();
else
_httpListener.requestHandled();
}
}
private void sendThexTree(OutputStream out) throws IOException {
if(!useBadThexResponseHeader) {
String str = "HTTP/1.1 200 OK\r\n" +
"ugly-header: ugly-value\r\n" +
"hot diggity doo\r\n" +
"\r\n";
out.write(str.getBytes());
TestFile.tree().write(out);
} else {
String body = "You have failed miserably in your attempts.";
String str = "HTTP/1.1 9000 Failed Miserably\r\n" +
"Content-Length: " + body.length() + "\r\n" +
"\r\n";
out.write(str.getBytes());
out.write(body.getBytes());
}
}
private void send(OutputStream out, int start, int stop)
throws IOException {
LOG.debug("starting to send data "+start+"-"+stop);
totalAmountToUpload += stop - start;
//Write header, stolen from NormalUploadState.writeHeader()
long t0 = System.currentTimeMillis();
if(minPollTime > 0)
Assert.that(t0 > minPollTime,
"queued downloader responded too quick by "+
(minPollTime-t0)+" mS");
if(maxPollTime > 0)
Assert.that(t0 < maxPollTime,
"queued downloader responded too late, by "+
(t0-maxPollTime) +" mS");
String httpValue = respondWithHTTP11 ? "HTTP/1.1" : "HTTP/1.0";
String str = httpValue + " " +
(busy || queue || partial==1 ?
"503 Service Unavailable\r\n" :
"200 OK \r\n");
out.write(str.getBytes());
if(busy && retryAfter != -1) {
str = "Retry-After: " + retryAfter + "\r\n";
out.write(str.getBytes());
}
if(queue) {
LOG.debug("Upload Queued");
str = "X-Queue: position="+queuePos+
", pollMin=" + MIN_POLL/1000 +
", pollMax=" + MAX_POLL/1000 +
"\r\n";
out.write(str.getBytes());
str = "\r\n";
out.write(str.getBytes());
out.flush();//don't close socket
long t = System.currentTimeMillis();
minPollTime = t+MIN_POLL;
maxPollTime = t+MAX_POLL;
return;
}
if(partial > 0) {
switch(partial) {
case 1:
str="X-Available-Ranges: ByTes 50000-400000, 500000-600000,800000-900000\r\n";
break;
case 2:
str="X-Available-Ranges: bytes 50000-900000\r\n";
break;
default:
str="X-Available-Ranges: bytes 50000-150000\r\n";
}
out.write(str.getBytes());
out.flush();
partial++;
if(partial==2) {//was 1 until last statement
str="\r\n";
out.write(str.getBytes());
out.flush();
return;
}
}
if(sendContentLength) {
str = "Content-Length:"+ (stop - start) + "\r\n";
out.write(str.getBytes());
}
if (start != 0 || (stop - start != TestFile.length())) {
//Note that HTTP stop values are INCLUSIVE. Our internal values
//are EXCLUSIVE. Hence the -1.
str = "Content-range: bytes " + start +
"-" + (stop-1) + "/" + TestFile.length() + "\r\n";
out.write(str.getBytes());
}
if(storedGoodLocs != null && storedGoodLocs.hasAlternateLocations()) {
LOG.debug("Writing alternate location header:\n"+storedGoodLocs+"\n");
HTTPUtils.writeHeader(HTTPHeaderName.ALT_LOCATION,
storedGoodLocs, out);
} else {
LOG.debug("Did not write alt locs:\n"+storedGoodLocs+"\n");
}
if(storedBadLocs != null && storedBadLocs.hasAlternateLocations()) {
LOG.debug("Writing alternate location header:\n"+storedBadLocs+"\n");
HTTPUtils.writeHeader(HTTPHeaderName.NALTS,
storedBadLocs, out);
} else {
LOG.debug("Did not write alt locs:\n"+storedBadLocs+"\n");
}
if(creationTime != null) {
LOG.debug("Writing out Creation Time.");
HTTPUtils.writeHeader(HTTPHeaderName.CREATION_TIME, ""+creationTime,
out);
}
if(sendThexTreeHeader) {
HTTPUtils.writeHeader(HTTPHeaderName.THEX_URI,
TestFile.tree(),
out);
}
if(interestedInFalts) {
if (!isFirewalled)
HTTPUtils.writeFeatures(out);
else {
boolean previous = RouterService.acceptedIncomingConnection();
try{
PrivilegedAccessor.setValue(RouterService.getAcceptor(),
"_acceptedIncoming",new Boolean(false));
HTTPUtils.writeFeatures(out);
PrivilegedAccessor.setValue(RouterService.getAcceptor(),
"_acceptedIncoming",new Boolean(previous));
}catch(Exception bad) {
ErrorService.error(bad);
}
}
}
if (isFirewalled && _proxiesString!=null) {
HTTPUtils.writeHeader(HTTPHeaderName.PROXIES,_proxiesString,out);
}
out.flush();
if (stallHeaders) {
LOG.debug("stalling as requested");
try {
Thread.sleep(100000000);
} catch (InterruptedException end) {
return;
}
}
str = "\r\n";
out.write(str.getBytes());
out.flush();
if (busy) {
//turn busy off for the next time if necessary
if( connects >= timesBusy )
busy = false;
out.close();
return;
}
int length = stop-start;
int okLength = length - (int)(length * corruptPercentage);
amountThisRequest = 0;
boolean sentCorrupt = false;
for (int i=start; i<stop; ) {
//1 second write cycle
if (stopAfter > -1){
Assert.that(fullRequestsUploaded + amountThisRequest <= stopAfter,
"total "+fullRequestsUploaded+" this "+amountThisRequest+" stop "+stopAfter);
if (fullRequestsUploaded + amountThisRequest == stopAfter) {
stopped=true;
out.flush();
LOG.debug(name+" stopped at "+(fullRequestsUploaded + amountThisRequest));
throw new IOException();
}
}
throttle.request(1);
if(sendCorrupt
&& (i-start) >= okLength) {
sentCorrupt = true;
out.write(TestFile.getByte(i)+(byte)1);
}
else
out.write(TestFile.getByte(i));
amountThisRequest++;
i++;
}
if (sentCorrupt)
LOG.debug("corrupt data sent");
out.flush();
// only if the flush didn't throw do we add to fullRequestsUploaded
fullRequestsUploaded += amountThisRequest;
amountThisRequest = 0;
}
/**
* Returns start (inclusive) and stop range (exclusive) from the given Range
* header. In other words, "Range; 2-5" will return [2, 6). Extracted from
* HTTPUploader.
*
* @exception IndexOutOfBoundsException parse error
* @exception NumberFormatException parse error
* @exception IOException socket closed
*/
private IntPair parseRange(String str)
throws IndexOutOfBoundsException,
NumberFormatException, IOException {
int start=0;
int stop=TestFile.length();
//Set 'sub' to the value after the "bytes=" or "bytes ". Note
//that we don't validate the data between "Range:" and the
//bytes.
String sub;
String second;
try {
int i=str.indexOf("bytes"); //TODO: use constant
if (i<0)
throw new IOException();
i+=6; //TODO: use constant
sub = str.substring(i);
} catch (IndexOutOfBoundsException e) {
throw new IOException();
}
// remove the white space
sub = sub.trim();
char c;
// get the first character
try {
c = sub.charAt(0);
} catch (IndexOutOfBoundsException e) {
throw new IOException();
}
// - n
if (c == '-') {
// String second;
try {
second = sub.substring(1);
} catch (IndexOutOfBoundsException e) {
throw new IOException();
}
second = second.trim();
try {
//A range request for "-3" means return the last 3 bytes
//of the file. (LW used to incorrectly return bytes
//0-3.)
start = Math.max(0, TestFile.length()-Integer.parseInt(second));
} catch (NumberFormatException e) {
throw new IOException();
}
}
else {
// m - n or 0 -
int dash = sub.indexOf("-");
String first;
try {
first = sub.substring(0, dash);
} catch (IndexOutOfBoundsException e) {
throw new IOException();
}
first = first.trim();
try {
start = java.lang.Integer.parseInt(first);
} catch (NumberFormatException e) {
throw new IOException();
}
try {
second = sub.substring(dash+1);
} catch (IndexOutOfBoundsException e) {
throw new IOException();
}
second = second.trim();
if (!second.equals(""))
try {
stop = java.lang.Integer.parseInt(second)+1; //sic
} catch (NumberFormatException e) {
throw new IOException();
}
}
//check that the downloader made a correct range request if we are
//partial and this is the second iteration.
if(partial==3) {//check this before checking partial2 -- we set it there
assertEquals("downloader picked incorrect start range in iteration",
150010, start);
assertEquals("downloader pick incorrect stop range in iteration",
250020, stop);
}
if(partial==2) {
assertEquals("invalid start range", 50000, start);
assertEquals("invalid stop range", 150010, stop);
}
return new IntPair(start, stop);
}
/**
* Reads alternate location header. The header can contain only one
* alternate location, or it can contain many in the same header.
* This method adds them all to the <tt>FileDesc</tt> for this
* uploader. This will not allow more than 20 alternate locations
* for a single file.
*
* @param altHeader the full alternate locations header
* @param alc the <tt>AlternateLocationCollector</tt> that read alternate
* locations should be added to
*/
private void readAlternateLocations (final String altHeader,boolean good) {
final String alternateLocations=HTTPUtils.extractHeaderValue(altHeader);
// return if the alternate locations could not be properly extracted
if(alternateLocations == null)
return;
StringTokenizer st = new StringTokenizer(alternateLocations, ",");
while(st.hasMoreTokens()) {
try {
// note that the trim method removes any CRLF character
// sequences that may be used if the sender is using
// continuations.
AlternateLocation al =
AlternateLocation.create(st.nextToken().trim(), _sha1);
if(al instanceof PushAltLoc)
((PushAltLoc)al).updateProxies(good);
LOG.debug("adding good "+good+" al "+al);
if (good)
incomingGoodAltLocs.add(al);
else
incomingBadAltLocs.add(al);
} catch(IOException e) {
// just return without adding it.
continue;
}
}
}
/**
* This method parses the "X-Gnutella-Content-URN" header, as specified
* in HUGE v0.93. This assigns the requested urn value for this
* upload, which otherwise remains null.
*
* @param contentUrnStr the string containing the header
* @return a new <tt>URN</tt> instance for the request line, or
* <tt>null</tt> if there was any problem creating it
*/
private static URN readContentUrn(final String contentUrnStr) {
String urnStr = HTTPUtils.extractHeaderValue(contentUrnStr);
// return null if the header value could not be extracted
if(urnStr == null) return null;
try {
return URN.createSHA1Urn(urnStr);
} catch(IOException e) {
// this will be thrown if the URN string was invalid for any
// reason -- just return null
return null;
}
}
private static final RoundRobinQueue rr = new RoundRobinQueue();
private int amountThisRequest;
private class SocketHandler implements Runnable {
private final Socket mySocket;
public SocketHandler(Socket s) {
mySocket=s;
}
public void run() {
LOG.debug(name+" starting to upload.. ");
try {
while(http11 && !stopped) {
handleRequest(mySocket);
if (queue) {
mySocket.setSoTimeout(MAX_POLL);
if(unqueue) // second time give slot
queue = false;
handleRequest(mySocket);
}
mySocket.setSoTimeout(8000);
}
} catch (IOException e) {
if(fullRequestsUploaded < totalAmountToUpload)
killedByDownloader = true;
LOG.debug("Exception in uploader (" + name + ")", e);
} catch(Throwable t) {
ErrorService.error(t);
} finally {
synchronized(rr) {
rr.remove(this);
if (rr.size() > 0){
Object next = rr.next();
synchronized(next){
next.notify();
}
}
}
try {
mySocket.close();
} catch (IOException e) {
return;
}
}//end of finally
}
}
}