diff options
author | Shawn O. Pearce <sop@google.com> | 2009-12-12 19:22:51 -0800 |
---|---|---|
committer | Shawn O. Pearce <sop@google.com> | 2009-12-12 19:30:25 -0800 |
commit | d89d7fe6901c2b5c4d7926c6876aace1460116bd (patch) | |
tree | 42ba611eb88cd92808ea68036d8e5d236630ed3e | |
parent | d223e91ec36b709d10ef9870c32b3ee41efbbbbc (diff) | |
download | gwtjsonrpc-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>
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; |