From 418e4d229a8b607b022cfb867bb702bec1765d13 Mon Sep 17 00:00:00 2001 From: Chris Jaekl Date: Sat, 20 Dec 2014 22:24:59 -0500 Subject: [PATCH] Add ability to see raw response from server when something goes wrong. (OC Transpo returns a non-html plain-text response when things go wrong). Also, clean up a warning about deprecation in one unit test. --- cov.sh | 4 +- go.sh | 2 +- .../qd/http/InvalidResponseException.java | 6 +- prod/net/jaekl/qd/http/RequestBroker.java | 18 ++-- .../net/jaekl/qd/util/InputStreamWrapper.java | 47 ++++++++++ .../jaekl/qd/http/HttpServletRequestMock.java | 2 + test/net/jaekl/qd/http/RequestBrokerTest.java | 4 +- .../jaekl/qd/util/InputStreamWrapperTest.java | 90 +++++++++++++++++++ 8 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 prod/net/jaekl/qd/util/InputStreamWrapper.java create mode 100644 test/net/jaekl/qd/util/InputStreamWrapperTest.java diff --git a/cov.sh b/cov.sh index c9fb54c..a9fe22c 100755 --- a/cov.sh +++ b/cov.sh @@ -4,8 +4,8 @@ INSTR_DIR="${WEB_ROOT}/../instr" ##################### echo Compiling... -find "${WEB_ROOT}/prod" -name "*.java" | xargs javac -find "${WEB_ROOT}/test" -name "*.java" | xargs javac -classpath ${WEB_ROOT}/prod:${CLASSPATH} +find "${WEB_ROOT}/prod" -name "*.java" | xargs javac -Xlint:deprecation +find "${WEB_ROOT}/test" -name "*.java" | xargs javac -classpath ${WEB_ROOT}/prod:${CLASSPATH} -Xlint:deprecation ##################### echo Cleaning old coverage files... diff --git a/go.sh b/go.sh index 5822733..c112804 100755 --- a/go.sh +++ b/go.sh @@ -1,7 +1,7 @@ #!/bin/bash WEB_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" echo Compiling... -find "${WEB_ROOT}/prod" -name "*.java" | xargs javac -classpath ${WEB_ROOT}/prod:${CLASSPATH} +find "${WEB_ROOT}/prod" -name "*.java" | xargs javac -classpath ${WEB_ROOT}/prod:${CLASSPATH} -Xlint:deprecation cp -r ${WEB_ROOT}/prod/* ${WEB_ROOT}/WEB-INF/classes/ find "${WEB_ROOT}/prod" -name '*.class' -exec rm {} \; echo Launching... diff --git a/prod/net/jaekl/qd/http/InvalidResponseException.java b/prod/net/jaekl/qd/http/InvalidResponseException.java index 525772e..7d0bdc8 100644 --- a/prod/net/jaekl/qd/http/InvalidResponseException.java +++ b/prod/net/jaekl/qd/http/InvalidResponseException.java @@ -7,12 +7,14 @@ public class InvalidResponseException extends QDException { String m_url; String m_method; + String m_response; // first few bytes of the actual response - public InvalidResponseException(String url, String method, Throwable cause) { + public InvalidResponseException(Throwable cause, String url, String method, String response) { super(cause); m_url = url; m_method = method; + m_response = response; } public String getUrl() { return m_url; } @@ -20,6 +22,6 @@ public class InvalidResponseException extends QDException { @Override public String toString() { - return getClass().getName() + "; " + m_url + "/" + m_method; + return getClass().getName() + "; " + m_url + "/" + m_method + "=>\"" + m_response + "\""; } } diff --git a/prod/net/jaekl/qd/http/RequestBroker.java b/prod/net/jaekl/qd/http/RequestBroker.java index d063008..b111289 100644 --- a/prod/net/jaekl/qd/http/RequestBroker.java +++ b/prod/net/jaekl/qd/http/RequestBroker.java @@ -8,10 +8,12 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; +import java.nio.charset.Charset; import java.util.ArrayList; import net.jaekl.qd.QDException; import net.jaekl.qd.util.ExceptionUtils; +import net.jaekl.qd.util.InputStreamWrapper; import net.jaekl.qd.xml.ParseErrorHandler; import net.jaekl.qd.xml.ParseHandler; import net.jaekl.qd.xml.ParseResult; @@ -120,25 +122,31 @@ public class RequestBroker throws QDException { ParseResult result = null; - InputStream is = null; + InputStreamWrapper isw = null; + Charset utf8 = null; try { + utf8 = Charset.forName(UTF_8); if (null == rootTagName) { result = (ParseResult) rootParserClass.newInstance(); } else { result = (ParseResult) rootParserClass.getDeclaredConstructor(String.class).newInstance(rootTagName); } - is = doSubmit(method, passedParams); + isw = new InputStreamWrapper(doSubmit(method, passedParams)); XMLReader reader = XMLReaderFactory.createXMLReader(); ParseHandler ph = new ParseHandler(result); ParseErrorHandler peh = new ParseErrorHandler(); reader.setContentHandler(ph); reader.setErrorHandler(peh); - reader.parse(new InputSource(is)); + reader.parse(new InputSource(isw)); } catch ( SAXParseException saxpe ) { - throw new InvalidResponseException(m_gatewayUrl, method, saxpe); + String response = ""; + if (null != isw) { + response = new String(isw.getHeadBytes(), utf8); + } + throw new InvalidResponseException(saxpe, m_gatewayUrl, method, response); } catch ( InstantiationException | InvocationTargetException @@ -151,7 +159,7 @@ public class RequestBroker throw new QDException(e); } finally { - ExceptionUtils.tryClose(is); + ExceptionUtils.tryClose(isw); } return result; diff --git a/prod/net/jaekl/qd/util/InputStreamWrapper.java b/prod/net/jaekl/qd/util/InputStreamWrapper.java new file mode 100644 index 0000000..f78cf61 --- /dev/null +++ b/prod/net/jaekl/qd/util/InputStreamWrapper.java @@ -0,0 +1,47 @@ +// Copyright (C) 2014 Christian Jaekl + +// Wrap an inputstream, keeping a copy of the first few bytes that are read from it. +// This is useful when passing an inputstream directly to the SAX parser, +// because we may want to do a post-mortem examination of the input being parsed +// if parsing fails. + +package net.jaekl.qd.util; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +public class InputStreamWrapper extends InputStream { + final static int HEAD_MAX = 1024; + + InputStream m_is; // the stream being wrapped + byte[] m_head; // the first (up to HEAD_MAX) bytes that were read from the wrapped stream + int m_headBytes; // number of bytes stored in m_head + + public InputStreamWrapper(InputStream is) + { + super(); + + m_is = is; + m_head = new byte[HEAD_MAX]; + m_headBytes = 0; + } + + @Override + public int read() throws IOException { + int b = m_is.read(); + if ((-1) == b) { + // end-of-stream + return b; + } + if (m_headBytes < HEAD_MAX) { + m_head[m_headBytes] = (byte)b; + m_headBytes++; + } + return b; + } + + public byte[] getHeadBytes() { + return Arrays.copyOf(m_head, m_headBytes); + } +} diff --git a/test/net/jaekl/qd/http/HttpServletRequestMock.java b/test/net/jaekl/qd/http/HttpServletRequestMock.java index 8d22b03..456bc82 100644 --- a/test/net/jaekl/qd/http/HttpServletRequestMock.java +++ b/test/net/jaekl/qd/http/HttpServletRequestMock.java @@ -125,6 +125,7 @@ public class HttpServletRequestMock implements HttpServletRequest { return null; } + @Deprecated @Override public String getRealPath(String arg0) { // TODO Auto-generated method stub @@ -330,6 +331,7 @@ public class HttpServletRequestMock implements HttpServletRequest { return false; } + @Deprecated @Override public boolean isRequestedSessionIdFromUrl() { // TODO Auto-generated method stub diff --git a/test/net/jaekl/qd/http/RequestBrokerTest.java b/test/net/jaekl/qd/http/RequestBrokerTest.java index 2892a17..307fe90 100644 --- a/test/net/jaekl/qd/http/RequestBrokerTest.java +++ b/test/net/jaekl/qd/http/RequestBrokerTest.java @@ -189,7 +189,8 @@ public class RequestBrokerTest { @Test public void test_submitAndParse_throwsInvalidResultException() { - InvalidResponseException ire = new InvalidResponseException(GATEWAY, METHOD, new SAXParseException("dummy", null)); + final String RESPONSE = "Must specify stop number."; + InvalidResponseException ire = new InvalidResponseException(new SAXParseException("dummy", null), GATEWAY, METHOD, RESPONSE); ArrayList emptyParams = new ArrayList(); RequestBrokerMock rbm = new RequestBrokerMock(GATEWAY, emptyParams); @@ -204,6 +205,7 @@ public class RequestBrokerTest { Assert.assertEquals(ire, qde); Assert.assertTrue(ire.toString().contains(GATEWAY)); Assert.assertTrue(ire.toString().contains(METHOD)); + Assert.assertTrue(ire.toString().contains(RESPONSE)); } } } diff --git a/test/net/jaekl/qd/util/InputStreamWrapperTest.java b/test/net/jaekl/qd/util/InputStreamWrapperTest.java new file mode 100644 index 0000000..9db2252 --- /dev/null +++ b/test/net/jaekl/qd/util/InputStreamWrapperTest.java @@ -0,0 +1,90 @@ +package net.jaekl.qd.util; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; + +import junit.framework.Assert; + +import org.junit.Test; + +public class InputStreamWrapperTest { + static final String[] DATA = { + "", + "\n", + "古池\n蛙飛び込む\n水の音\n", + + "arma virumque cano, Troiae qui primus ab oris\n" + + "Italiam fato profugus Laviniaque venit\n" + + "litora, multum ille et terris iactatus et alto\n" + + "vi superum, saevae memorem Iunonis ob iram,\n" + + "multa quoque et bello passus, dum conderet urbem\n" + + "inferretque deos Latio; genus unde\n" + + "Albanique patres atque altae moenia Romae.\n" + + "Musa, mihi causas memora, quo numine laeso\n" + + "quidve dolens regina deum tot volvere\n" + + "insignem pietate virum, tot adire labores\n" + + "impulerit. tantaene animis caelestibus irae?\n" + + "urbs antiqua fuit (Tyrii tenuere coloni)\n" + + "Karthago, Italiam contra Tiberinaque longe\n" + + "ostia, dives opum studiisque asperrima belli,\n" + + "quam Iuno fertur terris magis omnibus unam\n" + + "posthabita coluisse Samo. hic illius arma,\n" + + "hic currus fuit; hoc regnum dea gentibus esse,\n" + + "si qua fata sinant, iam tum tenditque fovetque.\n" + + "progeniem sed enim Troiano a sanguine duci\n" + + "audierat Tyrias olim quae verteret arces;\n" + + "hinc populum late regem belloque superbum\n" + + "venturum excidio Libyae; sic volvere Parcas.\n" + + "id metuens veterisque memor Saturnia belli,\n" + + "prima quod ad Troiam pro caris gesserat Argis_\n" + + "necdum etiam causae irarum saevique dolores\n" + + "exciderant animo; manet alta mente repostum\n" + + "iudicium Paridis spretaeque iniuria formae\n" + + "et genus invisum et rapti Ganymedis honores:\n" + + "his accensa super iactatos aequore toto\n" + + "Troas, reliquias Danaum atque immitis Achilli,\n" + + "arcebat longe Latio, multosque per annos\n" + + "errabant acti fatis maria omnia circum.\n" + }; + + @Test + public void testRead() throws IOException { + Charset utf8 = Charset.forName("UTF-8"); + StringBuilder sb = new StringBuilder(); + ByteArrayInputStream bais = null; + BufferedReader br = null; + String line; + for (String datum : DATA) { + sb.setLength(0); // reset the builder + int end = datum.length(); + if (end > InputStreamWrapper.HEAD_MAX) { + end = InputStreamWrapper.HEAD_MAX; + } + String expected = datum.substring(0, end); + + try { + bais = new ByteArrayInputStream(datum.getBytes(utf8)); + InputStreamWrapper isw = new InputStreamWrapper(bais); + br = new BufferedReader(new InputStreamReader(isw)); + line = br.readLine(); + while (null != line) { + sb.append(line).append("\n"); + line = br.readLine(); + } + String actualRead = sb.toString(); + String actualHead = new String(isw.getHeadBytes(), utf8); + + Assert.assertEquals(datum, actualRead); + Assert.assertEquals(expected, actualHead); + } + finally { + if (null != br) { + br.close(); + } + } + } + } +} -- 2.30.2