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' apply plugin: 'eclipse'
dependencies { 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 'com.google.guava:guava:18.0'
compile 'org.slf4j:slf4j-api:1.7.12' compile 'org.slf4j:slf4j-api:1.7.12'
compile 'com.google.code.findbugs:jsr305:3.0.0' compile 'com.google.code.findbugs:jsr305:3.0.0'

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

@ -18,7 +18,6 @@
package de.schildbach.pte; package de.schildbach.pte;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.text.ParsePosition; import java.text.ParsePosition;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -33,6 +32,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable; 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.HttpClient;
import de.schildbach.pte.util.XmlPullUtil; import de.schildbach.pte.util.XmlPullUtil;
import okhttp3.HttpUrl;
import okhttp3.ResponseBody;
/** /**
* @author Mats Sjöberg <mats@sjoberg.fi> * @author Mats Sjöberg <mats@sjoberg.fi>
*/ */
@ -137,34 +140,32 @@ public class HslProvider extends AbstractNetworkProvider {
private Location queryStop(final String stationId) throws IOException { private Location queryStop(final String stationId) throws IOException {
final StringBuilder uri = apiUri("stop"); final StringBuilder uri = apiUri("stop");
uri.append("&code=").append(stationId); uri.append("&code=").append(stationId);
uri.append(String.format("&dep_limit=1")); uri.append(String.format("&dep_limit=1"));
final AtomicReference<Location> result = new AtomicReference<Location>();
InputStream is = null; final HttpClient.Callback callback = new HttpClient.Callback() {
String firstChars = null; @Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(body.charStream());
try { XmlPullUtil.enter(pp, "response");
is = httpClient.getInputStream(uri.toString()); XmlPullUtil.enter(pp, "node");
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser(); final String id = xmlValueTag(pp, "code");
pp.setInput(is, null); final String name = xmlValueTag(pp, "name_fi");
final Point pt = coordStrToPoint(xmlValueTag(pp, "coords"));
result.set(new Location(LocationType.STATION, id, pt.lat, pt.lon, null, name));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
XmlPullUtil.enter(pp, "response"); httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()));
XmlPullUtil.enter(pp, "node"); return result.get();
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);
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
}
} }
// Determine stations near to given location. At least one of // Determine stations near to given location. At least one of
@ -174,7 +175,6 @@ public class HslProvider extends AbstractNetworkProvider {
public NearbyLocationsResult queryNearbyLocations(EnumSet<LocationType> types, Location location, int maxDistance, public NearbyLocationsResult queryNearbyLocations(EnumSet<LocationType> types, Location location, int maxDistance,
int maxStations) throws IOException { int maxStations) throws IOException {
final StringBuilder uri = apiUri("stops_area"); final StringBuilder uri = apiUri("stops_area");
if (!location.hasLocation()) { if (!location.hasLocation()) {
if (location.type != LocationType.STATION) if (location.type != LocationType.STATION)
throw new IllegalArgumentException("cannot handle: " + location.type); throw new IllegalArgumentException("cannot handle: " + location.type);
@ -185,42 +185,42 @@ public class HslProvider extends AbstractNetworkProvider {
uri.append("&center_coordinate=").append(locationToCoords(location)); uri.append("&center_coordinate=").append(locationToCoords(location));
uri.append(String.format("&limit=%d", maxStations)); uri.append(String.format("&limit=%d", maxStations));
uri.append(String.format("&diameter=%d", maxDistance * 2)); uri.append(String.format("&diameter=%d", maxDistance * 2));
final AtomicReference<NearbyLocationsResult> result = new AtomicReference<NearbyLocationsResult>();
InputStream is = null; final HttpClient.Callback callback = new HttpClient.Callback() {
String firstChars = null; @Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(body.charStream());
try { final List<Location> stations = new ArrayList<Location>();
is = httpClient.getInputStream(uri.toString()); final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT);
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser(); XmlPullUtil.enter(pp, "response");
pp.setInput(is, null);
final List<Location> stations = new ArrayList<Location>(); while (XmlPullUtil.test(pp, "node")) {
final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); XmlPullUtil.enter(pp, "node");
XmlPullUtil.enter(pp, "response"); final String id = xmlValueTag(pp, "code");
final String name = xmlValueTag(pp, "name");
final Point pt = coordStrToPoint(xmlValueTag(pp, "coords"));
final String place = xmlValueTag(pp, "address");
while (XmlPullUtil.test(pp, "node")) { XmlPullUtil.skipExit(pp, "node");
XmlPullUtil.enter(pp, "node");
final String id = xmlValueTag(pp, "code"); stations.add(new Location(LocationType.STATION, id, pt.lat, pt.lon, place, name));
final String name = xmlValueTag(pp, "name"); }
final Point pt = coordStrToPoint(xmlValueTag(pp, "coords"));
final String place = xmlValueTag(pp, "address");
XmlPullUtil.skipExit(pp, "node"); result.set(new NearbyLocationsResult(header, stations));
} catch (final XmlPullParserException x) {
stations.add(new Location(LocationType.STATION, id, pt.lat, pt.lon, place, name)); throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
} }
};
return new NearbyLocationsResult(header, stations); httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()));
} catch (final XmlPullParserException x) { return result.get();
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
}
} }
private Line newLine(String code, int type, String message) { private Line newLine(String code, int type, String message) {
@ -254,78 +254,75 @@ public class HslProvider extends AbstractNetworkProvider {
// Get departures at a given station, probably live // Get departures at a given station, probably live
@Override @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 { boolean equivs) throws IOException {
final StringBuilder uri = apiUri("stop"); final StringBuilder uri = apiUri("stop");
uri.append("&code=").append(stationId); uri.append("&code=").append(stationId);
if (queryDate != null) { if (queryDate != null) {
uri.append("&date=").append(new SimpleDateFormat("yyyyMMdd").format(queryDate)); uri.append("&date=").append(new SimpleDateFormat("yyyyMMdd").format(queryDate));
uri.append("&time=").append(new SimpleDateFormat("HHmm").format(queryDate)); uri.append("&time=").append(new SimpleDateFormat("HHmm").format(queryDate));
} }
uri.append(String.format("&dep_limit=%d", maxDepartures)); uri.append(String.format("&dep_limit=%d", maxDepartures));
final AtomicReference<QueryDeparturesResult> result = new AtomicReference<QueryDeparturesResult>();
InputStream is = null; final HttpClient.Callback callback = new HttpClient.Callback() {
String firstChars = null; @Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(body.charStream());
try { XmlPullUtil.enter(pp, "response");
is = httpClient.getInputStream(uri.toString()); XmlPullUtil.enter(pp, "node");
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser(); // FIXME: id is never used!?
pp.setInput(is, null); final String id = xmlValueTag(pp, "code");
final String name = xmlValueTag(pp, "name_fi");
XmlPullUtil.enter(pp, "response"); final Map<String, Line> lines = new HashMap<String, Line>();
XmlPullUtil.enter(pp, "node");
// FIXME: id is never used!? XmlPullUtil.skipUntil(pp, "lines");
final String id = xmlValueTag(pp, "code"); XmlPullUtil.enter(pp, "lines");
final String name = xmlValueTag(pp, "name_fi"); while (XmlPullUtil.test(pp, "node")) {
final String[] parts = XmlPullUtil.valueTag(pp, "node").split(":");
lines.put(parts[0], newLine(parts[0], 0, parts[1]));
}
XmlPullUtil.skipExit(pp, "lines");
final Map<String, Line> lines = new HashMap<String, Line>(); final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT);
final QueryDeparturesResult r = new QueryDeparturesResult(header);
XmlPullUtil.skipUntil(pp, "lines"); XmlPullUtil.skipUntil(pp, "departures");
XmlPullUtil.enter(pp, "lines"); XmlPullUtil.enter(pp, "departures");
while (XmlPullUtil.test(pp, "node")) {
final String[] parts = XmlPullUtil.valueTag(pp, "node").split(":"); final List<Departure> departures = new ArrayList<Departure>(maxDepartures);
lines.put(parts[0], newLine(parts[0], 0, parts[1])); while (XmlPullUtil.test(pp, "node")) {
XmlPullUtil.enter(pp, "node");
final String code = xmlValueTag(pp, "code");
final String time = xmlValueTag(pp, "time");
final String date = xmlValueTag(pp, "date");
XmlPullUtil.skipExit(pp, "node");
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm");
Date depDate = sdf.parse(date + time, new ParsePosition(0));
final Line line = lines.get(code);
final Location destination = new Location(LocationType.ANY, line.message, null, null);
final Departure departure = new Departure(depDate, null, line, null, destination, null, null);
departures.add(departure);
}
Location station = new Location(LocationType.STATION, id, null, name);
r.stationDepartures.add(new StationDepartures(station, departures, null));
result.set(r);
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
} }
XmlPullUtil.skipExit(pp, "lines"); };
final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()));
final QueryDeparturesResult result = new QueryDeparturesResult(header); return result.get();
XmlPullUtil.skipUntil(pp, "departures");
XmlPullUtil.enter(pp, "departures");
final List<Departure> departures = new ArrayList<Departure>(maxDepartures);
while (XmlPullUtil.test(pp, "node")) {
XmlPullUtil.enter(pp, "node");
final String code = xmlValueTag(pp, "code");
final String time = xmlValueTag(pp, "time");
final String date = xmlValueTag(pp, "date");
XmlPullUtil.skipExit(pp, "node");
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm");
Date depDate = sdf.parse(date + time, new ParsePosition(0));
final Line line = lines.get(code);
final Location destination = new Location(LocationType.ANY, line.message, null, null);
final Departure departure = new Departure(depDate, null, line, null, destination, null, null);
departures.add(departure);
}
Location station = new Location(LocationType.STATION, id, null, name);
result.stationDepartures.add(new StationDepartures(station, departures, null));
return result;
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
}
} }
/** /**
@ -339,71 +336,68 @@ public class HslProvider extends AbstractNetworkProvider {
@Override @Override
public SuggestLocationsResult suggestLocations(CharSequence constraint) throws IOException { public SuggestLocationsResult suggestLocations(CharSequence constraint) throws IOException {
final StringBuilder uri = apiUri("geocode"); final StringBuilder uri = apiUri("geocode");
// Since HSL is picky about the input we clean out any // Since HSL is picky about the input we clean out any
// character that isn't alphabetic, numeral, -, ', / // character that isn't alphabetic, numeral, -, ', /
// or a space. Those should be all chars needed for a // or a space. Those should be all chars needed for a
// name. // name.
String constraintStr = constraint.toString().replaceAll("[^\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}\\p{Nd}\\d-'/ ]", ""); 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")); uri.append("&key=").append(URLEncoder.encode(constraintStr, "utf-8"));
final AtomicReference<SuggestLocationsResult> result = new AtomicReference<SuggestLocationsResult>();
InputStream is = null; final HttpClient.Callback callback = new HttpClient.Callback() {
String firstChars = null; @Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT);
final List<SuggestedLocation> locations = new ArrayList<SuggestedLocation>();
try { final XmlPullParser pp = parserFactory.newPullParser();
is = httpClient.getInputStream(uri.toString()); pp.setInput(body.charStream());
firstChars = HttpClient.peekFirstChars(is);
final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); XmlPullUtil.enter(pp, "response");
final List<SuggestedLocation> locations = new ArrayList<SuggestedLocation>();
if (firstChars.isEmpty()) int weight = 10000;
return new SuggestLocationsResult(header, locations);
final XmlPullParser pp = parserFactory.newPullParser(); while (XmlPullUtil.test(pp, "node")) {
pp.setInput(is, null); XmlPullUtil.enter(pp, "node");
final String locType = xmlValueTag(pp, "locType");
String name = xmlValueTag(pp, "name");
final Point pt = coordStrToPoint(xmlValueTag(pp, "coords"));
XmlPullUtil.enter(pp, "response"); LocationType type = LocationType.ANY;
if (locType.equals("poi"))
type = LocationType.POI;
if (locType.equals("address"))
type = LocationType.ADDRESS;
if (locType.equals("stop"))
type = LocationType.STATION;
int weight = 10000; XmlPullUtil.skipUntil(pp, "details");
XmlPullUtil.enter(pp, "details");
XmlPullUtil.optSkip(pp, "address");
final String id = XmlPullUtil.optValueTag(pp, "code", null);
final String shortCode = XmlPullUtil.optValueTag(pp, "shortCode", null);
XmlPullUtil.skipExit(pp, "details");
while (XmlPullUtil.test(pp, "node")) { XmlPullUtil.skipExit(pp, "node");
XmlPullUtil.enter(pp, "node");
final String locType = xmlValueTag(pp, "locType");
String name = xmlValueTag(pp, "name");
final Point pt = coordStrToPoint(xmlValueTag(pp, "coords"));
LocationType type = LocationType.ANY; if (shortCode != null)
if (locType.equals("poi")) name = name + " (" + shortCode + ")";
type = LocationType.POI;
if (locType.equals("address"))
type = LocationType.ADDRESS;
if (locType.equals("stop"))
type = LocationType.STATION;
XmlPullUtil.skipUntil(pp, "details"); locations
XmlPullUtil.enter(pp, "details"); .add(new SuggestedLocation(new Location(type, id, pt.lat, pt.lon, null, name), weight));
XmlPullUtil.optSkip(pp, "address"); weight -= 1;
final String id = XmlPullUtil.optValueTag(pp, "code", null); }
final String shortCode = XmlPullUtil.optValueTag(pp, "shortCode", null);
XmlPullUtil.skipExit(pp, "details");
XmlPullUtil.skipExit(pp, "node"); result.set(new SuggestLocationsResult(header, locations));
} catch (final XmlPullParserException x) {
if (shortCode != null) throw new ParserException("cannot parse xml: " + bodyPeek, x);
name = name + " (" + shortCode + ")"; }
locations.add(new SuggestedLocation(new Location(type, id, pt.lat, pt.lon, null, name), weight));
weight -= 1;
} }
};
return new SuggestLocationsResult(header, locations); httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()));
} catch (final XmlPullParserException x) { return result.get();
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
}
} }
@SuppressWarnings("serial") @SuppressWarnings("serial")
@ -553,165 +547,166 @@ public class HslProvider extends AbstractNetworkProvider {
} }
private QueryTripsResult queryHslTrips(final Location from, final Location via, final Location to, 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); final StringBuilder uri = new StringBuilder(context.uri);
uri.append("&date=").append(new SimpleDateFormat("yyyyMMdd").format(date)); uri.append("&date=").append(new SimpleDateFormat("yyyyMMdd").format(date));
uri.append("&time=").append(new SimpleDateFormat("HHmm").format(date)); uri.append("&time=").append(new SimpleDateFormat("HHmm").format(date));
final AtomicReference<QueryTripsResult> result = new AtomicReference<QueryTripsResult>();
InputStream is = null; final HttpClient.Callback callback = new HttpClient.Callback() {
String firstChars = null; @Override
public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
try {
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(body.charStream());
XmlPullUtil.enter(pp, "response");
final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT);
final List<Trip> trips = new ArrayList<Trip>(context.trips);
// we use this for quick checking if trip already exists
Set<String> tripSet = new HashSet<String>();
for (Trip t : trips)
tripSet.add(t.getId());
int insert = later ? trips.size() : 0;
while (XmlPullUtil.test(pp, "node")) {
XmlPullUtil.enter(pp, "node");
XmlPullUtil.enter(pp, "node");
List<Trip.Leg> legs = new ArrayList<Trip.Leg>();
XmlPullUtil.skipUntil(pp, "legs");
XmlPullUtil.enter(pp, "legs");
int numTransfers = 0;
while (XmlPullUtil.test(pp, "node")) {
XmlPullUtil.enter(pp, "node");
int distance = Integer.parseInt(xmlValueTag(pp, "length"));
String legType = xmlValueTag(pp, "type");
String lineCode = XmlPullUtil.optValueTag(pp, "code", null);
List<Point> path = new ArrayList<Point>();
Location departure = null;
Date departureTime = null;
Stop departureStop = null;
Location arrival = null;
Date arrivalTime = null;
LinkedList<Stop> stops = new LinkedList<Stop>();
XmlPullUtil.skipUntil(pp, "locs");
XmlPullUtil.enter(pp, "locs");
while (XmlPullUtil.test(pp, "node")) {
XmlPullUtil.enter(pp, "node");
Point pt = xmlCoordsToPoint(pp);
String arrTime = xmlValueTag(pp, "arrTime");
String depTime = xmlValueTag(pp, "depTime");
String name = XmlPullUtil.optValueTag(pp, "name", null);
String code = XmlPullUtil.optValueTag(pp, "code", null);
String shortCode = XmlPullUtil.optValueTag(pp, "shortCode", null);
String stopAddress = XmlPullUtil.optValueTag(pp, "stopAddress", null);
if (name == null) {
name = (path.size() == 0 && from != null && from.name != null) ? from.name : null;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm");
Date arrDate = sdf.parse(arrTime, new ParsePosition(0));
Date depDate = sdf.parse(depTime, new ParsePosition(0));
LocationType type = LocationType.ANY;
if (code != null)
type = LocationType.STATION;
Location loc = new Location(type, code, pt.lat, pt.lon, stopAddress, name);
if (path.size() == 0) {
departure = loc;
departureTime = depDate;
if (type == LocationType.STATION)
departureStop = new Stop(loc, true, departureTime, null, null, null);
} else {
arrival = loc;
arrivalTime = arrDate;
if (type == LocationType.STATION) {
stops.add(new Stop(loc, arrDate, null, depDate, null));
}
}
path.add(pt);
XmlPullUtil.skipExit(pp, "node");
}
XmlPullUtil.skipExit(pp, "locs");
XmlPullUtil.skipExit(pp, "node");
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);
}
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);
stops.removeLast();
}
Line line = null;
if (lineCode != null)
line = newLine(lineCode, Integer.parseInt(legType), null);
legs.add(new Trip.Public(line, null, departureStop, arrivalStop, stops, path, null));
numTransfers++;
}
}
XmlPullUtil.skipExit(pp, "legs");
XmlPullUtil.skipExit(pp, "node");
XmlPullUtil.skipExit(pp, "node");
Trip t = new Trip(null, from, to, legs, null, null, numTransfers - 1);
if (!tripSet.contains(t.getId())) {
Date thisTime = t.getFirstDepartureTime();
while (insert < trips.size() && thisTime.after(trips.get(insert).getFirstDepartureTime()))
insert++;
trips.add(insert++, t);
tripSet.add(t.getId());
}
}
Date lastDate = trips.get(trips.size() - 1).getFirstDepartureTime();
Date firstDate = trips.get(0).getFirstDepartureTime();
if (context.nextDate == null || lastDate.after(context.nextDate))
context.nextDate = lastDate;
if (context.prevDate == null || firstDate.before(context.prevDate))
context.prevDate = firstDate;
context.trips = trips;
result.set(new QueryTripsResult(header, uri.toString(), from, via, to, context, trips));
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + bodyPeek, x);
}
}
};
context.date = date; context.date = date;
try { httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()));
is = httpClient.getInputStream(uri.toString()); return result.get();
firstChars = HttpClient.peekFirstChars(is);
final XmlPullParser pp = parserFactory.newPullParser();
pp.setInput(is, null);
XmlPullUtil.enter(pp, "response");
final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT);
final List<Trip> trips = new ArrayList<Trip>(context.trips);
// we use this for quick checking if trip already exists
Set<String> tripSet = new HashSet<String>();
for (Trip t : trips)
tripSet.add(t.getId());
int insert = later ? trips.size() : 0;
while (XmlPullUtil.test(pp, "node")) {
XmlPullUtil.enter(pp, "node");
XmlPullUtil.enter(pp, "node");
List<Trip.Leg> legs = new ArrayList<Trip.Leg>();
XmlPullUtil.skipUntil(pp, "legs");
XmlPullUtil.enter(pp, "legs");
int numTransfers = 0;
while (XmlPullUtil.test(pp, "node")) {
XmlPullUtil.enter(pp, "node");
int distance = Integer.parseInt(xmlValueTag(pp, "length"));
String legType = xmlValueTag(pp, "type");
String lineCode = XmlPullUtil.optValueTag(pp, "code", null);
List<Point> path = new ArrayList<Point>();
Location departure = null;
Date departureTime = null;
Stop departureStop = null;
Location arrival = null;
Date arrivalTime = null;
LinkedList<Stop> stops = new LinkedList<Stop>();
XmlPullUtil.skipUntil(pp, "locs");
XmlPullUtil.enter(pp, "locs");
while (XmlPullUtil.test(pp, "node")) {
XmlPullUtil.enter(pp, "node");
Point pt = xmlCoordsToPoint(pp);
String arrTime = xmlValueTag(pp, "arrTime");
String depTime = xmlValueTag(pp, "depTime");
String name = XmlPullUtil.optValueTag(pp, "name", null);
String code = XmlPullUtil.optValueTag(pp, "code", null);
String shortCode = XmlPullUtil.optValueTag(pp, "shortCode", null);
String stopAddress = XmlPullUtil.optValueTag(pp, "stopAddress", null);
if (name == null) {
name = (path.size() == 0 && from != null && from.name != null) ? from.name : null;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm");
Date arrDate = sdf.parse(arrTime, new ParsePosition(0));
Date depDate = sdf.parse(depTime, new ParsePosition(0));
LocationType type = LocationType.ANY;
if (code != null)
type = LocationType.STATION;
Location loc = new Location(type, code, pt.lat, pt.lon, stopAddress, name);
if (path.size() == 0) {
departure = loc;
departureTime = depDate;
if (type == LocationType.STATION)
departureStop = new Stop(loc, true, departureTime, null, null, null);
} else {
arrival = loc;
arrivalTime = arrDate;
if (type == LocationType.STATION) {
stops.add(new Stop(loc, arrDate, null, depDate, null));
}
}
path.add(pt);
XmlPullUtil.skipExit(pp, "node");
}
XmlPullUtil.skipExit(pp, "locs");
XmlPullUtil.skipExit(pp, "node");
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);
}
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);
stops.removeLast();
}
Line line = null;
if (lineCode != null)
line = newLine(lineCode, Integer.parseInt(legType), null);
legs.add(new Trip.Public(line, null, departureStop, arrivalStop, stops, path, null));
numTransfers++;
}
}
XmlPullUtil.skipExit(pp, "legs");
XmlPullUtil.skipExit(pp, "node");
XmlPullUtil.skipExit(pp, "node");
Trip t = new Trip(null, from, to, legs, null, null, numTransfers - 1);
if (!tripSet.contains(t.getId())) {
Date thisTime = t.getFirstDepartureTime();
while (insert < trips.size() && thisTime.after(trips.get(insert).getFirstDepartureTime()))
insert++;
trips.add(insert++, t);
tripSet.add(t.getId());
}
}
Date lastDate = trips.get(trips.size() - 1).getFirstDepartureTime();
Date firstDate = trips.get(0).getFirstDepartureTime();
if (context.nextDate == null || lastDate.after(context.nextDate))
context.nextDate = lastDate;
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);
} catch (final XmlPullParserException x) {
throw new ParserException("cannot parse xml: " + firstChars, x);
} finally {
if (is != null)
is.close();
}
} }
} }

View file

@ -54,6 +54,8 @@ import de.schildbach.pte.dto.StationDepartures;
import de.schildbach.pte.dto.Style; import de.schildbach.pte.dto.Style;
import de.schildbach.pte.util.ParserUtils; import de.schildbach.pte.util.ParserUtils;
import okhttp3.HttpUrl;
/** /**
* @author Andreas Schildbach * @author Andreas Schildbach
*/ */
@ -171,7 +173,7 @@ public class InvgProvider extends AbstractHafasProvider {
// scrape page // scrape page
final StringBuilder uri = new StringBuilder(stationBoardEndpoint); final StringBuilder uri = new StringBuilder(stationBoardEndpoint);
appendXmlStationBoardParameters(uri, time, stationId, maxDepartures, false, null); 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 // parse page
final Matcher mHeadCoarse = P_DEPARTURES_HEAD_COARSE.matcher(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.dto.StationDepartures;
import de.schildbach.pte.util.ParserUtils; import de.schildbach.pte.util.ParserUtils;
import okhttp3.HttpUrl;
/** /**
* @author Andreas Schildbach * @author Andreas Schildbach
*/ */
@ -134,7 +136,7 @@ public class SeptaProvider extends AbstractHafasProvider {
// scrape page // scrape page
final StringBuilder uri = new StringBuilder(stationBoardEndpoint); final StringBuilder uri = new StringBuilder(stationBoardEndpoint);
appendXmlStationBoardParameters(uri, time, stationId, maxDepartures, false, null); 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 // parse page
final Matcher mPageCoarse = P_DEPARTURES_PAGE_COARSE.matcher(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.dto.Trip.Leg;
import de.schildbach.pte.util.ParserUtils; import de.schildbach.pte.util.ParserUtils;
import okhttp3.HttpUrl;
/** /**
* @author Michael Dyrna * @author Michael Dyrna
*/ */
@ -370,7 +372,7 @@ public class VrsProvider extends AbstractNetworkProvider {
uri.append("&s=").append(Math.min(16, maxLocations)); // artificial server limit 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 { try {
final List<Location> locations = new ArrayList<Location>(); final List<Location> locations = new ArrayList<Location>();
@ -427,7 +429,7 @@ public class VrsProvider extends AbstractNetworkProvider {
uri.append("&t="); uri.append("&t=");
appendDate(uri, time); 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 { try {
final JSONObject head = new JSONObject(page.toString()); final JSONObject head = new JSONObject(page.toString());
@ -514,7 +516,7 @@ public class VrsProvider extends AbstractNetworkProvider {
final StringBuilder uri = new StringBuilder(API_BASE); final StringBuilder uri = new StringBuilder(API_BASE);
uri.append("?eID=tx_vrsinfo_his_info&i=").append(ParserUtils.urlEncode(stationId)); 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 { try {
final JSONObject head = new JSONObject(page.toString()); 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=" 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); + 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 { try {
final List<SuggestedLocation> locations = new ArrayList<SuggestedLocation>(); final List<SuggestedLocation> locations = new ArrayList<SuggestedLocation>();
@ -691,7 +693,7 @@ public class VrsProvider extends AbstractNetworkProvider {
uri.append("p"); 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 { try {
final List<Trip> trips = new ArrayList<Trip>(); final List<Trip> trips = new ArrayList<Trip>();

View file

@ -18,45 +18,32 @@
package de.schildbach.pte.exception; package de.schildbach.pte.exception;
import java.io.IOException; import java.io.IOException;
import java.io.Reader;
import java.net.URL;
import de.schildbach.pte.util.HttpClient; import okhttp3.HttpUrl;
/** /**
* @author Andreas Schildbach * @author Andreas Schildbach
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public abstract class AbstractHttpException extends IOException { public abstract class AbstractHttpException extends IOException {
private final URL url; private final HttpUrl url;
private final Reader errorReader; private final CharSequence bodyPeek;
public AbstractHttpException(final URL url) { public AbstractHttpException(final HttpUrl url) {
this(url, null); this(url, null);
} }
public AbstractHttpException(final URL url, final Reader errorReader) { public AbstractHttpException(final HttpUrl url, final CharSequence bodyPeek) {
super(url.toString()); super(url.toString());
this.url = url; this.url = url;
this.errorReader = errorReader; this.bodyPeek = bodyPeek;
} }
public URL getUrl() { public HttpUrl getUrl() {
return url; return url;
} }
public Reader getErrorReader() { public CharSequence getBodyPeek() {
return errorReader; return bodyPeek;
}
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;
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -17,27 +17,19 @@
package de.schildbach.pte.util; package de.schildbach.pte.util;
import java.io.BufferedInputStream;
import java.io.IOException; 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.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import org.slf4j.Logger; 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.SessionExpiredException;
import de.schildbach.pte.exception.UnexpectedRedirectException; 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 * @author Andreas Schildbach
*/ */
@ -61,15 +65,33 @@ public final class HttpClient {
@Nullable @Nullable
private String sessionCookieName = null; private String sessionCookieName = null;
@Nullable @Nullable
private HttpCookie sessionCookie = null; private Cookie sessionCookie = null;
private boolean sslAcceptAllHostnames = false; 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"; 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; 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_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); private static final Logger log = LoggerFactory.getLogger(HttpClient.class);
@ -89,201 +111,129 @@ public final class HttpClient {
this.sslAcceptAllHostnames = sslAcceptAllHostnames; this.sslAcceptAllHostnames = sslAcceptAllHostnames;
} }
public CharSequence get(final String url) throws IOException { public CharSequence get(final HttpUrl url) throws IOException {
return get(url, null); return get(url, null);
} }
public CharSequence get(final String urlStr, final Charset requestEncoding) throws IOException { public CharSequence get(final HttpUrl url, final Charset requestEncoding) throws IOException {
return get(urlStr, null, null, requestEncoding); 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 { Charset requestEncoding) throws IOException {
if (requestEncoding == null) if (requestEncoding == null)
requestEncoding = Charsets.ISO_8859_1; requestEncoding = Charsets.ISO_8859_1;
final StringBuilder buffer = new StringBuilder(SCRAPE_INITIAL_CAPACITY); final StringBuilder buffer = new StringBuilder(SCRAPE_INITIAL_CAPACITY);
final InputStream is = getInputStream(urlStr, postRequest, requestContentType, requestEncoding, null); final Callback callback = new Callback() {
final Reader pageReader = new InputStreamReader(is, requestEncoding); @Override
copy(pageReader, buffer); public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException {
pageReader.close(); buffer.append(body.string());
}
};
getInputStream(callback, url, postRequest, requestContentType, requestEncoding, null);
return buffer; return buffer;
} }
public InputStream getInputStream(final String url) throws IOException { public interface Callback {
return getInputStream(url, null, null); void onSuccessful(CharSequence bodyPeek, ResponseBody body) throws IOException;
} }
public InputStream getInputStream(final String urlStr, final Charset requestEncoding, final String referer) public void getInputStream(final Callback callback, final HttpUrl url) throws IOException {
throws IOException { getInputStream(callback, url, null, null);
return getInputStream(urlStr, null, null, requestEncoding, referer);
} }
public InputStream getInputStream(final String urlStr, final String postRequest, final String requestContentType, public void getInputStream(final Callback callback, final HttpUrl url, final Charset requestEncoding,
Charset requestEncoding, final String referer) throws IOException { final String referer) throws IOException {
log.debug("{}: {}", postRequest != null ? "POST" : "GET", urlStr); 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) if (requestEncoding == null)
requestEncoding = Charsets.ISO_8859_1; requestEncoding = Charsets.ISO_8859_1;
int tries = 3; int tries = 3;
while (true) { while (true) {
final URL url = new URL(urlStr); final Request.Builder request = new Request.Builder();
final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); request.url(url);
request.headers(Headers.of(headers));
if (connection instanceof HttpsURLConnection && sslAcceptAllHostnames) if (postRequest != null)
((HttpsURLConnection) connection).setHostnameVerifier(SSL_ACCEPT_ALL_HOSTNAMES); request.post(RequestBody.create(MediaType.parse(requestContentType), postRequest));
request.header("Accept", SCRAPE_ACCEPT);
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());
if (userAgent != null) if (userAgent != null)
connection.addRequestProperty("User-Agent", userAgent); request.header("User-Agent", userAgent);
connection.addRequestProperty("Accept", SCRAPE_ACCEPT);
connection.addRequestProperty("Accept-Encoding", "gzip");
// workaround to disable Vodafone compression
connection.addRequestProperty("Cache-Control", "no-cache");
if (referer != null) 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; final OkHttpClient okHttpClient;
if (sessionCookie != null && sessionCookie.getName().equals(sessionCookieName)) if (sslAcceptAllHostnames)
connection.addRequestProperty("Cookie", sessionCookie.toString()); okHttpClient = OKHTTP_CLIENT.newBuilder().hostnameVerifier(SSL_ACCEPT_ALL_HOSTNAMES).build();
else
okHttpClient = OKHTTP_CLIENT;
if (postRequest != null) { final Call call = okHttpClient.newCall(request.build());
final byte[] postRequestBytes = postRequest.getBytes(requestEncoding.name()); 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) {
connection.setRequestMethod("POST"); final HttpUrl redirectUrl = testRedirect(url, bodyPeek);
connection.addRequestProperty("Content-Type", requestContentType); if (redirectUrl != null)
connection.addRequestProperty("Content-Length", Integer.toString(postRequestBytes.length)); throw new UnexpectedRedirectException(url, redirectUrl);
final OutputStream os = connection.getOutputStream(); if (testExpired(bodyPeek))
os.write(postRequestBytes); throw new SessionExpiredException();
os.close(); if (testInternalError(bodyPeek))
} throw new InternalErrorException(url, bodyPeek);
final int responseCode = connection.getResponseCode(); // save cookie
if (responseCode == HttpURLConnection.HTTP_OK) { if (sessionCookieName != null) {
final String contentType = connection.getContentType(); final List<Cookie> cookies = Cookie.parseAll(url, response.headers());
final String contentEncoding = connection.getContentEncoding(); for (final Iterator<Cookie> i = cookies.iterator(); i.hasNext();) {
final Cookie cookie = i.next();
InputStream is = new BufferedInputStream(connection.getInputStream()); if (cookie.name().equals(sessionCookieName)) {
this.sessionCookie = cookie;
if ("gzip".equalsIgnoreCase(contentEncoding) break;
|| "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);
if (redirectUrl != null)
throw new UnexpectedRedirectException(url, redirectUrl);
if (testExpired(firstChars))
throw new SessionExpiredException();
if (testInternalError(firstChars))
throw new InternalErrorException(url, new InputStreamReader(is, requestEncoding));
// 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)) {
this.sessionCookie = cookie;
break c;
}
}
} }
} }
} }
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, bodyPeek);
} else if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
throw new NotFoundException(url, bodyPeek);
} else if (responseCode == HttpURLConnection.HTTP_MOVED_PERM
|| responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
throw new UnexpectedRedirectException(url, HttpUrl.parse(response.header("Location")));
} else if (responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
throw new InternalErrorException(url, bodyPeek);
} else {
final String message = "got response: " + responseCode + " " + response.message();
if (tries-- > 0)
log.info("{}, retrying...", message);
else
throw new IOException(message + ": " + url);
} }
} finally {
return is; if (response != null)
} else if (responseCode == HttpURLConnection.HTTP_BAD_REQUEST response.close();
|| 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));
} else if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
throw new NotFoundException(url, new InputStreamReader(connection.getErrorStream(), requestEncoding));
} else if (responseCode == HttpURLConnection.HTTP_MOVED_PERM
|| responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
throw new UnexpectedRedirectException(url, connection.getURL());
} else if (responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
throw new InternalErrorException(url,
new InputStreamReader(connection.getErrorStream(), requestEncoding));
} else {
final String message = "got response: " + responseCode + " " + connection.getResponseMessage();
if (tries-- > 0)
log.info("{}, retrying...", message);
else
throw new IOException(message + ": " + url);
} }
} }
} }
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( private static final Pattern P_REDIRECT_HTTP_EQUIV = Pattern.compile(
"<META\\s+http-equiv=\"?refresh\"?\\s+content=\"\\d+;\\s*URL=([^\"]+)\"", Pattern.CASE_INSENSITIVE); "<META\\s+http-equiv=\"?refresh\"?\\s+content=\"\\d+;\\s*URL=([^\"]+)\"", Pattern.CASE_INSENSITIVE);
@ -291,16 +241,16 @@ public final class HttpClient {
"<script\\s+(?:type=\"text/javascript\"|language=\"javascript\")>\\s*(?:window.location|location.href)\\s*=\\s*\"([^\"]+)\"", "<script\\s+(?:type=\"text/javascript\"|language=\"javascript\")>\\s*(?:window.location|location.href)\\s*=\\s*\"([^\"]+)\"",
Pattern.CASE_INSENSITIVE); 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 // check for redirect by http-equiv meta tag header
final Matcher mHttpEquiv = P_REDIRECT_HTTP_EQUIV.matcher(content); final Matcher mHttpEquiv = P_REDIRECT_HTTP_EQUIV.matcher(content);
if (mHttpEquiv.find()) if (mHttpEquiv.find())
return new URL(context, mHttpEquiv.group(1)); return base.resolve(mHttpEquiv.group(1));
// check for redirect by window.location javascript // check for redirect by window.location javascript
final Matcher mScript = P_REDIRECT_SCRIPT.matcher(content); final Matcher mScript = P_REDIRECT_SCRIPT.matcher(content);
if (mScript.find()) if (mScript.find())
return new URL(context, mScript.group(1)); return base.resolve(mScript.group(1));
return null; 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.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.net.URL;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import okhttp3.HttpUrl;
/** /**
* @author Andreas Schildbach * @author Andreas Schildbach
*/ */
public class HttpClientTest { public class HttpClientTest {
private URL context; private HttpUrl base;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
context = new URL("http://example.com"); base = HttpUrl.parse("http://example.com");
} }
@Test @Test
public void vodafoneRedirect() throws Exception { 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"); "<?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); assertNotNull(url);
assertEquals("center.vodafone.de", url.getHost()); assertEquals("center.vodafone.de", url.host());
} }
public void kabelDeutschlandRedirect() throws Exception { 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>"); "<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); assertNotNull(url);
assertEquals("www.hotspot.kabeldeutschland.de", url.getHost()); assertEquals("www.hotspot.kabeldeutschland.de", url.host());
} }
@Test @Test
public void tplinkRedirect() throws Exception { 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>"); "<body><script language=\"javaScript\">location.href=\"http://tplinkextender.net/\";</script></body></html>");
assertNotNull(url); assertNotNull(url);
assertEquals("tplinkextender.net", url.getHost()); assertEquals("tplinkextender.net", url.host());
} }
@Test @Test
public void mshtmlRedirect() throws Exception { 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>"); "<!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); assertNotNull(url);
assertEquals("example.com", url.getHost()); assertEquals("example.com", url.host());
} }
@Test @Test