Migrate all HTTP calls to use OkHttp rather than URLConnection.

This commit is contained in:
Andreas Schildbach 2015-06-15 11:18:52 +02:00
parent 4c64746e75
commit 74d552d187
16 changed files with 2135 additions and 2062 deletions

View file

@ -2,6 +2,8 @@ apply plugin: 'java'
apply plugin: 'eclipse'
dependencies {
compile 'com.squareup.okhttp3:okhttp:3.4.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
compile 'com.google.guava:guava:18.0'
compile 'org.slf4j:slf4j-api:1.7.12'
compile 'com.google.code.findbugs:jsr305:3.0.0'

View file

@ -83,6 +83,9 @@ import de.schildbach.pte.util.HttpClient;
import de.schildbach.pte.util.ParserUtils;
import de.schildbach.pte.util.XmlPullUtil;
import okhttp3.HttpUrl;
import okhttp3.ResponseBody;
/**
* @author Andreas Schildbach
*/
@ -244,10 +247,10 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
final StringBuilder parameters = stopfinderRequestParameters(constraint, "JSON");
final CharSequence page;
if (httpPost)
page = httpClient.get(uri.toString(), parameters.substring(1), "application/x-www-form-urlencoded",
Charsets.UTF_8);
page = httpClient.get(HttpUrl.parse(uri.toString()), parameters.substring(1),
"application/x-www-form-urlencoded", Charsets.UTF_8);
else
page = httpClient.get(uri.append(parameters).toString(), Charsets.UTF_8);
page = httpClient.get(HttpUrl.parse(uri.append(parameters).toString()), Charsets.UTF_8);
final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT);
try {
@ -354,20 +357,14 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
protected SuggestLocationsResult xmlStopfinderRequest(final Location constraint) throws IOException {
final StringBuilder uri = new StringBuilder(stopFinderEndpoint);
final StringBuilder parameters = stopfinderRequestParameters(constraint, "XML");
final AtomicReference<SuggestLocationsResult> result = new AtomicReference<SuggestLocationsResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
if (httpPost)
is = httpClient.getInputStream(uri.toString(), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer);
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(body.byteStream(), null); // Read encoding from XML declaration
final ResultHeader header = enterItdRequest(pp);
final List<SuggestedLocation> locations = new ArrayList<SuggestedLocation>();
@ -383,32 +380,33 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
XmlPullUtil.skipExit(pp, "itdStopFinderRequest");
return new SuggestLocationsResult(header, locations);
result.set(new SuggestLocationsResult(header, locations));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
if (httpPost)
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer);
return result.get();
}
protected SuggestLocationsResult mobileStopfinderRequest(final Location constraint) throws IOException {
final StringBuilder uri = new StringBuilder(stopFinderEndpoint);
final StringBuilder parameters = stopfinderRequestParameters(constraint, "XML");
final AtomicReference<SuggestLocationsResult> result = new AtomicReference<SuggestLocationsResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
if (httpPost)
is = httpClient.getInputStream(uri.toString(), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer);
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(body.byteStream(), null); // Read encoding from XML declaration
final ResultHeader header = enterEfa(pp);
final List<SuggestedLocation> locations = new ArrayList<SuggestedLocation>();
@ -456,8 +454,8 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
XmlPullUtil.skipExit(pp, "p");
final Location location = new Location(type, type == LocationType.STATION ? id : null, coord, place,
name);
final Location location = new Location(type, type == LocationType.STATION ? id : null,
coord, place, name);
final SuggestedLocation locationAndQuality = new SuggestedLocation(location, quality);
locations.add(locationAndQuality);
}
@ -467,14 +465,21 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
XmlPullUtil.next(pp);
}
return new SuggestLocationsResult(header, locations);
result.set(new SuggestLocationsResult(header, locations));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
if (httpPost)
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer);
return result.get();
}
private StringBuilder xmlCoordRequestParameters(final EnumSet<LocationType> types, final int lat, final int lon,
final int maxDistance, final int maxLocations) {
@ -507,20 +512,14 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
final int maxDistance, final int maxStations) throws IOException {
final StringBuilder uri = new StringBuilder(coordEndpoint);
final StringBuilder parameters = xmlCoordRequestParameters(types, lat, lon, maxDistance, maxStations);
final AtomicReference<NearbyLocationsResult> result = new AtomicReference<NearbyLocationsResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
if (httpPost)
is = httpClient.getInputStream(uri.toString(), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer);
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(body.byteStream(), null); // Read encoding from XML declaration
final ResultHeader header = enterItdRequest(pp);
XmlPullUtil.enter(pp, "itdCoordInfoRequest");
@ -566,33 +565,34 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
XmlPullUtil.skipExit(pp, "coordInfoItemList");
}
return new NearbyLocationsResult(header, locations);
result.set(new NearbyLocationsResult(header, locations));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
if (httpPost)
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer);
return result.get();
}
protected NearbyLocationsResult mobileCoordRequest(final EnumSet<LocationType> types, final int lat, final int lon,
final int maxDistance, final int maxStations) throws IOException {
final StringBuilder uri = new StringBuilder(coordEndpoint);
final StringBuilder parameters = xmlCoordRequestParameters(types, lat, lon, maxDistance, maxStations);
final AtomicReference<NearbyLocationsResult> result = new AtomicReference<NearbyLocationsResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
if (httpPost)
is = httpClient.getInputStream(uri.toString(), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer);
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(body.byteStream(), null); // Read encoding from XML declaration
final ResultHeader header = enterEfa(pp);
XmlPullUtil.enter(pp, "ci");
@ -643,14 +643,21 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
XmlPullUtil.skipExit(pp, "ci");
return new NearbyLocationsResult(header, stations);
result.set(new NearbyLocationsResult(header, stations));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
if (httpPost)
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer);
return result.get();
}
@Override
public SuggestLocationsResult suggestLocations(final CharSequence constraint) throws IOException {
@ -856,20 +863,14 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
parameters.append("&mergeDep=1");
parameters.append("&useAllStops=1");
parameters.append("&mode=direct");
final AtomicReference<NearbyLocationsResult> result = new AtomicReference<NearbyLocationsResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
if (httpPost)
is = httpClient.getInputStream(uri.toString(), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer);
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(body.byteStream(), null); // Read encoding from XML declaration
final ResultHeader header = enterItdRequest(pp);
XmlPullUtil.enter(pp, "itdDepartureMonitorRequest");
@ -891,23 +892,32 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
}
});
if ("notidentified".equals(nameState))
return new NearbyLocationsResult(header, NearbyLocationsResult.Status.INVALID_ID);
if ("notidentified".equals(nameState)) {
result.set(new NearbyLocationsResult(header, NearbyLocationsResult.Status.INVALID_ID));
return;
}
if (ownStation.get() != null && !stations.contains(ownStation))
stations.add(ownStation.get());
if (maxLocations == 0 || maxLocations >= stations.size())
return new NearbyLocationsResult(header, stations);
result.set(new NearbyLocationsResult(header, stations));
else
return new NearbyLocationsResult(header, stations.subList(0, maxLocations));
result.set(new NearbyLocationsResult(header, stations.subList(0, maxLocations)));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
if (httpPost)
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer);
return result.get();
}
private static final Pattern P_LINE_RE = Pattern.compile("RE ?\\d+");
private static final Pattern P_LINE_RB = Pattern.compile("RB ?\\d+");
@ -1442,23 +1452,17 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
final int maxDepartures, final boolean equivs) throws IOException {
final StringBuilder uri = new StringBuilder(departureMonitorEndpoint);
final StringBuilder parameters = xsltDepartureMonitorRequestParameters(stationId, time, maxDepartures, equivs);
final AtomicReference<QueryDeparturesResult> result = new AtomicReference<QueryDeparturesResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
if (httpPost)
is = httpClient.getInputStream(uri.toString(), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer);
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(body.byteStream(), null); // Read encoding from XML declaration
final ResultHeader header = enterItdRequest(pp);
final QueryDeparturesResult result = new QueryDeparturesResult(header);
final QueryDeparturesResult r = new QueryDeparturesResult(header);
XmlPullUtil.enter(pp, "itdDepartureMonitorRequest");
@ -1468,14 +1472,16 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
@Override
public void location(final String nameState, final Location location, final int matchQuality) {
if (location.type == LocationType.STATION)
if (findStationDepartures(result.stationDepartures, location.id) == null)
result.stationDepartures.add(new StationDepartures(location, new LinkedList<Departure>(),
if (findStationDepartures(r.stationDepartures, location.id) == null)
r.stationDepartures.add(new StationDepartures(location, new LinkedList<Departure>(),
new LinkedList<LineDestination>()));
}
});
if ("notidentified".equals(nameState) || "list".equals(nameState))
return new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION);
if ("notidentified".equals(nameState) || "list".equals(nameState)) {
result.set(new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION));
return;
}
XmlPullUtil.optSkip(pp, "itdDateTime");
@ -1495,13 +1501,15 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
XmlPullUtil.enter(pp, "itdServingLines");
while (XmlPullUtil.test(pp, "itdServingLine")) {
final String assignedStopId = XmlPullUtil.optAttr(pp, "assignedStopID", null);
final String destinationName = normalizeLocationName(XmlPullUtil.optAttr(pp, "direction", null));
final String destinationName = normalizeLocationName(
XmlPullUtil.optAttr(pp, "direction", null));
final String destinationIdStr = XmlPullUtil.optAttr(pp, "destID", null);
final String destinationId = !"-1".equals(destinationIdStr) ? destinationIdStr : null;
final Location destination;
if (destinationId != null || destinationName != null)
destination = new Location(destinationId != null ? LocationType.STATION : LocationType.ANY,
destinationId, null, destinationName);
destination = new Location(
destinationId != null ? LocationType.STATION : LocationType.ANY, destinationId,
null, destinationName);
else
destination = null;
@ -1509,9 +1517,9 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
StationDepartures assignedStationDepartures;
if (assignedStopId == null)
assignedStationDepartures = result.stationDepartures.get(0);
assignedStationDepartures = r.stationDepartures.get(0);
else
assignedStationDepartures = findStationDepartures(result.stationDepartures, assignedStopId);
assignedStationDepartures = findStationDepartures(r.stationDepartures, assignedStopId);
if (assignedStationDepartures == null)
assignedStationDepartures = new StationDepartures(
@ -1534,7 +1542,7 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
while (XmlPullUtil.test(pp, "itdDeparture")) {
final String assignedStopId = XmlPullUtil.attr(pp, "stopID");
StationDepartures assignedStationDepartures = findStationDepartures(result.stationDepartures,
StationDepartures assignedStationDepartures = findStationDepartures(r.stationDepartures,
assignedStopId);
if (assignedStationDepartures == null) {
final Point coord = processCoordAttr(pp);
@ -1542,8 +1550,8 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
// final String name = normalizeLocationName(XmlPullUtil.attr(pp, "nameWO"));
assignedStationDepartures = new StationDepartures(
new Location(LocationType.STATION, assignedStopId, coord), new LinkedList<Departure>(),
new LinkedList<LineDestination>());
new Location(LocationType.STATION, assignedStopId, coord),
new LinkedList<Departure>(), new LinkedList<LineDestination>());
}
final Position position = parsePosition(XmlPullUtil.optAttr(pp, "platformName", null));
@ -1563,13 +1571,15 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
XmlPullUtil.require(pp, "itdServingLine");
final boolean isRealtime = XmlPullUtil.attr(pp, "realtime").equals("1");
final String destinationName = normalizeLocationName(XmlPullUtil.optAttr(pp, "direction", null));
final String destinationName = normalizeLocationName(
XmlPullUtil.optAttr(pp, "direction", null));
final String destinationIdStr = XmlPullUtil.optAttr(pp, "destID", null);
final String destinationId = !"-1".equals(destinationIdStr) ? destinationIdStr : null;
final Location destination;
if (destinationId != null || destinationName != null)
destination = new Location(destinationId != null ? LocationType.STATION : LocationType.ANY,
destinationId, null, destinationName);
destination = new Location(
destinationId != null ? LocationType.STATION : LocationType.ANY, destinationId,
null, destinationName);
else
destination = null;
final Line line = processItdServingLine(pp);
@ -1580,8 +1590,8 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
XmlPullUtil.skipExit(pp, "itdDeparture");
final Departure departure = new Departure(plannedDepartureTime.getTime(),
predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY) ? predictedDepartureTime.getTime()
: null,
predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY)
? predictedDepartureTime.getTime() : null,
line, position, destination, null, null);
assignedStationDepartures.departures.add(departure);
}
@ -1591,35 +1601,36 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
XmlPullUtil.next(pp);
}
return result;
result.set(r);
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
if (httpPost)
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer);
return result.get();
}
protected QueryDeparturesResult queryDeparturesMobile(final String stationId, final @Nullable Date time,
final int maxDepartures, final boolean equivs) throws IOException {
final StringBuilder uri = new StringBuilder(departureMonitorEndpoint);
final StringBuilder parameters = xsltDepartureMonitorRequestParameters(stationId, time, maxDepartures, equivs);
final AtomicReference<QueryDeparturesResult> result = new AtomicReference<QueryDeparturesResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
if (httpPost)
is = httpClient.getInputStream(uri.toString(), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer);
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(body.byteStream(), null); // Read encoding from XML declaration
final ResultHeader header = enterEfa(pp);
final QueryDeparturesResult result = new QueryDeparturesResult(header);
final QueryDeparturesResult r = new QueryDeparturesResult(header);
XmlPullUtil.require(pp, "dps");
if (!pp.isEmptyElementTag()) {
@ -1652,15 +1663,17 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
// TODO messages
StationDepartures stationDepartures = findStationDepartures(result.stationDepartures, assignedId);
StationDepartures stationDepartures = findStationDepartures(r.stationDepartures,
assignedId);
if (stationDepartures == null) {
stationDepartures = new StationDepartures(new Location(LocationType.STATION, assignedId),
stationDepartures = new StationDepartures(
new Location(LocationType.STATION, assignedId),
new ArrayList<Departure>(maxDepartures), null);
result.stationDepartures.add(stationDepartures);
r.stationDepartures.add(stationDepartures);
}
stationDepartures.departures.add(new Departure(
plannedDepartureTime.getTime(), predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY)
stationDepartures.departures.add(new Departure(plannedDepartureTime.getTime(),
predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY)
? predictedDepartureTime.getTime() : null,
lineDestination.line, position, lineDestination.destination, null, null));
@ -1669,17 +1682,24 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
XmlPullUtil.skipExit(pp, "dps");
return result;
result.set(r);
} else {
return new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION);
result.set(new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION));
}
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
if (httpPost)
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpReferer);
else
httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer);
return result.get();
}
private static final Pattern P_MOBILE_M_SYMBOL = Pattern.compile("([^\\s]*)\\s+([^\\s]*)");
@ -2032,28 +2052,30 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
final StringBuilder uri = new StringBuilder(tripEndpoint);
final String parameters = xsltTripRequestParameters(from, via, to, date, dep, products, optimize, walkSpeed,
accessibility, options);
final AtomicReference<QueryTripsResult> result = new AtomicReference<QueryTripsResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
if (httpPost)
is = httpClient.getInputStream(uri.toString(), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpRefererTrip);
else
is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpRefererTrip);
firstChars = HttpClient.peekFirstChars(is);
return queryTrips(uri.toString(), is);
result.set(queryTrips(uri.toString(), body.byteStream()));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
throw new ParserException("cannot parse xml: " + bodyPeek, x);
} catch (final RuntimeException x) {
throw new RuntimeException("uncategorized problem while processing " + uri, x);
} finally {
if (is != null)
is.close();
}
}
};
if (httpPost)
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpRefererTrip);
else
httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null,
httpRefererTrip);
return result.get();
}
protected QueryTripsResult queryTripsMobile(final Location from, final @Nullable Location via, final Location to,
final Date date, final boolean dep, final @Nullable Collection<Product> products,
@ -2062,28 +2084,30 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
final StringBuilder uri = new StringBuilder(tripEndpoint);
final String parameters = xsltTripRequestParameters(from, via, to, date, dep, products, optimize, walkSpeed,
accessibility, options);
final AtomicReference<QueryTripsResult> result = new AtomicReference<QueryTripsResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
if (httpPost)
is = httpClient.getInputStream(uri.toString(), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpRefererTrip);
else
is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpRefererTrip);
firstChars = HttpClient.peekFirstChars(is);
return queryTripsMobile(uri.toString(), from, via, to, is);
result.set(queryTripsMobile(uri.toString(), from, via, to, body.byteStream()));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
throw new ParserException("cannot parse xml: " + bodyPeek, x);
} catch (final RuntimeException x) {
throw new RuntimeException("uncategorized problem while processing " + uri, x);
} finally {
if (is != null)
is.close();
}
}
};
if (httpPost)
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1),
"application/x-www-form-urlencoded", null, httpRefererTrip);
else
httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null,
httpRefererTrip);
return result.get();
}
@Override
public QueryTripsResult queryMoreTrips(final QueryTripsContext contextObj, final boolean later) throws IOException {
@ -2091,24 +2115,25 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
final String commandUri = context.context;
final StringBuilder uri = new StringBuilder(commandUri);
uri.append("&command=").append(later ? "tripNext" : "tripPrev");
final AtomicReference<QueryTripsResult> result = new AtomicReference<QueryTripsResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
is = httpClient.getInputStream(uri.toString(), null, httpRefererTrip);
firstChars = HttpClient.peekFirstChars(is);
return queryTrips(uri.toString(), is);
result.set(queryTrips(uri.toString(), body.byteStream()));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
throw new ParserException("cannot parse xml: " + bodyPeek, x);
} catch (final RuntimeException x) {
throw new RuntimeException("uncategorized problem while processing " + uri, x);
} finally {
if (is != null)
is.close();
}
}
};
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), null, httpRefererTrip);
return result.get();
}
protected QueryTripsResult queryMoreTripsMobile(final QueryTripsContext contextObj, final boolean later)
throws IOException {
@ -2116,30 +2141,30 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
final String commandUri = context.context;
final StringBuilder uri = new StringBuilder(commandUri);
uri.append("&command=").append(later ? "tripNext" : "tripPrev");
final AtomicReference<QueryTripsResult> result = new AtomicReference<QueryTripsResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
is = httpClient.getInputStream(uri.toString(), null, httpRefererTrip);
firstChars = HttpClient.peekFirstChars(is);
is.mark(512);
return queryTripsMobile(uri.toString(), null, null, null, is);
result.set(queryTripsMobile(uri.toString(), null, null, null, body.byteStream()));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
throw new ParserException("cannot parse xml: " + bodyPeek, x);
} catch (final RuntimeException x) {
throw new RuntimeException("uncategorized problem while processing " + uri, x);
} finally {
if (is != null)
is.close();
}
}
};
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), null, httpRefererTrip);
return result.get();
}
private QueryTripsResult queryTrips(final String uri, final InputStream is)
throws XmlPullParserException, IOException {
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(is, null); // Read encoding from XML declaration
final ResultHeader header = enterItdRequest(pp);
final Object context = header.context;
@ -2659,7 +2684,7 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider {
private QueryTripsResult queryTripsMobile(final String uri, final Location from, final @Nullable Location via,
final Location to, final InputStream is) throws XmlPullParserException, IOException {
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(is, null); // Read encoding from XML declaration
final ResultHeader header = enterEfa(pp);
final Calendar plannedTimeCal = new GregorianCalendar(timeZone);

View file

@ -27,7 +27,6 @@ import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Calendar;
@ -44,8 +43,10 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import javax.annotation.Nullable;
@ -89,6 +90,9 @@ import de.schildbach.pte.util.ParserUtils;
import de.schildbach.pte.util.StringReplaceReader;
import de.schildbach.pte.util.XmlPullUtil;
import okhttp3.HttpUrl;
import okhttp3.ResponseBody;
/**
* @author Andreas Schildbach
*/
@ -448,7 +452,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
private static final Pattern P_AJAX_GET_STOPS_ID = Pattern.compile(".*?@L=0*(\\d+)@.*?");
protected final SuggestLocationsResult jsonGetStops(final String uri) throws IOException {
final CharSequence page = httpClient.get(uri, jsonGetStopsEncoding);
final CharSequence page = httpClient.get(HttpUrl.parse(uri), jsonGetStopsEncoding);
final Matcher mJson = P_AJAX_GET_STOPS_JSON.matcher(page);
if (mJson.matches()) {
@ -565,15 +569,17 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
protected final QueryDeparturesResult xmlStationBoard(final String uri, final String stationId) throws IOException {
final String normalizedStationId = normalizeStationId(stationId);
final AtomicReference<QueryDeparturesResult> result = new AtomicReference<QueryDeparturesResult>();
httpClient.getInputStream(new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
StringReplaceReader reader = null;
String firstChars = null;
try {
// work around unparsable XML
final InputStream is = httpClient.getInputStream(uri);
firstChars = HttpClient.peekFirstChars(is);
reader = new StringReplaceReader(new InputStreamReader(is, Charsets.ISO_8859_1), " & ", " &amp; ");
reader = new StringReplaceReader(body.charStream(), " & ", " &amp; ");
reader.replace("<b>", " ");
reader.replace("</b>", " ");
reader.replace("<u>", " ");
@ -586,6 +592,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
reader.replace(" <> ", " &#x2194; "); // left-right arrow
addCustomReplaces(reader);
try {
final XmlPullParserFactory factory = XmlPullParserFactory
.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
final XmlPullParser pp = factory.newPullParser();
@ -594,19 +601,22 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
pp.nextTag();
final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT);
final QueryDeparturesResult result = new QueryDeparturesResult(header);
final QueryDeparturesResult r = new QueryDeparturesResult(header);
if (XmlPullUtil.test(pp, "Err")) {
final String code = XmlPullUtil.attr(pp, "code");
final String text = XmlPullUtil.attr(pp, "text");
if (code.equals("H730")) // Your input is not valid
return new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION);
if (code.equals("H730")) {
result.set(new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION));
return;
}
if (code.equals("H890")) {
result.stationDepartures
r.stationDepartures
.add(new StationDepartures(new Location(LocationType.STATION, normalizedStationId),
Collections.<Departure> emptyList(), null));
return result;
result.set(r);
return;
}
throw new IllegalArgumentException("unknown error " + code + ", " + text);
}
@ -624,7 +634,8 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
final String evaId = XmlPullUtil.attr(pp, "evaId");
if (evaId != null) {
if (!evaId.equals(normalizedStationId))
throw new IllegalStateException("stationId: " + normalizedStationId + ", evaId: " + evaId);
throw new IllegalStateException(
"stationId: " + normalizedStationId + ", evaId: " + evaId);
final String name = XmlPullUtil.attr(pp, "name");
if (name != null)
@ -734,7 +745,8 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
final int[] capacity;
if (capacityStr != null && !"0|0".equals(capacityStr)) {
final String[] capacityParts = capacityStr.split("\\|");
capacity = new int[] { Integer.parseInt(capacityParts[0]), Integer.parseInt(capacityParts[1]) };
capacity = new int[] { Integer.parseInt(capacityParts[0]),
Integer.parseInt(capacityParts[1]) };
} else {
capacity = null;
}
@ -758,13 +770,14 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
stationPlaceAndName != null ? stationPlaceAndName[1] : null);
} else {
final String[] depPlaceAndName = splitStationName(depStation);
location = new Location(LocationType.STATION, null, depPlaceAndName[0], depPlaceAndName[1]);
location = new Location(LocationType.STATION, null, depPlaceAndName[0],
depPlaceAndName[1]);
}
StationDepartures stationDepartures = findStationDepartures(result.stationDepartures, location);
StationDepartures stationDepartures = findStationDepartures(r.stationDepartures, location);
if (stationDepartures == null) {
stationDepartures = new StationDepartures(location, new ArrayList<Departure>(8), null);
result.stationDepartures.add(stationDepartures);
r.stationDepartures.add(stationDepartures);
}
stationDepartures.departures.add(departure);
@ -779,17 +792,18 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
XmlPullUtil.requireEndDocument(pp);
// sort departures
for (final StationDepartures stationDepartures : result.stationDepartures)
for (final StationDepartures stationDepartures : r.stationDepartures)
Collections.sort(stationDepartures.departures, Departure.TIME_COMPARATOR);
return result;
result.set(r);
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (reader != null)
reader.close();
}
}
}, HttpUrl.parse(uri));
return result.get();
}
private StationDepartures findStationDepartures(final List<StationDepartures> stationDepartures,
final Location location) {
@ -813,7 +827,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
false);
final String uri = checkNotNull(mgateEndpoint);
final CharSequence page = httpClient.get(uri, request, "application/json", Charsets.UTF_8);
final CharSequence page = httpClient.get(HttpUrl.parse(uri), request, "application/json", Charsets.UTF_8);
try {
final JSONObject head = new JSONObject(page.toString());
@ -878,7 +892,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
false);
final String uri = checkNotNull(mgateEndpoint);
final CharSequence page = httpClient.get(uri, request, "application/json", Charsets.UTF_8);
final CharSequence page = httpClient.get(HttpUrl.parse(uri), request, "application/json", Charsets.UTF_8);
try {
final JSONObject head = new JSONObject(page.toString());
@ -977,7 +991,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
true);
final String uri = checkNotNull(mgateEndpoint);
final CharSequence page = httpClient.get(uri, request, "application/json", Charsets.UTF_8);
final CharSequence page = httpClient.get(HttpUrl.parse(uri), request, "application/json", Charsets.UTF_8);
try {
final JSONObject head = new JSONObject(page.toString());
@ -1058,7 +1072,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
false);
final String uri = checkNotNull(mgateEndpoint);
final CharSequence page = httpClient.get(uri, request, "application/json", Charsets.UTF_8);
final CharSequence page = httpClient.get(HttpUrl.parse(uri), request, "application/json", Charsets.UTF_8);
try {
final JSONObject head = new JSONObject(page.toString());
@ -1466,20 +1480,16 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
final CharSequence conReq, final Location from, final @Nullable Location via, final Location to)
throws IOException {
final String request = wrapReqC(conReq, null);
Reader reader = null;
String firstChars = null;
try {
final String endpoint = extXmlEndpoint != null ? extXmlEndpoint : queryEndpoint;
final InputStream is = httpClient.getInputStream(endpoint, request, "application/xml", null, null);
firstChars = HttpClient.peekFirstChars(is);
reader = new InputStreamReader(is, Charsets.ISO_8859_1);
final AtomicReference<QueryTripsResult> result = new AtomicReference<QueryTripsResult>();
httpClient.getInputStream(new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
final XmlPullParserFactory factory = XmlPullParserFactory
.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
final XmlPullParser pp = factory.newPullParser();
pp.setInput(reader);
pp.setInput(body.charStream());
XmlPullUtil.require(pp, "ResC");
final String product = XmlPullUtil.attr(pp, "prod").split(" ")[0];
@ -1488,10 +1498,14 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
if (XmlPullUtil.test(pp, "Err")) {
final String code = XmlPullUtil.attr(pp, "code");
if (code.equals("I3")) // Input: date outside of the timetable period
return new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE);
if (code.equals("F1")) // Spool: Error reading the spoolfile
return new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN);
if (code.equals("I3")) {
result.set(new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE));
return;
}
if (code.equals("F1")) {
result.set(new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN));
return;
}
throw new IllegalStateException("error " + code + " " + XmlPullUtil.attr(pp, "text"));
}
@ -1500,35 +1514,96 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
if (XmlPullUtil.test(pp, "Err")) {
final String code = XmlPullUtil.attr(pp, "code");
log.debug("Hafas error: {}", code);
if (code.equals("K9260")) // Unknown departure station
return new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_FROM);
if (code.equals("K9280")) // Unknown intermediate station
return new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_VIA);
if (code.equals("K9300")) // Unknown arrival station
return new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_TO);
if (code.equals("K9360")) // Date outside of the timetable period
return new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE);
if (code.equals("K9380")) // Dep./Arr./Intermed. or equivalent station defined more that once
return new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE);
if (code.equals("K895")) // Departure/Arrival are too near
return new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE);
if (code.equals("K9220")) // Nearby to the given address stations could not be found
return new QueryTripsResult(header, QueryTripsResult.Status.UNRESOLVABLE_ADDRESS);
if (code.equals("K9240")) // Internal error
return new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN);
if (code.equals("K890")) // No connections found
return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS);
if (code.equals("K891")) // No route found (try entering an intermediate station)
return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS);
if (code.equals("K899")) // An error occurred
return new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN);
if (code.equals("K1:890")) // Unsuccessful or incomplete search (direction: forward)
return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS);
if (code.equals("K2:890")) // Unsuccessful or incomplete search (direction: backward)
return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS);
if (code.equals("K9260")) {
// Unknown departure station
result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_FROM));
return;
}
if (code.equals("K9280")) {
// Unknown intermediate station
result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_VIA));
return;
}
if (code.equals("K9300")) {
// Unknown arrival station
result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_TO));
return;
}
if (code.equals("K9360")) {
// Date outside of the timetable period
result.set(new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE));
return;
}
if (code.equals("K9380")) {
// Dep./Arr./Intermed. or equivalent station defined more than once
result.set(new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE));
return;
}
if (code.equals("K895")) {
// Departure/Arrival are too near
result.set(new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE));
return;
}
if (code.equals("K9220")) {
// Nearby to the given address stations could not be found
result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNRESOLVABLE_ADDRESS));
return;
}
if (code.equals("K9240")) {
// Internal error
result.set(new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN));
return;
}
if (code.equals("K890")) {
// No connections found
result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS));
return;
}
if (code.equals("K891")) {
// No route found (try entering an intermediate station)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS));
return;
}
if (code.equals("K899")) {
// An error occurred
result.set(new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN));
return;
}
if (code.equals("K1:890")) {
// Unsuccessful or incomplete search (direction: forward)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS));
return;
}
if (code.equals("K2:890")) {
// Unsuccessful or incomplete search (direction: backward)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS));
return;
}
throw new IllegalStateException("error " + code + " " + XmlPullUtil.attr(pp, "text"));
}
// H9380 Dep./Arr./Intermed. or equivalent stations defined more than once
// H9360 Error in data field
// H9320 The input is incorrect or incomplete
// H9300 Unknown arrival station
// H9280 Unknown intermediate station
// H9260 Unknown departure station
// H9250 Part inquiry interrupted
// H9240 Unsuccessful search
// H9230 An internal error occurred
// H9220 Nearby to the given address stations could not be found
// H900 Unsuccessful or incomplete search (timetable change)
// H892 Inquiry too complex (try entering less intermediate stations)
// H891 No route found (try entering an intermediate station)
// H890 Unsuccessful search.
// H500 Because of too many trains the connection is not complete
// H460 One or more stops are passed through multiple times.
// H455 Prolonged stop
// H410 Display may be incomplete due to change of timetable
// H390 Departure/Arrival replaced by an equivalent station
// H895 Departure/Arrival are too near
// H899 Unsuccessful or incomplete search (timetable change
final String c = XmlPullUtil.optValueTag(pp, "ConResCtxt", null);
final Context context;
if (previousContext == null)
@ -1700,8 +1775,8 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
XmlPullUtil.skipExit(pp, "Dep");
}
intermediateStops.add(new Stop(location, stopArrivalTime, stopArrivalPosition,
stopDepartureTime, stopDeparturePosition));
intermediateStops.add(new Stop(location, stopArrivalTime,
stopArrivalPosition, stopDepartureTime, stopDeparturePosition));
}
XmlPullUtil.skipExit(pp, "BasicStop");
}
@ -1770,15 +1845,18 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
if (line != null) {
final Stop departure = new Stop(sectionDepartureLocation, true, departureTime, null,
departurePos, null);
final Stop arrival = new Stop(sectionArrivalLocation, false, arrivalTime, null, arrivalPos,
null);
final Stop arrival = new Stop(sectionArrivalLocation, false, arrivalTime, null,
arrivalPos, null);
legs.add(new Trip.Public(line, destination, departure, arrival, intermediateStops, path, null));
legs.add(new Trip.Public(line, destination, departure, arrival, intermediateStops, path,
null));
} else {
if (legs.size() > 0 && legs.get(legs.size() - 1) instanceof Trip.Individual) {
final Trip.Individual lastIndividualLeg = (Trip.Individual) legs.remove(legs.size() - 1);
final Trip.Individual lastIndividualLeg = (Trip.Individual) legs
.remove(legs.size() - 1);
legs.add(new Trip.Individual(Trip.Individual.Type.WALK, lastIndividualLeg.departure,
lastIndividualLeg.departureTime, sectionArrivalLocation, arrivalTime, null, 0));
lastIndividualLeg.departureTime, sectionArrivalLocation, arrivalTime, null,
0));
} else {
legs.add(new Trip.Individual(Trip.Individual.Type.WALK, sectionDepartureLocation,
departureTime, sectionArrivalLocation, arrivalTime, null, 0));
@ -1795,14 +1873,15 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
XmlPullUtil.skipExit(pp, "ConnectionList");
return new QueryTripsResult(header, null, from, via, to, context, trips);
result.set(new QueryTripsResult(header, null, from, via, to, context, trips));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (reader != null)
reader.close();
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
}, HttpUrl.parse(endpoint), request, "application/xml", null, null);
return result.get();
}
private final Location parseLocation(final XmlPullParser pp) throws XmlPullParserException, IOException {
final Location location;
@ -2050,21 +2129,24 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
* Many thanks to Malte Starostik and Robert, who helped a lot with analyzing this API!
*/
LittleEndianDataInputStream is = null;
final AtomicReference<QueryTripsResult> result = new AtomicReference<QueryTripsResult>();
try {
final CustomBufferedInputStream bis = new CustomBufferedInputStream(httpClient.getInputStream(uri));
final String firstChars = HttpClient.peekFirstChars(bis);
httpClient.getInputStream(new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
final CustomBufferedInputStream bis = new CustomBufferedInputStream(
new GZIPInputStream(body.byteStream()));
// initialize input stream
is = new LittleEndianDataInputStream(bis);
final LittleEndianDataInputStream is = new LittleEndianDataInputStream(bis);
is.mark(expectedBufferSize);
// quick check of status
final int version = is.readShortReverse();
if (version != 6 && version != 5)
throw new IllegalStateException("unknown version: " + version + ", first chars: " + firstChars);
final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT, Integer.toString(version), 0, null);
throw new IllegalStateException("unknown version: " + version + ", first chars: " + bodyPeek);
final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT, Integer.toString(version), 0,
null);
// quick seek for pointers
is.reset();
@ -2106,8 +2188,10 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
is.skipBytes(30);
final int numTrips = is.readShortReverse();
if (numTrips == 0)
return new QueryTripsResult(header, uri, from, via, to, null, new LinkedList<Trip>());
if (numTrips == 0) {
result.set(new QueryTripsResult(header, uri, from, via, to, null, new LinkedList<Trip>()));
return;
}
// read rest of header
is.reset();
@ -2171,12 +2255,12 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
final int stopsOffset = is.readShortReverse();
// read stations
final StationTable stations = new StationTable(is, stationTablePtr, commentTablePtr - stationTablePtr,
strings);
final StationTable stations = new StationTable(is, stationTablePtr,
commentTablePtr - stationTablePtr, strings);
// read comments
final CommentTable comments = new CommentTable(is, commentTablePtr, tripDetailsPtr - commentTablePtr,
strings);
final CommentTable comments = new CommentTable(is, commentTablePtr,
tripDetailsPtr - commentTablePtr, strings);
final List<Trip> trips = new ArrayList<Trip>(numTrips);
@ -2323,11 +2407,12 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
lineCategory = categoryFromName(lineName);
is.reset();
is.skipBytes(
tripDetailsPtr + tripDetailsOffset + tripDetailsLegOffset + iLegs * tripDetailsLegSize);
is.skipBytes(tripDetailsPtr + tripDetailsOffset + tripDetailsLegOffset
+ iLegs * tripDetailsLegSize);
if (tripDetailsLegSize != 16)
throw new IllegalStateException("unhandled trip details leg size: " + tripDetailsLegSize);
throw new IllegalStateException(
"unhandled trip details leg size: " + tripDetailsLegSize);
final long predictedDepartureTime = time(is, resDate, tripDayOffset);
final long predictedArrivalTime = time(is, resDate, tripDayOffset);
@ -2441,8 +2526,8 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
final Stop stop = new Stop(stopLocation, plannedStopArrivalDate,
validPredictedDate ? predictedStopArrivalDate : null,
plannedStopArrivalPosition, predictedStopArrivalPosition, stopArrivalCancelled,
plannedStopDepartureDate,
plannedStopArrivalPosition, predictedStopArrivalPosition,
stopArrivalCancelled, plannedStopDepartureDate,
validPredictedDate ? predictedStopDepartureDate : null,
plannedStopDeparturePosition, predictedStopDeparturePosition,
stopDepartureCancelled);
@ -2455,7 +2540,8 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
if (type == 1 /* Fussweg */ || type == 3 /* Uebergang */ || type == 4 /* Uebergang */) {
final Trip.Individual.Type individualType;
if (routingType == null)
individualType = type == 1 ? Trip.Individual.Type.WALK : Trip.Individual.Type.TRANSFER;
individualType = type == 1 ? Trip.Individual.Type.WALK
: Trip.Individual.Type.TRANSFER;
else if ("FOOT".equals(routingType))
individualType = Trip.Individual.Type.WALK;
else if ("BIKE".equals(routingType))
@ -2530,84 +2616,105 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
final boolean canQueryMore = trips.size() != 1 || trips.get(0).legs.size() != 1
|| !(trips.get(0).legs.get(0) instanceof Trip.Individual);
final QueryTripsResult result = new QueryTripsResult(header, uri, from, via, to,
new QueryTripsBinaryContext(requestId, seqNr, ld, bis.getCount(), canQueryMore), trips);
return result;
result.set(new QueryTripsResult(header, uri, from, via, to,
new QueryTripsBinaryContext(requestId, seqNr, ld, bis.getCount(), canQueryMore), trips));
} else {
log.debug("Hafas error: {}", errorCode);
if (errorCode == 1)
if (errorCode == 1) {
throw new SessionExpiredException();
else if (errorCode == 2)
} else if (errorCode == 2) {
// F2: Your search results could not be stored internally.
throw new SessionExpiredException();
else if (errorCode == 8)
return new QueryTripsResult(header, QueryTripsResult.Status.AMBIGUOUS);
else if (errorCode == 13)
// IN13: Our booking system is currently being used by too many users at the same time.
return new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN);
else if (errorCode == 19)
return new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN);
else if (errorCode == 207)
} else if (errorCode == 8) {
result.set(new QueryTripsResult(header, QueryTripsResult.Status.AMBIGUOUS));
return;
} else if (errorCode == 13) {
// IN13: Our booking system is currently being used by too many users at the same
// time.
result.set(new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN));
return;
} else if (errorCode == 19) {
result.set(new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN));
return;
} else if (errorCode == 207) {
// H207: Unfortunately your connection request can currently not be processed.
return new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN);
else if (errorCode == 887)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN));
return;
} else if (errorCode == 887) {
// H887: Your inquiry was too complex. Please try entering less intermediate stations.
return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS);
else if (errorCode == 890)
// H890: No connections have been found that correspond to your request. It is possible
result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS));
return;
} else if (errorCode == 890) {
// H890: No connections have been found that correspond to your request. It is
// possible
// that the requested service does not operate from or to the places you stated on the
// requested date of travel.
return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS);
else if (errorCode == 891)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS));
return;
} else if (errorCode == 891) {
// H891: Unfortunately there was no route found. Missing timetable data could be the
// reason.
return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS);
else if (errorCode == 892)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS));
return;
} else if (errorCode == 892) {
// H892: Your inquiry was too complex. Please try entering less intermediate stations.
return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS);
else if (errorCode == 899)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS));
return;
} else if (errorCode == 899) {
// H899: there was an unsuccessful or incomplete search due to a timetable change.
return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS);
else if (errorCode == 900)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS));
return;
} else if (errorCode == 900) {
// Unsuccessful or incomplete search (timetable change)
return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS);
else if (errorCode == 9220)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS));
return;
} else if (errorCode == 9220) {
// H9220: Nearby to the given address stations could not be found.
return new QueryTripsResult(header, QueryTripsResult.Status.UNRESOLVABLE_ADDRESS);
else if (errorCode == 9240)
// H9240: Unfortunately there was no route found. Perhaps your start or destination is not
result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNRESOLVABLE_ADDRESS));
return;
} else if (errorCode == 9240) {
// H9240: Unfortunately there was no route found. Perhaps your start or destination is
// not
// served at all or with the selected means of transport on the required date/time.
return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS);
else if (errorCode == 9260)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS));
return;
} else if (errorCode == 9260) {
// H9260: Unknown departure station
return new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_FROM);
else if (errorCode == 9280)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_FROM));
return;
} else if (errorCode == 9280) {
// H9280: Unknown intermediate station
return new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_VIA);
else if (errorCode == 9300)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_VIA));
return;
} else if (errorCode == 9300) {
// H9300: Unknown arrival station
return new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_TO);
else if (errorCode == 9320)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_TO));
return;
} else if (errorCode == 9320) {
// The input is incorrect or incomplete
return new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE);
else if (errorCode == 9360)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE));
return;
} else if (errorCode == 9360) {
// H9360: Unfortunately your connection request can currently not be processed.
return new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE);
else if (errorCode == 9380)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE));
return;
} else if (errorCode == 9380) {
// H9380: Dep./Arr./Intermed. or equivalent station defined more than once
return new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE);
else if (errorCode == 895)
result.set(new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE));
return;
} else if (errorCode == 895) {
// H895: Departure/Arrival are too near
return new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE);
else
result.set(new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE));
return;
} else {
throw new IllegalStateException("error " + errorCode + " on " + uri);
}
} finally {
if (is != null)
is.close();
}
}
}, HttpUrl.parse(uri));
return result.get();
}
private Location location(final LittleEndianDataInputStream is, final StringTable strings) throws IOException {
final String name = strings.read(is);
@ -2853,7 +2960,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
protected final NearbyLocationsResult xmlNearbyStations(final String uri) throws IOException {
// scrape page
final CharSequence page = httpClient.get(uri);
final CharSequence page = httpClient.get(HttpUrl.parse(uri));
final List<Location> stations = new ArrayList<Location>();
@ -2929,7 +3036,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
}
protected final NearbyLocationsResult jsonNearbyLocations(final String uri) throws IOException {
final CharSequence page = httpClient.get(uri, jsonNearbyLocationsEncoding);
final CharSequence page = httpClient.get(HttpUrl.parse(uri), jsonNearbyLocationsEncoding);
try {
final JSONObject head = new JSONObject(page.toString());
@ -3010,7 +3117,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider {
protected final NearbyLocationsResult htmlNearbyStations(final String uri) throws IOException {
final List<Location> stations = new ArrayList<Location>();
final CharSequence page = httpClient.get(uri);
final CharSequence page = httpClient.get(HttpUrl.parse(uri));
String oldZebra = null;
final Matcher mCoarse = htmlNearbyStationsPattern.matcher(page);

View file

@ -66,6 +66,8 @@ import de.schildbach.pte.exception.NotFoundException;
import de.schildbach.pte.exception.ParserException;
import de.schildbach.pte.util.ParserUtils;
import okhttp3.HttpUrl;
/**
* @author Antonio El Khoury
* @author Andreas Schildbach
@ -641,7 +643,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider {
private String getStopAreaId(final String stopPointId) throws IOException {
final String uri = uri() + "stop_points/" + ParserUtils.urlEncode(stopPointId) + "?depth=1";
final CharSequence page = httpClient.get(uri);
final CharSequence page = httpClient.get(HttpUrl.parse(uri));
try {
final JSONObject head = new JSONObject(page.toString());
@ -698,7 +700,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider {
if (maxLocations > 0)
queryUri.append("&count=").append(maxLocations);
queryUri.append("&depth=3");
final CharSequence page = httpClient.get(queryUri.toString());
final CharSequence page = httpClient.get(HttpUrl.parse(queryUri.toString()));
try {
final JSONObject head = new JSONObject(page.toString());
@ -773,7 +775,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider {
queryUri.append("&duration=86400");
queryUri.append("&depth=0");
final CharSequence page = httpClient.get(queryUri.toString());
final CharSequence page = httpClient.get(HttpUrl.parse(queryUri.toString()));
final JSONObject head = new JSONObject(page.toString());
@ -821,7 +823,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider {
throw new ParserException(parseExc);
} catch (final NotFoundException fnfExc) {
try {
final JSONObject head = new JSONObject(fnfExc.scrapeErrorStream().toString());
final JSONObject head = new JSONObject(fnfExc.getBodyPeek().toString());
final JSONObject error = head.getJSONObject("error");
final String id = error.getString("id");
@ -841,7 +843,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider {
final String queryUri = uri() + "places?q=" + ParserUtils.urlEncode(nameCstr)
+ "&type[]=stop_area&type[]=address&type[]=poi&type[]=administrative_region" + "&depth=1";
final CharSequence page = httpClient.get(queryUri);
final CharSequence page = httpClient.get(HttpUrl.parse(queryUri));
try {
final List<SuggestedLocation> locations = new ArrayList<SuggestedLocation>();
@ -945,7 +947,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider {
}
}
final CharSequence page = httpClient.get(queryUri.toString());
final CharSequence page = httpClient.get(HttpUrl.parse(queryUri.toString()));
try {
final JSONObject head = new JSONObject(page.toString());
@ -1013,7 +1015,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider {
return new QueryTripsResult(resultHeader, QueryTripsResult.Status.NO_TRIPS);
} catch (final NotFoundException fnfExc) {
try {
final JSONObject head = new JSONObject(fnfExc.scrapeErrorStream().toString());
final JSONObject head = new JSONObject(fnfExc.getBodyPeek().toString());
final JSONObject error = head.getJSONObject("error");
final String id = error.getString("id");
@ -1046,7 +1048,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider {
final Location from = context.from;
final Location to = context.to;
final String queryUri = later ? context.nextQueryUri : context.prevQueryUri;
final CharSequence page = httpClient.get(queryUri);
final CharSequence page = httpClient.get(HttpUrl.parse(queryUri));
try {
if (from.isIdentified() && to.isIdentified()) {
@ -1076,7 +1078,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider {
@Override
public Point[] getArea() throws IOException {
final String queryUri = uri();
final CharSequence page = httpClient.get(queryUri);
final CharSequence page = httpClient.get(HttpUrl.parse(queryUri));
try {
// Get shape string.

View file

@ -59,6 +59,8 @@ import de.schildbach.pte.dto.Trip;
import de.schildbach.pte.exception.ParserException;
import de.schildbach.pte.util.ParserUtils;
import okhttp3.HttpUrl;
/**
* @author Kjell Braden <afflux@pentabarf.de>
*/
@ -197,7 +199,7 @@ public abstract class AbstractTsiProvider extends AbstractNetworkProvider {
final StringBuilder uri = new StringBuilder(stopFinderEndpoint);
uri.append(parameters);
final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8);
final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8);
try {
final List<SuggestedLocation> locations = new ArrayList<SuggestedLocation>();
final JSONObject head = new JSONObject(page.toString());
@ -277,7 +279,7 @@ public abstract class AbstractTsiProvider extends AbstractNetworkProvider {
final StringBuilder uri = new StringBuilder(stopFinderEndpoint);
uri.append(parameters);
final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8);
final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8);
try {
final List<Location> stations = new ArrayList<Location>();
final JSONObject head = new JSONObject(page.toString());
@ -315,7 +317,7 @@ public abstract class AbstractTsiProvider extends AbstractNetworkProvider {
final StringBuilder uri = new StringBuilder(stopFinderEndpoint);
uri.append(parameters);
final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8);
final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8);
try {
final JSONObject head = new JSONObject(page.toString());
@ -674,7 +676,7 @@ public abstract class AbstractTsiProvider extends AbstractNetworkProvider {
final StringBuilder uri = new StringBuilder(tripEndpoint);
uri.append(parameters);
final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8);
final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8);
try {
final JSONObject head = new JSONObject(page.toString());

View file

@ -18,7 +18,6 @@
package de.schildbach.pte;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
@ -33,6 +32,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
@ -63,6 +63,9 @@ import de.schildbach.pte.exception.ParserException;
import de.schildbach.pte.util.HttpClient;
import de.schildbach.pte.util.XmlPullUtil;
import okhttp3.HttpUrl;
import okhttp3.ResponseBody;
/**
* @author Mats Sjöberg <mats@sjoberg.fi>
*/
@ -137,19 +140,16 @@ public class HslProvider extends AbstractNetworkProvider {
private Location queryStop(final String stationId) throws IOException {
final StringBuilder uri = apiUri("stop");
uri.append("&code=").append(stationId);
uri.append(String.format("&dep_limit=1"));
final AtomicReference<Location> result = new AtomicReference<Location>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
is = httpClient.getInputStream(uri.toString());
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(body.charStream());
XmlPullUtil.enter(pp, "response");
XmlPullUtil.enter(pp, "node");
@ -157,15 +157,16 @@ public class HslProvider extends AbstractNetworkProvider {
final String id = xmlValueTag(pp, "code");
final String name = xmlValueTag(pp, "name_fi");
final Point pt = coordStrToPoint(xmlValueTag(pp, "coords"));
return new Location(LocationType.STATION, id, pt.lat, pt.lon, null, name);
result.set(new Location(LocationType.STATION, id, pt.lat, pt.lon, null, name));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()));
return result.get();
}
// Determine stations near to given location. At least one of
// stationId or lat/lon pair must be present.
@ -174,7 +175,6 @@ public class HslProvider extends AbstractNetworkProvider {
public NearbyLocationsResult queryNearbyLocations(EnumSet<LocationType> types, Location location, int maxDistance,
int maxStations) throws IOException {
final StringBuilder uri = apiUri("stops_area");
if (!location.hasLocation()) {
if (location.type != LocationType.STATION)
throw new IllegalArgumentException("cannot handle: " + location.type);
@ -185,16 +185,14 @@ public class HslProvider extends AbstractNetworkProvider {
uri.append("&center_coordinate=").append(locationToCoords(location));
uri.append(String.format("&limit=%d", maxStations));
uri.append(String.format("&diameter=%d", maxDistance * 2));
final AtomicReference<NearbyLocationsResult> result = new AtomicReference<NearbyLocationsResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
is = httpClient.getInputStream(uri.toString());
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(body.charStream());
final List<Location> stations = new ArrayList<Location>();
final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT);
@ -214,14 +212,16 @@ public class HslProvider extends AbstractNetworkProvider {
stations.add(new Location(LocationType.STATION, id, pt.lat, pt.lon, place, name));
}
return new NearbyLocationsResult(header, stations);
result.set(new NearbyLocationsResult(header, stations));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()));
return result.get();
}
private Line newLine(String code, int type, String message) {
String label = code.substring(1, 5).trim().replaceAll("^0+", "");
@ -254,26 +254,23 @@ public class HslProvider extends AbstractNetworkProvider {
// Get departures at a given station, probably live
@Override
public QueryDeparturesResult queryDepartures(String stationId, @Nullable Date queryDate, int maxDepartures,
public QueryDeparturesResult queryDepartures(String stationId, @Nullable Date queryDate, final int maxDepartures,
boolean equivs) throws IOException {
final StringBuilder uri = apiUri("stop");
uri.append("&code=").append(stationId);
if (queryDate != null) {
uri.append("&date=").append(new SimpleDateFormat("yyyyMMdd").format(queryDate));
uri.append("&time=").append(new SimpleDateFormat("HHmm").format(queryDate));
}
uri.append(String.format("&dep_limit=%d", maxDepartures));
final AtomicReference<QueryDeparturesResult> result = new AtomicReference<QueryDeparturesResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
is = httpClient.getInputStream(uri.toString());
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(body.charStream());
XmlPullUtil.enter(pp, "response");
XmlPullUtil.enter(pp, "node");
@ -293,7 +290,7 @@ public class HslProvider extends AbstractNetworkProvider {
XmlPullUtil.skipExit(pp, "lines");
final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT);
final QueryDeparturesResult result = new QueryDeparturesResult(header);
final QueryDeparturesResult r = new QueryDeparturesResult(header);
XmlPullUtil.skipUntil(pp, "departures");
XmlPullUtil.enter(pp, "departures");
@ -316,17 +313,17 @@ public class HslProvider extends AbstractNetworkProvider {
}
Location station = new Location(LocationType.STATION, id, null, name);
result.stationDepartures.add(new StationDepartures(station, departures, null));
return result;
r.stationDepartures.add(new StationDepartures(station, departures, null));
result.set(r);
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()));
return result.get();
}
/**
* Meant for auto-completion of location names, like in an {@link android.widget.AutoCompleteTextView}
@ -339,29 +336,23 @@ public class HslProvider extends AbstractNetworkProvider {
@Override
public SuggestLocationsResult suggestLocations(CharSequence constraint) throws IOException {
final StringBuilder uri = apiUri("geocode");
// Since HSL is picky about the input we clean out any
// character that isn't alphabetic, numeral, -, ', /
// or a space. Those should be all chars needed for a
// name.
String constraintStr = constraint.toString().replaceAll("[^\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}\\p{Nd}\\d-'/ ]", "");
uri.append("&key=").append(URLEncoder.encode(constraintStr, "utf-8"));
final AtomicReference<SuggestLocationsResult> result = new AtomicReference<SuggestLocationsResult>();
InputStream is = null;
String firstChars = null;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
is = httpClient.getInputStream(uri.toString());
firstChars = HttpClient.peekFirstChars(is);
final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT);
final List<SuggestedLocation> locations = new ArrayList<SuggestedLocation>();
if (firstChars.isEmpty())
return new SuggestLocationsResult(header, locations);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(body.charStream());
XmlPullUtil.enter(pp, "response");
@ -393,18 +384,21 @@ public class HslProvider extends AbstractNetworkProvider {
if (shortCode != null)
name = name + " (" + shortCode + ")";
locations.add(new SuggestedLocation(new Location(type, id, pt.lat, pt.lon, null, name), weight));
locations
.add(new SuggestedLocation(new Location(type, id, pt.lat, pt.lon, null, name), weight));
weight -= 1;
}
return new SuggestLocationsResult(header, locations);
result.set(new SuggestLocationsResult(header, locations));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()));
return result.get();
}
@SuppressWarnings("serial")
public static class QueryTripsHslContext implements QueryTripsContext {
@ -553,23 +547,18 @@ public class HslProvider extends AbstractNetworkProvider {
}
private QueryTripsResult queryHslTrips(final Location from, final Location via, final Location to,
QueryTripsHslContext context, Date date, boolean later) throws IOException {
final QueryTripsHslContext context, Date date, final boolean later) throws IOException {
final StringBuilder uri = new StringBuilder(context.uri);
uri.append("&date=").append(new SimpleDateFormat("yyyyMMdd").format(date));
uri.append("&time=").append(new SimpleDateFormat("HHmm").format(date));
final AtomicReference<QueryTripsResult> result = new AtomicReference<QueryTripsResult>();
InputStream is = null;
String firstChars = null;
context.date = date;
final HttpClient.Callback callback = new HttpClient.Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
is = httpClient.getInputStream(uri.toString());
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
pp.setInput(body.charStream());
XmlPullUtil.enter(pp, "response");
@ -662,17 +651,18 @@ public class HslProvider extends AbstractNetworkProvider {
if (legType.equals("walk")) {
// ugly hack to set the name of the last arrival
if (arrival != null && arrival.name == null) {
arrival = new Location(arrival.type, arrival.id, arrival.lat, arrival.lon, arrival.place,
to.name);
arrival = new Location(arrival.type, arrival.id, arrival.lat, arrival.lon,
arrival.place, to.name);
}
legs.add(new Trip.Individual(Trip.Individual.Type.WALK, departure, departureTime, arrival,
arrivalTime, path, distance));
legs.add(new Trip.Individual(Trip.Individual.Type.WALK, departure, departureTime,
arrival, arrivalTime, path, distance));
} else {
Stop arrivalStop = null;
if (stops.size() > 0) {
Stop last = stops.getLast();
arrivalStop = new Stop(last.location, false, last.plannedArrivalTime, null, null, null);
arrivalStop = new Stop(last.location, false, last.plannedArrivalTime, null, null,
null);
stops.removeLast();
}
@ -706,12 +696,17 @@ public class HslProvider extends AbstractNetworkProvider {
if (context.prevDate == null || firstDate.before(context.prevDate))
context.prevDate = firstDate;
context.trips = trips;
return new QueryTripsResult(header, uri.toString(), from, via, to, context, trips);
result.set(new QueryTripsResult(header, uri.toString(), from, via, to, context, trips));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
context.date = date;
httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()));
return result.get();
}
}

View file

@ -54,6 +54,8 @@ import de.schildbach.pte.dto.StationDepartures;
import de.schildbach.pte.dto.Style;
import de.schildbach.pte.util.ParserUtils;
import okhttp3.HttpUrl;
/**
* @author Andreas Schildbach
*/
@ -171,7 +173,7 @@ public class InvgProvider extends AbstractHafasProvider {
// scrape page
final StringBuilder uri = new StringBuilder(stationBoardEndpoint);
appendXmlStationBoardParameters(uri, time, stationId, maxDepartures, false, null);
final CharSequence page = httpClient.get(uri.toString());
final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()));
// parse page
final Matcher mHeadCoarse = P_DEPARTURES_HEAD_COARSE.matcher(page);

View file

@ -51,6 +51,8 @@ import de.schildbach.pte.dto.ResultHeader;
import de.schildbach.pte.dto.StationDepartures;
import de.schildbach.pte.util.ParserUtils;
import okhttp3.HttpUrl;
/**
* @author Andreas Schildbach
*/
@ -134,7 +136,7 @@ public class SeptaProvider extends AbstractHafasProvider {
// scrape page
final StringBuilder uri = new StringBuilder(stationBoardEndpoint);
appendXmlStationBoardParameters(uri, time, stationId, maxDepartures, false, null);
final CharSequence page = httpClient.get(uri.toString());
final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()));
// parse page
final Matcher mPageCoarse = P_DEPARTURES_PAGE_COARSE.matcher(page);

View file

@ -73,6 +73,8 @@ import de.schildbach.pte.dto.Trip;
import de.schildbach.pte.dto.Trip.Leg;
import de.schildbach.pte.util.ParserUtils;
import okhttp3.HttpUrl;
/**
* @author Michael Dyrna
*/
@ -370,7 +372,7 @@ public class VrsProvider extends AbstractNetworkProvider {
uri.append("&s=").append(Math.min(16, maxLocations)); // artificial server limit
}
final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8);
final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8);
try {
final List<Location> locations = new ArrayList<Location>();
@ -427,7 +429,7 @@ public class VrsProvider extends AbstractNetworkProvider {
uri.append("&t=");
appendDate(uri, time);
}
final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8);
final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8);
try {
final JSONObject head = new JSONObject(page.toString());
@ -514,7 +516,7 @@ public class VrsProvider extends AbstractNetworkProvider {
final StringBuilder uri = new StringBuilder(API_BASE);
uri.append("?eID=tx_vrsinfo_his_info&i=").append(ParserUtils.urlEncode(stationId));
final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8);
final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8);
try {
final JSONObject head = new JSONObject(page.toString());
@ -573,7 +575,7 @@ public class VrsProvider extends AbstractNetworkProvider {
final String uri = API_BASE + "?eID=tx_vrsinfo_ass2_objects&sc=" + sc + "&ac=" + ac + "&pc=" + ac + "&t=sap&q="
+ ParserUtils.urlEncode(new Location(LocationType.ANY, null, null, constraint.toString()).name);
final CharSequence page = httpClient.get(uri, Charsets.UTF_8);
final CharSequence page = httpClient.get(HttpUrl.parse(uri), Charsets.UTF_8);
try {
final List<SuggestedLocation> locations = new ArrayList<SuggestedLocation>();
@ -691,7 +693,7 @@ public class VrsProvider extends AbstractNetworkProvider {
uri.append("p");
}
final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8);
final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8);
try {
final List<Trip> trips = new ArrayList<Trip>();

View file

@ -18,45 +18,32 @@
package de.schildbach.pte.exception;
import java.io.IOException;
import java.io.Reader;
import java.net.URL;
import de.schildbach.pte.util.HttpClient;
import okhttp3.HttpUrl;
/**
* @author Andreas Schildbach
*/
@SuppressWarnings("serial")
public abstract class AbstractHttpException extends IOException {
private final URL url;
private final Reader errorReader;
private final HttpUrl url;
private final CharSequence bodyPeek;
public AbstractHttpException(final URL url) {
public AbstractHttpException(final HttpUrl url) {
this(url, null);
}
public AbstractHttpException(final URL url, final Reader errorReader) {
public AbstractHttpException(final HttpUrl url, final CharSequence bodyPeek) {
super(url.toString());
this.url = url;
this.errorReader = errorReader;
this.bodyPeek = bodyPeek;
}
public URL getUrl() {
public HttpUrl getUrl() {
return url;
}
public Reader getErrorReader() {
return errorReader;
}
public CharSequence scrapeErrorStream() throws IOException {
if (errorReader == null)
return null;
final StringBuilder error = new StringBuilder(HttpClient.SCRAPE_INITIAL_CAPACITY);
HttpClient.copy(errorReader, error);
errorReader.close();
return error;
public CharSequence getBodyPeek() {
return bodyPeek;
}
}

View file

@ -17,15 +17,14 @@
package de.schildbach.pte.exception;
import java.io.Reader;
import java.net.URL;
import okhttp3.HttpUrl;
/**
* @author Andreas Schildbach
*/
@SuppressWarnings("serial")
public class BlockedException extends AbstractHttpException {
public BlockedException(final URL url, final Reader errorReader) {
super(url, errorReader);
public BlockedException(final HttpUrl url, final CharSequence bodyPeek) {
super(url, bodyPeek);
}
}

View file

@ -17,15 +17,14 @@
package de.schildbach.pte.exception;
import java.io.Reader;
import java.net.URL;
import okhttp3.HttpUrl;
/**
* @author Andreas Schildbach
*/
@SuppressWarnings("serial")
public class InternalErrorException extends AbstractHttpException {
public InternalErrorException(final URL url, final Reader errorReader) {
super(url, errorReader);
public InternalErrorException(final HttpUrl url, final CharSequence bodyPeek) {
super(url, bodyPeek);
}
}

View file

@ -17,15 +17,14 @@
package de.schildbach.pte.exception;
import java.io.Reader;
import java.net.URL;
import okhttp3.HttpUrl;
/**
* @author Andreas Schildbach
*/
@SuppressWarnings("serial")
public class NotFoundException extends AbstractHttpException {
public NotFoundException(final URL url, final Reader errorReader) {
super(url, errorReader);
public NotFoundException(final HttpUrl url, final CharSequence bodyPeek) {
super(url, bodyPeek);
}
}

View file

@ -17,21 +17,21 @@
package de.schildbach.pte.exception;
import java.net.URL;
import okhttp3.HttpUrl;
/**
* @author Andreas Schildbach
*/
@SuppressWarnings("serial")
public class UnexpectedRedirectException extends AbstractHttpException {
private final URL redirectedUrl;
private final HttpUrl redirectedUrl;
public UnexpectedRedirectException(final URL originalUrl, final URL redirectedUrl) {
public UnexpectedRedirectException(final HttpUrl originalUrl, final HttpUrl redirectedUrl) {
super(originalUrl);
this.redirectedUrl = redirectedUrl;
}
public URL getRedirectedUrl() {
public HttpUrl getRedirectedUrl() {
return redirectedUrl;
}

View file

@ -17,27 +17,19 @@
package de.schildbach.pte.util;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import javax.annotation.Nullable;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import org.slf4j.Logger;
@ -51,6 +43,18 @@ import de.schildbach.pte.exception.NotFoundException;
import de.schildbach.pte.exception.SessionExpiredException;
import de.schildbach.pte.exception.UnexpectedRedirectException;
import okhttp3.Call;
import okhttp3.Cookie;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
/**
* @author Andreas Schildbach
*/
@ -61,15 +65,33 @@ public final class HttpClient {
@Nullable
private String sessionCookieName = null;
@Nullable
private HttpCookie sessionCookie = null;
private Cookie sessionCookie = null;
private boolean sslAcceptAllHostnames = false;
private static final OkHttpClient OKHTTP_CLIENT;
static {
final HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(
new HttpLoggingInterceptor.Logger() {
@Override
public void log(final String message) {
log.debug(message);
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.followRedirects(false);
builder.followSslRedirects(true);
builder.connectTimeout(5, TimeUnit.SECONDS);
builder.writeTimeout(5, TimeUnit.SECONDS);
builder.readTimeout(15, TimeUnit.SECONDS);
builder.addNetworkInterceptor(loggingInterceptor);
OKHTTP_CLIENT = builder.build();
}
private static final String SCRAPE_ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
public static final int SCRAPE_INITIAL_CAPACITY = 4096;
private static final int SCRAPE_COPY_SIZE = 2048;
private static final int SCRAPE_PEEK_SIZE = 4096;
private static final int SCRAPE_CONNECT_TIMEOUT = 5000;
private static final int SCRAPE_READ_TIMEOUT = 15000;
private static final Logger log = LoggerFactory.getLogger(HttpClient.class);
@ -89,199 +111,127 @@ public final class HttpClient {
this.sslAcceptAllHostnames = sslAcceptAllHostnames;
}
public CharSequence get(final String url) throws IOException {
public CharSequence get(final HttpUrl url) throws IOException {
return get(url, null);
}
public CharSequence get(final String urlStr, final Charset requestEncoding) throws IOException {
return get(urlStr, null, null, requestEncoding);
public CharSequence get(final HttpUrl url, final Charset requestEncoding) throws IOException {
return get(url, null, null, requestEncoding);
}
public CharSequence get(final String urlStr, final String postRequest, final String requestContentType,
public CharSequence get(final HttpUrl url, final String postRequest, final String requestContentType,
Charset requestEncoding) throws IOException {
if (requestEncoding == null)
requestEncoding = Charsets.ISO_8859_1;
final StringBuilder buffer = new StringBuilder(SCRAPE_INITIAL_CAPACITY);
final InputStream is = getInputStream(urlStr, postRequest, requestContentType, requestEncoding, null);
final Reader pageReader = new InputStreamReader(is, requestEncoding);
copy(pageReader, buffer);
pageReader.close();
final Callback callback = new Callback() {
@Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
buffer.append(body.string());
}
};
getInputStream(callback, url, postRequest, requestContentType, requestEncoding, null);
return buffer;
}
public InputStream getInputStream(final String url) throws IOException {
return getInputStream(url, null, null);
public interface Callback {
void onSuccessful(CharSequence bodyPeek, ResponseBody body) throws IOException;
}
public InputStream getInputStream(final String urlStr, final Charset requestEncoding, final String referer)
throws IOException {
return getInputStream(urlStr, null, null, requestEncoding, referer);
public void getInputStream(final Callback callback, final HttpUrl url) throws IOException {
getInputStream(callback, url, null, null);
}
public InputStream getInputStream(final String urlStr, final String postRequest, final String requestContentType,
Charset requestEncoding, final String referer) throws IOException {
log.debug("{}: {}", postRequest != null ? "POST" : "GET", urlStr);
public void getInputStream(final Callback callback, final HttpUrl url, final Charset requestEncoding,
final String referer) throws IOException {
getInputStream(callback, url, null, null, requestEncoding, referer);
}
public void getInputStream(final Callback callback, final HttpUrl url, final String postRequest,
final String requestContentType, Charset requestEncoding, final String referer) throws IOException {
if (requestEncoding == null)
requestEncoding = Charsets.ISO_8859_1;
int tries = 3;
while (true) {
final URL url = new URL(urlStr);
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (connection instanceof HttpsURLConnection && sslAcceptAllHostnames)
((HttpsURLConnection) connection).setHostnameVerifier(SSL_ACCEPT_ALL_HOSTNAMES);
connection.setDoInput(true);
connection.setDoOutput(postRequest != null);
connection.setConnectTimeout(SCRAPE_CONNECT_TIMEOUT);
connection.setReadTimeout(SCRAPE_READ_TIMEOUT);
for (final Map.Entry<String, String> entry : headers.entrySet())
connection.addRequestProperty(entry.getKey(), entry.getValue());
final Request.Builder request = new Request.Builder();
request.url(url);
request.headers(Headers.of(headers));
if (postRequest != null)
request.post(RequestBody.create(MediaType.parse(requestContentType), postRequest));
request.header("Accept", SCRAPE_ACCEPT);
if (userAgent != null)
connection.addRequestProperty("User-Agent", userAgent);
connection.addRequestProperty("Accept", SCRAPE_ACCEPT);
connection.addRequestProperty("Accept-Encoding", "gzip");
// workaround to disable Vodafone compression
connection.addRequestProperty("Cache-Control", "no-cache");
request.header("User-Agent", userAgent);
if (referer != null)
connection.addRequestProperty("Referer", referer);
request.header("Referer", referer);
final Cookie sessionCookie = this.sessionCookie;
if (sessionCookie != null && sessionCookie.name().equals(sessionCookieName))
request.header("Cookie", sessionCookie.toString());
final HttpCookie sessionCookie = this.sessionCookie;
if (sessionCookie != null && sessionCookie.getName().equals(sessionCookieName))
connection.addRequestProperty("Cookie", sessionCookie.toString());
final OkHttpClient okHttpClient;
if (sslAcceptAllHostnames)
okHttpClient = OKHTTP_CLIENT.newBuilder().hostnameVerifier(SSL_ACCEPT_ALL_HOSTNAMES).build();
else
okHttpClient = OKHTTP_CLIENT;
if (postRequest != null) {
final byte[] postRequestBytes = postRequest.getBytes(requestEncoding.name());
connection.setRequestMethod("POST");
connection.addRequestProperty("Content-Type", requestContentType);
connection.addRequestProperty("Content-Length", Integer.toString(postRequestBytes.length));
final OutputStream os = connection.getOutputStream();
os.write(postRequestBytes);
os.close();
}
final int responseCode = connection.getResponseCode();
final Call call = okHttpClient.newCall(request.build());
Response response = null;
try {
response = call.execute();
final int responseCode = response.code();
final String bodyPeek = response.peekBody(SCRAPE_PEEK_SIZE).string().replaceAll("\\p{C}", "");
if (responseCode == HttpURLConnection.HTTP_OK) {
final String contentType = connection.getContentType();
final String contentEncoding = connection.getContentEncoding();
InputStream is = new BufferedInputStream(connection.getInputStream());
if ("gzip".equalsIgnoreCase(contentEncoding)
|| "application/octet-stream".equalsIgnoreCase(contentType))
is = wrapGzip(is);
if (!url.getHost().equals(connection.getURL().getHost()))
throw new UnexpectedRedirectException(url, connection.getURL());
final String firstChars = peekFirstChars(is);
final URL redirectUrl = testRedirect(url, firstChars);
final HttpUrl redirectUrl = testRedirect(url, bodyPeek);
if (redirectUrl != null)
throw new UnexpectedRedirectException(url, redirectUrl);
if (testExpired(firstChars))
if (testExpired(bodyPeek))
throw new SessionExpiredException();
if (testInternalError(firstChars))
throw new InternalErrorException(url, new InputStreamReader(is, requestEncoding));
if (testInternalError(bodyPeek))
throw new InternalErrorException(url, bodyPeek);
// save cookie
if (sessionCookieName != null) {
c: for (final Map.Entry<String, List<String>> entry : connection.getHeaderFields().entrySet()) {
if ("set-cookie".equalsIgnoreCase(entry.getKey())
|| "set-cookie2".equalsIgnoreCase(entry.getKey())) {
for (final String value : entry.getValue()) {
for (final HttpCookie cookie : HttpCookie.parse(value)) {
if (cookie.getName().equals(sessionCookieName)) {
final List<Cookie> cookies = Cookie.parseAll(url, response.headers());
for (final Iterator<Cookie> i = cookies.iterator(); i.hasNext();) {
final Cookie cookie = i.next();
if (cookie.name().equals(sessionCookieName)) {
this.sessionCookie = cookie;
break c;
}
}
}
break;
}
}
}
return is;
callback.onSuccessful(bodyPeek, response.body());
return;
} else if (responseCode == HttpURLConnection.HTTP_BAD_REQUEST
|| responseCode == HttpURLConnection.HTTP_UNAUTHORIZED
|| responseCode == HttpURLConnection.HTTP_FORBIDDEN
|| responseCode == HttpURLConnection.HTTP_NOT_ACCEPTABLE
|| responseCode == HttpURLConnection.HTTP_UNAVAILABLE) {
throw new BlockedException(url, new InputStreamReader(connection.getErrorStream(), requestEncoding));
throw new BlockedException(url, bodyPeek);
} else if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
throw new NotFoundException(url, new InputStreamReader(connection.getErrorStream(), requestEncoding));
throw new NotFoundException(url, bodyPeek);
} else if (responseCode == HttpURLConnection.HTTP_MOVED_PERM
|| responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
throw new UnexpectedRedirectException(url, connection.getURL());
throw new UnexpectedRedirectException(url, HttpUrl.parse(response.header("Location")));
} else if (responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
throw new InternalErrorException(url,
new InputStreamReader(connection.getErrorStream(), requestEncoding));
throw new InternalErrorException(url, bodyPeek);
} else {
final String message = "got response: " + responseCode + " " + connection.getResponseMessage();
final String message = "got response: " + responseCode + " " + response.message();
if (tries-- > 0)
log.info("{}, retrying...", message);
else
throw new IOException(message + ": " + url);
}
} finally {
if (response != null)
response.close();
}
}
public static final long copy(final Reader reader, final StringBuilder builder) throws IOException {
final char[] buffer = new char[SCRAPE_COPY_SIZE];
long count = 0;
int n = 0;
while (-1 != (n = reader.read(buffer))) {
builder.append(buffer, 0, n);
count += n;
}
return count;
}
private static InputStream wrapGzip(final InputStream is) throws IOException {
is.mark(2);
final int byte0 = is.read();
final int byte1 = is.read();
is.reset();
// check for gzip header
if (byte0 == 0x1f && byte1 == 0x8b) {
final BufferedInputStream is2 = new BufferedInputStream(new GZIPInputStream(is));
is2.mark(2);
final int byte0_2 = is2.read();
final int byte1_2 = is2.read();
is2.reset();
// check for gzip header again
if (byte0_2 == 0x1f && byte1_2 == 0x8b) {
// double gzipped
return new BufferedInputStream(new GZIPInputStream(is2));
} else {
// gzipped
return is2;
}
} else {
// uncompressed
return is;
}
}
public static String peekFirstChars(final InputStream is) throws IOException {
is.mark(SCRAPE_PEEK_SIZE);
final byte[] firstBytes = new byte[SCRAPE_PEEK_SIZE];
final int read = is.read(firstBytes);
if (read == -1)
return "";
is.reset();
return new String(firstBytes, 0, read).replaceAll("\\p{C}", "");
}
private static final Pattern P_REDIRECT_HTTP_EQUIV = Pattern.compile(
@ -291,16 +241,16 @@ public final class HttpClient {
"<script\\s+(?:type=\"text/javascript\"|language=\"javascript\")>\\s*(?:window.location|location.href)\\s*=\\s*\"([^\"]+)\"",
Pattern.CASE_INSENSITIVE);
public static URL testRedirect(final URL context, final String content) throws MalformedURLException {
public static HttpUrl testRedirect(final HttpUrl base, final String content) {
// check for redirect by http-equiv meta tag header
final Matcher mHttpEquiv = P_REDIRECT_HTTP_EQUIV.matcher(content);
if (mHttpEquiv.find())
return new URL(context, mHttpEquiv.group(1));
return base.resolve(mHttpEquiv.group(1));
// check for redirect by window.location javascript
final Matcher mScript = P_REDIRECT_SCRIPT.matcher(content);
if (mScript.find())
return new URL(context, mScript.group(1));
return base.resolve(mScript.group(1));
return null;
}

View file

@ -21,51 +21,51 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.net.URL;
import org.junit.Before;
import org.junit.Test;
import okhttp3.HttpUrl;
/**
* @author Andreas Schildbach
*/
public class HttpClientTest {
private URL context;
private HttpUrl base;
@Before
public void setUp() throws Exception {
context = new URL("http://example.com");
base = HttpUrl.parse("http://example.com");
}
@Test
public void vodafoneRedirect() throws Exception {
final URL url = HttpClient.testRedirect(context,
final HttpUrl url = HttpClient.testRedirect(base,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE html PUBLIC \"-//WAPFORUM//DTD XHTML Mobile 1.1//EN \" \"http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd\"><html xmlns=\"http://www.w3.org/1999/xhtml\"; xml:lang=\"en\"><head><title>Vodafone Center</title><meta http-equiv=\"Cache-Control\" content=\"no-cache\"/><meta http-equiv=\"refresh\" content=\"1;URL=https://center.vodafone.de/vfcenter/index.html?targetUrl=http%3A%2F%2Fwww.fahrinfo-berlin.de/Fahrinfo/bin/query.bin/dn%3fstart=Suchen&REQ0JourneyStopsS0ID=A%253D1%2540L%253D9083301&REQ0JourneyStopsZ0ID=A%253D1%2540L%253D9195009&REQ0HafasSearchForw=1&REQ0JourneyDate=16.06.14&REQ0JourneyTime=16%253A32&REQ0JourneyProduct_prod_list_1=11111011&h2g-direct=11&L=vs_oeffi\"/><style type=\"text/css\">*{border:none;font-family:Arial,Helvetica,sans-serif} body{font-size:69%;line-height:140%;background-color:#F4F4F4 !important}</style></head><body><h1>Sie werden weitergeleitet ...</h1><p>Sollten Sie nicht weitergeleitet werden, klicken Sie bitte <a href=\"https://center.vodafo");
assertNotNull(url);
assertEquals("center.vodafone.de", url.getHost());
assertEquals("center.vodafone.de", url.host());
}
public void kabelDeutschlandRedirect() throws Exception {
final URL url = HttpClient.testRedirect(context,
final HttpUrl url = HttpClient.testRedirect(base,
"<script type=\"text/javascript\"> window.location = \"http://www.hotspot.kabeldeutschland.de/portal/?RequestedURI=http%3A%2F%2Fwww.fahrinfo-berlin.de%2FFahrinfo%2Fbin%2Fajax-getstop.bin%2Fdny%3Fgetstop%3D1%26REQ0JourneyStopsS0A%3D255%26REQ0JourneyStopsS0G%3Dgneisenustra%25DFe%3F%26js%3Dtrue&RedirectReason=Policy&RedirectAqpId=100&DiscardAqpId=100&SubscriberId=4fa432d4a653e5f8b2acb27aa862f98d&SubscriberType=ESM&ClientIP=10.136.25.241&SystemId=10.143.181.2-1%2F2&GroupId=1&PartitionId=2&Application=Unknown&ApplicationGroup=Unknown\" </script>");
assertNotNull(url);
assertEquals("www.hotspot.kabeldeutschland.de", url.getHost());
assertEquals("www.hotspot.kabeldeutschland.de", url.host());
}
@Test
public void tplinkRedirect() throws Exception {
final URL url = HttpClient.testRedirect(context,
final HttpUrl url = HttpClient.testRedirect(base,
"<body><script language=\"javaScript\">location.href=\"http://tplinkextender.net/\";</script></body></html>");
assertNotNull(url);
assertEquals("tplinkextender.net", url.getHost());
assertEquals("tplinkextender.net", url.host());
}
@Test
public void mshtmlRedirect() throws Exception {
final URL url = HttpClient.testRedirect(context,
final HttpUrl url = HttpClient.testRedirect(base,
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html xmlns=\"http://www.w3.org/1999/xhtml\"><HEAD><TITLE>HTML Redirection</TITLE><META http-equiv=Content-Type content=\"text/html; \"><META http-equiv=Refresh content=\"0;URL=/cgi-bin/index.cgi\"><META content=\"MSHTML 6.00.2900.2873\" name=GENERATOR></HEAD><BODY > <NOSCRIPT> If your browser can not redirect you to home page automatically.<br> Please click <a href=/cgi-bin/welcome.cgi?lang=0>here</a>. </NOSCRIPT></BODY></HTML>");
assertNotNull(url);
assertEquals("example.com", url.getHost());
assertEquals("example.com", url.host());
}
@Test