[tor-commits] [onionoo/master] Add parameters for filtering and sorting results.

karsten at torproject.org karsten at torproject.org
Mon May 14 13:11:01 UTC 2012


commit d7f391663ba31daef6ced853d944d92dffd13049
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Tue Apr 3 19:23:30 2012 +0200

    Add parameters for filtering and sorting results.
    
    Parameters are more flexible than our current URLs.  Current functionality
    can be implemented with the type, running, search, and lookup parameters.
    The order and limit parameters can be used, e.g., for top-10 lists of
    relays by bandwidth.  The offset parameter might be useful for paged
    results.
    
    This is a major API change, so we're leaving the deprecated methods in for
    two months instead of one.
---
 src/org/torproject/onionoo/DetailDataWriter.java |   29 ++
 src/org/torproject/onionoo/ResourceServlet.java  |  526 ++++++++++++++--------
 web/index.html                                   |  206 ++++++++-
 3 files changed, 555 insertions(+), 206 deletions(-)

diff --git a/src/org/torproject/onionoo/DetailDataWriter.java b/src/org/torproject/onionoo/DetailDataWriter.java
index 90c2160..cfae5ff 100644
--- a/src/org/torproject/onionoo/DetailDataWriter.java
+++ b/src/org/torproject/onionoo/DetailDataWriter.java
@@ -211,6 +211,8 @@ public class DetailDataWriter {
     SimpleDateFormat dateTimeFormat = new SimpleDateFormat(
         "yyyy-MM-dd HH:mm:ss");
     dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    Map<String, Long> relaysByConsensusWeight =
+        new HashMap<String, Long>();
     for (Map.Entry<String, Node> relay : this.relays.entrySet()) {
       String fingerprint = relay.getKey();
 
@@ -414,6 +416,33 @@ public class DetailDataWriter {
             + "broken.  Ignoring.");
         e.printStackTrace();
       }
+
+      /* Remember consensus weight to facilitate ordering of results in
+       * the servlet. */
+      relaysByConsensusWeight.put(fingerprint, consensusWeight);
+    }
+
+    /* Write consensus weights to disk to facilitate ordering of results
+     * in the servlet. */
+    File relaysByConsensusWeightFile =
+        new File("out/relays-by-consensus-weight.csv");
+    try {
+      relaysByConsensusWeightFile.getParentFile().mkdirs();
+      BufferedWriter bw = new BufferedWriter(new FileWriter(
+          relaysByConsensusWeightFile));
+      for (Map.Entry<String, Long> e :
+          relaysByConsensusWeight.entrySet()) {
+        String fingerprint = e.getKey();
+        long consensusWeight = e.getValue();
+        bw.write(fingerprint + "," + String.valueOf(consensusWeight)
+            + "\n");
+      }
+      bw.close();
+    } catch (IOException e) {
+      System.err.println("Could not write file '"
+          + relaysByConsensusWeightFile.getAbsolutePath() + "'.  "
+          + "Ordering by consensus_weight may now be broken.  Ignoring.");
+      e.printStackTrace();
     }
 
     /* Return the files that we didn't update. */
diff --git a/src/org/torproject/onionoo/ResourceServlet.java b/src/org/torproject/onionoo/ResourceServlet.java
index 4149375..a85f3ec 100644
--- a/src/org/torproject/onionoo/ResourceServlet.java
+++ b/src/org/torproject/onionoo/ResourceServlet.java
@@ -8,6 +8,8 @@ import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -41,7 +43,8 @@ public class ResourceServlet extends HttpServlet {
   boolean readSummaryFile = false;
   private String relaysPublishedLine = null, bridgesPublishedLine = null;
   private List<String> relayLines = new ArrayList<String>(),
-      bridgeLines = new ArrayList<String>();
+      bridgeLines = new ArrayList<String>(),
+      relaysByConsensusWeight = new ArrayList<String>();
   private Map<String, String>
       relayFingerprintSummaryLines = new HashMap<String, String>(),
       bridgeFingerprintSummaryLines = new HashMap<String, String>();
@@ -107,6 +110,44 @@ public class ResourceServlet extends HttpServlet {
       } catch (DecoderException e) {
         return;
       }
+      List<String> orderRelaysByConsensusWeight = new ArrayList<String>();
+      File relaysByConsensusWeightFile =
+          new File(this.outDirString + "/relays-by-consensus-weight.csv");
+      if (relaysByConsensusWeightFile.exists()) {
+        try {
+          BufferedReader br = new BufferedReader(new FileReader(
+              relaysByConsensusWeightFile));
+          String line;
+          while ((line = br.readLine()) != null) {
+            String[] parts = line.split(",");
+            if (parts.length != 2) {
+              return;
+            }
+            long consensusWeight = Long.parseLong(parts[1]);
+            String fingerprint = parts[0];
+            orderRelaysByConsensusWeight.add(
+                String.format("%020d %s", consensusWeight, fingerprint));
+            String hashedFingerprint = DigestUtils.shaHex(
+                Hex.decodeHex(fingerprint.toCharArray())).
+                toUpperCase();
+            orderRelaysByConsensusWeight.add(
+                String.format("%020d %s", consensusWeight,
+                hashedFingerprint));
+          }
+          br.close();
+          Collections.sort(orderRelaysByConsensusWeight);
+          this.relaysByConsensusWeight = new ArrayList<String>();
+          for (String relay : orderRelaysByConsensusWeight) {
+            this.relaysByConsensusWeight.add(relay.split(" ")[1]);
+          }
+        } catch (IOException e) {
+          return;
+        } catch (NumberFormatException e) {
+          return;
+        } catch (DecoderException e) {
+          return;
+        }
+      }
     }
     this.summaryFileLastModified = summaryFile.lastModified();
     this.readSummaryFile = true;
