序列化 RPC-GWT

Serializing RPC-GWT

我想知道是否有从 "string table".

中获取序列化字符串的方法

我需要的是 POST 网页表单中的一些数据。这是我手动执行时发送的内容:

7|0|48|https://aps2.senasa.gov.ar/embalaje-madera-web/embalajeApp/|03152A2DEBABDCE5D33BF4C88511DD1E|net.customware.gwt.dispatch.client.standard.StandardDispatchService|execute|net.customware.gwt.dispatch.shared.Action|gov.senasa.embalajemadera.shared.rpc.actions.IngresarDeclaracionJuradaAction/2514804035|gov.senasa.embalajemadera.shared.domain.DeclaracionJurada/1628723960|java.util.ArrayList/4159755760|gov.senasa.embalajemadera.shared.domain.TipoEmbalajeCantidad/4152068152|java.lang.Integer/3438268394|Pallet|gov.senasa.embalajemadera.shared.domain.TipoEmbalaje/309031988|java.util.HashSet/3273092938|gov.senasa.embalajemadera.shared.domain.Contenedor/1178264080|nro contenedor 1|java.lang.Boolean/476441737|gov.senasa.embalajemadera.shared.domain.Despachante/3149599025|DESP||java.lang.Long/4227064769|Treyes 8978 - CAPITAL FEDERAL|gonzalo@rsystem.com.ar|Spina Gonzalo|45510141|direccion destino|direccion exportador|java.util.Date/3385151746|chasis/|gov.senasa.embalajemadera.shared.domain.ImportadorExportador/918958990|gonzalo@gmail.com|46326066|gov.senasa.embalajemadera.shared.domain.DatoAduana/2671264783|NRODESPACHO|IC01|gov.senasa.embalajemadera.shared.domain.LugarDeArribo/3008903128|NROMANIIMPO|gov.senasa.embalajemadera.shared.domain.PuntoIngreso/1183502123|717.3|aduana origen|Terrestre camion|merca|gov.senasa.embalajemadera.shared.domain.Pais/3238585366|AUSTRALIA|AFGHANISTAN|nombre exportador|gov.senasa.embalajemadera.shared.domain.TransportePatente/1923027028|acoplado|chasis|1|2|3|4|1|5|6|7|0|0|8|1|9|0|10|1|10|0|11|0|12|0|0|11|10|126951|0|0|0|0|0|13|1|14|0|0|15|16|1|17|0|18|0|19|20|ZRCrAA|21|22|19|0|0|23|24|0|0|0|0|0|25|26|0|0|27|VnTkM$A|0|0|0|0|0|0|0|28|-11|29|0|0|19|-13|21|30|0|0|0|23|31|0|0|0|0|0|0|8|1|32|0|16|0|-18|0|0|-2|0|0|33|0|0|0|27|VnTkM$A|0|34|35|0|0|0|10|24754701|0|-18|36|19|37|0|38|0|0|0|0|0|0|0|-20|39|40|41|0|0|0|42|0|43|10|10|42|0|44|10|1|0|-22|-5|45|0|0|0|-18|8|1|46|0|0|0|19|47|48|0|0|0|0|0|0|0|0|0|

我已经阅读了 this 但如您所见,这比文档中的示例稍微复杂一些。我无法构建有效载荷。我需要一个与 VB6 或 PHP 兼容的方法,或者只是一个很好的解释,以便我可以制作自己的例程。谢谢

编辑:我回答了反序列化(如果你要对来自服务器的结果做一些事情,你可能仍然需要反序列化),但你要求序列化。第一部分仍然是请求的反序列化,但在中断下面,我将展示如何序列化请求,再次使用与我们在这个问题中一样有限的信息


请求反序列化

它可能比文档中的示例更大,但适用相同的规则。对于像这样的 request,我们将其分解如下,在 | 字符上拆分。

7

版本 7。

0

没有设置标志。

48

接下来的 48 个标记是字符串 table,用它们构建一个数组。这些字符串将代表正在传递的数据类型,以及实际的 Java 字符串。

所以我们将字符串读入一个String[48],然后剩下的所有数字要么是引用,要么是原语。 ZRCrAAVnTkM$A 之类的数据可能是 base64 编码的 long。也将其视为字符串列表,我们将在开始从数据请求 object 时使用它。

如文档所述,我们现在从第二个列表中读取参考文献(以 1|2|3|4|1|5|6|7|0|0|8... 开头)。随着我们继续反序列化,我们还需要四个东西:应用程序的 url、策略的强名称、我们将要调用的服务 class 以及该服务中的方法名称 class.

