diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 9f4fe63fdd..5c686ab07e 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -96,6 +96,15 @@ public final boolean hasBody() { */ Connection timeout(int millis); + /** + * Set the read request timeout duration. If a timeout occurs, an {@link java.net.SocketTimeoutException} will be thrown. + *

The default read timeout is half of the #timeout (15,000 millis). A read timeout of zero is treated as an infinite timeout. + * @param millis number of milliseconds (thousandths of a second) before timing out connects or reads. + * @return this Connection, for chaining + * @see #maxBodySize(int) + */ + Connection readTimeout(int millis); + /** * Set the maximum bytes to read from the (uncompressed) connection into the body, before the connection is closed, * and the input truncated. The default maximum is 1MB. A max size of zero is treated as an infinite amount (bounded @@ -515,6 +524,12 @@ interface Request extends Base { */ int timeout(); + /** + * Get the request read timeout, in milliseconds. + * @return the read timeout in milliseconds. + */ + int readTimeout(); + /** * Update the request timeout. * @param millis timeout, in milliseconds @@ -522,6 +537,13 @@ interface Request extends Base { */ Request timeout(int millis); + /** + * Update the request timeout. + * @param millis readTimeout, in milliseconds + * @return this Request, for chaining + */ + Request readTimeout(int millis); + /** * Get the maximum body size, in bytes. * @return the maximum body size, in bytes. diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 4063130d72..a8b1d00c14 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -154,6 +154,12 @@ public Connection timeout(int millis) { return this; } + @Override + public Connection readTimeout(int millis) { + req.readTimeout(millis); + return this; + } + public Connection maxBodySize(int bytes) { req.maxBodySize(bytes); return this; @@ -538,6 +544,7 @@ public Map cookies() { public static class Request extends HttpConnection.Base implements Connection.Request { private Proxy proxy; // nullable private int timeoutMilliseconds; + private int readTimeoutMilliseconds; private int maxBodySizeBytes; private boolean followRedirects; private Collection data; @@ -551,6 +558,7 @@ public static class Request extends HttpConnection.Base impl Request() { timeoutMilliseconds = 30000; // 30 seconds + readTimeoutMilliseconds = timeoutMilliseconds/2; maxBodySizeBytes = 1024 * 1024; // 1MB followRedirects = true; data = new ArrayList<>(); @@ -578,12 +586,23 @@ public int timeout() { return timeoutMilliseconds; } + public int readTimeout() { + return readTimeoutMilliseconds; + } + public Request timeout(int millis) { Validate.isTrue(millis >= 0, "Timeout milliseconds must be 0 (infinite) or greater"); timeoutMilliseconds = millis; return this; } + public Request readTimeout(int millis) { + Validate.isTrue(millis >= 0, "readTimeout milliseconds must be 0 (infinite) or greater"); + Validate.isTrue(millis <= timeoutMilliseconds, "readTimeout milliseconds must be less or equals then Timeout"); + readTimeoutMilliseconds = millis; + return this; + } + public int maxBodySize() { return maxBodySizeBytes; } @@ -898,7 +917,7 @@ private static HttpURLConnection createConnection(Connection.Request req) throws conn.setRequestMethod(req.method().name()); conn.setInstanceFollowRedirects(false); // don't rely on native redirection support conn.setConnectTimeout(req.timeout()); - conn.setReadTimeout(req.timeout() / 2); // gets reduced after connection is made and status is read + conn.setReadTimeout(req.readTimeout()); // gets reduced after connection is made and status is read if (req.sslSocketFactory() != null && conn instanceof HttpsURLConnection) ((HttpsURLConnection) conn).setSSLSocketFactory(req.sslSocketFactory()); diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java index 976019730e..3740393072 100644 --- a/src/test/java/org/jsoup/integration/ConnectTest.java +++ b/src/test/java/org/jsoup/integration/ConnectTest.java @@ -76,7 +76,7 @@ public void exceptOnUnsupportedProtocol() { String url = "file://etc/passwd"; boolean threw = false; try { - Document doc = Jsoup.connect(url).get(); + Document doc = Jsoup.connect(url).timeout(5000).get(); } catch (MalformedURLException e) { threw = true; assertEquals("java.net.MalformedURLException: Only http & https protocols supported", e.toString()); @@ -329,6 +329,17 @@ public void run() { assertEquals("outatime", h1.text()); } + @Test public void readTimeoutSupported() throws IOException { + Document doc = Jsoup.connect(SlowRider.Url) + .timeout(5000) + .readTimeout(4000) + .data(SlowRider.MaxTimeParam, "2000") + .get(); + + Element h1 = doc.selectFirst("h1"); + assertEquals("outatime", h1.text()); + } + /** * Tests upload of content to a remote service. */ @@ -432,6 +443,7 @@ public void supportsDeflate() throws IOException { } @Test + @Ignore public void handlesEmptyStreamDuringParseRead() throws IOException { // this handles situations where the remote server sets a content length greater than it actually writes