【问题标题】:Serializing RPC-GWT序列化 RPC-GWT
【发布时间】:2016-01-27 20:11:44
【问题描述】:

我想知道是否有一种方法可以从“字符串表”中获取序列化字符串。

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

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 竞争者 1|java.lang.Boolean/476441737|gov.senasa.embalajemadera.shared.domain.Despachante/3149599025|DESP||java.lang.Long/4227064769|Treyes 8978 - 首都联邦|gonzalo@rsystem.com.ar|Spina Gonzalo|45510141|direction destino|direction 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|acopla |机箱|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 兼容的方法,或者只是一个很好的解释,这样我就可以制定自己的例程了。谢谢

【问题讨论】:

    标签: java php json gwt vb6


    【解决方案1】:

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


    请求反序列化

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

    7

    第 7 版。

    0

    没有设置标志。

    48

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

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

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

    由于这些都是字符串对象,我们将读取字符串。在 com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader 中,实际的 Java 类会读取这个,我们看到 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 的文件,其中概述了可以在服务器上创建的内容的安全策略(以禁止黑客在您的服务器上创建任何类型的对象)。

    接下来我们寻找服务类,3

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

    最后是调用的方法,4

    执行

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

    • 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

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


    请求序列化

    我们已经介绍了许多试图分解此消息的基础知识,因此让我们尝试将其组合在一起。 GWT 客户端代码中管理它的类是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() 方法显示了我们将写出的三个阶段:标头、字符串表和“有效负载”,或对象引用和原始值。

    为什么我们跟踪的字符串与其他叶值不同?这样,我们将所有字符串放在一个位置,这样我们就可以多次引用它们,并且每次只发送一次。与 XML 或 JSON 相比,每次您想使用一个值时,您都必须重新写入该值,即使它完全相同。

    标头包含版本(最新为 7)和要设置的标志(在您的示例中,只有 0 就可以了)。

    在超类AbstractSerializationStreamWriter中,有四个字段:

    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>();
    

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

    如果您为 List&lt;String&gt; filterStrings(List&lt;String&gt; strings, String startsWith) 之类的服务方法拆分已编译应用程序生成的 Java,您将看到如下内容:

    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 的实现获取对象并首先记录其类型。如果对象为空,那么我们只需要写一个空字符串(a.k.a.0)就可以了。否则,我们检查我们之前是否已经写过这个对象(因此写一个负数来查看有效载荷中的位置),或者我们需要查找如何编写该对象的其余部分,并序列化每个字段。

    每个可以序列化的对象都必须有一个 FieldSerializer,它描述了如何对该对象进行编码和解码。 GWT 中有很多 CustomFieldSerializer,用于特定目的的自定义实现,它们告诉 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 字符串,作为方法的第二个参数。

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

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

    在我们的有效负载中,假设我们调用了filterStrings(["a", "ab", "abc", "a"], "ab"),我们将有引用 1(baseurl 字符串索引)、2(策略强名称字符串索引)、3(服务名称字符串索引)、4(方法名称字符串索引), 2 (int 表示期望的字段数), 5 (strings 列表参数的类名), 4 (列表中的项目数), 6 (列表中第一项的类型, 字符串), 7(“a”的内容)、6(字符串类型)、8(“ab”的内容)、6(字符串类型)、9(“abc”的内容)、6(字符串类型)、7(“的内容” a")、6(第二个参数的字符串类型),最后是 8(又是 "ab" 的内容)。

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


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

    【讨论】:

    • 首先,谢谢,但我不需要解析数据,我需要从字符串表构建有效负载。我意识到没有什么可以做我需要做的事情,至少,不是我正在做的事情。解释真的很好。我会看到自己构建有效载荷的方法@colinalworth 再次感谢
    • 哦,对不起 - 我以为你是在解码有效载荷,而不是对它们进行编码。我会花一点时间为你写下相反的答案,但它主要归结为查看 AbstractSerializationStreamWriter 并重新实现它,然后以正确的顺序发出 header、stringtable、payload。应该在第二天左右完成,会再次评论让您知道。
    • 问题已更新,抱歉耽搁了。如果您有其他问题,请在此处或gitter.im/gwtproject/gwt 或 freenode.net 上的##gwt 中告诉我。
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2012-04-29
      • 2012-04-18
      • 2012-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-01
      相关资源
      最近更新 更多