因为这些都是字符串 object,我们将读取字符串。在 com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader 中,实际的 Java class 会读取这个,我们看到 readString() 看起来像这样:

@Override
public String readString() throws SerializationException {
  return getString(readInt());
}

所以我们首先读取一个 int,然后用它来查找一个字符串。这是 getString:

@Override
protected String getString(int index) {
  if (index == 0) {
    return null;
  }
  // index is 1-based
  assert (index > 0);
  assert (index <= stringTable.length);
  return stringTable[index - 1];
}

现在,我们的第一条数据是基础 url,读取为字符串。我们看到 1 是我们读取的 int,然后我们在上面创建的 String[48] 中得到第 (1 - 1) 个字符串:

https://aps2.senasa.gov.ar/embalaje-madera-web/embalajeApp/

接下来是强策略名称,2,我们读作

03152A2DEBABDCE5D33BF4C88511DD1E

服务器将使用它并找到一个名为 03152A2DEBABDCE5D33BF4C88511DD1E.gwt.rpc 的文件,概述可以在服务器上创建的内容的安全策略(以禁止黑客创建任何类型的 objects 在你的服务器上)。

接下来我们寻找服务 class, 3:

net.customware.gwt.dispatch.client.standard.StandardDispatchService

最后是要调用的方法,4:

execute

从这里,你需要知道什么是 StandardDispatchService.execute - 我们知道它只需要一个参数,可能是一个 Action 实例,因为我们看到 1 表示一个参数,然后5,如果解码为 Object,则意味着我们读取第 5 个字符串(查看 readObject 的工作原理以了解原因)。在不知道 Action 或其他 classes 有哪些字段(如下所列)的情况下,我们无法真正准确地猜测接下来会发生什么:

  • net.customware.gwt.dispatch.shared.Action
  • gov.senasa.embalajemadera.shared.rpc.actions.IngresarDeclaracionJuradaAction
  • gov.senasa.embalajemadera.shared.domain.DeclaracionJurada
  • gov.senasa.embalajemadera.shared.domain.TipoEmbalajeCantidad
  • gov.senasa.embalajemadera.shared.domain.TipoEmbalaje
  • gov.senasa.embalajemadera.shared.domain.Contenedor
  • gov.senasa.embalajemadera.shared.domain.Despachante

(请注意,我只是猜测这些可以通过它们的包和名称序列化 classes - 它们可能只是一些普通字符串,有人决定作为他们请求的一部分通过网络发送! )


请求序列化

我们已经介绍了很多试图分解这条消息的基础知识,所以让我们试着把它放在一起。管理它的 GWT 客户端代码中的 class 是 com.google.gwt.user.client.rpc.impl.ClientSerializationStreamWriterprepareToWrite()toString() 方法有助于设置一些舞台,显示我们将围绕我们的工作做的第一件事和最后一件事:

/**
 * Call this method before attempting to append any tokens. This method
 * implementation <b>must</b> be called by any overridden version.
 */
@Override
public void prepareToWrite() {
  super.prepareToWrite();
  encodeBuffer = new StringBuilder();

  // Write serialization policy info
  writeString(moduleBaseURL);
  writeString(serializationPolicyStrongName);
}

@Override
public String toString() {
  StringBuilder buffer = new StringBuilder();
  writeHeader(buffer);
  writeStringTable(buffer);
  writePayload(buffer);
  return buffer.toString();
}

prepareToWrite() 方法从添加两个字符串开始流 - 模块基础 url 和策略强名称,您将从反序列化过程中识别的字符串。 toString() 方法显示了我们将写出的三个阶段:header、字符串 table 和“有效载荷”,或 object 引用和原始值。

为什么我们以不同于其他叶值的方式跟踪字符串?通过这种方式,我们将所有字符串都放在一个地方,这样我们就可以多次引用它们,并且每次只发送一次。将此与 XML 或 JSON 进行对比,每次要使用一个值时,即使完全相同,也必须重新写入该值。

header 由版本(最新版本为 7)和要设置的标志(在您的示例中,只需 0 即可)组成。

superclassAbstractSerializationStreamWriter里面有四个字段:

private int objectCount;
private Map<Object, Integer> objectMap = new IdentityHashMap<Object, Integer>();
private Map<String, Integer> stringMap = new HashMap<String, Integer>();
private List<String> stringTable = new ArrayList<String>();

