Skip to content
Browse files

Adding a Servlet Filter and getting rid of Xalan

Contribution by:
https://github.com/rubyorchard

Squashed commit of the following:

commit 60076047ffaa4406561efd76ae04ffecb0619826
Author: Tiago Macedo <tiago@3scale.net>
Date:   Fri Feb 4 13:29:24 2011 +0100

    we no longer need xalan

commit d74fe52
Author: rubyorchard <rubyorchard@gmail.com>
Date:   Thu Feb 3 13:25:58 2011 -0800

    removed duplicate code in ApiException

commit 7d699e0
Merge: 5c19158 332b6ed
Author: rubyorchard <rubyorchard@gmail.com>
Date:   Thu Feb 3 12:52:52 2011 -0800

    merged changes from the 3scale repo

commit 5c19158
Author: rubyorchard <rubyorchard@gmail.com>
Date:   Thu Feb 3 11:12:28 2011 -0800

    ApiFilter which works with a servlet container. Remove dependency on xalan.jar and bunch of refactoring and new business methods
  • Loading branch information...
1 parent 332b6ed commit 896feb5f7b7e27a5111733b4ab7fc005c38e8321 Tiago Macedo committed Feb 4, 2011
View
4 README
@@ -1,4 +0,0 @@
-Please see the embedded documentation in dist/docs.
-
-Copyright (c) 2008 3scale networks S.L., released under the MIT license.
-
View
24 README.md
@@ -0,0 +1,24 @@
+Please see the embedded documentation in dist/docs.
+
+Copyright (c) 2008 3scale networks S.L., released under the MIT license.
+
+
+To configure ServletFilter
+
+ <context-param>
+ <param-name>3scale.provider_private_key</param-name>
+ <param-value>abc-e313daaa98cfd7bb6bc4c906fe233c4b</param-value>
+ </context-param>
+ <filter>
+ <filter-name>Api Filter</filter-name>
+ <filter-class>net.threescale.api.v2.ApiFilter</filter-class>
+ </filter>
+ <filter-mapping>
+ <filter-name>Api Filter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+To test
+
+ for f in {1..2}; do curl -H"X-App-Id: a07cc69b" -H"X-App-Key: a424d9790800b149948dd4b7a0c61d41" \
+ -H"X-App-Rate: 10" -I "http://localhost:8083/v2/videos.json"; done
View
6 Threescale_api.iml
@@ -1,14 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_5" inherit-compiler-output="false">
+ <output url="file://$USER_HOME$/code/java/middleman/target/classes" />
+ <output-test url="file://$USER_HOME$/code/java/middleman/classes/test/Threescale_api" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="library" name="lib" level="project" />
+ <orderEntry type="library" name="3scale-deps" level="project" />
</component>
</module>
View
BIN dist/threescale-api.jar
Binary file not shown.
View
BIN lib/servlet-api-2.5.jar
Binary file not shown.
View
BIN lib/xalan.jar
Binary file not shown.
View
2 src/net/threescale/api/ApiAuthorizeResponse.java
@@ -25,7 +25,7 @@ public ApiAuthorizeResponse(String responseFromServer) throws ApiException {
if (responseFromServer != null && responseFromServer.trim().length() != 0) {
- XPathFactory xPathFactory = new org.apache.xpath.jaxp.XPathFactoryImpl();
+ XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
log.info("Extracting usage info");
View
5 src/net/threescale/api/ApiException.java
@@ -26,8 +26,7 @@ public ApiException(int responseCode, String xmlMessage) {
this.responseCode = responseCode;
try {
- // XPathFactory xPathFactory = XPathFactory.newInstance();
- XPathFactory xPathFactory = new org.apache.xpath.jaxp.XPathFactoryImpl();
+ XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
if (xmlMessage != null && xmlMessage.length() > 0) {
@@ -103,4 +102,4 @@ public String toString() {
return "[code:" + responseCode + " id:" + getErrorId() + ", message: " + getMessage() + "]";
}
}
-
+
View
10 src/net/threescale/api/ApiFactory.java
@@ -9,6 +9,7 @@
* Factory class to create 3scale Api objects.
*/
public class ApiFactory {
+ public static String DEFAULT_3SCALE_PROVIDER_API_URL = "http://su1.3scale.net";
/**
* Creates a new Api object.
@@ -41,6 +42,15 @@
return new Api2Impl(url, application_id, provider_private_key);
}
+ /**
+ * Creates a new Version 2 Api object using <code>DEFAULT_3SCALE_PROVIDER_API_URL</code>
+ * @param provider_private_key The Providers private key obtained from 3scale.
+ * @return A new Api object.
+ */
+ public static Api2 createV2Api(String app_id, String provider_private_key) {
+ return createV2Api(DEFAULT_3SCALE_PROVIDER_API_URL, app_id, provider_private_key);
+ }
+
/**
* Creates a new Api object.
* @param url URL of the server to connect to. e.g. http://server.3scale.net.
View
2 src/net/threescale/api/ApiStartResponse.java
@@ -29,7 +29,7 @@ public ApiStartResponse(String xmlString, int responseCode) throws ApiException
if (xmlString != null && xmlString.trim().length() != 0) {
- XPathFactory xPathFactory = new org.apache.xpath.jaxp.XPathFactoryImpl();
+ XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
log.info("Extracting transaction info");
View
6 src/net/threescale/api/v2/Api2Impl.java
@@ -169,14 +169,14 @@ private String urlEncodeField(String field_to_encode) {
}
}
- private String formatMetrics(String prefix, HashMap<String, String> metrics) {
+ private String formatMetrics(String prefix, Map<String, String> metrics) {
StringBuffer data = new StringBuffer();
Set<Map.Entry<String, String>> entries = metrics.entrySet();
for (Map.Entry<String, String> entry : entries) {
data.append("&").append(prefix).append("[usage]");
- data.append("[" + entry.getKey() + "]=" + entry.getValue());
+ data.append("[").append(entry.getKey()).append("]=").append(entry.getValue());
}
return data.toString();
}
@@ -186,7 +186,7 @@ private String formatMetrics(String prefix, HashMap<String, String> metrics) {
*/
public String formatPostData(ApiTransaction[] transactions) {
StringBuffer post_data = new StringBuffer();
- post_data.append("provider_key=" + provider_key);
+ post_data.append("provider_key=").append(provider_key);
for (int index = 0; index < transactions.length; index++) {
post_data.append("&");
post_data.append(formatTransactionDataForPost(index, transactions[index]));
View
18 src/net/threescale/api/v2/ApiException.java
@@ -29,9 +29,8 @@ public ApiException(String errorCode, String errorMessage) {
* @param xml error xml.
*/
public ApiException(String xml) {
- XPathFactory xPathFactory = new org.apache.xpath.jaxp.XPathFactoryImpl();
+ XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
-
NodeList nodes = XmlHelper.extractNodeList(xpath, "//error[@code]", xml);
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
@@ -52,4 +51,19 @@ public String getErrorCode() {
public String getErrorMessage() {
return errorMessage;
}
+
+ public int toHttpStatusCode() {
+ if("application_not_found".equals(errorCode)) {
+ return 404;
+ }
+ //todo other errorCodes
+ return 500;
+ }
+ public String toHttpStatusMessage() {
+ if("application_not_found".equals(errorCode)) {
+ return "Application Not Found";
+ }
+ //todo other errorCodes
+ return errorCode;
+ }
}
View
112 src/net/threescale/api/v2/ApiFilter.java
@@ -0,0 +1,112 @@
+package net.threescale.api.v2;
+
+import net.threescale.api.ApiFactory;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static net.threescale.api.v2.ApiTransaction.buildHitsMetricApiTransaction;
+
+/**
+ * User: cpatni
+ * Date: 2/1/11
+ * Time: 1:53 PM
+ */
+public class ApiFilter implements Filter {
+
+
+ // This is YOUR key from your api contract.
+ private String provider_private_key;
+ private ServletContext servletContext;
+
+
+ public void init(FilterConfig filterConfig) throws ServletException {
+ servletContext = filterConfig.getServletContext();
+ provider_private_key = servletContext.getInitParameter("3scale.provider_private_key");
+
+ }
+
+ void log(String message) {
+ if (servletContext != null) {
+ servletContext.log(message);
+ } else {
+ System.out.println(message);
+ }
+ }
+
+ public void doFilter(ServletRequest res, ServletResponse req, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) res;
+ HttpServletResponse response = (HttpServletResponse) req;
+
+
+ String app_id = getHeader(request, "X-App-Id", null);
+ String app_key = getHeader(request, "X-App-Key", null);
+ String api_rate = getHeader(request, "X-App-Rate", "1");
+ try {
+ Api2 server = ApiFactory.createV2Api(app_id, provider_private_key);
+ AuthorizeResponse apiResponse = server.authorize(app_key, null);
+
+ if (apiResponse.getAuthorized()) {
+ ApiUsageMetric hitsMetric = apiResponse.firstHitsMetric();
+ log("API Usage: " + hitsMetric.getCurrentValue() + "/" + hitsMetric.getMaxValue());
+ /*
+ X-FeatureRateLimit-Limit Ð Number of requests allowed for that IP address per hour.
+ X-FeatureRateLimit-Remaining Ð Number of requests remaining.
+ X-FeatureRateLimit-Reset Ð Time at which your quota is reset, in Unix epoch time.
+ */
+ response.setHeader("X-FeatureRateLimit-Limit", hitsMetric.getMaxValue());
+ response.setHeader("X-FeatureRateLimit-Remaining", String.valueOf(hitsMetric.getRemaining()));
+ response.setHeader("X-FeatureRateLimit-Reset", String.valueOf(hitsMetric.getPeriodEndEpoch()));
+
+ // Check that caller has available resources
+ if (hitsMetric.marginFor(Integer.parseInt(api_rate))) {
+ // Process your api call here
+ ApiTransaction[] transactions = new ApiTransaction[]{buildHitsMetricApiTransaction(app_id, api_rate)};
+ server.report(transactions);
+ } else {
+ response.setHeader("Retry-After", String.valueOf(hitsMetric.getPeriodEndEpoch()));
+ response.sendError(420, "Enhance Your Calm");
+ }
+ chain.doFilter(request, response);
+ } else {
+ //System.out.println(apiResponse);
+ //TODO in this case we should still be able to get apiResponse hits to write the headers
+ //right now we get AuthorizeResponse: [authorized: false, plan: "", reason: "", usage_reports: []]
+ response.sendError(401);
+ }
+ } catch (ApiException e) {
+
+ response.sendError(e.toHttpStatusCode(), e.toHttpStatusMessage());
+ }
+
+ }
+
+ private String getHeader(HttpServletRequest request, String name, String defaultValue) {
+ String header = request.getHeader(name);
+ if (header == null || header.trim().isEmpty()) {
+ return defaultValue;
+ }
+ return header;
+ }
+
+ private int parseStatusCode(ApiException e) {
+ try {
+ return Integer.parseInt(e.getErrorCode());
+ } catch (NumberFormatException e1) {
+ return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ public void destroy() {
+ }
+
+
+}
View
38 src/net/threescale/api/v2/ApiTransaction.java
@@ -1,29 +1,53 @@
package net.threescale.api.v2;
-import java.util.HashMap;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
/**
* Data to be sent for the server for a transaction
- *
*/
public class ApiTransaction {
private final String app_id;
private final String timestamp;
- private final HashMap<String, String> metrics;
+ private final Map<String, String> metrics;
/**
* Constructor
- * @param app_id Application ID for this report
+ *
+ * @param app_id Application ID for this report
* @param timestamp When the transaction took place
- * @param metrics What resources were used.
+ * @param metrics What resources were used.
*/
- public ApiTransaction(String app_id, String timestamp, HashMap<String, String> metrics) {
+ public ApiTransaction(String app_id, String timestamp, Map<String, String> metrics) {
this.app_id = app_id;
this.timestamp = timestamp;
this.metrics = metrics;
}
+ /**
+ * Constructor
+ *
+ * @param app_id Application ID for this report
+ * @param metrics What resources were used.
+ */
+ public ApiTransaction(String app_id, Map<String, String> metrics) {
+ this.app_id = app_id;
+ this.timestamp = Dates.formatDate(new Date());
+ this.metrics = metrics;
+ }
+
+ public static ApiTransaction buildSingletonMetricApiTransaction(String app_id, String metricName, String metricValue) {
+ Map<String, String> metrics = Collections.singletonMap(metricName, metricValue);
+ return new ApiTransaction(app_id, metrics);
+ }
+
+ public static ApiTransaction buildHitsMetricApiTransaction(String app_id, String metricValue) {
+ Map<String, String> metrics = Collections.singletonMap("hits", metricValue);
+ return new ApiTransaction(app_id, metrics);
+ }
+
public String getApp_id() {
return app_id;
}
@@ -32,7 +56,7 @@ public String getTimestamp() {
return timestamp;
}
- public HashMap<String, String> getMetrics() {
+ public Map<String, String> getMetrics() {
return metrics;
}
}
View
34 src/net/threescale/api/v2/ApiUsageMetric.java
@@ -1,5 +1,7 @@
package net.threescale.api.v2;
+import java.util.Date;
+
/**
* Created by IntelliJ IDEA.
* User: geoffd
@@ -68,4 +70,36 @@ public String getMaxValue() {
public Boolean getExceeded() {
return exceeded;
}
+
+ public int margin() {
+ return Integer.parseInt(getMaxValue()) - Integer.parseInt(getCurrentValue());
+ }
+
+ public boolean marginFor(int allowance) {
+ return Integer.parseInt(getCurrentValue()) + allowance <= Integer.parseInt(getMaxValue());
+ }
+
+ public int getRemaining() {
+ return margin();
+ }
+
+ public Date getPeriodEndDate() {
+ return Dates.parseDate(getPeriodEnd());
+ }
+
+
+ public long getPeriodEndEpoch() {
+ Date date = getPeriodStartDate();
+ return date == null ? 0 : (date.getTime() + 500) / 1000L;
+ }
+
+ public Date getPeriodStartDate() {
+ return Dates.parseDate(getPeriodEnd());
+ }
+
+
+ public long getPeriodStartEpoch() {
+ Date date = getPeriodStartDate();
+ return date == null ? 0 : (date.getTime() + 500) / 1000L;
+ }
}
View
46 src/net/threescale/api/v2/AuthorizeResponse.java
@@ -135,4 +135,50 @@ public void characters(char[] chars, int start, int length) throws SAXException
}
}
+
+ public int maxHits() {
+ ApiUsageMetric metric = firstHitsMetric();
+ return (metric != null) ? Integer.parseInt(metric.getMaxValue()) : 1;
+ }
+
+ public int currentHits() {
+ ApiUsageMetric metric = firstHitsMetric();
+ return (metric != null) ? Integer.parseInt(metric.getCurrentValue()) : 0;
+ }
+
+
+ /**
+ * Returns the first hits metric. Hits metric is the default metric used by 3scale
+ * @return returns the first hits metric or null, if the metric is not found
+ */
+ public ApiUsageMetric firstHitsMetric() {
+ return firstMetricByName("hits");
+
+ }
+
+ /**
+ * Returns the first metric which by name
+ * @param metric_key name of the metric
+ * @return returns the first hits metric or null, if the metric is not found
+ */
+ public ApiUsageMetric firstMetricByName(String metric_key) {
+
+ for (ApiUsageMetric metric : usage_reports) {
+ if (metric.getMetric().equals(metric_key)) {
+ return metric;
+ }
+ }
+ return null;
+ }
+
+ // Find a specific metric/period usage metric
+ public ApiUsageMetric findMetricForPeriod(String metric_key, String period_key) {
+ for (ApiUsageMetric metric : usage_reports) {
+ if (metric.getMetric().equals(metric_key) && metric.getPeriod().equals(period_key)) {
+ return metric;
+ }
+ }
+ return null;
+ }
+
}
View
33 src/net/threescale/api/v2/Dates.java
@@ -0,0 +1,33 @@
+package net.threescale.api.v2;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * User: cpatni
+ * Date: Feb 3, 2011
+ * Time: 8:39:13 AM
+ */
+public class Dates {
+ private static final String YYYY_MM_DD_HH_MM_SS_Z = "yyyy-MM-dd HH:mm:ss Z";
+
+ public static Date parseDate(String date) {
+ return parseDate(date, null);
+ }
+
+ public static Date parseDate(String date, Date defaultValue) {
+ try {
+ SimpleDateFormat dateFormatter = new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS_Z);
+ return dateFormatter.parse(date);
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+
+ public static String formatDate(Date date) {
+ SimpleDateFormat dateFormatter = new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS_Z);
+ return dateFormatter.format(date);
+ }
+
+}
View
1 threescale_api.ipr
@@ -244,7 +244,6 @@
<root url="jar://$PROJECT_DIR$/lib/jmock-junit4-2.5.0.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/mockito-all-1.8.0-rc2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/junit-4.4.jar!/" />
- <root url="jar://$PROJECT_DIR$/lib/xalan.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/junit-dep-4.4.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/hamcrest-core-1.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/jmock-2.5.0.jar!/" />

0 comments on commit 896feb5

Please sign in to comment.
Something went wrong with that request. Please try again.