@@ -131,85 +172,237 @@ public class ResourceServlet extends HttpServlet {
       uri = uri.substring("/onionoo".length());
     }
     String resourceType = null;
+    boolean isOldStyleUri = false;
     if (uri.startsWith("/summary/")) {
       resourceType = "summary";
+      isOldStyleUri = true;
     } else if (uri.startsWith("/details/")) {
       resourceType = "details";
+      isOldStyleUri = true;
     } else if (uri.startsWith("/bandwidth/")) {
       resourceType = "bandwidth";
+      isOldStyleUri = true;
+    } else if (uri.startsWith("/summary")) {
+      resourceType = "summary";
+    } else if (uri.startsWith("/details")) {
+      resourceType = "details";
+    } else if (uri.startsWith("/bandwidth")) {
+      resourceType = "bandwidth";
     } else {
       response.sendError(HttpServletResponse.SC_BAD_REQUEST);
       return;
     }
 
-    /* Handle any errors resulting from invalid requests. */
-    if (uri.equals("/" + resourceType + "/all")) {
-    } else if (uri.equals("/" + resourceType + "/running")) {
-    } else if (uri.equals("/" + resourceType + "/relays")) {
-    } else if (uri.equals("/" + resourceType + "/bridges")) {
-    } else if (uri.startsWith("/" + resourceType + "/search/")) {
-      String searchParameter = this.parseSearchParameter(uri.substring(
-          ("/" + resourceType + "/search/").length()));
-      if (searchParameter == null) {
+    /* Extract parameters either from the old-style URI or from request
+     * parameters. */
+    Map<String, String> parameterMap;
+    if (isOldStyleUri) {
+      parameterMap = this.getParameterMapForOldStyleUri(uri,
+          resourceType);
+      if (parameterMap == null) {
         response.sendError(HttpServletResponse.SC_BAD_REQUEST);
         return;
       }
-    } else if (uri.startsWith("/" + resourceType + "/lookup/")) {
-      Set<String> fingerprintParameters = this.parseFingerprintParameters(
-          uri.substring(("/" + resourceType + "/lookup/").length()));
-      if (fingerprintParameters == null) {
+    } else {
+      parameterMap = new HashMap<String, String>();
+      for (Object parameterKey : request.getParameterMap().keySet()) {
+        String[] parameterValues =
+            request.getParameterValues((String) parameterKey);
+        parameterMap.put((String) parameterKey, parameterValues[0]);
+      }
+    }
+
+    /* Make sure that the request doesn't contain any unknown
+     * parameters. */
+    Set<String> knownParameters = new HashSet<String>(Arrays.asList(
+        "type,running,search,lookup,order,limit,offset".split(",")));
+    for (String parameterKey : parameterMap.keySet()) {
+      if (!knownParameters.contains(parameterKey)) {
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+        return;
+      }
+    }
+
+    /* Filter relays and bridges matching the request. */
+    Map<String, String> filteredRelays = new HashMap<String, String>(
+        this.relayFingerprintSummaryLines);
+    Map<String, String> filteredBridges = new HashMap<String, String>(
+        this.bridgeFingerprintSummaryLines);
+    if (parameterMap.containsKey("type")) {
+      String typeParameterValue = parameterMap.get("type");
+      boolean relaysRequested = true;
+      if (typeParameterValue.equals("bridge")) {
+        relaysRequested = false;
+      } else if (!typeParameterValue.equals("relay")) {
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+        return;
+      }
+      this.filterByType(filteredRelays, filteredBridges, relaysRequested);
+    }
+    if (parameterMap.containsKey("running")) {
+      String runningParameterValue = parameterMap.get("running");
+      boolean runningRequested = true;
+      if (runningParameterValue.equals("false")) {
+        runningRequested = false;
+      } else if (!runningParameterValue.equals("true")) {
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+        return;
+      }
+      this.filterByRunning(filteredRelays, filteredBridges,
+          runningRequested);
+    }
+    if (parameterMap.containsKey("search")) {
+      String searchTerm = this.parseSearchParameter(
+          parameterMap.get("search"));
+      if (searchTerm == null) {
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+        return;
+      }
+      this.filterBySearchTerm(filteredRelays, filteredBridges,
+          searchTerm);
+    }
+    if (parameterMap.containsKey("lookup")) {
+      String fingerprintParameter = this.parseFingerprintParameter(
+          parameterMap.get("lookup"));
+      if (fingerprintParameter == null) {
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+        return;
+      }
+      String fingerprint = fingerprintParameter.toUpperCase();
+      this.filterByFingerprint(filteredRelays, filteredBridges,
+          fingerprint);
+    }
+
+    /* Re-order and limit results. */
+    List<String> orderedRelays = new ArrayList<String>();
+    List<String> orderedBridges = new ArrayList<String>();
+    if (parameterMap.containsKey("order")) {
+      String orderParameter = parameterMap.get("order");
+      boolean descending = false;
+      if (orderParameter.startsWith("-")) {
+        descending = true;
+        orderParameter = orderParameter.substring(1);
+      }
+      if (!orderParameter.equals("consensus_weight")) {
         response.sendError(HttpServletResponse.SC_BAD_REQUEST);
         return;
       }
+      List<String> orderBy = new ArrayList<String>(
+          this.relaysByConsensusWeight);
+      if (descending) {
+        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.values()) {
+        if (!orderedRelays.contains(filteredRelays.get(relay))) {
+          orderedRelays.add(filteredRelays.remove(relay));
+        }
+      }
+      Set<String> uniqueBridges = new HashSet<String>(
+          filteredBridges.values());
+      orderedBridges.addAll(uniqueBridges);
     } else {
-      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-      return;
+      Set<String> uniqueRelays = new HashSet<String>(
+          filteredRelays.values());
+      orderedRelays.addAll(uniqueRelays);
+      Set<String> uniqueBridges = new HashSet<String>(
+          filteredBridges.values());
+      orderedBridges.addAll(uniqueBridges);
+    }
+    if (parameterMap.containsKey("offset")) {
+      String offsetParameter = parameterMap.get("offset");
+      if (offsetParameter.length() > 6) {
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+        return;
+      }
+      int offset = 0;
+      try {
+        offset = Integer.parseInt(offsetParameter);
+      } catch (NumberFormatException e) {
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+        return;
+      }
+      while (offset-- > 0 &&
+          (!orderedRelays.isEmpty() || !orderedBridges.isEmpty())) {
+        if (!orderedRelays.isEmpty()) {
+          orderedRelays.remove(0);
+        } else {
+          orderedBridges.remove(0);
+        }
+      }
+    }
+    if (parameterMap.containsKey("limit")) {
+      String limitParameter = parameterMap.get("limit");
+      if (limitParameter.length() > 6) {
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+        return;
+      }
+      int limit = -1;
+      try {
+        limit = Integer.parseInt(limitParameter);
+      } catch (NumberFormatException e) {
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+        return;
+      }
+      if (limit >= 0) {
+        while (limit < orderedRelays.size()) {
+          orderedRelays.remove(orderedRelays.size() - 1);
+        }
+      }
+      limit -= orderedRelays.size();
+      if (limit >= 0) {
+        while (limit < orderedBridges.size()) {
+          orderedBridges.remove(orderedBridges.size() - 1);
+        }
+      }
     }
 
-    /* Set response headers and start writing the response. */
+    /* Set response headers and write the response. */
     response.setHeader("Access-Control-Allow-Origin", "*");
     response.setContentType("application/json");
     response.setCharacterEncoding("utf-8");
     PrintWriter pw = response.getWriter();
+    this.writeRelays(orderedRelays, pw, resourceType);
+    this.writeBridges(orderedBridges, pw, resourceType);
+    pw.flush();
+    pw.close();
+  }
+
+  private Map<String, String> getParameterMapForOldStyleUri(String uri,
+      String resourceType) {
+    Map<String, String> result = new HashMap<String, String>();
     if (uri.equals("/" + resourceType + "/all")) {
-      pw.print(this.relaysPublishedLine + "\n");
-      this.writeAllRelays(pw, resourceType);
-      pw.print(this.bridgesPublishedLine + "\n");
-      this.writeAllBridges(pw, resourceType);
     } else if (uri.equals("/" + resourceType + "/running")) {
-      pw.print(this.relaysPublishedLine + "\n");
-      this.writeRunningRelays(pw, resourceType);
-      pw.print(this.bridgesPublishedLine + "\n");
-      this.writeRunningBridges(pw, resourceType);
+      result.put("running", "true");
     } else if (uri.equals("/" + resourceType + "/relays")) {
-      pw.print(this.relaysPublishedLine + "\n");
-      this.writeAllRelays(pw, resourceType);
-      pw.print(this.bridgesPublishedLine + "\n");
-      this.writeNoBridges(pw);
+      result.put("type", "relays");
     } else if (uri.equals("/" + resourceType + "/bridges")) {
-      pw.print(this.relaysPublishedLine + "\n");
-      this.writeNoRelays(pw);
-      pw.print(this.bridgesPublishedLine + "\n");
-      this.writeAllBridges(pw, resourceType);
+      result.put("type", "bridges");
     } else if (uri.startsWith("/" + resourceType + "/search/")) {
       String searchParameter = this.parseSearchParameter(uri.substring(
           ("/" + resourceType + "/search/").length()));
-      pw.print(this.relaysPublishedLine + "\n");
-      this.writeMatchingRelays(pw, searchParameter, resourceType);
-      pw.print(this.bridgesPublishedLine + "\n");
-      this.writeMatchingBridges(pw, searchParameter, resourceType);
+      if (searchParameter == null) {
+        result = null;
+      } else {
+        result.put("search", searchParameter);
+      }
     } else if (uri.startsWith("/" + resourceType + "/lookup/")) {
-      Set<String> fingerprintParameters = this.parseFingerprintParameters(
+      String fingerprintParameter = this.parseFingerprintParameter(
           uri.substring(("/" + resourceType + "/lookup/").length()));
-      pw.print(this.relaysPublishedLine + "\n");
-      this.writeRelaysWithFingerprints(pw, fingerprintParameters,
-          resourceType);
-      pw.print(this.bridgesPublishedLine + "\n");
-      this.writeBridgesWithFingerprints(pw, fingerprintParameters,
-          resourceType);
+      if (fingerprintParameter == null) {
+        result = null;
+      } else {
+        result.put("lookup", fingerprintParameter);
+      }
+    } else {
+      result = null;
     }
-    pw.flush();
-    pw.close();
+    return result;
   }
 
   private static Pattern searchParameterPattern =
@@ -223,180 +416,143 @@ public class ResourceServlet extends HttpServlet {
 
   private static Pattern fingerprintParameterPattern =
       Pattern.compile("^[0-9a-zA-Z]{1,40}$");
-  private Set<String> parseFingerprintParameters(String parameter) {
+  private String parseFingerprintParameter(String parameter) {
     if (!fingerprintParameterPattern.matcher(parameter).matches()) {
       return null;
     }
-    Set<String> parsedFingerprints = new HashSet<String>();
     if (parameter.length() != 40) {
       return null;
     }
-    parsedFingerprints.add(parameter);
-    return parsedFingerprints;
+    return parameter;
   }
 
-  private void writeAllRelays(PrintWriter pw, String resourceType) {
-    pw.print("\"relays\":[");
-    int written = 0;
-    for (String line : this.relayLines) {
-      String lines = this.getFromSummaryLine(line, resourceType);
-      if (lines.length() > 0) {
-        pw.print((written++ > 0 ? ",\n" : "\n") + lines);
-      }
+  private void filterByType(Map<String, String> filteredRelays,
+      Map<String, String> filteredBridges, boolean relaysRequested) {
+    if (relaysRequested) {
+      filteredBridges.clear();
+    } else {
+      filteredRelays.clear();
     }
-    pw.print("],\n");
   }
 
-  private void writeRunningRelays(PrintWriter pw, String resourceType) {
-    pw.print("\"relays\":[");
-    int written = 0;
-    for (String line : this.relayLines) {
-      if (line.contains("\"r\":true")) {
-        String lines = this.getFromSummaryLine(line, resourceType);
-        if (lines.length() > 0) {
-          pw.print((written++ > 0 ? ",\n" : "\n") + lines);
-        }
+  private void filterByRunning(Map<String, String> filteredRelays,
+      Map<String, String> filteredBridges, boolean runningRequested) {
+    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());
       }
     }
-    pw.print("\n],\n");
-  }
-
-  private void writeNoRelays(PrintWriter pw) {
-    pw.print("\"relays\":[\n");
-    pw.print("],\n");
+    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 writeMatchingRelays(PrintWriter pw, String searchTerm,
-      String resourceType) {
-    if (searchTerm.length() == 40) {
-      Set<String> fingerprints = new HashSet<String>();
-      fingerprints.add(searchTerm);
-      this.writeRelaysWithFingerprints(pw, fingerprints, resourceType);
-    } else {
-      pw.print("\"relays\":[");
-      int written = 0;
-      for (String line : this.relayLines) {
-        boolean lineMatches = false;
-        if (searchTerm.startsWith("$")) {
-          /* Search is for $-prefixed fingerprint. */
-          if (line.contains("\"f\":\""
-              + searchTerm.substring(1).toUpperCase())) {
-            /* $-prefixed fingerprint matches. */
-            lineMatches = true;
-          }
-        } else if (line.toLowerCase().contains("\"n\":\""
-            + searchTerm.toLowerCase())) {
-          /* Nickname matches. */
+  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 line = e.getValue();
+      boolean lineMatches = false;
+      if (searchTerm.startsWith("$")) {
+        /* Search is for $-prefixed fingerprint. */
+        if (line.contains("\"f\":\""
+            + searchTerm.substring(1).toUpperCase())) {
+          /* $-prefixed fingerprint matches. */
           lineMatches = true;
-        } else if ("unnamed".startsWith(searchTerm.toLowerCase()) &&
-            line.startsWith("{\"f\":")) {
-          /* Nickname "Unnamed" matches. */
-          lineMatches = true;
-        } else if (line.contains("\"f\":\"" + searchTerm.toUpperCase())) {
-          /* Non-$-prefixed fingerprint matches. */
-          lineMatches = true;
-        } else if (line.substring(line.indexOf("\"a\":[")).contains("\""
-            + searchTerm.toLowerCase())) {
-          /* Address matches. */
-          lineMatches = true;
-        }
-        if (lineMatches) {
-          String lines = this.getFromSummaryLine(line, resourceType);
-          if (lines.length() > 0) {
-            pw.print((written++ > 0 ? ",\n" : "\n") + lines);
-          }
         }
+      } else if (line.toLowerCase().contains("\"n\":\""
+          + searchTerm.toLowerCase())) {
+        /* Nickname matches. */
+        lineMatches = true;
+      } else if ("unnamed".startsWith(searchTerm.toLowerCase()) &&
+          line.startsWith("{\"f\":")) {
+        /* Nickname "Unnamed" matches. */
+        lineMatches = true;
+      } else if (line.contains("\"f\":\"" + 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>();
+    if (searchTerm.startsWith("$")) {
+      searchTerm = searchTerm.substring(1);
+    }
+    for (Map.Entry<String, String> e : filteredBridges.entrySet()) {
+      String line = e.getValue();
+      if (!line.contains("\"h\":\"" + searchTerm.toUpperCase())) {
+        removeBridges.add(e.getKey());
       }
-      pw.print("\n],\n");
+    }
+    for (String fingerprint : removeBridges) {
+      filteredBridges.remove(fingerprint);
     }
   }
 
-  private void writeRelaysWithFingerprints(PrintWriter pw,
-      Set<String> fingerprints, String resourceType) {
-    pw.print("\"relays\":[");
-    int written = 0;
-    for (String fingerprint : fingerprints) {
-      if (this.relayFingerprintSummaryLines.containsKey(
-          fingerprint.toUpperCase())) {
-        String summaryLine = this.relayFingerprintSummaryLines.get(
-            fingerprint.toUpperCase());
-        String lines = this.getFromSummaryLine(summaryLine, resourceType);
-        if (lines.length() > 0) {
-          pw.print((written++ > 0 ? ",\n" : "\n") + lines);
-        }
-      }
+  private void filterByFingerprint(Map<String, String> filteredRelays,
+      Map<String, String> filteredBridges, String fingerprint) {
+    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);
     }
-    pw.print("\n],\n");
   }
 
-  private void writeAllBridges(PrintWriter pw, String resourceType) {
-    pw.print("\"bridges\":[");
+  private void writeRelays(List<String> relays, PrintWriter pw,
+      String resourceType) {
+    pw.print(this.relaysPublishedLine + "\n");
+    pw.print("\"relays\":[");
     int written = 0;
-    for (String line : this.bridgeLines) {
+    for (String line : relays) {
+      if (line == null) {
+        /* TODO This is a workaround for a bug; line shouldn't be null. */
+        continue;
+      }
       String lines = this.getFromSummaryLine(line, resourceType);
       if (lines.length() > 0) {
         pw.print((written++ > 0 ? ",\n" : "\n") + lines);
       }
     }
-    pw.print("\n]}\n");
-  }
-
-  private void writeRunningBridges(PrintWriter pw, String resourceType) {
-    pw.print("\"bridges\":[");
-    int written = 0;
-    for (String line : this.bridgeLines) {
-      if (line.contains("\"r\":true")) {
-        String lines = this.getFromSummaryLine(line, resourceType);
-        if (lines.length() > 0) {
-          pw.print((written++ > 0 ? ",\n" : "\n") + lines);
-        }
-      }
-    }
-    pw.print("\n]}\n");
-  }
-
-  private void writeNoBridges(PrintWriter pw) {
-    pw.print("\"bridges\":[\n");
-    pw.print("]}\n");
+    pw.print("\n],\n");
   }
 
-  private void writeMatchingBridges(PrintWriter pw, String searchTerm,
+  private void writeBridges(List<String> bridges, PrintWriter pw,
       String resourceType) {
-    if (searchTerm.startsWith("$")) {
-      searchTerm = searchTerm.substring(1);
-    }
-    if (searchTerm.length() == 40) {
-      Set<String> fingerprints = new HashSet<String>();
-      fingerprints.add(searchTerm);
-      this.writeBridgesWithFingerprints(pw, fingerprints, resourceType);
-    } else {
-      pw.print("\"bridges\":[");
-      int written = 0;
-      for (String line : this.bridgeLines) {
-        if (line.contains("\"h\":\"" + searchTerm.toUpperCase())) {
-          String lines = this.getFromSummaryLine(line, resourceType);
-          if (lines.length() > 0) {
-            pw.print((written++ > 0 ? ",\n" : "\n") + lines);
-          }
-        }
-      }
-      pw.print("\n]}\n");
-    }
-  }
-
-  private void writeBridgesWithFingerprints(PrintWriter pw,
-      Set<String> fingerprints, String resourceType) {
+    pw.print(this.bridgesPublishedLine + "\n");
     pw.print("\"bridges\":[");
     int written = 0;
-    for (String fingerprint : fingerprints) {
-      if (this.bridgeFingerprintSummaryLines.containsKey(
-          fingerprint.toUpperCase())) {
-        String summaryLine = this.bridgeFingerprintSummaryLines.get(
-            fingerprint.toUpperCase());
-        String lines = this.getFromSummaryLine(summaryLine, resourceType);
-        if (lines.length() > 0) {
-          pw.print((written++ > 0 ? ",\n" : "\n") + lines);
-        }
+    for (String line : bridges) {
+      if (line == null) {
+        /* TODO This is a workaround for a bug; line shouldn't be null. */
+        continue;
+      }
+      String lines = this.getFromSummaryLine(line, resourceType);
+      if (lines.length() > 0) {
+        pw.print((written++ > 0 ? ",\n" : "\n") + lines);
       }
     }
     pw.print("\n]}\n");
diff --git a/web/index.html b/web/index.html
index c4c154c..7a73591 100755
--- a/web/index.html
+++ b/web/index.html
@@ -84,7 +84,11 @@ Required field.</li>
 <p>The following methods are available that all return a single summary
 document.
 The method and its parameters determines which relay and/or bridge
-summaries are included in the response.</p>
+summaries are included in the response.
+<font color="red">These methods have been deprecated on April 3, 2012 and
+will be removed after June 3, 2012.
+See the Methods section at the end of this page.</font>
+</p>
 <table border="0" cellpadding="4" cellspacing="0" summary="">
 <colgroup>
 <col width="350">
@@ -93,22 +97,34 @@ summaries are included in the response.</p>
 <tr>
 <td><b>GET summary/all</b></td>
 <td>Return summaries of all relays and bridges that are currently running
-or that have been running in the past week.</td>
+or that have been running in the past week.
+<font color="red">New method:
+<b>GET summary</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET summary/running</b></td>
 <td>Return summaries of all relays and bridges that are currently
-running.</td>
+running.
+<font color="red">New method:
+<b>GET summary?running=true</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET summary/relays</b></td>
 <td>Return summaries of all relays that are currently running or that have
-been running in the past week, but don't include any bridges.</td>
+been running in the past week, but don't include any bridges.
+<font color="red">New method:
+<b>GET summary?type=relay</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET summary/bridges</b></td>
 <td>Return summaries of all bridges that are currently running or that
-have been running in the past week, but don't include any relays.</td>
+have been running in the past week, but don't include any relays.
+<font color="red">New method:
+<b>GET summary?type=bridge</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET summary/search/<i>:searchtext</i></b></td>
@@ -119,7 +135,10 @@ Searches are case-insensitive.
 <font color="blue">Full fingerprints should always be hashed using SHA-1,
 regardless of searching for a relay or a bridge, in order to not
 accidentally leak non-hashed bridge fingerprints in the URL.</font>
-Added the option to search for hashed fingerprints on March 19, 2012.</td>
+Added the option to search for hashed fingerprints on March 19, 2012.
+<font color="red">New method:
+<b>GET summary?search=:searchtext</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET summary/lookup/<i>:fingerprint</i></b></td>
@@ -128,7 +147,10 @@ matching the fingerprint or hashed fingerprint.
 <font color="blue">Fingerprints should always be hashed using SHA-1,
 regardless of looking up a relay or a bridge, in order to not accidentally
 leak non-hashed bridge fingerprints in the URL.</font>
-Added the option to look up hashed fingerprints on March 19, 2012.</td>
+Added the option to look up hashed fingerprints on March 19, 2012.
+<font color="red">New method:
+<b>GET summary?lookup=:fingerprint</b></font>
+</td>
 </tr>
 </table>
 <br>
@@ -304,7 +326,11 @@ Optional field.</li>
 <p>The following methods are available that all return a single details
 document.
 The method and its parameters determines which relay and/or bridge
-details are included in the response.</p>
+details are included in the response.
+<font color="red">These methods have been deprecated on April 3, 2012 and
+will be removed after June 3, 2012.
+See the Methods section at the end of this page.</font>
+</p>
 <table border="0" cellpadding="4" cellspacing="0" summary="">
 <colgroup>
 <col width="350">
@@ -313,22 +339,34 @@ details are included in the response.</p>
 <tr>
 <td><b>GET details/all</b></td>
 <td>Return details of all relays and bridges that are currently running
-or that have been running in the past week.</td>
+or that have been running in the past week.
+<font color="red">New method:
+<b>GET details</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET details/running</b></td>
 <td>Return details of all relays and bridges that are currently
-running.</td>
+running.
+<font color="red">New method:
+<b>GET details?running=true</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET details/relays</b></td>
 <td>Return details of all relays that are currently running or that have
-been running in the past week, but don't include any bridges.</td>
+been running in the past week, but don't include any bridges.
+<font color="red">New method:
+<b>GET details?type=relay</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET details/bridges</b></td>
 <td>Return details of all bridges that are currently running or that have
-been running in the past week, but don't include any relays.</td>
+been running in the past week, but don't include any relays.
+<font color="red">New method:
+<b>GET details?type=bridge</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET details/search/<i>:searchtext</i></b></td>
@@ -339,7 +377,10 @@ Searches are case-insensitive.
 <font color="blue">Full fingerprints should always be hashed using SHA-1,
 regardless of searching for a relay or a bridge, in order to not
 accidentally leak non-hashed bridge fingerprints in the URL.</font>
-Added the option to search for hashed fingerprints on March 19, 2012.</td>
+Added the option to search for hashed fingerprints on March 19, 2012.
+<font color="red">New method:
+<b>GET details?search=:searchtext</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET details/lookup/<i>:fingerprint</i></b></td>
@@ -348,7 +389,10 @@ matching the fingerprint or hashed fingerprint.
 <font color="blue">Fingerprints should always be hashed using SHA-1,
 regardless of looking up a relay or a bridge, in order to not accidentally
 leak non-hashed bridge fingerprints in the URL.</font>
-Added the option to look up hashed fingerprints on March 19, 2012.</td>
+Added the option to look up hashed fingerprints on March 19, 2012.
+<font color="red">New method:
+<b>GET details?lookup=:fingerprint</b></font>
+</td>
 </tr>
 </table>
 <br>
@@ -446,7 +490,11 @@ The specification of bandwidth history objects is similar to those in the
 </ul>
 <p>The following methods are available that all return a single document.
 The method and its parameters determines which relay and/or bridge
-bandwidth documents are included in the response.</p>
+bandwidth documents are included in the response.
+<font color="red">These methods have been deprecated on April 3, 2012 and
+will be removed after June 3, 2012.
+See the Methods section at the end of this page.</font>
+</p>
 <table border="0" cellpadding="4" cellspacing="0" summary="">
 <colgroup>
 <col width="350">
@@ -455,23 +503,35 @@ bandwidth documents are included in the response.</p>
 <tr>
 <td><b>GET bandwidth/all</b></td>
 <td>Return bandwidth documents of all relays and bridges that are
-currently running or that have been running in the past week.</td>
+currently running or that have been running in the past week.
+<font color="red">New method:
+<b>GET bandwidth</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET bandwidth/running</b></td>
 <td>Return bandwidth documents of all relays and bridges that are
-currently running.</td>
+currently running.
+<font color="red">New method:
+<b>GET bandwidth?running=true</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET bandwidth/relays</b></td>
 <td>Return bandwidth documents of all relays that are currently running or
-that have been running in the past week, but don't include any bridges.</td>
+that have been running in the past week, but don't include any bridges.
+<font color="red">New method:
+<b>GET bandwidth?type=relay</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET bandwidth/bridges</b></td>
 <td>Return bandwidth documents of all bridges that are currently running
 or that have been running in the past week, but don't include any
-relays.</td>
+relays.
+<font color="red">New method:
+<b>GET bandwidth?type=bridge</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET details/search/<i>:searchtext</i></b></td>
@@ -483,7 +543,10 @@ Searches are case-insensitive.
 <font color="blue">Full fingerprints should always be hashed using SHA-1,
 regardless of searching for a relay or a bridge, in order to not
 accidentally leak non-hashed bridge fingerprints in the URL.</font>
-Added the option to search for hashed fingerprints on March 19, 2012.</td>
+Added the option to search for hashed fingerprints on March 19, 2012.
+<font color="red">New method:
+<b>GET bandwidth?search=:searchtext</b></font>
+</td>
 </tr>
 <tr>
 <td><b>GET bandwidth/lookup/<i>:fingerprint</i></b></td>
@@ -492,9 +555,110 @@ Added the option to search for hashed fingerprints on March 19, 2012.</td>
 <font color="blue">Fingerprints should always be hashed using SHA-1,
 regardless of looking up a relay or a bridge, in order to not accidentally
 leak non-hashed bridge fingerprints in the URL.</font>
-Added the option to look up hashed fingerprints on March 19, 2012.</td>
+Added the option to look up hashed fingerprints on March 19, 2012.
+<font color="red">New method:
+<b>GET bandwidth?lookup=:fingerprint</b></font>
+</td>
 </tr>
 </table>
+<br>
+<h3>Methods</h3>
+<p>The following methods each return a single document containing zero or
+more relay and/or bridge documents.</p>
+<table border="0" cellpadding="4" cellspacing="0" summary="">
+<colgroup>
+<col width="150">
+<col width="850">
+</colgroup>
+<tr>
+<td><b>GET summary</b></td>
+<td>Return summaries of all relays and bridges that are currently running
+or that have been running in the past week.
+<font color="blue">Method added on April 3, 2012.</font>
+</td>
+</tr>
+<tr>
+<td><b>GET details</b></td>
+<td>Return details of all relays and bridges that are currently running
+or that have been running in the past week.
+<font color="blue">Method added on April 3, 2012.</font>
+</td>
+</tr>
+<tr>
+<td><b>GET bandwidth</b></td>
+<td>Return bandwidth documents of all relays and bridges that are
+currently running or that have been running in the past week.
+<font color="blue">Method added on April 3, 2012.</font>
+</td>
+</tr>
+</table>
+<p>Each of the methods above can be parameterized to select only a subset
+of relay and/or bridge documents to be included in the response.</p>
+<table border="0" cellpadding="4" cellspacing="0" summary="">
+<colgroup>
+<col width="150">
+<col width="850">
+</colgroup>
+<tr><td><b>type</b></td><td>Return only relay (parameter value
+<b>relay</b>) or only bridge documents (parameter value
+<b>bridge</b>).
+<font color="blue">Parameter added on April 3, 2012.</font>
+</td></tr>
+<tr><td><b>running</b></td><td>Return only running (parameter value
+<b>true</b>) or only non-running relays and/or bridges (paramter value
+<b>false</b>).
+<font color="blue">Parameter added on April 3, 2012.</font>
+</td></tr>
+<tr><td><b>search</b></td><td>Return only relays with the parameter value
+matching the beginning of a nickname, (possibly $-prefixed) fingerprint,
+or IP address, and bridges with the (possibly $-prefixed) hashed
+fingerprint.
+Searches are case-insensitive.
+Full fingerprints should always be hashed using SHA-1, regardless of
+searching for a relay or a bridge, in order to not accidentally leak
+non-hashed bridge fingerprints in the URL.
+<font color="blue">Parameter added on April 3, 2012.</font>
+</td></tr>
+<tr><td><b>lookup</b></td><td>Return only the relay with the parameter
+value matching the fingerprint or the bridge with the parameter value
+matching the hashed fingerprint.
+Fingerprints should always be hashed using SHA-1, regardless of looking up
+a relay or a bridge, in order to not accidentally leak non-hashed bridge
+fingerprints in the URL.
+<font color="blue">Parameter added on April 3, 2012.</font>
+</td></tr>
+</table>
+<p>Relay and/or bridge documents in the response can be ordered and
+limited by providing further parameters.</p>
+<table border="0" cellpadding="4" cellspacing="0" summary="">
+<colgroup>
+<col width="150">
+<col width="850">
+</colgroup>
+<tr><td><b>order</b></td><td>Re-order results by a comma-separated list
+of fields in ascending or descening order.
+Results are first ordered by the second list element, then by the second,
+and so on.
+Possible fields for ordering are: <b>consensus_weight</b>.
+Ascending order is the default; descending order is selected by prepending
+fields with a minus sign (<b>-</b>).
+Relays which don't have any value for a field to be ordered by are always
+appended to the end, regardless or sorting order.
+If no <b>order</b> parameter is given, ordering of results is
+undefined.
+<font color="blue">Parameter added on April 3, 2012.</font>
+</td></tr>
+<tr><td><b>offset</b></td><td>Skip the given number of relays and/or
+bridges.
+<font color="blue">Parameter added on April 3, 2012.</font>
+</td></tr>
+<tr><td><b>limit</b></td><td>Limit result to the given number of
+relays and/or bridges.
+When used together with <b>offset</b>, the offsetting step precedes the
+limiting step.
+<font color="blue">Parameter added on April 3, 2012.</font>
+</td></tr>
+</table>
 </body>
 </html>
 





More information about the tor-commits mailing list