第一个是每个 object 的当前索引 - 我们将使用它来跟踪我们之前看到的 objects。接下来 objectMap,这样我们就可以检查每个 object 并检查我们之前是否见过它,如果见过,在哪里,这样我们就可以写回对该位置的引用。 stringMap 字段做同样的事情,但对于字符串 - JS 特别对待字符串键。最后,stringTable 本身,我们看到的所有字符串的列表,每个只添加一次。

如果您将已编译的应用程序的生成 Java 拆分为服务方法,例如List<String> filterStrings(List<String> strings, String startsWith),你会看到这样的东西:

ClientSerializationStreamWriter streamWriter = ...;//create with serializer
streamWriter.prepareToWrite();
streamWriter.writeString("com.acme.project.shared.MyService");//service interface
streamWriter.writeString("filterStrings");//method name
streamWriter.writeInt(2);//number of arguments to be found in the stream
streamWriter.writeObject(strings);
streamWriter.writeString(startsWith);

要准确了解每种方法将要编写的内容,取决于了解 Java 中的方法签名是什么 - 使用仅编译的 GWT JS 和有效负载示例,逆向工程有些困难。但是让我们继续看看接下来会发生什么。

writeObject 的实现采用 object 并首先记录其类型。如果 object 为空,那么我们只需写入一个空字符串 (a.k.a. 0) 即可。否则我们检查我们之前是否已经写过这个object(因此写一个负数以查看有效负载中的去向),或者我们需要查找如何写剩下的object , 并序列化每个字段。

每个可以序列化的 object 必须有一个 FieldSerializer,它描述了如何编码和解码那个 object。 GWT 中有很多 CustomFieldSerializers,为特定目的自定义实现,告诉 RPC 不要自动生成序列化程序。一个例子可能是 ArrayList,如果我们将它传递给 ArrayList_CustomFieldSerializer 委托给 Collection_CustomFieldSerializerBase,它会这样做:

public static void serialize(SerializationStreamWriter streamWriter,
    Collection instance) throws SerializationException {
  int size = instance.size();
  streamWriter.writeInt(size);
  for (Object obj : instance) {
    streamWriter.writeObject(obj);
  }
}

首先我们写下列表的大小,这样反序列化器就知道要读取多少元素,然后我们写下列表中的每一项。在我们的例子中,我们将把这些都写成字符串。然后,我们将编写一个 more 字符串,该方法的第二个参数。

所以,我们的字符串中有这些数据 table:

  • baseUrl
  • 策略字符串名称
  • "com.acme.project.shared.MyService"
  • “filterStrings”
  • "java.util.ArrayList"
  • "java.lang.String"
  • 参数中的每个字符串 strings,以及字符串 startsWith,但由于我们不知道是否有重复,所以我们无法知道是否会相同字符串数。

在我们的有效载荷中,假设我们调用了 filterStrings(["a", "ab", "abc", "a"], "ab"),我们将有引用 1(baseurl 字符串索引)、2(策略强名称字符串索引)、3(服务名称字符串index), 4 (method name string index), 2 (int for number of fields to expect), 5 (class strings 列表参数的名称), 4 (列表中的项目数) , 6(列表第一项的类型,字符串), 7("a"的内容), 6(字符串类型), 8("ab"的内容), 6(字符串类型), 9("的内容abc")、6(字符串类型)、7("a"的内容)、6(第二个参数的字符串类型),最后是 8(又是"ab"的内容)。

这对于 class 像 Action、DeclaracionJurada 等会如何?在不知道有哪些字段以及它们发生的顺序的情况下,我们无法确定。没有什么好方法只从有效载荷来重建内容,但如果您可以在发送有效载荷之前调试 运行 应用程序,您可以观察要序列化的 object 的结构,并且用它来决定你在流中找到了什么。我观察到样本流中有几个负数,这表明负值对于用例很重要,或者存在反向引用,这不是简单的 object 树,而是完整的图表,这会使事情稍微复杂一些。


RPC 序列化格式并不复杂 - 我强烈建议阅读各种 com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamReader 子 classes 中的代码以了解它的作用。从那里,您应该能够将这两个值列表(字符串和 references/primitives)解析为实际的 objects,以及可能发送的所有 classes 的结构通过网络,并用任何语言或框架重新实现它。

如果您不能更改服务层以导出更传统的 REST 端点,我可能会使用 java 桥接器,使用 gwt-syncproxy 之类的东西,并导出可在 php。