forked from I2P_Developers/i2p.i2p
Add ReadLine with timeouts
Implement PING Handle QUIT, STOP, EXIT synch DatagramServer start/stop
This commit is contained in:
70
apps/sam/java/src/net/i2p/sam/ReadLine.java
Normal file
70
apps/sam/java/src/net/i2p/sam/ReadLine.java
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package net.i2p.sam;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modified from I2PTunnelHTTPServer
|
||||||
|
*
|
||||||
|
* @since 0.9.22
|
||||||
|
*/
|
||||||
|
class ReadLine {
|
||||||
|
|
||||||
|
private static final int MAX_LINE_LENGTH = 8*1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a line teriminated by newline, with a total read timeout.
|
||||||
|
*
|
||||||
|
* Warning - strips \n but not \r
|
||||||
|
* Warning - 8KB line length limit as of 0.7.13, @throws IOException if exceeded
|
||||||
|
*
|
||||||
|
* @param buf output
|
||||||
|
* @param timeout throws SocketTimeoutException immediately if zero or negative
|
||||||
|
* @throws SocketTimeoutException if timeout is reached before newline
|
||||||
|
* @throws EOFException if EOF is reached before newline
|
||||||
|
* @throws LineTooLongException if too long
|
||||||
|
* @throws IOException on other errors in the underlying stream
|
||||||
|
*/
|
||||||
|
public static void readLine(Socket socket, StringBuilder buf, int timeout) throws IOException {
|
||||||
|
if (timeout <= 0)
|
||||||
|
throw new SocketTimeoutException();
|
||||||
|
long expires = System.currentTimeMillis() + timeout;
|
||||||
|
InputStreamReader in = new InputStreamReader(socket.getInputStream(), "UTF-8");
|
||||||
|
int c;
|
||||||
|
int i = 0;
|
||||||
|
socket.setSoTimeout(timeout);
|
||||||
|
while ( (c = in.read()) != -1) {
|
||||||
|
if (++i > MAX_LINE_LENGTH)
|
||||||
|
throw new LineTooLongException("Line too long - max " + MAX_LINE_LENGTH);
|
||||||
|
if (c == '\n')
|
||||||
|
break;
|
||||||
|
int newTimeout = (int) (expires - System.currentTimeMillis());
|
||||||
|
if (newTimeout <= 0)
|
||||||
|
throw new SocketTimeoutException();
|
||||||
|
buf.append((char)c);
|
||||||
|
if (newTimeout != timeout) {
|
||||||
|
timeout = newTimeout;
|
||||||
|
socket.setSoTimeout(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c == -1) {
|
||||||
|
if (System.currentTimeMillis() >= expires)
|
||||||
|
throw new SocketTimeoutException();
|
||||||
|
else
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LineTooLongException extends IOException {
|
||||||
|
public LineTooLongException(String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +97,7 @@ class SAMv3DatagramServer implements Handler {
|
|||||||
* Only call once.
|
* Only call once.
|
||||||
* @since 0.9.22
|
* @since 0.9.22
|
||||||
*/
|
*/
|
||||||
public void start() {
|
private synchronized void start() {
|
||||||
_listener.start();
|
_listener.start();
|
||||||
if (_parent != null)
|
if (_parent != null)
|
||||||
_parent.register(this);
|
_parent.register(this);
|
||||||
@ -107,7 +107,7 @@ class SAMv3DatagramServer implements Handler {
|
|||||||
* Cannot be restarted.
|
* Cannot be restarted.
|
||||||
* @since 0.9.22
|
* @since 0.9.22
|
||||||
*/
|
*/
|
||||||
public void stopHandling() {
|
public synchronized void stopHandling() {
|
||||||
synchronized(SAMv3DatagramServer.class) {
|
synchronized(SAMv3DatagramServer.class) {
|
||||||
if (server != null) {
|
if (server != null) {
|
||||||
try {
|
try {
|
||||||
|
@ -15,7 +15,10 @@ import java.io.InputStream;
|
|||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.NoRouteToHostException;
|
import java.net.NoRouteToHostException;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@ -51,6 +54,8 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
private volatile boolean stolenSocket;
|
private volatile boolean stolenSocket;
|
||||||
private volatile boolean streamForwardingSocket;
|
private volatile boolean streamForwardingSocket;
|
||||||
private final boolean sendPorts;
|
private final boolean sendPorts;
|
||||||
|
private long _lastPing;
|
||||||
|
private static final int READ_TIMEOUT = 3*60*1000;
|
||||||
|
|
||||||
interface Session {
|
interface Session {
|
||||||
String getNick();
|
String getNick();
|
||||||
@ -226,6 +231,11 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
public void stealSocket()
|
public void stealSocket()
|
||||||
{
|
{
|
||||||
stolenSocket = true ;
|
stolenSocket = true ;
|
||||||
|
if (sendPorts) {
|
||||||
|
try {
|
||||||
|
socket.socket().setSoTimeout(0);
|
||||||
|
} catch (SocketException se) {}
|
||||||
|
}
|
||||||
this.stopHandling();
|
this.stopHandling();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,6 +256,7 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void handle() {
|
public void handle() {
|
||||||
String msg = null;
|
String msg = null;
|
||||||
String domain = null;
|
String domain = null;
|
||||||
@ -259,15 +270,72 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
_log.debug("SAMv3 handling started");
|
_log.debug("SAMv3 handling started");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
InputStream in = getClientSocket().socket().getInputStream();
|
Socket socket = getClientSocket().socket();
|
||||||
|
InputStream in = socket.getInputStream();
|
||||||
|
|
||||||
|
StringBuilder buf = new StringBuilder(1024);
|
||||||
while (true) {
|
while (true) {
|
||||||
if (shouldStop()) {
|
if (shouldStop()) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Stop request found");
|
_log.debug("Stop request found");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
String line = DataHelper.readLine(in) ;
|
String line;
|
||||||
|
if (sendPorts) {
|
||||||
|
// client supports PING
|
||||||
|
try {
|
||||||
|
ReadLine.readLine(socket, buf, READ_TIMEOUT);
|
||||||
|
line = buf.toString();
|
||||||
|
buf.setLength(0);
|
||||||
|
} catch (SocketTimeoutException ste) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (buf.length() <= 0) {
|
||||||
|
if (_lastPing > 0) {
|
||||||
|
if (now - _lastPing >= READ_TIMEOUT) {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("Failed to respond to PING");
|
||||||
|
writeString("PING STATUS RESULT=I2P_ERROR MESSAGE=\"PONG timeout\"\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("Sendng PING " + now);
|
||||||
|
_lastPing = now;
|
||||||
|
if (!writeString("PING " + now + '\n'))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_lastPing > 0) {
|
||||||
|
if (now - _lastPing >= 2*READ_TIMEOUT) {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("Failed to respond to PING");
|
||||||
|
writeString("PING STATUS RESULT=I2P_ERROR MESSAGE=\"PONG timeout\"\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (_lastPing < 0) {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("2nd timeout");
|
||||||
|
writeString("XXX STATUS RESULT=I2P_ERROR MESSAGE=\"command timeout, bye\"\n");
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// don't clear buffer, don't send ping,
|
||||||
|
// go around again
|
||||||
|
_lastPing = -1;
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("timeout after partial: " + buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_log.shouldDebug())
|
||||||
|
_log.debug("loop after timeout");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.setLength(0);
|
||||||
|
if (DataHelper.readLine(in, buf))
|
||||||
|
line = buf.toString();
|
||||||
|
else
|
||||||
|
line = null;
|
||||||
|
}
|
||||||
if (line==null) {
|
if (line==null) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Connection closed by client (line read : null)");
|
_log.debug("Connection closed by client (line read : null)");
|
||||||
@ -302,6 +370,10 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
} else if (domain.equals("PONG")) {
|
} else if (domain.equals("PONG")) {
|
||||||
execPongMessage(tok);
|
execPongMessage(tok);
|
||||||
continue;
|
continue;
|
||||||
|
} else if (domain.equals("QUIT") || domain.equals("STOP") ||
|
||||||
|
domain.equals("EXIT")) {
|
||||||
|
writeString(domain + " STATUS RESULT=OK MESSAGE=bye\n");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (count <= 1) {
|
if (count <= 1) {
|
||||||
// This is not a correct message, for sure
|
// This is not a correct message, for sure
|
||||||
@ -345,7 +417,7 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
if (!canContinue) {
|
if (!canContinue) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} // while
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Caught IOException for message [" + msg + "]", e);
|
_log.debug("Caught IOException for message [" + msg + "]", e);
|
||||||
@ -855,7 +927,26 @@ class SAMv3Handler extends SAMv1Handler
|
|||||||
* @since 0.9.22
|
* @since 0.9.22
|
||||||
*/
|
*/
|
||||||
private void execPongMessage(StringTokenizer tok) {
|
private void execPongMessage(StringTokenizer tok) {
|
||||||
// TODO. We don't send PINGs yet.
|
String s;
|
||||||
|
if (tok.hasMoreTokens()) {
|
||||||
|
s = tok.nextToken();
|
||||||
|
} else {
|
||||||
|
s = "";
|
||||||
|
}
|
||||||
|
if (_lastPing > 0) {
|
||||||
|
String expected = Long.toString(_lastPing);
|
||||||
|
if (expected.equals(s)) {
|
||||||
|
_lastPing = 0;
|
||||||
|
if (_log.shouldInfo())
|
||||||
|
_log.warn("Got expected pong: " + s);
|
||||||
|
} else {
|
||||||
|
if (_log.shouldInfo())
|
||||||
|
_log.warn("Got unexpected pong: " + s);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_log.shouldWarn())
|
||||||
|
_log.warn("Pong received without a ping: " + s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user