[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [exonerator/master] Make ExoneraTor's code testable.
commit ab2f5660c17887a84140dd5e50c4e24e6daedb2b
Author: iwakeh <iwakeh@xxxxxxxxxxxxxx>
Date: Tue Sep 5 13:21:27 2017 +0000
Make ExoneraTor's code testable.
This includes making utils methods static and also moving all
Gson/JSON related code into QueryResponse for better encapsulation.
---
.../torproject/exonerator/ExoneraTorServlet.java | 89 +++++++++------------
.../org/torproject/exonerator/QueryResponse.java | 92 +++++++++++++++++++++-
2 files changed, 129 insertions(+), 52 deletions(-)
diff --git a/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java b/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
index 749becc..8e88882 100644
--- a/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
+++ b/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
@@ -3,10 +3,10 @@
package org.torproject.exonerator;
-import com.google.gson.Gson;
import org.apache.commons.lang.StringEscapeUtils;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -40,12 +40,6 @@ public class ExoneraTorServlet extends HttpServlet {
private String exoneraTorHost = "https://exonerator.torproject.org";
- /* Don't accept query responses with versions lower than this. */
- private static final String firstRecognizedVersion = "1.0";
-
- /* Don't accept query responses with this version or higher. */
- private static final String firstUnrecognizedVersion = "2.0";
-
private List<String> availableLanguages =
Arrays.asList("de", "en", "fr", "ro", "sv");
@@ -54,10 +48,7 @@ public class ExoneraTorServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
-
- /* Initialize logger. */
this.logger = Logger.getLogger(ExoneraTorServlet.class.toString());
-
this.availableLanguageNames = new TreeMap<>();
for (String locale : this.availableLanguages) {
ResourceBundle rb = ResourceBundle.getBundle("ExoneraTor",
@@ -76,13 +67,12 @@ public class ExoneraTorServlet extends HttpServlet {
/* Parse ip parameter. */
String ipParameter = request.getParameter("ip");
- String relayIp = this.parseIpParameter(ipParameter);
+ String relayIp = parseIpParameter(ipParameter);
final boolean relayIpHasError = relayIp == null;
/* Parse timestamp parameter. */
String timestampParameter = request.getParameter("timestamp");
- String timestampStr = this.parseTimestampParameter(
- timestampParameter);
+ String timestampStr = parseTimestampParameter(timestampParameter);
final boolean timestampHasError = timestampStr == null;
/* Parse lang parameter. */
@@ -93,21 +83,21 @@ public class ExoneraTorServlet extends HttpServlet {
langStr = langParameter;
}
- /* Step 2: Query the database. */
+ /* Step 2: Query the backend server. */
- boolean successfullyConnectedToDatabase = false;
+ boolean successfullyConnectedToBackend = false;
String firstDate = null;
String lastDate = null;
boolean noRelevantConsensuses = true;
List<String[]> statusEntries = new ArrayList<>();
List<String> addressesInSameNetwork = null;
- /* Only query the database if we received valid user input. */
+ /* Only query, if we received valid user input. */
if (null != relayIp && !relayIp.isEmpty() && null != timestampStr
&& !timestampStr.isEmpty()) {
- QueryResponse queryResponse = this.queryDatabase(relayIp, timestampStr);
+ QueryResponse queryResponse = this.queryBackend(relayIp, timestampStr);
if (null != queryResponse) {
- successfullyConnectedToDatabase = true;
+ successfullyConnectedToBackend = true;
firstDate = queryResponse.firstDateInDatabase;
lastDate = queryResponse.lastDateInDatabase;
if (null != queryResponse.relevantStatuses
@@ -165,8 +155,8 @@ public class ExoneraTorServlet extends HttpServlet {
this.writeFooter(out, rb, null, null);
/* If we were unable to connect to the database, write an error message. */
- } else if (!successfullyConnectedToDatabase) {
- this.writeSummaryUnableToConnectToDatabase(out, rb);
+ } else if (!successfullyConnectedToBackend) {
+ this.writeSummaryUnableToConnectToBackend(out, rb);
this.writeFooter(out, rb, null, null);
/* Similarly, if we found the database to be empty, write an error message,
@@ -228,7 +218,10 @@ public class ExoneraTorServlet extends HttpServlet {
/* Helper methods for handling the request. */
- private String parseIpParameter(String passedIpParameter) {
+ /** Parse an IP parameter and return either a non-<code>null</code> value in
+ * case the parameter was valid or empty, or <code>null</code> if it was
+ * non-empty and invalid. */
+ static String parseIpParameter(String passedIpParameter) {
String relayIp = null;
if (passedIpParameter != null && passedIpParameter.length() > 0) {
String ipParameter = passedIpParameter.trim();
@@ -283,8 +276,10 @@ public class ExoneraTorServlet extends HttpServlet {
return relayIp;
}
- private String parseTimestampParameter(
- String passedTimestampParameter) {
+ /** Parse a timestamp parameter and return either a non-<code>null</code>
+ * value in case the parameter was valid or empty, or <code>null</code> if it
+ * was non-empty and invalid. */
+ static String parseTimestampParameter(String passedTimestampParameter) {
String timestampStr = "";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
@@ -304,31 +299,17 @@ public class ExoneraTorServlet extends HttpServlet {
/* Helper method for fetching a query response via URL. */
- private QueryResponse queryDatabase(String relayIp, String timestampStr) {
- QueryResponse response;
+ private QueryResponse queryBackend(String relayIp, String timestampStr) {
try (InputStreamReader isr = new InputStreamReader(new URL(
this.exoneraTorHost + "/query.json?ip=" + relayIp + "×tamp="
+ timestampStr).openStream())) {
- Gson gson = new Gson();
- response = gson.fromJson(isr, QueryResponse.class);
- if (null == response || null == response.version) {
- logger.warning("Response is either empty or does not contain "
- + "version information.");
- response = null;
- } else if (response.version.compareTo(firstRecognizedVersion) < 0
- || response.version.compareTo(firstUnrecognizedVersion) >= 0) {
- logger.warning("Response has either an older or a newer version ("
- + response.version + ") than we can handle ("
- + firstRecognizedVersion + " <= x < " + firstUnrecognizedVersion
- + ").");
- response = null;
- }
- } catch (IOException | RuntimeException e) {
- /* This could be anything, but the effect is that we don't have a query
- * response to process further. */
- response = null;
+ return QueryResponse.fromJson(isr);
+ } catch (IOException e) {
+ /* No result from backend, so that we don't have a query response to
+ * process further. */
+ logger.severe("Backend query failed: " + e.getMessage());
}
- return response;
+ return null;
}
/* Helper methods for writing the response. */
@@ -389,7 +370,7 @@ public class ExoneraTorServlet extends HttpServlet {
rb.getString("form.search.label"));
}
- private void writeSummaryUnableToConnectToDatabase(PrintWriter out,
+ private void writeSummaryUnableToConnectToBackend(PrintWriter out,
ResourceBundle rb) throws IOException {
String contactLink =
"<a href=\"https://www.torproject.org/about/contact\">"
@@ -592,12 +573,18 @@ public class ExoneraTorServlet extends HttpServlet {
content = "("
+ rb.getString("technicaldetails.nickname.unknown") + ")";
} else if (i == 4) {
- if (content.equals("U")) {
- content = rb.getString("technicaldetails.exit.unknown");
- } else if (content.equals("Y")) {
- content = rb.getString("technicaldetails.exit.yes");
- } else {
- content = rb.getString("technicaldetails.exit.no");
+ switch (content) {
+ case "U":
+ content = rb.getString("technicaldetails.exit.unknown");
+ break;
+ case "Y":
+ content = rb.getString("technicaldetails.exit.yes");
+ break;
+ case "N":
+ content = rb.getString("technicaldetails.exit.no");
+ break;
+ default: // should never happen
+ logger.warning("Unknown content: " + content);
}
}
out.print(" <td" + attributes + ">" + content + "</td>");
diff --git a/src/main/java/org/torproject/exonerator/QueryResponse.java b/src/main/java/org/torproject/exonerator/QueryResponse.java
index 45dd017..8053e94 100644
--- a/src/main/java/org/torproject/exonerator/QueryResponse.java
+++ b/src/main/java/org/torproject/exonerator/QueryResponse.java
@@ -3,41 +3,117 @@
package org.torproject.exonerator;
+import com.google.gson.Gson;
+import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Objects;
+import java.util.logging.Logger;
+
/** Query response from the ExoneraTor database. */
public class QueryResponse {
+ @Expose(serialize = false, deserialize = false)
+ private static Logger logger = Logger.getLogger(QueryResponse.class.toString());
+
+ /* Actual response format version implemented by this class. */
+ @Expose(serialize = false, deserialize = false)
+ private static final String VERSION = "1.0";
+
+ /* Don't accept query responses with versions lower than this. */
+ @Expose(serialize = false, deserialize = false)
+ private static final String FIRST_RECOGNIZED_VERSION = "1.0";
+
+ /* Don't accept query responses with this version or higher. */
+ @Expose(serialize = false, deserialize = false)
+ private static final String FIRST_UNRECOGNIZED_VERSION = "2.0";
+
/** Version of this response format. */
- String version = "1.0";
+ @Expose
+ String version = VERSION;
/** Query IP address passed in the request; never <code>null</code>. */
+ @Expose
@SerializedName("query_address")
String queryAddress;
/** Query date passed in the request; never <code>null</code>. */
+ @Expose
@SerializedName("query_date")
String queryDate;
/** ISO-formatted valid-after time of the first status contained in the
* database; only <code>null</code> if the database is empty. */
+ @Expose
@SerializedName("first_date_in_database")
String firstDateInDatabase;
/** ISO-formatted valid-after time of the last status contained in the
* database; only <code>null</code> if the database is empty. */
+ @Expose
@SerializedName("last_date_in_database")
String lastDateInDatabase;
/** Whether there is at least one relevant status in the database on or within
* a day of the requested date; <code>null</code> if the database is empty. */
+ @Expose
@SerializedName("relevant_statuses")
Boolean relevantStatuses;
/** All matches for the given IP address and date; <code>null</code> if there
* were no matches at all. */
+ @Expose
Match[] matches;
+ /** Constructor for Gson. */
+ public QueryResponse() {}
+
+ /** Constructor for tests. */
+ QueryResponse(String version, String queryAddress, String queryDate,
+ String firstDateInDatabase, String lastDateInDatabase,
+ Boolean relevantStatuses, Match[] matches, String[] nearbyAddresses) {
+ this.version = version;
+ this.queryAddress = queryAddress;
+ this.queryDate = queryDate;
+ this.firstDateInDatabase = firstDateInDatabase;
+ this.lastDateInDatabase = lastDateInDatabase;
+ this.relevantStatuses = relevantStatuses;
+ this.matches = matches;
+ this.nearbyAddresses = nearbyAddresses;
+ }
+
+ /** Return JSON string for given QueryResponse. */
+ public static String toJson(QueryResponse response) {
+ return new Gson().toJson(response);
+ }
+
+ /** Return QueryResponse parsed from the given input stream, or
+ * <code>null</code> if something fails or an unrecognized version is found. */
+ public static QueryResponse fromJson(Reader reader) {
+ Gson gson = new Gson();
+ try {
+ QueryResponse response = gson.fromJson(reader, QueryResponse.class);
+ if (null == response || null == response.version) {
+ logger.warning("Response is either empty or does not contain "
+ + "version information.");
+ return null;
+ } else if (response.version.compareTo(FIRST_RECOGNIZED_VERSION) < 0
+ || response.version.compareTo(FIRST_UNRECOGNIZED_VERSION) >= 0) {
+ logger.warning("Response has either an older or a newer version ("
+ + response.version + ") than we can handle ("
+ + FIRST_RECOGNIZED_VERSION + " <= x < " + FIRST_UNRECOGNIZED_VERSION
+ + ").");
+ return null;
+ }
+ return response;
+ } catch (RuntimeException e) {
+ logger.severe("JSON decoding failed: " + e.getMessage());
+ }
+ return null;
+ }
+
/** Match details. */
static class Match {
@@ -56,10 +132,24 @@ public class QueryResponse {
/** Whether this relay permitted exiting or not; <code>null</code> if
* unknown. */
Boolean exit;
+
+ /** Constructor for Gson. */
+ public Match() {}
+
+ /** Constructor for tests. */
+ Match(String timestamp, String[] addresses, String fingerprint,
+ String nickname, Boolean exit) {
+ this.timestamp = timestamp;
+ this.addresses = addresses;
+ this.fingerprint = fingerprint;
+ this.nickname = nickname;
+ this.exit = exit;
+ }
}
/** All known IP addresses in the same /24 or /48 network; <code>null</code>
* if there were direct matches for the given IP address. */
+ @Expose
@SerializedName("nearby_addresses")
String[] nearbyAddresses;
}
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits