You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by li...@apache.org on 2010/05/04 21:45:53 UTC
svn commit: r941012 - in /shindig/trunk/java:
common/src/main/java/org/apache/shindig/common/servlet/
common/src/main/java/org/apache/shindig/common/util/
common/src/main/java/org/apache/shindig/protocol/
common/src/test/java/org/apache/shindig/common/...
Author: lindner
Date: Tue May 4 19:45:53 2010
New Revision: 941012
URL: http://svn.apache.org/viewvc?rev=941012&view=rev
Log:
Add jsonp support for json-rpc requests by using the 'request' and 'callback' parameters
Modified:
shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/HttpUtil.java
shindig/trunk/java/common/src/main/java/org/apache/shindig/common/util/JsonConversionUtil.java
shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/JsonRpcServlet.java
shindig/trunk/java/common/src/test/java/org/apache/shindig/common/util/JsonConversionUtilTest.java
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/RpcServlet.java
Modified: shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/HttpUtil.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/HttpUtil.java?rev=941012&r1=941011&r2=941012&view=diff
==============================================================================
--- shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/HttpUtil.java (original)
+++ shindig/trunk/java/common/src/main/java/org/apache/shindig/common/servlet/HttpUtil.java Tue May 4 19:45:53 2010
@@ -20,7 +20,9 @@ package org.apache.shindig.common.servle
import org.apache.shindig.common.util.TimeSource;
+import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.util.regex.Pattern;
/**
* Collection of HTTP utilities
@@ -107,4 +109,24 @@ public class HttpUtil {
public static void setDefaultTtl(int defaultTtl) {
HttpUtil.defaultTtl = defaultTtl;
}
-}
+
+
+ static final Pattern GET_REQUEST_CALLBACK_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_\\.]+");
+
+ public static boolean isJSONP(HttpServletRequest request) throws IllegalArgumentException {
+ String callback = request.getParameter("callback");
+
+ // Must be a GET
+ if (!"GET".equals(request.getMethod()))
+ return false;
+
+ // No callback specified
+ if (callback == null) return false;
+
+ if (!GET_REQUEST_CALLBACK_PATTERN.matcher(callback).matches()) {
+ throw new IllegalArgumentException("Wrong format for parameter 'callback' specified. Must match: " +
+ GET_REQUEST_CALLBACK_PATTERN.toString());
+ }
+ return true;
+ }
+}
\ No newline at end of file
Modified: shindig/trunk/java/common/src/main/java/org/apache/shindig/common/util/JsonConversionUtil.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/util/JsonConversionUtil.java?rev=941012&r1=941011&r2=941012&view=diff
==============================================================================
--- shindig/trunk/java/common/src/main/java/org/apache/shindig/common/util/JsonConversionUtil.java (original)
+++ shindig/trunk/java/common/src/main/java/org/apache/shindig/common/util/JsonConversionUtil.java Tue May 4 19:45:53 2010
@@ -25,14 +25,13 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import javax.servlet.http.HttpServletRequest;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import javax.servlet.http.HttpServletRequest;
-
/**
* Utilities for converting a JSON object to and from a URL encoding
*/
@@ -44,10 +43,13 @@ public class JsonConversionUtil {
@SuppressWarnings("unchecked")
public static JSONObject fromRequest(HttpServletRequest request) throws JSONException {
- //String methodName = request.getPathInfo().replaceAll("/", "");
+ Map<String, String[]> params = request.getParameterMap();
+
+ if (!params.containsKey("method")) {
+ return null;
+ }
JSONObject root = new JSONObject();
- Map<String, String[]> params = request.getParameterMap();
root.put("method", params.get("method")[0]);
if (params.containsKey("id")) {
root.put("id", params.get("id")[0]);
Modified: shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/JsonRpcServlet.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/JsonRpcServlet.java?rev=941012&r1=941011&r2=941012&view=diff
==============================================================================
--- shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/JsonRpcServlet.java (original)
+++ shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/JsonRpcServlet.java Tue May 4 19:45:53 2010
@@ -20,6 +20,7 @@ package org.apache.shindig.protocol;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.shindig.auth.SecurityToken;
+import org.apache.shindig.common.servlet.HttpUtil;
import org.apache.shindig.common.util.JsonConversionUtil;
import org.apache.shindig.protocol.multipart.FormDataItem;
import org.apache.shindig.protocol.multipart.MultipartFormParser;
@@ -36,6 +37,7 @@ import org.json.JSONObject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
+import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -43,6 +45,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
+import java.util.regex.Pattern;
/**
* JSON-RPC handler servlet.
@@ -72,85 +75,100 @@ public class JsonRpcServlet extends ApiS
}
@Override
- protected void doGet(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
+ protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
throws IOException {
setCharacterEncodings(servletRequest, servletResponse);
servletResponse.setContentType(ContentTypes.OUTPUT_JSON_CONTENT_TYPE);
+ // only GET/POST
+ String method = servletRequest.getMethod();
+
+ if (!("GET".equals(method) || "POST".equals(method))) {
+ sendError(servletResponse, new ResponseItem(HttpServletResponse.SC_BAD_REQUEST, "Only POST/GET"));
+ return;
+ }
+
SecurityToken token = getSecurityToken(servletRequest);
if (token == null) {
sendSecurityError(servletResponse);
return;
}
- try {
- JSONObject request = JsonConversionUtil.fromRequest(servletRequest);
- dispatch(request, null, servletRequest, servletResponse, token);
- } catch (JSONException je) {
- sendJsonParseError(je, servletResponse);
- }
- }
-
- @Override
- protected void doPost(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
- throws IOException {
- setCharacterEncodings(servletRequest, servletResponse);
- servletResponse.setContentType(ContentTypes.OUTPUT_JSON_CONTENT_TYPE);
try {
- checkContentTypes(ALLOWED_CONTENT_TYPES, servletRequest.getContentType());
- SecurityToken token = getSecurityToken(servletRequest);
- if (token == null) {
- sendSecurityError(servletResponse);
- return;
- }
-
String content = null;
- Map<String, FormDataItem> formItems = Collections.emptyMap(); // default for most requests
+ String callback = null; // for JSONP
+ Map<String,FormDataItem> formData = Maps.newHashMap();
- if (formParser.isMultipartContent(servletRequest)) {
- formItems = new HashMap<String, FormDataItem>();
- for (FormDataItem item : formParser.parse(servletRequest)) {
- if (item.isFormField() && REQUEST_PARAM.equals(item.getFieldName()) && content == null) {
- // As per spec, in case of a multipart/form-data content, there will be one form field
- // with field name as "request". It will contain the json request. Any further form
- // field or file item will not be parsed out, but will be exposed via getFormItem
- // method of RequestItem.
- if (!StringUtils.isEmpty(item.getContentType())) {
- checkContentTypes(ContentTypes.ALLOWED_JSON_CONTENT_TYPES, item.getContentType());
- }
- content = item.getAsString();
- } else {
- formItems.put(item.getFieldName(), item);
- }
- }
+ // Get content or deal with JSON-RPC GET
+ if ("POST".equals(method)) {
+ content = getPostContent(servletRequest, formData);
+ } else if (HttpUtil.isJSONP(servletRequest)) {
+ content = servletRequest.getParameter("request");
+ callback = servletRequest.getParameter("callback");
+ } else {
+ // GET request, fromRequest() creates the json objects directly.
+ JSONObject request = JsonConversionUtil.fromRequest(servletRequest);
- if (content == null) {
- content = "";
+ if (request != null) {
+ dispatch(request, formData, servletRequest, servletResponse, token, null);
+ return;
}
- } else {
- content = IOUtils.toString(servletRequest.getInputStream(),
- servletRequest.getCharacterEncoding());
+ }
+
+ if (content == null) {
+ sendError(servletResponse, new ResponseItem(HttpServletResponse.SC_BAD_REQUEST, "No content specified"));
+ return;
}
- if ((content.indexOf('[') != -1) && content.indexOf('[') < content.indexOf('{')) {
- // Is a batch
+ if (isContentJsonBatch(content)) {
JSONArray batch = new JSONArray(content);
- dispatchBatch(batch, formItems, servletRequest, servletResponse, token);
+ dispatchBatch(batch, formData, servletRequest, servletResponse, token, callback);
} else {
JSONObject request = new JSONObject(content);
- dispatch(request, formItems, servletRequest, servletResponse, token);
+ dispatch(request, formData, servletRequest, servletResponse, token, callback);
}
+ return;
} catch (JSONException je) {
sendJsonParseError(je, servletResponse);
- } catch (ContentTypes.InvalidContentTypeException icte) {
+ } catch (IllegalArgumentException e) {
+ // a bad jsonp request..
+ sendBadRequest(e, servletResponse);
+ } catch (ContentTypes.InvalidContentTypeException icte) {
sendBadRequest(icte, servletResponse);
}
}
+ protected String getPostContent(HttpServletRequest request, Map<String,FormDataItem> formItems)
+ throws ContentTypes.InvalidContentTypeException, IOException {
+ String content = null;
+
+ checkContentTypes(ALLOWED_CONTENT_TYPES, request.getContentType());
+
+ if (formParser.isMultipartContent(request)) {
+ for (FormDataItem item : formParser.parse(request)) {
+ if (item.isFormField() && REQUEST_PARAM.equals(item.getFieldName()) && content == null) {
+ // As per spec, in case of a multipart/form-data content, there will be one form field
+ // with field name as "request". It will contain the json request. Any further form
+ // field or file item will not be parsed out, but will be exposed via getFormItem
+ // method of RequestItem.
+ if (!StringUtils.isEmpty(item.getContentType())) {
+ checkContentTypes(ContentTypes.ALLOWED_JSON_CONTENT_TYPES, item.getContentType());
+ }
+ content = item.getAsString();
+ } else {
+ formItems.put(item.getFieldName(), item);
+ }
+ }
+ } else {
+ content = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
+ }
+ return content;
+ }
+
protected void dispatchBatch(JSONArray batch, Map<String, FormDataItem> formItems ,
HttpServletRequest servletRequest, HttpServletResponse servletResponse,
- SecurityToken token) throws JSONException, IOException {
+ SecurityToken token, String callback) throws JSONException, IOException {
// Use linked hash map to preserve order
List<Future<?>> responses = Lists.newArrayListWithCapacity(batch.length());
@@ -174,14 +192,19 @@ public class JsonRpcServlet extends ApiS
}
result.add(getJSONResponse(key, getResponseItem(responses.get(i))));
}
-
- jsonConverter.append(servletResponse.getWriter(), result);
+
+ // Generate the output
+ Writer writer = servletResponse.getWriter();
+ if (callback != null) writer.append(callback+'(');
+ jsonConverter.append(writer, result);
+ if (callback != null) writer.append(");\n");
}
protected void dispatch(JSONObject request, Map<String, FormDataItem> formItems,
HttpServletRequest servletRequest, HttpServletResponse servletResponse,
- SecurityToken token) throws JSONException, IOException {
+ SecurityToken token, String callback) throws JSONException, IOException {
String key = null;
+
if (request.has("id")) {
key = request.getString("id");
}
@@ -194,10 +217,24 @@ public class JsonRpcServlet extends ApiS
ResponseItem response = getResponseItem(future);
Object result = getJSONResponse(key, response);
- jsonConverter.append(servletResponse.getWriter(), result);
+ // Generate the output
+ Writer writer = servletResponse.getWriter();
+ if (callback != null) writer.append(callback+'(');
+ jsonConverter.append(writer, result);
+ if (callback != null) writer.append(");\n");
}
/**
+ * Determine if the content contains a batch request
+ *
+ * @param content json content or null
+ * @return true if content contains is a json array, not a json object or null
+ */
+ private boolean isContentJsonBatch(String content) {
+ if (content == null) return false;
+ return ((content.indexOf('[') != -1) && content.indexOf('[') < content.indexOf('{'));
+ }
+ /**
* Wrap call to dispatcher to allow for implementation specific overrides
* and servlet-request contextual handling
*/
@@ -295,7 +332,7 @@ public class JsonRpcServlet extends ApiS
private void sendBadRequest(Throwable t, HttpServletResponse response) throws IOException {
sendError(response, new ResponseItem(HttpServletResponse.SC_BAD_REQUEST,
- "Invalid batch - " + t.getMessage()));
+ "Invalid input - " + t.getMessage()));
}
private void sendJsonParseError(JSONException e, HttpServletResponse response) throws IOException {
Modified: shindig/trunk/java/common/src/test/java/org/apache/shindig/common/util/JsonConversionUtilTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/util/JsonConversionUtilTest.java?rev=941012&r1=941011&r2=941012&view=diff
==============================================================================
--- shindig/trunk/java/common/src/test/java/org/apache/shindig/common/util/JsonConversionUtilTest.java (original)
+++ shindig/trunk/java/common/src/test/java/org/apache/shindig/common/util/JsonConversionUtilTest.java Tue May 4 19:45:53 2010
@@ -17,9 +17,12 @@
*/
package org.apache.shindig.common.util;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
+import org.apache.shindig.common.testing.FakeHttpServletRequest;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -27,6 +30,7 @@ import org.json.JSONObject;
import org.junit.Assert;
import org.junit.Test;
+import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
@@ -118,6 +122,15 @@ public class JsonConversionUtilTest exte
assertEquals("hello", resultMap.get(".a.b(1).c"));
}
+ @Test
+ public void testJsonFromRequest() throws Exception {
+ HttpServletRequest fakeRequest;
+ for (String badParms : ImmutableList.of("x=1", "x=1&callback=")) {
+ fakeRequest = new FakeHttpServletRequest("http://foo.com/gadgets/rpc?" + badParms);
+ assertNull(JsonConversionUtil.fromRequest(fakeRequest));
+ }
+ }
+
public static void assertJsonEquals(Object expected, Object actual)
throws JSONException {
if (expected == null) {
Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/RpcServlet.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/RpcServlet.java?rev=941012&r1=941011&r2=941012&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/RpcServlet.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/RpcServlet.java Tue May 4 19:45:53 2010
@@ -19,6 +19,7 @@
package org.apache.shindig.gadgets.servlet;
import org.apache.commons.io.IOUtils;
+import org.apache.shindig.common.servlet.HttpUtil;
import org.apache.shindig.common.servlet.InjectedServlet;
import org.json.JSONException;
import org.json.JSONObject;
@@ -42,8 +43,6 @@ import java.util.regex.Pattern;
public class RpcServlet extends InjectedServlet {
static final String GET_REQUEST_REQ_PARAM = "req";
static final String GET_REQUEST_CALLBACK_PARAM = "callback";
- // Starts with alpha or underscore, followed by alphanum, underscore or period
- static final Pattern GET_REQUEST_CALLBACK_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_\\.]+");
private static final int POST_REQUEST_MAX_SIZE = 1024 * 128;
private static final Logger logger = Logger.getLogger("org.apache.shindig.gadgets");
@@ -62,14 +61,9 @@ public class RpcServlet extends Injected
String callbackValue;
try {
+ HttpUtil.isJSONP(request);
reqValue = validateParameterValue(request, GET_REQUEST_REQ_PARAM);
callbackValue = validateParameterValue(request, GET_REQUEST_CALLBACK_PARAM);
- if (!GET_REQUEST_CALLBACK_PATTERN.matcher(callbackValue).matches()) {
- throw new IllegalArgumentException("Wrong format for parameter '" +
- GET_REQUEST_CALLBACK_PARAM + "' specified. Expected: " +
- GET_REQUEST_CALLBACK_PATTERN.toString());
- }
-
} catch (IllegalArgumentException e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
logger.log(Level.INFO, e.getMessage(), e);
@@ -150,5 +144,4 @@ public class RpcServlet extends Injected
return success;
}
}
-
}