commit 46743dc511827a12199a6d8177ff2d5591d7dd61
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Thu Mar 20 18:36:53 2014 +0100
Split up ResponseBuilder class.
Suggested by SonarQube.
---
src/org/torproject/onionoo/RequestHandler.java | 757 +++++++++++++++++++++++
src/org/torproject/onionoo/ResourceServlet.java | 49 +-
src/org/torproject/onionoo/ResponseBuilder.java | 724 +---------------------
3 files changed, 800 insertions(+), 730 deletions(-)
diff --git a/src/org/torproject/onionoo/RequestHandler.java b/src/org/torproject/onionoo/RequestHandler.java
new file mode 100644
index 0000000..6a25549
--- /dev/null
+++ b/src/org/torproject/onionoo/RequestHandler.java
@@ -0,0 +1,757 @@
+/* Copyright 2011--2014 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.onionoo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+
+public class RequestHandler {
+
+ private static long summaryFileLastModified = -1L;
+ private static DocumentStore documentStore;
+ private static Time time;
+ private static boolean successfullyReadSummaryFile = false;
+ private static String relaysPublishedString, bridgesPublishedString;
+ private static List<String> relaysByConsensusWeight = null;
+ private static Map<String, String> relayFingerprintSummaryLines = null,
+ bridgeFingerprintSummaryLines = null;
+ private static Map<String, Set<String>> relaysByCountryCode = null,
+ relaysByASNumber = null, relaysByFlag = null, bridgesByFlag = null,
+ relaysByContact = null;
+ private static SortedMap<Integer, Set<String>>
+ relaysByFirstSeenDays = null, bridgesByFirstSeenDays = null,
+ relaysByLastSeenDays = null, bridgesByLastSeenDays = null;
+ private static final long SUMMARY_MAX_AGE = DateTimeHelper.SIX_HOURS;
+
+ public static void initialize(DocumentStore documentStoreParam,
+ Time timeParam) {
+ documentStore = documentStoreParam;
+ time = timeParam;
+ readSummaryFile();
+ }
+
+ public static boolean update() {
+ readSummaryFile();
+ return successfullyReadSummaryFile;
+ }
+
+ private static void readSummaryFile() {
+ long newSummaryFileLastModified = -1L;
+ UpdateStatus updateStatus = documentStore.retrieve(UpdateStatus.class,
+ false);
+ if (updateStatus != null &&
+ updateStatus.getDocumentString() != null) {
+ String updateString = updateStatus.getDocumentString();
+ try {
+ newSummaryFileLastModified = Long.parseLong(updateString.trim());
+ } catch (NumberFormatException e) {
+ /* Handle below. */
+ }
+ }
+ if (newSummaryFileLastModified < 0L) {
+ // TODO Does this actually solve anything? Should we instead
+ // switch to a variant of the maintenance mode and re-check when
+ // the next requests comes in that happens x seconds after this one?
+ successfullyReadSummaryFile = false;
+ return;
+ }
+ if (newSummaryFileLastModified + SUMMARY_MAX_AGE
+ < time.currentTimeMillis()) {
+ // TODO Does this actually solve anything? Should we instead
+ // switch to a variant of the maintenance mode and re-check when
+ // the next requests comes in that happens x seconds after this one?
+ successfullyReadSummaryFile = false;
+ return;
+ }
+ if (newSummaryFileLastModified > summaryFileLastModified) {
+ List<String> newRelaysByConsensusWeight = new ArrayList<String>();
+ Map<String, String>
+ newRelayFingerprintSummaryLines = new HashMap<String, String>(),
+ newBridgeFingerprintSummaryLines =
+ new HashMap<String, String>();
+ Map<String, Set<String>>
+ newRelaysByCountryCode = new HashMap<String, Set<String>>(),
+ newRelaysByASNumber = new HashMap<String, Set<String>>(),
+ newRelaysByFlag = new HashMap<String, Set<String>>(),
+ newBridgesByFlag = new HashMap<String, Set<String>>(),
+ newRelaysByContact = new HashMap<String, Set<String>>();
+ SortedMap<Integer, Set<String>>
+ newRelaysByFirstSeenDays = new TreeMap<Integer, Set<String>>(),
+ newBridgesByFirstSeenDays = new TreeMap<Integer, Set<String>>(),
+ newRelaysByLastSeenDays = new TreeMap<Integer, Set<String>>(),
+ newBridgesByLastSeenDays = new TreeMap<Integer, Set<String>>();
+ long relaysLastValidAfterMillis = -1L,
+ bridgesLastPublishedMillis = -1L;
+ String newRelaysPublishedString, newBridgesPublishedString;
+ Set<NodeStatus> currentRelays = new HashSet<NodeStatus>(),
+ currentBridges = new HashSet<NodeStatus>();
+ SortedSet<String> fingerprints = documentStore.list(
+ NodeStatus.class, false);
+ // TODO We should be able to learn if something goes wrong when
+ // reading the summary file, rather than silently having an empty
+ // list of fingerprints.
+ for (String fingerprint : fingerprints) {
+ NodeStatus node = documentStore.retrieve(NodeStatus.class, true,
+ fingerprint);
+ if (node.isRelay()) {
+ relaysLastValidAfterMillis = Math.max(
+ relaysLastValidAfterMillis, node.getLastSeenMillis());
+ currentRelays.add(node);
+ } else {
+ bridgesLastPublishedMillis = Math.max(
+ bridgesLastPublishedMillis, node.getLastSeenMillis());
+ currentBridges.add(node);
+ }
+ }
+ newRelaysPublishedString = DateTimeHelper.format(
+ relaysLastValidAfterMillis);
+ newBridgesPublishedString = DateTimeHelper.format(
+ bridgesLastPublishedMillis);
+ List<String> orderRelaysByConsensusWeight = new ArrayList<String>();
+ for (NodeStatus entry : currentRelays) {
+ String fingerprint = entry.getFingerprint().toUpperCase();
+ String hashedFingerprint = entry.getHashedFingerprint().
+ toUpperCase();
+ entry.setRunning(entry.getLastSeenMillis() ==
+ relaysLastValidAfterMillis);
+ String line = formatRelaySummaryLine(entry);
+ newRelayFingerprintSummaryLines.put(fingerprint, line);
+ newRelayFingerprintSummaryLines.put(hashedFingerprint, line);
+ long consensusWeight = entry.getConsensusWeight();
+ orderRelaysByConsensusWeight.add(String.format("%020d %s",
+ consensusWeight, fingerprint));
+ orderRelaysByConsensusWeight.add(String.format("%020d %s",
+ consensusWeight, hashedFingerprint));
+ if (entry.getCountryCode() != null) {
+ String countryCode = entry.getCountryCode();
+ if (!newRelaysByCountryCode.containsKey(countryCode)) {
+ newRelaysByCountryCode.put(countryCode,
+ new HashSet<String>());
+ }
+ newRelaysByCountryCode.get(countryCode).add(fingerprint);
+ newRelaysByCountryCode.get(countryCode).add(hashedFingerprint);
+ }
+ if (entry.getASNumber() != null) {
+ String aSNumber = entry.getASNumber();
+ if (!newRelaysByASNumber.containsKey(aSNumber)) {
+ newRelaysByASNumber.put(aSNumber, new HashSet<String>());
+ }
+ newRelaysByASNumber.get(aSNumber).add(fingerprint);
+ newRelaysByASNumber.get(aSNumber).add(hashedFingerprint);
+ }
+ for (String flag : entry.getRelayFlags()) {
+ String flagLowerCase = flag.toLowerCase();
+ if (!newRelaysByFlag.containsKey(flagLowerCase)) {
+ newRelaysByFlag.put(flagLowerCase, new HashSet<String>());
+ }
+ newRelaysByFlag.get(flagLowerCase).add(fingerprint);
+ newRelaysByFlag.get(flagLowerCase).add(hashedFingerprint);
+ }
+ int daysSinceFirstSeen = (int) ((newSummaryFileLastModified
+ - entry.getFirstSeenMillis()) / 86400000L);
+ if (!newRelaysByFirstSeenDays.containsKey(daysSinceFirstSeen)) {
+ newRelaysByFirstSeenDays.put(daysSinceFirstSeen,
+ new HashSet<String>());
+ }
+ newRelaysByFirstSeenDays.get(daysSinceFirstSeen).add(fingerprint);
+ newRelaysByFirstSeenDays.get(daysSinceFirstSeen).add(
+ hashedFingerprint);
+ int daysSinceLastSeen = (int) ((newSummaryFileLastModified
+ - entry.getLastSeenMillis()) / 86400000L);
+ if (!newRelaysByLastSeenDays.containsKey(daysSinceLastSeen)) {
+ newRelaysByLastSeenDays.put(daysSinceLastSeen,
+ new HashSet<String>());
+ }
+ newRelaysByLastSeenDays.get(daysSinceLastSeen).add(fingerprint);
+ newRelaysByLastSeenDays.get(daysSinceLastSeen).add(
+ hashedFingerprint);
+ String contact = entry.getContact();
+ if (!newRelaysByContact.containsKey(contact)) {
+ newRelaysByContact.put(contact, new HashSet<String>());
+ }
+ newRelaysByContact.get(contact).add(fingerprint);
+ newRelaysByContact.get(contact).add(hashedFingerprint);
+ }
+ Collections.sort(orderRelaysByConsensusWeight);
+ newRelaysByConsensusWeight = new ArrayList<String>();
+ for (String relay : orderRelaysByConsensusWeight) {
+ newRelaysByConsensusWeight.add(relay.split(" ")[1]);
+ }
+ for (NodeStatus entry : currentBridges) {
+ String hashedFingerprint = entry.getFingerprint().toUpperCase();
+ String hashedHashedFingerprint = entry.getHashedFingerprint().
+ toUpperCase();
+ entry.setRunning(entry.getRelayFlags().contains("Running") &&
+ entry.getLastSeenMillis() == bridgesLastPublishedMillis);
+ String line = formatBridgeSummaryLine(entry);
+ newBridgeFingerprintSummaryLines.put(hashedFingerprint, line);
+ newBridgeFingerprintSummaryLines.put(hashedHashedFingerprint,
+ line);
+ for (String flag : entry.getRelayFlags()) {
+ String flagLowerCase = flag.toLowerCase();
+ if (!newBridgesByFlag.containsKey(flagLowerCase)) {
+ newBridgesByFlag.put(flagLowerCase, new HashSet<String>());
+ }
+ newBridgesByFlag.get(flagLowerCase).add(hashedFingerprint);
+ newBridgesByFlag.get(flagLowerCase).add(
+ hashedHashedFingerprint);
+ }
+ int daysSinceFirstSeen = (int) ((newSummaryFileLastModified
+ - entry.getFirstSeenMillis()) / 86400000L);
+ if (!newBridgesByFirstSeenDays.containsKey(daysSinceFirstSeen)) {
+ newBridgesByFirstSeenDays.put(daysSinceFirstSeen,
+ new HashSet<String>());
+ }
+ newBridgesByFirstSeenDays.get(daysSinceFirstSeen).add(
+ hashedFingerprint);
+ newBridgesByFirstSeenDays.get(daysSinceFirstSeen).add(
+ hashedHashedFingerprint);
+ int daysSinceLastSeen = (int) ((newSummaryFileLastModified
+ - entry.getLastSeenMillis()) / 86400000L);
+ if (!newBridgesByLastSeenDays.containsKey(daysSinceLastSeen)) {
+ newBridgesByLastSeenDays.put(daysSinceLastSeen,
+ new HashSet<String>());
+ }
+ newBridgesByLastSeenDays.get(daysSinceLastSeen).add(
+ hashedFingerprint);
+ newBridgesByLastSeenDays.get(daysSinceLastSeen).add(
+ hashedHashedFingerprint);
+ }
+ relaysByConsensusWeight = newRelaysByConsensusWeight;
+ relayFingerprintSummaryLines = newRelayFingerprintSummaryLines;
+ bridgeFingerprintSummaryLines = newBridgeFingerprintSummaryLines;
+ relaysByCountryCode = newRelaysByCountryCode;
+ relaysByASNumber = newRelaysByASNumber;
+ relaysByFlag = newRelaysByFlag;
+ bridgesByFlag = newBridgesByFlag;
+ relaysByContact = newRelaysByContact;
+ relaysByFirstSeenDays = newRelaysByFirstSeenDays;
+ relaysByLastSeenDays = newRelaysByLastSeenDays;
+ bridgesByFirstSeenDays = newBridgesByFirstSeenDays;
+ bridgesByLastSeenDays = newBridgesByLastSeenDays;
+ relaysPublishedString = newRelaysPublishedString;
+ bridgesPublishedString = newBridgesPublishedString;
+ }
+ summaryFileLastModified = newSummaryFileLastModified;
+ successfullyReadSummaryFile = true;
+ }
+
+ private static String formatRelaySummaryLine(NodeStatus entry) {
+ String nickname = !entry.getNickname().equals("Unnamed") ?
+ entry.getNickname() : null;
+ String fingerprint = entry.getFingerprint();
+ String running = entry.getRunning() ? "true" : "false";
+ List<String> addresses = new ArrayList<String>();
+ addresses.add(entry.getAddress());
+ for (String orAddress : entry.getOrAddresses()) {
+ addresses.add(orAddress);
+ }
+ for (String exitAddress : entry.getExitAddresses()) {
+ if (!addresses.contains(exitAddress)) {
+ addresses.add(exitAddress);
+ }
+ }
+ StringBuilder addressesBuilder = new StringBuilder();
+ int written = 0;
+ for (String address : addresses) {
+ addressesBuilder.append((written++ > 0 ? "," : "") + "\""
+ + address.toLowerCase() + "\"");
+ }
+ return String.format("{%s\"f\":\"%s\",\"a\":[%s],\"r\":%s}",
+ (nickname == null ? "" : "\"n\":\"" + nickname + "\","),
+ fingerprint, addressesBuilder.toString(), running);
+ }
+
+ private static String formatBridgeSummaryLine(NodeStatus entry) {
+ String nickname = !entry.getNickname().equals("Unnamed") ?
+ entry.getNickname() : null;
+ String hashedFingerprint = entry.getFingerprint();
+ String running = entry.getRunning() ? "true" : "false";
+ return String.format("{%s\"h\":\"%s\",\"r\":%s}",
+ (nickname == null ? "" : "\"n\":\"" + nickname + "\","),
+ hashedFingerprint, running);
+ }
+
+ public static long getLastModified() {
+ readSummaryFile();
+ return summaryFileLastModified;
+ }
+
+ private String resourceType;
+ public void setResourceType(String resourceType) {
+ this.resourceType = resourceType;
+ }
+
+ private String type;
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ private String running;
+ public void setRunning(String running) {
+ this.running = running;
+ }
+
+ private String[] search;
+ public void setSearch(String[] search) {
+ this.search = new String[search.length];
+ System.arraycopy(search, 0, this.search, 0, search.length);
+ }
+
+ private String lookup;
+ public void setLookup(String lookup) {
+ this.lookup = lookup;
+ }
+
+ private String country;
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ private String as;
+ public void setAs(String as) {
+ this.as = as;
+ }
+
+ private String flag;
+ public void setFlag(String flag) {
+ this.flag = flag;
+ }
+
+ private String[] contact;
+ public void setContact(String[] contact) {
+ this.contact = new String[contact.length];
+ System.arraycopy(contact, 0, this.contact, 0, contact.length);
+ }
+
+ private String[] order;
+ public void setOrder(String[] order) {
+ this.order = new String[order.length];
+ System.arraycopy(order, 0, this.order, 0, order.length);
+ }
+
+ private String offset;
+ public void setOffset(String offset) {
+ this.offset = offset;
+ }
+
+ private String limit;
+ public void setLimit(String limit) {
+ this.limit = limit;
+ }
+
+ private int[] firstSeenDays;
+ public void setFirstSeenDays(int[] firstSeenDays) {
+ this.firstSeenDays = new int[firstSeenDays.length];
+ System.arraycopy(firstSeenDays, 0, this.firstSeenDays, 0,
+ firstSeenDays.length);
+ }
+
+ private int[] lastSeenDays;
+ public void setLastSeenDays(int[] lastSeenDays) {
+ this.lastSeenDays = new int[lastSeenDays.length];
+ System.arraycopy(lastSeenDays, 0, this.lastSeenDays, 0,
+ lastSeenDays.length);
+ }
+
+ private Map<String, String> filteredRelays =
+ new HashMap<String, String>();
+
+ private Map<String, String> filteredBridges =
+ new HashMap<String, String>();
+
+ public void handleRequest() {
+ this.filteredRelays.putAll(relayFingerprintSummaryLines);
+ this.filteredBridges.putAll(bridgeFingerprintSummaryLines);
+ this.filterByResourceType();
+ this.filterByType();
+ this.filterByRunning();
+ this.filterBySearchTerms();
+ this.filterByFingerprint();
+ this.filterByCountryCode();
+ this.filterByASNumber();
+ this.filterByFlag();
+ this.filterNodesByFirstSeenDays();
+ this.filterNodesByLastSeenDays();
+ this.filterByContact();
+ this.order();
+ this.offset();
+ this.limit();
+ }
+
+
+ private void filterByResourceType() {
+ if (this.resourceType.equals("clients")) {
+ this.filteredRelays.clear();
+ }
+ if (this.resourceType.equals("weights")) {
+ this.filteredBridges.clear();
+ }
+ }
+
+ private void filterByType() {
+ if (this.type == null) {
+ return;
+ } else if (this.type.equals("relay")) {
+ this.filteredBridges.clear();
+ } else {
+ this.filteredRelays.clear();
+ }
+ }
+
+ private void filterByRunning() {
+ if (this.running == null) {
+ return;
+ }
+ boolean runningRequested = this.running.equals("true");
+ Set<String> removeRelays = new HashSet<String>();
+ for (Map.Entry<String, String> e : filteredRelays.entrySet()) {
+ if (e.getValue().contains("\"r\":true") != runningRequested) {
+ removeRelays.add(e.getKey());
+ }
+ }
+ for (String fingerprint : removeRelays) {
+ this.filteredRelays.remove(fingerprint);
+ }
+ Set<String> removeBridges = new HashSet<String>();
+ for (Map.Entry<String, String> e : filteredBridges.entrySet()) {
+ if (e.getValue().contains("\"r\":true") != runningRequested) {
+ removeBridges.add(e.getKey());
+ }
+ }
+ for (String fingerprint : removeBridges) {
+ this.filteredBridges.remove(fingerprint);
+ }
+ }
+
+ private void filterBySearchTerms() {
+ if (this.search == null) {
+ return;
+ }
+ for (String searchTerm : this.search) {
+ filterBySearchTerm(searchTerm);
+ }
+ }
+
+ private void filterBySearchTerm(String searchTerm) {
+ Set<String> removeRelays = new HashSet<String>();
+ for (Map.Entry<String, String> e : filteredRelays.entrySet()) {
+ String fingerprint = e.getKey();
+ String line = e.getValue();
+ boolean lineMatches = false;
+ String nickname = "unnamed";
+ if (line.contains("\"n\":\"")) {
+ nickname = line.substring(line.indexOf("\"n\":\"") + 5).
+ split("\"")[0].toLowerCase();
+ }
+ if (searchTerm.startsWith("$")) {
+ /* Search is for $-prefixed fingerprint. */
+ if (fingerprint.startsWith(
+ searchTerm.substring(1).toUpperCase())) {
+ /* $-prefixed fingerprint matches. */
+ lineMatches = true;
+ }
+ } else if (nickname.contains(searchTerm.toLowerCase())) {
+ /* Nickname matches. */
+ lineMatches = true;
+ } else if (fingerprint.startsWith(searchTerm.toUpperCase())) {
+ /* Non-$-prefixed fingerprint matches. */
+ lineMatches = true;
+ } else if (line.substring(line.indexOf("\"a\":[")).contains("\""
+ + searchTerm.toLowerCase())) {
+ /* Address matches. */
+ lineMatches = true;
+ }
+ if (!lineMatches) {
+ removeRelays.add(e.getKey());
+ }
+ }
+ for (String fingerprint : removeRelays) {
+ this.filteredRelays.remove(fingerprint);
+ }
+ Set<String> removeBridges = new HashSet<String>();
+ for (Map.Entry<String, String> e : filteredBridges.entrySet()) {
+ String hashedFingerprint = e.getKey();
+ String line = e.getValue();
+ boolean lineMatches = false;
+ String nickname = "unnamed";
+ if (line.contains("\"n\":\"")) {
+ nickname = line.substring(line.indexOf("\"n\":\"") + 5).
+ split("\"")[0].toLowerCase();
+ }
+ if (searchTerm.startsWith("$")) {
+ /* Search is for $-prefixed hashed fingerprint. */
+ if (hashedFingerprint.startsWith(
+ searchTerm.substring(1).toUpperCase())) {
+ /* $-prefixed hashed fingerprint matches. */
+ lineMatches = true;
+ }
+ } else if (nickname.contains(searchTerm.toLowerCase())) {
+ /* Nickname matches. */
+ lineMatches = true;
+ } else if (hashedFingerprint.startsWith(searchTerm.toUpperCase())) {
+ /* Non-$-prefixed hashed fingerprint matches. */
+ lineMatches = true;
+ }
+ if (!lineMatches) {
+ removeBridges.add(e.getKey());
+ }
+ }
+ for (String fingerprint : removeBridges) {
+ this.filteredBridges.remove(fingerprint);
+ }
+ }
+
+ private void filterByFingerprint() {
+ if (this.lookup == null) {
+ return;
+ }
+ String fingerprint = this.lookup;
+ String relayLine = this.filteredRelays.get(fingerprint);
+ this.filteredRelays.clear();
+ if (relayLine != null) {
+ this.filteredRelays.put(fingerprint, relayLine);
+ }
+ String bridgeLine = this.filteredBridges.get(fingerprint);
+ this.filteredBridges.clear();
+ if (bridgeLine != null) {
+ this.filteredBridges.put(fingerprint, bridgeLine);
+ }
+ }
+
+ private void filterByCountryCode() {
+ if (this.country == null) {
+ return;
+ }
+ String countryCode = this.country.toLowerCase();
+ if (!relaysByCountryCode.containsKey(countryCode)) {
+ this.filteredRelays.clear();
+ } else {
+ Set<String> relaysWithCountryCode =
+ relaysByCountryCode.get(countryCode);
+ Set<String> removeRelays = new HashSet<String>();
+ for (Map.Entry<String, String> e : this.filteredRelays.entrySet()) {
+ String fingerprint = e.getKey();
+ if (!relaysWithCountryCode.contains(fingerprint)) {
+ removeRelays.add(fingerprint);
+ }
+ }
+ for (String fingerprint : removeRelays) {
+ this.filteredRelays.remove(fingerprint);
+ }
+ }
+ this.filteredBridges.clear();
+ }
+
+ private void filterByASNumber() {
+ if (this.as == null) {
+ return;
+ }
+ String aSNumber = this.as.toUpperCase();
+ if (!aSNumber.startsWith("AS")) {
+ aSNumber = "AS" + aSNumber;
+ }
+ if (!relaysByASNumber.containsKey(aSNumber)) {
+ this.filteredRelays.clear();
+ } else {
+ Set<String> relaysWithASNumber =
+ relaysByASNumber.get(aSNumber);
+ Set<String> removeRelays = new HashSet<String>();
+ for (Map.Entry<String, String> e : this.filteredRelays.entrySet()) {
+ String fingerprint = e.getKey();
+ if (!relaysWithASNumber.contains(fingerprint)) {
+ removeRelays.add(fingerprint);
+ }
+ }
+ for (String fingerprint : removeRelays) {
+ this.filteredRelays.remove(fingerprint);
+ }
+ }
+ this.filteredBridges.clear();
+ }
+
+ private void filterByFlag() {
+ if (this.flag == null) {
+ return;
+ }
+ String flag = this.flag.toLowerCase();
+ if (!relaysByFlag.containsKey(flag)) {
+ this.filteredRelays.clear();
+ } else {
+ Set<String> relaysWithFlag = relaysByFlag.get(flag);
+ Set<String> removeRelays = new HashSet<String>();
+ for (Map.Entry<String, String> e : this.filteredRelays.entrySet()) {
+ String fingerprint = e.getKey();
+ if (!relaysWithFlag.contains(fingerprint)) {
+ removeRelays.add(fingerprint);
+ }
+ }
+ for (String fingerprint : removeRelays) {
+ this.filteredRelays.remove(fingerprint);
+ }
+ }
+ if (!bridgesByFlag.containsKey(flag)) {
+ this.filteredBridges.clear();
+ } else {
+ Set<String> bridgesWithFlag = bridgesByFlag.get(flag);
+ Set<String> removeBridges = new HashSet<String>();
+ for (Map.Entry<String, String> e :
+ this.filteredBridges.entrySet()) {
+ String fingerprint = e.getKey();
+ if (!bridgesWithFlag.contains(fingerprint)) {
+ removeBridges.add(fingerprint);
+ }
+ }
+ for (String fingerprint : removeBridges) {
+ this.filteredBridges.remove(fingerprint);
+ }
+ }
+ }
+
+ private void filterNodesByFirstSeenDays() {
+ if (this.firstSeenDays == null) {
+ return;
+ }
+ filterNodesByDays(this.filteredRelays, relaysByFirstSeenDays,
+ this.firstSeenDays);
+ filterNodesByDays(this.filteredBridges, bridgesByFirstSeenDays,
+ this.firstSeenDays);
+ }
+
+ private void filterNodesByLastSeenDays() {
+ if (this.lastSeenDays == null) {
+ return;
+ }
+ filterNodesByDays(this.filteredRelays, relaysByLastSeenDays,
+ this.lastSeenDays);
+ filterNodesByDays(this.filteredBridges, bridgesByLastSeenDays,
+ this.lastSeenDays);
+ }
+
+ private void filterNodesByDays(Map<String, String> filteredNodes,
+ SortedMap<Integer, Set<String>> nodesByDays, int[] days) {
+ Set<String> removeNodes = new HashSet<String>();
+ for (Set<String> nodes : nodesByDays.headMap(days[0]).values()) {
+ removeNodes.addAll(nodes);
+ }
+ if (days[1] < Integer.MAX_VALUE) {
+ for (Set<String> nodes :
+ nodesByDays.tailMap(days[1] + 1).values()) {
+ removeNodes.addAll(nodes);
+ }
+ }
+ for (String fingerprint : removeNodes) {
+ filteredNodes.remove(fingerprint);
+ }
+ }
+
+ private void filterByContact() {
+ if (this.contact == null) {
+ return;
+ }
+ Set<String> removeRelays = new HashSet<String>();
+ for (Map.Entry<String, Set<String>> e : relaysByContact.entrySet()) {
+ String contact = e.getKey();
+ for (String contactPart : this.contact) {
+ if (contact == null ||
+ !contact.contains(contactPart.toLowerCase())) {
+ removeRelays.addAll(e.getValue());
+ break;
+ }
+ }
+ }
+ for (String fingerprint : removeRelays) {
+ this.filteredRelays.remove(fingerprint);
+ }
+ this.filteredBridges.clear();
+ }
+
+ private void order() {
+ if (this.order != null && this.order.length == 1) {
+ List<String> orderBy = new ArrayList<String>(
+ relaysByConsensusWeight);
+ if (this.order[0].startsWith("-")) {
+ Collections.reverse(orderBy);
+ }
+ for (String relay : orderBy) {
+ if (this.filteredRelays.containsKey(relay) &&
+ !this.orderedRelays.contains(filteredRelays.get(relay))) {
+ this.orderedRelays.add(this.filteredRelays.remove(relay));
+ }
+ }
+ for (String relay : this.filteredRelays.keySet()) {
+ if (!this.orderedRelays.contains(this.filteredRelays.get(relay))) {
+ this.orderedRelays.add(this.filteredRelays.remove(relay));
+ }
+ }
+ Set<String> uniqueBridges = new HashSet<String>(
+ this.filteredBridges.values());
+ this.orderedBridges.addAll(uniqueBridges);
+ } else {
+ Set<String> uniqueRelays = new HashSet<String>(
+ this.filteredRelays.values());
+ this.orderedRelays.addAll(uniqueRelays);
+ Set<String> uniqueBridges = new HashSet<String>(
+ this.filteredBridges.values());
+ this.orderedBridges.addAll(uniqueBridges);
+ }
+ }
+
+ private void offset() {
+ if (this.offset == null) {
+ return;
+ }
+ int offsetValue = Integer.parseInt(this.offset);
+ while (offsetValue-- > 0 &&
+ (!this.orderedRelays.isEmpty() ||
+ !this.orderedBridges.isEmpty())) {
+ if (!this.orderedRelays.isEmpty()) {
+ this.orderedRelays.remove(0);
+ } else {
+ this.orderedBridges.remove(0);
+ }
+ }
+ }
+
+ private void limit() {
+ if (this.limit == null) {
+ return;
+ }
+ int limitValue = Integer.parseInt(this.limit);
+ while (!this.orderedRelays.isEmpty() &&
+ limitValue < this.orderedRelays.size()) {
+ this.orderedRelays.remove(this.orderedRelays.size() - 1);
+ }
+ limitValue -= this.orderedRelays.size();
+ while (!this.orderedBridges.isEmpty() &&
+ limitValue < this.orderedBridges.size()) {
+ this.orderedBridges.remove(this.orderedBridges.size() - 1);
+ }
+ }
+
+ private List<String> orderedRelays = new ArrayList<String>();
+ public List<String> getOrderedRelays() {
+ return this.orderedRelays;
+ }
+
+ private List<String> orderedBridges = new ArrayList<String>();
+ public List<String> getOrderedBridges() {
+ return this.orderedBridges;
+ }
+
+ public String getRelaysPublishedString() {
+ return relaysPublishedString;
+ }
+
+ public String getBridgesPublishedString() {
+ return bridgesPublishedString;
+ }
+}
diff --git a/src/org/torproject/onionoo/ResourceServlet.java b/src/org/torproject/onionoo/ResourceServlet.java
index 27ff414..0c01d9f 100644
--- a/src/org/torproject/onionoo/ResourceServlet.java
+++ b/src/org/torproject/onionoo/ResourceServlet.java
@@ -42,7 +42,8 @@ public class ResourceServlet extends HttpServlet {
DocumentStore documentStore, Time time) {
this.maintenanceMode = maintenanceMode;
if (!maintenanceMode) {
- ResponseBuilder.initialize(documentStore, time);
+ RequestHandler.initialize(documentStore, time);
+ ResponseBuilder.initialize(documentStore);
}
}
@@ -50,7 +51,7 @@ public class ResourceServlet extends HttpServlet {
if (this.maintenanceMode) {
return super.getLastModified(request);
} else {
- return ResponseBuilder.getLastModified();
+ return RequestHandler.getLastModified();
}
}
@@ -109,12 +110,11 @@ public class ResourceServlet extends HttpServlet {
return;
}
- if (!ResponseBuilder.update()) {
+ if (!RequestHandler.update()) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
- ResponseBuilder rb = new ResponseBuilder();
String uri = request.getRequestURI();
if (uri.startsWith("/onionoo/")) {
uri = uri.substring("/onionoo".length());
@@ -136,7 +136,8 @@ public class ResourceServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
- rb.setResourceType(resourceType);
+ RequestHandler rh = new RequestHandler();
+ rh.setResourceType(resourceType);
/* Extract parameters either from the old-style URI or from request
* parameters. */
@@ -170,7 +171,7 @@ public class ResourceServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
- rb.setType(relaysRequested ? "relay" : "bridge");
+ rh.setType(relaysRequested ? "relay" : "bridge");
}
if (parameterMap.containsKey("running")) {
String runningParameterValue =
@@ -182,7 +183,7 @@ public class ResourceServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
- rb.setRunning(runningRequested ? "true" : "false");
+ rh.setRunning(runningRequested ? "true" : "false");
}
if (parameterMap.containsKey("search")) {
String[] searchTerms = this.parseSearchParameters(
@@ -191,7 +192,7 @@ public class ResourceServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
- rb.setSearch(searchTerms);
+ rh.setSearch(searchTerms);
}
if (parameterMap.containsKey("lookup")) {
String fingerprintParameter = this.parseFingerprintParameter(
@@ -201,7 +202,7 @@ public class ResourceServlet extends HttpServlet {
return;
}
String fingerprint = fingerprintParameter.toUpperCase();
- rb.setLookup(fingerprint);
+ rh.setLookup(fingerprint);
}
if (parameterMap.containsKey("country")) {
String countryCodeParameter = this.parseCountryCodeParameter(
@@ -210,7 +211,7 @@ public class ResourceServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
- rb.setCountry(countryCodeParameter);
+ rh.setCountry(countryCodeParameter);
}
if (parameterMap.containsKey("as")) {
String aSNumberParameter = this.parseASNumberParameter(
@@ -219,7 +220,7 @@ public class ResourceServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
- rb.setAs(aSNumberParameter);
+ rh.setAs(aSNumberParameter);
}
if (parameterMap.containsKey("flag")) {
String flagParameter = this.parseFlagParameter(
@@ -228,7 +229,7 @@ public class ResourceServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
- rb.setFlag(flagParameter);
+ rh.setFlag(flagParameter);
}
if (parameterMap.containsKey("first_seen_days")) {
int[] days = this.parseDaysParameter(
@@ -237,7 +238,7 @@ public class ResourceServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
- rb.setFirstSeenDays(days);
+ rh.setFirstSeenDays(days);
}
if (parameterMap.containsKey("last_seen_days")) {
int[] days = this.parseDaysParameter(
@@ -246,7 +247,7 @@ public class ResourceServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
- rb.setLastSeenDays(days);
+ rh.setLastSeenDays(days);
}
if (parameterMap.containsKey("contact")) {
String[] contactParts = this.parseContactParameter(
@@ -255,10 +256,8 @@ public class ResourceServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
- rb.setContact(contactParts);
+ rh.setContact(contactParts);
}
-
- /* Re-order and limit results. */
if (parameterMap.containsKey("order")) {
String orderParameter = parameterMap.get("order").toLowerCase();
String orderByField = orderParameter;
@@ -269,7 +268,7 @@ public class ResourceServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
- rb.setOrder(new String[] { orderParameter });
+ rh.setOrder(new String[] { orderParameter });
}
if (parameterMap.containsKey("offset")) {
String offsetParameter = parameterMap.get("offset");
@@ -283,7 +282,7 @@ public class ResourceServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
- rb.setOffset(offsetParameter);
+ rh.setOffset(offsetParameter);
}
if (parameterMap.containsKey("limit")) {
String limitParameter = parameterMap.get("limit");
@@ -297,11 +296,16 @@ public class ResourceServlet extends HttpServlet {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
- rb.setLimit(limitParameter);
+ rh.setLimit(limitParameter);
}
+ rh.handleRequest();
- /* Possibly include only a subset of fields in the response
- * document. */
+ ResponseBuilder rb = new ResponseBuilder();
+ rb.setResourceType(resourceType);
+ rb.setRelaysPublishedString(rh.getRelaysPublishedString());
+ rb.setBridgesPublishedString(rh.getBridgesPublishedString());
+ rb.setOrderedRelays(rh.getOrderedRelays());
+ rb.setOrderedBridges(rh.getOrderedBridges());
String[] fields = null;
if (parameterMap.containsKey("fields")) {
fields = this.parseFieldsParameter(parameterMap.get("fields"));
@@ -312,7 +316,6 @@ public class ResourceServlet extends HttpServlet {
rb.setFields(fields);
}
- /* Set response headers and write the response. */
response.setHeader("Access-Control-Allow-Origin", "*");
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
diff --git a/src/org/torproject/onionoo/ResponseBuilder.java b/src/org/torproject/onionoo/ResponseBuilder.java
index 080af52..69b5c62 100644
--- a/src/org/torproject/onionoo/ResponseBuilder.java
+++ b/src/org/torproject/onionoo/ResponseBuilder.java
@@ -4,743 +4,53 @@ package org.torproject.onionoo;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Scanner;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.SortedSet;
-import java.util.TreeMap;
public class ResponseBuilder {
- private static long summaryFileLastModified = -1L;
private static DocumentStore documentStore;
- private static Time time;
- private static boolean successfullyReadSummaryFile = false;
- private static String relaysPublishedString, bridgesPublishedString;
- private static List<String> relaysByConsensusWeight = null;
- private static Map<String, String> relayFingerprintSummaryLines = null,
- bridgeFingerprintSummaryLines = null;
- private static Map<String, Set<String>> relaysByCountryCode = null,
- relaysByASNumber = null, relaysByFlag = null, bridgesByFlag = null,
- relaysByContact = null;
- private static SortedMap<Integer, Set<String>>
- relaysByFirstSeenDays = null, bridgesByFirstSeenDays = null,
- relaysByLastSeenDays = null, bridgesByLastSeenDays = null;
- private static final long SUMMARY_MAX_AGE = DateTimeHelper.SIX_HOURS;
- public static void initialize(DocumentStore documentStoreParam,
- Time timeParam) {
+ public static void initialize(DocumentStore documentStoreParam) {
documentStore = documentStoreParam;
- time = timeParam;
- readSummaryFile();
}
- public static boolean update() {
- readSummaryFile();
- return successfullyReadSummaryFile;
+ private String resourceType;
+ public void setResourceType(String resourceType) {
+ this.resourceType = resourceType;
}
- private static void readSummaryFile() {
- long newSummaryFileLastModified = -1L;
- UpdateStatus updateStatus = documentStore.retrieve(UpdateStatus.class,
- false);
- if (updateStatus != null &&
- updateStatus.getDocumentString() != null) {
- String updateString = updateStatus.getDocumentString();
- try {
- newSummaryFileLastModified = Long.parseLong(updateString.trim());
- } catch (NumberFormatException e) {
- /* Handle below. */
- }
- }
- if (newSummaryFileLastModified < 0L) {
- // TODO Does this actually solve anything? Should we instead
- // switch to a variant of the maintenance mode and re-check when
- // the next requests comes in that happens x seconds after this one?
- successfullyReadSummaryFile = false;
- return;
- }
- if (newSummaryFileLastModified + SUMMARY_MAX_AGE
- < time.currentTimeMillis()) {
- // TODO Does this actually solve anything? Should we instead
- // switch to a variant of the maintenance mode and re-check when
- // the next requests comes in that happens x seconds after this one?
- successfullyReadSummaryFile = false;
- return;
- }
- if (newSummaryFileLastModified > summaryFileLastModified) {
- List<String> newRelaysByConsensusWeight = new ArrayList<String>();
- Map<String, String>
- newRelayFingerprintSummaryLines = new HashMap<String, String>(),
- newBridgeFingerprintSummaryLines =
- new HashMap<String, String>();
- Map<String, Set<String>>
- newRelaysByCountryCode = new HashMap<String, Set<String>>(),
- newRelaysByASNumber = new HashMap<String, Set<String>>(),
- newRelaysByFlag = new HashMap<String, Set<String>>(),
- newBridgesByFlag = new HashMap<String, Set<String>>(),
- newRelaysByContact = new HashMap<String, Set<String>>();
- SortedMap<Integer, Set<String>>
- newRelaysByFirstSeenDays = new TreeMap<Integer, Set<String>>(),
- newBridgesByFirstSeenDays = new TreeMap<Integer, Set<String>>(),
- newRelaysByLastSeenDays = new TreeMap<Integer, Set<String>>(),
- newBridgesByLastSeenDays = new TreeMap<Integer, Set<String>>();
- long relaysLastValidAfterMillis = -1L,
- bridgesLastPublishedMillis = -1L;
- String newRelaysPublishedString, newBridgesPublishedString;
- Set<NodeStatus> currentRelays = new HashSet<NodeStatus>(),
- currentBridges = new HashSet<NodeStatus>();
- SortedSet<String> fingerprints = documentStore.list(
- NodeStatus.class, false);
- // TODO We should be able to learn if something goes wrong when
- // reading the summary file, rather than silently having an empty
- // list of fingerprints.
- for (String fingerprint : fingerprints) {
- NodeStatus node = documentStore.retrieve(NodeStatus.class, true,
- fingerprint);
- if (node.isRelay()) {
- relaysLastValidAfterMillis = Math.max(
- relaysLastValidAfterMillis, node.getLastSeenMillis());
- currentRelays.add(node);
- } else {
- bridgesLastPublishedMillis = Math.max(
- bridgesLastPublishedMillis, node.getLastSeenMillis());
- currentBridges.add(node);
- }
- }
- newRelaysPublishedString = DateTimeHelper.format(
- relaysLastValidAfterMillis);
- newBridgesPublishedString = DateTimeHelper.format(
- bridgesLastPublishedMillis);
- List<String> orderRelaysByConsensusWeight = new ArrayList<String>();
- for (NodeStatus entry : currentRelays) {
- String fingerprint = entry.getFingerprint().toUpperCase();
- String hashedFingerprint = entry.getHashedFingerprint().
- toUpperCase();
- entry.setRunning(entry.getLastSeenMillis() ==
- relaysLastValidAfterMillis);
- String line = formatRelaySummaryLine(entry);
- newRelayFingerprintSummaryLines.put(fingerprint, line);
- newRelayFingerprintSummaryLines.put(hashedFingerprint, line);
- long consensusWeight = entry.getConsensusWeight();
- orderRelaysByConsensusWeight.add(String.format("%020d %s",
- consensusWeight, fingerprint));
- orderRelaysByConsensusWeight.add(String.format("%020d %s",
- consensusWeight, hashedFingerprint));
- if (entry.getCountryCode() != null) {
- String countryCode = entry.getCountryCode();
- if (!newRelaysByCountryCode.containsKey(countryCode)) {
- newRelaysByCountryCode.put(countryCode,
- new HashSet<String>());
- }
- newRelaysByCountryCode.get(countryCode).add(fingerprint);
- newRelaysByCountryCode.get(countryCode).add(hashedFingerprint);
- }
- if (entry.getASNumber() != null) {
- String aSNumber = entry.getASNumber();
- if (!newRelaysByASNumber.containsKey(aSNumber)) {
- newRelaysByASNumber.put(aSNumber, new HashSet<String>());
- }
- newRelaysByASNumber.get(aSNumber).add(fingerprint);
- newRelaysByASNumber.get(aSNumber).add(hashedFingerprint);
- }
- for (String flag : entry.getRelayFlags()) {
- String flagLowerCase = flag.toLowerCase();
- if (!newRelaysByFlag.containsKey(flagLowerCase)) {
- newRelaysByFlag.put(flagLowerCase, new HashSet<String>());
- }
- newRelaysByFlag.get(flagLowerCase).add(fingerprint);
- newRelaysByFlag.get(flagLowerCase).add(hashedFingerprint);
- }
- int daysSinceFirstSeen = (int) ((newSummaryFileLastModified
- - entry.getFirstSeenMillis()) / 86400000L);
- if (!newRelaysByFirstSeenDays.containsKey(daysSinceFirstSeen)) {
- newRelaysByFirstSeenDays.put(daysSinceFirstSeen,
- new HashSet<String>());
- }
- newRelaysByFirstSeenDays.get(daysSinceFirstSeen).add(fingerprint);
- newRelaysByFirstSeenDays.get(daysSinceFirstSeen).add(
- hashedFingerprint);
- int daysSinceLastSeen = (int) ((newSummaryFileLastModified
- - entry.getLastSeenMillis()) / 86400000L);
- if (!newRelaysByLastSeenDays.containsKey(daysSinceLastSeen)) {
- newRelaysByLastSeenDays.put(daysSinceLastSeen,
- new HashSet<String>());
- }
- newRelaysByLastSeenDays.get(daysSinceLastSeen).add(fingerprint);
- newRelaysByLastSeenDays.get(daysSinceLastSeen).add(
- hashedFingerprint);
- String contact = entry.getContact();
- if (!newRelaysByContact.containsKey(contact)) {
- newRelaysByContact.put(contact, new HashSet<String>());
- }
- newRelaysByContact.get(contact).add(fingerprint);
- newRelaysByContact.get(contact).add(hashedFingerprint);
- }
- Collections.sort(orderRelaysByConsensusWeight);
- newRelaysByConsensusWeight = new ArrayList<String>();
- for (String relay : orderRelaysByConsensusWeight) {
- newRelaysByConsensusWeight.add(relay.split(" ")[1]);
- }
- for (NodeStatus entry : currentBridges) {
- String hashedFingerprint = entry.getFingerprint().toUpperCase();
- String hashedHashedFingerprint = entry.getHashedFingerprint().
- toUpperCase();
- entry.setRunning(entry.getRelayFlags().contains("Running") &&
- entry.getLastSeenMillis() == bridgesLastPublishedMillis);
- String line = formatBridgeSummaryLine(entry);
- newBridgeFingerprintSummaryLines.put(hashedFingerprint, line);
- newBridgeFingerprintSummaryLines.put(hashedHashedFingerprint,
- line);
- for (String flag : entry.getRelayFlags()) {
- String flagLowerCase = flag.toLowerCase();
- if (!newBridgesByFlag.containsKey(flagLowerCase)) {
- newBridgesByFlag.put(flagLowerCase, new HashSet<String>());
- }
- newBridgesByFlag.get(flagLowerCase).add(hashedFingerprint);
- newBridgesByFlag.get(flagLowerCase).add(
- hashedHashedFingerprint);
- }
- int daysSinceFirstSeen = (int) ((newSummaryFileLastModified
- - entry.getFirstSeenMillis()) / 86400000L);
- if (!newBridgesByFirstSeenDays.containsKey(daysSinceFirstSeen)) {
- newBridgesByFirstSeenDays.put(daysSinceFirstSeen,
- new HashSet<String>());
- }
- newBridgesByFirstSeenDays.get(daysSinceFirstSeen).add(
- hashedFingerprint);
- newBridgesByFirstSeenDays.get(daysSinceFirstSeen).add(
- hashedHashedFingerprint);
- int daysSinceLastSeen = (int) ((newSummaryFileLastModified
- - entry.getLastSeenMillis()) / 86400000L);
- if (!newBridgesByLastSeenDays.containsKey(daysSinceLastSeen)) {
- newBridgesByLastSeenDays.put(daysSinceLastSeen,
- new HashSet<String>());
- }
- newBridgesByLastSeenDays.get(daysSinceLastSeen).add(
- hashedFingerprint);
- newBridgesByLastSeenDays.get(daysSinceLastSeen).add(
- hashedHashedFingerprint);
- }
- relaysByConsensusWeight = newRelaysByConsensusWeight;
- relayFingerprintSummaryLines = newRelayFingerprintSummaryLines;
- bridgeFingerprintSummaryLines = newBridgeFingerprintSummaryLines;
- relaysByCountryCode = newRelaysByCountryCode;
- relaysByASNumber = newRelaysByASNumber;
- relaysByFlag = newRelaysByFlag;
- bridgesByFlag = newBridgesByFlag;
- relaysByContact = newRelaysByContact;
- relaysByFirstSeenDays = newRelaysByFirstSeenDays;
- relaysByLastSeenDays = newRelaysByLastSeenDays;
- bridgesByFirstSeenDays = newBridgesByFirstSeenDays;
- bridgesByLastSeenDays = newBridgesByLastSeenDays;
- relaysPublishedString = newRelaysPublishedString;
- bridgesPublishedString = newBridgesPublishedString;
- }
- summaryFileLastModified = newSummaryFileLastModified;
- successfullyReadSummaryFile = true;
+ private String relaysPublishedString;
+ public void setRelaysPublishedString(String relaysPublishedString) {
+ this.relaysPublishedString = relaysPublishedString;
}
- private static String formatRelaySummaryLine(NodeStatus entry) {
- String nickname = !entry.getNickname().equals("Unnamed") ?
- entry.getNickname() : null;
- String fingerprint = entry.getFingerprint();
- String running = entry.getRunning() ? "true" : "false";
- List<String> addresses = new ArrayList<String>();
- addresses.add(entry.getAddress());
- for (String orAddress : entry.getOrAddresses()) {
- addresses.add(orAddress);
- }
- for (String exitAddress : entry.getExitAddresses()) {
- if (!addresses.contains(exitAddress)) {
- addresses.add(exitAddress);
- }
- }
- StringBuilder addressesBuilder = new StringBuilder();
- int written = 0;
- for (String address : addresses) {
- addressesBuilder.append((written++ > 0 ? "," : "") + "\""
- + address.toLowerCase() + "\"");
- }
- return String.format("{%s\"f\":\"%s\",\"a\":[%s],\"r\":%s}",
- (nickname == null ? "" : "\"n\":\"" + nickname + "\","),
- fingerprint, addressesBuilder.toString(), running);
+ private String bridgesPublishedString;
+ public void setBridgesPublishedString(String bridgesPublishedString) {
+ this.bridgesPublishedString = bridgesPublishedString;
}
- private static String formatBridgeSummaryLine(NodeStatus entry) {
- String nickname = !entry.getNickname().equals("Unnamed") ?
- entry.getNickname() : null;
- String hashedFingerprint = entry.getFingerprint();
- String running = entry.getRunning() ? "true" : "false";
- return String.format("{%s\"h\":\"%s\",\"r\":%s}",
- (nickname == null ? "" : "\"n\":\"" + nickname + "\","),
- hashedFingerprint, running);
+ private List<String> orderedRelays = new ArrayList<String>();
+ public void setOrderedRelays(List<String> orderedRelays) {
+ this.orderedRelays = orderedRelays;
}
- public static long getLastModified() {
- readSummaryFile();
- return summaryFileLastModified;
+ private List<String> orderedBridges = new ArrayList<String>();
+ public void setOrderedBridges(List<String> orderedBridges) {
+ this.orderedBridges = orderedBridges;
}
- private String resourceType, type, running, search[], lookup, country,
- as, flag, contact[], fields[], order[], offset, limit;
- private int[] firstSeenDays, lastSeenDays;
-
- public void setResourceType(String resourceType) {
- this.resourceType = resourceType;
- }
- public void setType(String type) {
- this.type = type;
- }
- public void setRunning(String running) {
- this.running = running;
- }
- public void setSearch(String[] search) {
- this.search = new String[search.length];
- System.arraycopy(search, 0, this.search, 0, search.length);
- }
- public void setLookup(String lookup) {
- this.lookup = lookup;
- }
- public void setCountry(String country) {
- this.country = country;
- }
- public void setAs(String as) {
- this.as = as;
- }
- public void setFlag(String flag) {
- this.flag = flag;
- }
- public void setFirstSeenDays(int[] firstSeenDays) {
- this.firstSeenDays = new int[firstSeenDays.length];
- System.arraycopy(firstSeenDays, 0, this.firstSeenDays, 0,
- firstSeenDays.length);
- }
- public void setLastSeenDays(int[] lastSeenDays) {
- this.lastSeenDays = new int[lastSeenDays.length];
- System.arraycopy(lastSeenDays, 0, this.lastSeenDays, 0,
- lastSeenDays.length);
- }
- public void setContact(String[] contact) {
- this.contact = new String[contact.length];
- System.arraycopy(contact, 0, this.contact, 0, contact.length);
- }
+ private String[] fields;
public void setFields(String[] fields) {
this.fields = new String[fields.length];
System.arraycopy(fields, 0, this.fields, 0, fields.length);
}
- public void setOrder(String[] order) {
- this.order = new String[order.length];
- System.arraycopy(order, 0, this.order, 0, order.length);
- }
- public void setOffset(String offset) {
- this.offset = offset;
- }
- public void setLimit(String limit) {
- this.limit = limit;
- }
public void buildResponse(PrintWriter pw) {
-
- /* Filter relays and bridges matching the request. */
- Map<String, String> filteredRelays = new HashMap<String, String>(
- relayFingerprintSummaryLines);
- Map<String, String> filteredBridges = new HashMap<String, String>(
- bridgeFingerprintSummaryLines);
- filterByResourceType(filteredRelays, filteredBridges);
- filterByType(filteredRelays, filteredBridges);
- filterByRunning(filteredRelays, filteredBridges);
- filterBySearchTerms(filteredRelays, filteredBridges);
- filterByFingerprint(filteredRelays, filteredBridges);
- filterByCountryCode(filteredRelays, filteredBridges);
- filterByASNumber(filteredRelays, filteredBridges);
- filterByFlag(filteredRelays, filteredBridges);
- filterNodesByFirstSeenDays(filteredRelays, filteredBridges);
- filterNodesByLastSeenDays(filteredRelays, filteredBridges);
- filterByContact(filteredRelays, filteredBridges);
-
- /* Re-order and limit results. */
- List<String> orderedRelays = new ArrayList<String>();
- List<String> orderedBridges = new ArrayList<String>();
- order(filteredRelays, filteredBridges, orderedRelays, orderedBridges);
- offset(orderedRelays, orderedBridges);
- limit(orderedRelays, orderedBridges);
-
- /* Write the response. */
writeRelays(orderedRelays, pw);
writeBridges(orderedBridges, pw);
}
- private void filterByResourceType(Map<String, String> filteredRelays,
- Map<String, String> filteredBridges) {
- if (this.resourceType.equals("clients")) {
- filteredRelays.clear();
- }
- if (this.resourceType.equals("weights")) {
- filteredBridges.clear();
- }
- }
-
- private void filterByType(Map<String, String> filteredRelays,
- Map<String, String> filteredBridges) {
- if (this.type == null) {
- return;
- } else if (this.type.equals("relay")) {
- filteredBridges.clear();
- } else {
- filteredRelays.clear();
- }
- }
-
- private void filterByRunning(Map<String, String> filteredRelays,
- Map<String, String> filteredBridges) {
- if (this.running == null) {
- return;
- }
- boolean runningRequested = this.running.equals("true");
- Set<String> removeRelays = new HashSet<String>();
- for (Map.Entry<String, String> e : filteredRelays.entrySet()) {
- if (e.getValue().contains("\"r\":true") != runningRequested) {
- removeRelays.add(e.getKey());
- }
- }
- for (String fingerprint : removeRelays) {
- filteredRelays.remove(fingerprint);
- }
- Set<String> removeBridges = new HashSet<String>();
- for (Map.Entry<String, String> e : filteredBridges.entrySet()) {
- if (e.getValue().contains("\"r\":true") != runningRequested) {
- removeBridges.add(e.getKey());
- }
- }
- for (String fingerprint : removeBridges) {
- filteredBridges.remove(fingerprint);
- }
- }
-
- private void filterBySearchTerms(Map<String, String> filteredRelays,
- Map<String, String> filteredBridges) {
- if (this.search == null) {
- return;
- }
- for (String searchTerm : this.search) {
- filterBySearchTerm(filteredRelays, filteredBridges, searchTerm);
- }
- }
-
- private void filterBySearchTerm(Map<String, String> filteredRelays,
- Map<String, String> filteredBridges, String searchTerm) {
- Set<String> removeRelays = new HashSet<String>();
- for (Map.Entry<String, String> e : filteredRelays.entrySet()) {
- String fingerprint = e.getKey();
- String line = e.getValue();
- boolean lineMatches = false;
- String nickname = "unnamed";
- if (line.contains("\"n\":\"")) {
- nickname = line.substring(line.indexOf("\"n\":\"") + 5).
- split("\"")[0].toLowerCase();
- }
- if (searchTerm.startsWith("$")) {
- /* Search is for $-prefixed fingerprint. */
- if (fingerprint.startsWith(
- searchTerm.substring(1).toUpperCase())) {
- /* $-prefixed fingerprint matches. */
- lineMatches = true;
- }
- } else if (nickname.contains(searchTerm.toLowerCase())) {
- /* Nickname matches. */
- lineMatches = true;
- } else if (fingerprint.startsWith(searchTerm.toUpperCase())) {
- /* Non-$-prefixed fingerprint matches. */
- lineMatches = true;
- } else if (line.substring(line.indexOf("\"a\":[")).contains("\""
- + searchTerm.toLowerCase())) {
- /* Address matches. */
- lineMatches = true;
- }
- if (!lineMatches) {
- removeRelays.add(e.getKey());
- }
- }
- for (String fingerprint : removeRelays) {
- filteredRelays.remove(fingerprint);
- }
- Set<String> removeBridges = new HashSet<String>();
- for (Map.Entry<String, String> e : filteredBridges.entrySet()) {
- String hashedFingerprint = e.getKey();
- String line = e.getValue();
- boolean lineMatches = false;
- String nickname = "unnamed";
- if (line.contains("\"n\":\"")) {
- nickname = line.substring(line.indexOf("\"n\":\"") + 5).
- split("\"")[0].toLowerCase();
- }
- if (searchTerm.startsWith("$")) {
- /* Search is for $-prefixed hashed fingerprint. */
- if (hashedFingerprint.startsWith(
- searchTerm.substring(1).toUpperCase())) {
- /* $-prefixed hashed fingerprint matches. */
- lineMatches = true;
- }
- } else if (nickname.contains(searchTerm.toLowerCase())) {
- /* Nickname matches. */
- lineMatches = true;
- } else if (hashedFingerprint.startsWith(searchTerm.toUpperCase())) {
- /* Non-$-prefixed hashed fingerprint matches. */
- lineMatches = true;
- }
- if (!lineMatches) {
- removeBridges.add(e.getKey());
- }
- }
- for (String fingerprint : removeBridges) {
- filteredBridges.remove(fingerprint);
- }
- }
-
- private void filterByFingerprint(Map<String, String> filteredRelays,
- Map<String, String> filteredBridges) {
- if (this.lookup == null) {
- return;
- }
- String fingerprint = this.lookup;
- String relayLine = filteredRelays.get(fingerprint);
- filteredRelays.clear();
- if (relayLine != null) {
- filteredRelays.put(fingerprint, relayLine);
- }
- String bridgeLine = filteredBridges.get(fingerprint);
- filteredBridges.clear();
- if (bridgeLine != null) {
- filteredBridges.put(fingerprint, bridgeLine);
- }
- }
-
- private void filterByCountryCode(Map<String, String> filteredRelays,
- Map<String, String> filteredBridges) {
- if (this.country == null) {
- return;
- }
- String countryCode = this.country.toLowerCase();
- if (!relaysByCountryCode.containsKey(countryCode)) {
- filteredRelays.clear();
- } else {
- Set<String> relaysWithCountryCode =
- relaysByCountryCode.get(countryCode);
- Set<String> removeRelays = new HashSet<String>();
- for (Map.Entry<String, String> e : filteredRelays.entrySet()) {
- String fingerprint = e.getKey();
- if (!relaysWithCountryCode.contains(fingerprint)) {
- removeRelays.add(fingerprint);
- }
- }
- for (String fingerprint : removeRelays) {
- filteredRelays.remove(fingerprint);
- }
- }
- filteredBridges.clear();
- }
-
- private void filterByASNumber(Map<String, String> filteredRelays,
- Map<String, String> filteredBridges) {
- if (this.as == null) {
- return;
- }
- String aSNumber = this.as.toUpperCase();
- if (!aSNumber.startsWith("AS")) {
- aSNumber = "AS" + aSNumber;
- }
- if (!relaysByASNumber.containsKey(aSNumber)) {
- filteredRelays.clear();
- } else {
- Set<String> relaysWithASNumber =
- relaysByASNumber.get(aSNumber);
- Set<String> removeRelays = new HashSet<String>();
- for (Map.Entry<String, String> e : filteredRelays.entrySet()) {
- String fingerprint = e.getKey();
- if (!relaysWithASNumber.contains(fingerprint)) {
- removeRelays.add(fingerprint);
- }
- }
- for (String fingerprint : removeRelays) {
- filteredRelays.remove(fingerprint);
- }
- }
- filteredBridges.clear();
- }
-
- private void filterByFlag(Map<String, String> filteredRelays,
- Map<String, String> filteredBridges) {
- if (this.flag == null) {
- return;
- }
- String flag = this.flag.toLowerCase();
- if (!relaysByFlag.containsKey(flag)) {
- filteredRelays.clear();
- } else {
- Set<String> relaysWithFlag = relaysByFlag.get(flag);
- Set<String> removeRelays = new HashSet<String>();
- for (Map.Entry<String, String> e : filteredRelays.entrySet()) {
- String fingerprint = e.getKey();
- if (!relaysWithFlag.contains(fingerprint)) {
- removeRelays.add(fingerprint);
- }
- }
- for (String fingerprint : removeRelays) {
- filteredRelays.remove(fingerprint);
- }
- }
- if (!bridgesByFlag.containsKey(flag)) {
- filteredBridges.clear();
- } else {
- Set<String> bridgesWithFlag = bridgesByFlag.get(flag);
- Set<String> removeBridges = new HashSet<String>();
- for (Map.Entry<String, String> e : filteredBridges.entrySet()) {
- String fingerprint = e.getKey();
- if (!bridgesWithFlag.contains(fingerprint)) {
- removeBridges.add(fingerprint);
- }
- }
- for (String fingerprint : removeBridges) {
- filteredBridges.remove(fingerprint);
- }
- }
- }
-
- private void filterNodesByFirstSeenDays(
- Map<String, String> filteredRelays,
- Map<String, String> filteredBridges) {
- if (this.firstSeenDays == null) {
- return;
- }
- filterNodesByDays(filteredRelays, relaysByFirstSeenDays,
- this.firstSeenDays);
- filterNodesByDays(filteredBridges, bridgesByFirstSeenDays,
- this.firstSeenDays);
- }
-
- private void filterNodesByLastSeenDays(
- Map<String, String> filteredRelays,
- Map<String, String> filteredBridges) {
- if (this.lastSeenDays == null) {
- return;
- }
- filterNodesByDays(filteredRelays, relaysByLastSeenDays,
- this.lastSeenDays);
- filterNodesByDays(filteredBridges, bridgesByLastSeenDays,
- this.lastSeenDays);
- }
-
- private void filterNodesByDays(Map<String, String> filteredNodes,
- SortedMap<Integer, Set<String>> nodesByDays, int[] days) {
- Set<String> removeNodes = new HashSet<String>();
- for (Set<String> nodes : nodesByDays.headMap(days[0]).values()) {
- removeNodes.addAll(nodes);
- }
- if (days[1] < Integer.MAX_VALUE) {
- for (Set<String> nodes :
- nodesByDays.tailMap(days[1] + 1).values()) {
- removeNodes.addAll(nodes);
- }
- }
- for (String fingerprint : removeNodes) {
- filteredNodes.remove(fingerprint);
- }
- }
-
- private void filterByContact(Map<String, String> filteredRelays,
- Map<String, String> filteredBridges) {
- if (this.contact == null) {
- return;
- }
- Set<String> removeRelays = new HashSet<String>();
- for (Map.Entry<String, Set<String>> e : relaysByContact.entrySet()) {
- String contact = e.getKey();
- for (String contactPart : this.contact) {
- if (contact == null ||
- !contact.contains(contactPart.toLowerCase())) {
- removeRelays.addAll(e.getValue());
- break;
- }
- }
- }
- for (String fingerprint : removeRelays) {
- filteredRelays.remove(fingerprint);
- }
- filteredBridges.clear();
- }
-
- private void order(Map<String, String> filteredRelays,
- Map<String, String> filteredBridges, List<String> orderedRelays,
- List<String> orderedBridges) {
- if (this.order != null && this.order.length == 1) {
- List<String> orderBy = new ArrayList<String>(
- relaysByConsensusWeight);
- if (this.order[0].startsWith("-")) {
- Collections.reverse(orderBy);
- }
- for (String relay : orderBy) {
- if (filteredRelays.containsKey(relay) &&
- !orderedRelays.contains(filteredRelays.get(relay))) {
- orderedRelays.add(filteredRelays.remove(relay));
- }
- }
- for (String relay : filteredRelays.keySet()) {
- if (!orderedRelays.contains(filteredRelays.get(relay))) {
- orderedRelays.add(filteredRelays.remove(relay));
- }
- }
- Set<String> uniqueBridges = new HashSet<String>(
- filteredBridges.values());
- orderedBridges.addAll(uniqueBridges);
- } else {
- Set<String> uniqueRelays = new HashSet<String>(
- filteredRelays.values());
- orderedRelays.addAll(uniqueRelays);
- Set<String> uniqueBridges = new HashSet<String>(
- filteredBridges.values());
- orderedBridges.addAll(uniqueBridges);
- }
- }
-
- private void offset(List<String> orderedRelays,
- List<String> orderedBridges) {
- if (offset == null) {
- return;
- }
- int offsetValue = Integer.parseInt(offset);
- while (offsetValue-- > 0 &&
- (!orderedRelays.isEmpty() || !orderedBridges.isEmpty())) {
- if (!orderedRelays.isEmpty()) {
- orderedRelays.remove(0);
- } else {
- orderedBridges.remove(0);
- }
- }
- }
-
- private void limit(List<String> orderedRelays,
- List<String> orderedBridges) {
- if (limit == null) {
- return;
- }
- int limitValue = Integer.parseInt(limit);
- while (!orderedRelays.isEmpty() &&
- limitValue < orderedRelays.size()) {
- orderedRelays.remove(orderedRelays.size() - 1);
- }
- limitValue -= orderedRelays.size();
- while (!orderedBridges.isEmpty() &&
- limitValue < orderedBridges.size()) {
- orderedBridges.remove(orderedBridges.size() - 1);
- }
- }
-
private void writeRelays(List<String> relays, PrintWriter pw) {
pw.write("{\"relays_published\":\"" + relaysPublishedString
+ "\",\n\"relays\":[");