Hi All,
Hope all is well, ok, I've modified the patch taking into account the
feedback from Anton and Carsten, and also the fact that this problem only
occurs on JDK 1.3.1 and less (JDK defect #4160499).
The JDK's should behave the same now, and will throw a SourceException
with the error code and text from the server.
The response code and error text are stored in the SourceException, and
can accessed via getErrorCode/Text(), which removes them from the log unless
the user/developer prints it themselves.
This patch does change the SourceException API though, so I'll wait again
for any comments before applying it. If anyone see's any problems with the
patch please let me know.
Assuming no one see's any issues with it, I'll apply it at the end of the
week.
Cheers,
Marcus
On Fri, Jun 27, 2003 at 06:53:01PM +0200, Marcus Crafter wrote:
> Hi All,
>
> Hope all is well!
>
> Have been experiencing some interesting behaviour with the source resolver
> today, and in particular the java.net.HttpURLConnection implementation.
>
> Have a look at this code:
>
> URL url = new URL("http://localhost:8080/something/somewhere/");
> URLConnection conn = url.openConnection();
> HttpURLConnection c = (HttpURLConnection) conn;
>
> System.out.println("1." + c.getInputStream());
> System.out.println("2." + c.getLastModified());
> System.out.println("3." + c.getContentType());
> System.out.println("4." + c.getResponseCode());
>
> Assuming that the URL points to a valid server, but a bogus path - then the
> following code raises a FileNotFoundException. However, if you move the
> call to getInputStream() to be after getResponseCode() then no exception is
> raised and and you'll receive a valid InputStream containing the error
> page from the server.
>
> This behaviour currently causes the SourceResolver to return server error
> pages as valid data when resolving a particular URI. From what I can tell,
> other than examine the content returned, there's currently no easy way to
> tell whether the SourceResolver resolved the document correctly or not.
>
> I've attached a patch that examines the return code of the
> HttpURLConnection and throws a SourceException if the code is not within
> the valid range of 200-206 as defined by the HTTP RFC.
>
> This fixes the problem, but prevents people from parsing error pages as
> Source objects (if anyone did that before?). Perhaps someone could take a
> look at the patch as maybe there's a better solution? Any ideas? If not
> let me know, and I'll apply the patch attached.
>
> Cheers,
>
> Marcus
>
>
> --
> .....
> ,,$$$$$$$$$, Marcus Crafter
> ;$' '$$$$: Computer Systems Engineer
> $: $$$$: ManageSoft GmbH
> $ o_)$$$: 82-84 Mainzer Landstrasse
> ;$, _/\ &&:' 60327 Frankfurt Germany
> ' /( &&&
> \_&&&&'
> &&&&.
> &&&&&&&:
> Index: src/java/org/apache/excalibur/source/impl/URLSource.java
> ===================================================================
> RCS file:
> /home/cvs/avalon-excalibur/sourceresolve/src/java/org/apache/excalibur/source/impl/URLSource.java,v
> retrieving revision 1.24
> diff -u -r1.24 URLSource.java
> --- src/java/org/apache/excalibur/source/impl/URLSource.java 4 Apr 2003 16:36:51
> -0000 1.24
> +++ src/java/org/apache/excalibur/source/impl/URLSource.java 27 Jun 2003 16:33:57
> -0000
> @@ -54,8 +54,10 @@
> */
> package org.apache.excalibur.source.impl;
>
> +import java.io.BufferedReader;
> import java.io.IOException;
> import java.io.InputStream;
> +import java.io.InputStreamReader;
> import java.lang.reflect.Method;
> import java.net.HttpURLConnection;
> import java.net.URL;
> @@ -278,9 +280,77 @@
> return input;
> }
> }
> + if (m_connection instanceof HttpURLConnection) {
> + validateConnection((HttpURLConnection)m_connection);
> + }
> input = m_connection.getInputStream();
> m_connection = null; // make sure a new m_connection is created next time
> return input;
> + }
> +
> + /**
> + * Method which ascertains whether a given [EMAIL PROTECTED] HttpURLConnection}
> is
> + * able to return valid data or not.
> + *
> + * <p>Valid return codes from HTTP servers are defined
> + * <a
> href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2">here</a>
> + * to be in the range 200-206 (inclusive)</p>
> + *
> + * <p>This method checks that the given connection object has a return code
> + * within this range, and if not, raises a [EMAIL PROTECTED] SourceException}
> with the return
> + * code and any data sent by the server</p>
> + *
> + * @param conn a [EMAIL PROTECTED] HttpURLConnection} instance
> + * @throws SourceException if the request did not succeed
> + * @exception IOException if an error occurs
> + */
> + private void validateConnection(final HttpURLConnection conn)
> + throws SourceException, IOException {
> + final int responseCode = conn.getResponseCode();
> + final int[] SUCCESS_CODES =
> + {
> + HttpURLConnection.HTTP_OK,
> + HttpURLConnection.HTTP_CREATED,
> + HttpURLConnection.HTTP_ACCEPTED,
> + HttpURLConnection.HTTP_NOT_AUTHORITATIVE,
> + HttpURLConnection.HTTP_NO_CONTENT,
> + HttpURLConnection.HTTP_RESET,
> + HttpURLConnection.HTTP_PARTIAL,
> + };
> +
> + for (int i = 0; i < SUCCESS_CODES.length; ++i) {
> + if (responseCode == SUCCESS_CODES[i])
> + return;
> + }
> +
> + // response from the server was not successful, return an error to the
> caller
> + final String responseText = readString(conn.getInputStream());
> +
> + throw new SourceException(
> + "Request for " + conn.getURL() + " was unsuccessful " +
> + "(" + responseCode + ")" + ", response from server:\n" + responseText
> + );
> + }
> +
> + /**
> + * Helper method to read an [EMAIL PROTECTED] InputStream} into a [EMAIL
> PROTECTED] String}.
> + *
> + * @param stream input [EMAIL PROTECTED] InputStream}
> + * @return a contents in the [EMAIL PROTECTED] InputStream} as a [EMAIL
> PROTECTED] String}
> + * @exception IOException if an error occurs
> + */
> + private String readString(final InputStream stream) throws IOException {
> + final BufferedReader is =
> + new BufferedReader(new InputStreamReader(stream));
> + String line;
> + StringBuffer resp = new StringBuffer();
> +
> + while ((line = is.readLine()) != null) {
> + resp.append(line);
> + resp.append('\n');
> + }
> +
> + return resp.toString();
> }
>
> private static boolean checkedURLClass = false;
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [EMAIL PROTECTED]
> For additional commands, e-mail: [EMAIL PROTECTED]
--
.....
,,$$$$$$$$$, Marcus Crafter
;$' '$$$$: Computer Systems Engineer
$: $$$$: ManageSoft GmbH
$ o_)$$$: 82-84 Mainzer Landstrasse
;$, _/\ &&:' 60327 Frankfurt Germany
' /( &&&
\_&&&&'
&&&&.
&&&&&&&:
? maven.log
? sourceresolve.diff
Index: src/java/org/apache/excalibur/source/SourceException.java
===================================================================
RCS file:
/home/cvs/avalon-excalibur/sourceresolve/src/java/org/apache/excalibur/source/SourceException.java,v
retrieving revision 1.7
diff -u -r1.7 SourceException.java
--- src/java/org/apache/excalibur/source/SourceException.java 29 Jan 2003 06:56:01
-0000 1.7
+++ src/java/org/apache/excalibur/source/SourceException.java 30 Jun 2003 14:35:49
-0000
@@ -64,6 +64,7 @@
*
* @author <a href="mailto:[EMAIL PROTECTED]">Carsten Ziegeler</a>
* @author <a href="mailto:[EMAIL PROTECTED]">Sylvain Wallez</a>
+ * @author <a href="mailto:[EMAIL PROTECTED]">Marcus Crafter</a>
* @version CVS $Revision: 1.7 $ $Date: 2003/01/29 06:56:01 $
*/
public class SourceException
@@ -75,6 +76,16 @@
private final Throwable m_throwable;
/**
+ * Error store for Sources that can return errors by code.
+ */
+ private int m_errorCode;
+
+ /**
+ * Error store for Sources that can return errors by text.
+ */
+ private String m_errorText;
+
+ /**
* Construct a new <code>SourceException</code> instance.
*
* @param message the detail message for this exception.
@@ -92,9 +103,27 @@
*/
public SourceException( final String message, final Throwable throwable )
{
- super( message );
+ super( message );
m_throwable = throwable;
}
+
+ /**
+ * Construct a new <code>SourceException</code> instance.
+ *
+ * @param message the detail message for this exception.
+ * @param errorCode error code identifying the error.
+ * @param errorText error text identifying the error.
+ */
+ public SourceException(
+ final String message,
+ final int errorCode,
+ final String errorText
+ )
+ {
+ this( message );
+ m_errorCode = errorCode;
+ m_errorText = errorText;
+ }
/**
* Retrieve the cause of the exception.
@@ -104,5 +133,25 @@
public final Throwable getCause()
{
return m_throwable;
+ }
+
+ /**
+ * Obtain the error text for this exception.
+ *
+ * @return error text
+ */
+ public String getErrorText()
+ {
+ return m_errorText;
+ }
+
+ /**
+ * Obtain the error code for this exception.
+ *
+ * @return error text
+ */
+ public int getErrorCode()
+ {
+ return m_errorCode;
}
}
Index: src/java/org/apache/excalibur/source/impl/AbstractSource.java
===================================================================
RCS file:
/home/cvs/avalon-excalibur/sourceresolve/src/java/org/apache/excalibur/source/impl/AbstractSource.java,v
retrieving revision 1.11
diff -u -r1.11 AbstractSource.java
--- src/java/org/apache/excalibur/source/impl/AbstractSource.java 20 May 2003
20:56:43 -0000 1.11
+++ src/java/org/apache/excalibur/source/impl/AbstractSource.java 30 Jun 2003
14:35:49 -0000
@@ -101,7 +101,7 @@
/**
* Return an <code>InputStream</code> object to read from the source.
*
- * @throws SourceException if file not found or
+ * @throws SourceException if file not found or null if the
* HTTP location does not exist.
* @throws IOException if I/O error occured.
*/
Index: src/java/org/apache/excalibur/source/impl/URLSource.java
===================================================================
RCS file:
/home/cvs/avalon-excalibur/sourceresolve/src/java/org/apache/excalibur/source/impl/URLSource.java,v
retrieving revision 1.24
diff -u -r1.24 URLSource.java
--- src/java/org/apache/excalibur/source/impl/URLSource.java 4 Apr 2003 16:36:51
-0000 1.24
+++ src/java/org/apache/excalibur/source/impl/URLSource.java 30 Jun 2003 14:35:49
-0000
@@ -54,8 +54,10 @@
*/
package org.apache.excalibur.source.impl;
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
@@ -79,6 +81,21 @@
*/
public class URLSource extends AbstractSource implements Source
{
+ /**
+ * Array of valid return codes from HTTP servers,as defined by the HTTP
+ * <a
href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2">RFC</a>,
+ * to be in the range 200-206 (inclusive)
+ */
+ public static final int[] SUCCESS_CODES =
+ {
+ HttpURLConnection.HTTP_OK,
+ HttpURLConnection.HTTP_CREATED,
+ HttpURLConnection.HTTP_ACCEPTED,
+ HttpURLConnection.HTTP_NOT_AUTHORITATIVE,
+ HttpURLConnection.HTTP_NO_CONTENT,
+ HttpURLConnection.HTTP_RESET,
+ HttpURLConnection.HTTP_PARTIAL,
+ };
/** The URL of the source */
protected URL m_url;
@@ -278,9 +295,79 @@
return input;
}
}
+ if (m_connection instanceof HttpURLConnection) {
+ validateConnection((HttpURLConnection)m_connection);
+ }
input = m_connection.getInputStream();
m_connection = null; // make sure a new m_connection is created next time
return input;
+ }
+
+ /**
+ * Method which ascertains whether a given [EMAIL PROTECTED] HttpURLConnection} is
+ * able to return valid data or not.
+ *
+ * <p>This method is required for JDK Bug #4160499 on JDK 1.3.1 versions and
+ * earlier (see http://marc.theaimsgroup.com/?l=avalon-dev&m=105673281522646&w=2
+ * for details).</p>
+ *
+ * <p>Essentially, given a broken link getInputStream() sometimes throws a
+ * FileNotFoundException, and other times returns an InputStream to the server's
+ * error page.</p>
+ *
+ * <p>Valid return codes from HTTP servers are defined
+ * <a
href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2">here</a>
+ * to be in the range 200-206 (inclusive)</p>
+ *
+ * <p>This method checks that the given connection object has a return code
+ * within this range, and if not, raises a [EMAIL PROTECTED] SourceException}
with the return
+ * code and any data sent by the server</p>
+ *
+ * @param conn a [EMAIL PROTECTED] HttpURLConnection} instance
+ * @throws SourceException if the request did not succeed
+ * @exception IOException if an error occurs
+ */
+ private void validateConnection(final HttpURLConnection conn)
+ throws SourceException, IOException {
+ final int responseCode = conn.getResponseCode();
+
+ for (int i = 0; i < SUCCESS_CODES.length; ++i) {
+ if (responseCode == SUCCESS_CODES[i])
+ return;
+ }
+
+ // response from the server was not successful, return an error to the caller
+ throw new SourceException(
+ "Request for " + conn.getURL() + " was unsuccessful",
+ responseCode, readString(conn.getErrorStream())
+ );
+ }
+
+ /**
+ * Helper method to read an [EMAIL PROTECTED] InputStream} into a [EMAIL
PROTECTED] String}.
+ *
+ * @param stream input [EMAIL PROTECTED] InputStream}
+ * @return a contents in the [EMAIL PROTECTED] InputStream} as a [EMAIL
PROTECTED] String}
+ */
+ private String readString(final InputStream stream) {
+ final BufferedReader is =
+ new BufferedReader(new InputStreamReader(stream));
+ String line;
+ StringBuffer resp = new StringBuffer();
+
+ try
+ {
+ while ((line = is.readLine()) != null) {
+ resp.append(line);
+ resp.append('\n');
+ }
+ }
+ catch ( IOException e )
+ {
+ // ignore any IOExceptions and return what we've read so far.
+ }
+
+ return resp.toString();
}
private static boolean checkedURLClass = false;
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]