aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShawn O. Pearce <sop@google.com>2009-12-12 19:22:51 -0800
committerShawn O. Pearce <sop@google.com>2009-12-12 19:30:25 -0800
commitd89d7fe6901c2b5c4d7926c6876aace1460116bd (patch)
tree42ba611eb88cd92808ea68036d8e5d236630ed3e
parentd223e91ec36b709d10ef9870c32b3ee41efbbbbc (diff)
downloadgwtjsonrpc-d89d7fe6901c2b5c4d7926c6876aace1460116bd.tar.gz
Add JSON-RPC 2.0 support to JsonServlet
We now support both the GET and POST syntax for JSON-RPC 2.0. Signed-off-by: Shawn O. Pearce <sop@google.com>
-rw-r--r--src/main/java/com/google/gwtjsonrpc/server/ActiveCall.java2
-rw-r--r--src/main/java/com/google/gwtjsonrpc/server/CallDeserializer.java23
-rw-r--r--src/main/java/com/google/gwtjsonrpc/server/JsonServlet.java120
3 files changed, 110 insertions, 35 deletions
diff --git a/src/main/java/com/google/gwtjsonrpc/server/ActiveCall.java b/src/main/java/com/google/gwtjsonrpc/server/ActiveCall.java
index 6f3ab68..0f1091e 100644
--- a/src/main/java/com/google/gwtjsonrpc/server/ActiveCall.java
+++ b/src/main/java/com/google/gwtjsonrpc/server/ActiveCall.java
@@ -30,6 +30,8 @@ public class ActiveCall implements AsyncCallback<Object> {
protected final HttpServletRequest httpRequest;
protected final HttpServletResponse httpResponse;
JsonElement id;
+ String versionName;
+ JsonElement versionValue;
SignedToken xsrf;
String xsrfKeyIn;
String xsrfKeyOut;
diff --git a/src/main/java/com/google/gwtjsonrpc/server/CallDeserializer.java b/src/main/java/com/google/gwtjsonrpc/server/CallDeserializer.java
index 34430ff..85f429b 100644
--- a/src/main/java/com/google/gwtjsonrpc/server/CallDeserializer.java
+++ b/src/main/java/com/google/gwtjsonrpc/server/CallDeserializer.java
@@ -26,7 +26,6 @@ import java.lang.reflect.Type;
final class CallDeserializer<CallType extends ActiveCall> implements
JsonDeserializer<CallType>, InstanceCreator<CallType> {
- private static final String RPC_VERSION = JsonServlet.RPC_VERSION;
private final CallType req;
private final JsonServlet<? extends ActiveCall> server;
@@ -49,9 +48,27 @@ final class CallDeserializer<CallType extends ActiveCall> implements
final JsonObject in = json.getAsJsonObject();
req.id = in.get("id");
+ final JsonElement jsonrpc = in.get("jsonrpc");
final JsonElement version = in.get("version");
- if (!isString(version) || !RPC_VERSION.equals(version.getAsString())) {
- throw new JsonParseException("Expected version = " + RPC_VERSION);
+ if (isString(jsonrpc) && version == null) {
+ final String v = jsonrpc.getAsString();
+ if ("2.0".equals(v)) {
+ req.versionName = "jsonrpc";
+ req.versionValue = jsonrpc;
+ } else {
+ throw new JsonParseException("Expected jsonrpc=2.0");
+ }
+
+ } else if (isString(version) && jsonrpc == null) {
+ final String v = version.getAsString();
+ if ("1.1".equals(v)) {
+ req.versionName = "version";
+ req.versionValue = version;
+ } else {
+ throw new JsonParseException("Expected version=1.1");
+ }
+ } else {
+ throw new JsonParseException("Expected version=1.1 or jsonrpc=2.0");
}
final JsonElement method = in.get("method");
diff --git a/src/main/java/com/google/gwtjsonrpc/server/JsonServlet.java b/src/main/java/com/google/gwtjsonrpc/server/JsonServlet.java
index d679e27..9ff4d75 100644
--- a/src/main/java/com/google/gwtjsonrpc/server/JsonServlet.java
+++ b/src/main/java/com/google/gwtjsonrpc/server/JsonServlet.java
@@ -24,6 +24,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -32,6 +33,8 @@ import com.google.gwtjsonrpc.client.CookieAccess;
import com.google.gwtjsonrpc.client.JsonUtil;
import com.google.gwtjsonrpc.client.RemoteJsonService;
+import org.apache.commons.codec.binary.Base64;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
@@ -67,10 +70,14 @@ import javax.servlet.http.HttpServletResponse;
* any interface(s) that extend from {@link RemoteJsonService}. Clients may
* invoke methods declared in any implemented interface.
* <p>
+ * <b>JSON-RPC 1.1</b><br>
* Calling conventions match the JSON-RPC 1.1 working draft from 7 August 2006
* (<a href="http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html">draft</a>).
* Only positional parameters are supported.
* <p>
+ * <b>JSON-RPC 2.0</b><br>
+ * Calling conventions match the JSON-RPC 2.0 specification.
+ * <p>
* When supported by the browser/client, the "gzip" encoding is used to compress
* the resulting JSON, reducing transfer time for the response data.
*/
@@ -110,7 +117,6 @@ public abstract class JsonServlet<CallType extends ActiveCall> extends
}
static final Object[] NO_PARAMS = {};
- static final String RPC_VERSION = "1.1";
private static final String ENC = "UTF-8";
private Map<String, MethodHandle> myMethods;
@@ -371,36 +377,64 @@ public abstract class JsonServlet<CallType extends ActiveCall> extends
}
}
- private void parseGetRequest(final ActiveCall call) {
- final Gson gs = createGsonBuilder().create();
+ private void parseGetRequest(final CallType call) {
final HttpServletRequest req = call.httpRequest;
- call.method = lookupMethod(req.getParameter("method"));
- if (call.method == null) {
- throw new NoSuchRemoteMethodException();
- }
-
- final Type[] paramTypes = call.method.getParamTypes();
- final Object[] r = new Object[paramTypes.length];
- for (int i = 0; i < r.length; i++) {
- final String v = req.getParameter("param" + i);
- if (v == null) {
- r[i] = null;
- } else if (paramTypes[i] == String.class) {
- r[i] = v;
- } else if (paramTypes[i] instanceof Class
- && ((Class<?>) paramTypes[i]).isPrimitive()) {
- // Primitive type, use the JSON representation of that type.
- //
- r[i] = gs.fromJson(v, paramTypes[i]);
- } else {
- // Assume it is like a java.sql.Timestamp or something and treat
- // the value as JSON string.
- //
- r[i] = gs.fromJson(gs.toJson(v), paramTypes[i]);
+
+ if ("2.0".equals(req.getParameter("jsonrpc"))) {
+ final JsonObject d = new JsonObject();
+ d.addProperty("jsonrpc", "2.0");
+ d.addProperty("method", req.getParameter("method"));
+ d.addProperty("id", req.getParameter("id"));
+ try {
+ final byte[] params = req.getParameter("params").getBytes("ISO-8859-1");
+ final String p = new String(Base64.decodeBase64(params), "UTF-8");
+ d.add("params", new JsonParser().parse(p));
+ } catch (UnsupportedEncodingException e) {
+ throw new JsonParseException("Cannot parse params", e);
+ }
+
+ try {
+ final GsonBuilder gb = createGsonBuilder();
+ gb.registerTypeAdapter(ActiveCall.class, //
+ new CallDeserializer<CallType>(call, this));
+ gb.create().fromJson(d, ActiveCall.class);
+ } catch (JsonParseException err) {
+ call.method = null;
+ call.params = null;
+ throw err;
+ }
+
+ } else { /* JSON-RPC 1.1 */
+ final Gson gs = createGsonBuilder().create();
+
+ call.method = lookupMethod(req.getParameter("method"));
+ if (call.method == null) {
+ throw new NoSuchRemoteMethodException();
}
+ final Type[] paramTypes = call.method.getParamTypes();
+ final Object[] r = new Object[paramTypes.length];
+
+ for (int i = 0; i < r.length; i++) {
+ final String v = req.getParameter("param" + i);
+ if (v == null) {
+ r[i] = null;
+ } else if (paramTypes[i] == String.class) {
+ r[i] = v;
+ } else if (paramTypes[i] instanceof Class
+ && ((Class<?>) paramTypes[i]).isPrimitive()) {
+ // Primitive type, use the JSON representation of that type.
+ //
+ r[i] = gs.fromJson(v, paramTypes[i]);
+ } else {
+ // Assume it is like a java.sql.Timestamp or something and treat
+ // the value as JSON string.
+ //
+ r[i] = gs.fromJson(gs.toJson(v), paramTypes[i]);
+ }
+ }
+ call.params = r;
+ call.callback = req.getParameter("callback");
}
- call.params = r;
- call.callback = req.getParameter("callback");
}
private static boolean isBodyJson(final ActiveCall call) {
@@ -499,7 +533,7 @@ public abstract class JsonServlet<CallType extends ActiveCall> extends
}
final JsonObject r = new JsonObject();
- r.addProperty("version", RPC_VERSION);
+ r.add(src.versionName, src.versionValue);
if (src.id != null) {
r.add("id", src.id);
}
@@ -508,9 +542,16 @@ public abstract class JsonServlet<CallType extends ActiveCall> extends
}
if (src.externalFailure != null) {
final JsonObject error = new JsonObject();
- error.addProperty("name", "JSONRPCError");
- error.addProperty("code", 999);
- error.addProperty("message", src.externalFailure.getMessage());
+ if ("jsonrpc".equals(src.versionName)) {
+ final int code = to2_0ErrorCode(src);
+
+ error.addProperty("code", code);
+ error.addProperty("message", src.externalFailure.getMessage());
+ } else {
+ error.addProperty("name", "JSONRPCError");
+ error.addProperty("code", 999);
+ error.addProperty("message", src.externalFailure.getMessage());
+ }
r.add("error", error);
} else {
r.add("result", context.serialize(src.result));
@@ -532,6 +573,21 @@ public abstract class JsonServlet<CallType extends ActiveCall> extends
return o.toString();
}
+ private int to2_0ErrorCode(final ActiveCall src) {
+ final Throwable e = src.externalFailure;
+ final Throwable i = src.internalFailure;
+
+ if (e instanceof NoSuchRemoteMethodException
+ || i instanceof NoSuchRemoteMethodException) {
+ return -32601 /* Method not found. */;
+ }
+ if (e instanceof JsonParseException || i instanceof JsonParseException) {
+ return -32700 /* Parse error. */;
+ }
+
+ return -32603 /* Internal error. */;
+ }
+
private static void textError(final ActiveCall call, final int status,
final String message) throws IOException {
final HttpServletResponse r = call.httpResponse;