我想知道是否有一种方法可以从"字符串表"中获取序列化字符串。
我需要的是在网页表单中发布一些数据。这是我手动执行时发送的内容:
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 |澳大利亚| AFGHANISTAN | nombre exportador | gov.senasa.embalajemadera.shared.domain.TransportePatente / 1923027028 | acoplado |底盘| 1 | 2 | 3 | 4 | 1 | 5 | 6 | 7 | 0 | 0 | 8 | 1 | 9 | 0 | 10 | 1 | 10 | 10 | 11 | 10 | 12 | 0 | 0 | 11 | 10 | 126951 | 0 | 0 | 0 | 0 | 0 | 13 | 1 | 14 | 0 | 0 | 15 | 16 | 1 | 17 | 0 | 18 | 10 | 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 | 10 | 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 | 10 | 38 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -20 | 39 | 40 | 41 | 0 | 0 | 0 | 42 | 0 | 43 | 10 | 10 | 42 | 10 | 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兼容的方法,或者只是一个很好的解释,所以我可以自己编写例程。感谢
答案 0 :(得分:5)
编辑:我回答了反序列化(如果要对服务器中的结果执行某些操作,您可能仍需要它),但是您要求进行序列化。第一部分仍然是对请求进行反序列化,但在休息之后,我将展示如何序列化请求,再次使用我们在此问题中提供的有限信息
它可能比文档中的示例更大,但适用相同的规则。对于像这样的请求,我们将其细分如下,拆分|
字符。
7
第7版。
0
没有设置标志。
48
接下来的48个令牌是字符串表,用它们构建一个数组。这些字符串将表示传递的数据类型,以及实际的Java字符串。
因此我们将字符串读入String [48],然后所有剩余的数字都是引用或原语。像ZRCrAA
和VnTkM$A
这样的数据可能是base64编码的长整数。将此视为字符串列表,当我们从数据开始请求对象时,我们将使用它。
正如文档所说,我们现在从第二个列表中读取引用(以1|2|3|4|1|5|6|7|0|0|8...
开头)。随着我们继续反序列化,我们还需要四件事:app的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];
}
现在,我们的第一个数据是基本网址,读作字符串。我们看到1
作为我们读取的int,然后我们在上面创建的String [48]中得到第(1 - 1)个字符串:
强大的政策名称是2
,我们将其视为
03152A2DEBABDCE5D33BF4C88511DD1E
服务器将使用它并找到名为03152A2DEBABDCE5D33BF4C88511DD1E.gwt.rpc的文件,概述可以在服务器上创建的内容的安全策略(以便禁止黑客在您的服务器上创建任何类型的对象)。
接下来,我们查找服务类3
:
net.customware.gwt.dispatch.client.standard.StandardDispatchService
最后调用方法4
:
执行
从这里开始,你需要知道StandardDispatchService.execute
- 我们知道它只需要一个参数,可能是一个Action
实例,因为我们看到1
表示一个参数,然后是{{ 1}},如果解码为Object意味着我们读取第5个字符串(看看readObject如何工作以查明原因)。如果不知道哪些字段5
或其他类别(下面列出),我们就无法确定接下来会发生什么:
(请注意,我只是猜测这些是可序列化的类及其名称 - 它们可能只是普通的字符串,有人决定通过电线发送它们作为其请求的一部分!)
我们已经涵盖了许多尝试拆分此消息的基础知识,所以让我们尝试将它放在一起。管理它的GWT客户端代码中的类是Action
。方法com.google.gwt.user.client.rpc.impl.ClientSerializationStreamWriter
和prepareToWrite()
有助于设定阶段,展示我们围绕工作做的第一件也是最后一件事:
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();
}
方法从流开始,添加两个字符串 - 模块基本URL和策略强名称,您将从反序列化过程中识别的字符串。 prepareToWrite()
方法显示了我们将要写出的三个阶段:标题,字符串表和&#34;有效负载&#34;,或对象引用和原始值。
为什么我们将字符串与其他叶子值进行不同的跟踪?这样我们就可以在一个地方拥有所有字符串,这样我们就可以多次引用它们,并且每次只发送它们。将此与XML或JSON进行对比,每次要使用值时,必须再次写入值,即使它完全相同。
标题包含版本(最新版本为7),要设置的标志(在您的示例中,只需0)。
在超类toString()
中,有四个字段:
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
字段执行相同的操作,但对于字符串 - JS特别处理字符串键。最后,stringMap
本身,我们看到的所有字符串的列表,每个只添加一次。
如果您将已编译的应用程序生成的Java用于stringTable
等服务方法,您将看到如下内容:
List<String> filterStrings(List<String> strings, String startsWith)
确切知道每个方法的编写内容取决于知道Java中的方法签名是什么 - 只需编译GWT JS和有效负载样本,逆向工程就有些困难了。但让我们继续看看接下来会发生什么。
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);
的实现接受对象并首先记录其类型。如果对象为null,那么我们只写一个空字符串(a.k.a。writeObject
)并完成。否则我们检查我们之前是否已经写过这个对象(所以写一个负数来看看有效载荷的去向),或者我们需要查找如何编写该对象的其余部分,并序列化每个字段。
每个可以序列化的对象都必须有一个FieldSerializer,它描述了如何编码和解码该对象。 GWT中有许多CustomFieldSerializers,用于特定目的的自定义实现,它告诉RPC不自动生成序列化程序。一个例子可能是ArrayList,如果我们已经将0
委托传递给ArrayList_CustomFieldSerializer
,这样做:
Collection_CustomFieldSerializerBase
首先我们编写列表的大小,以便反序列化器知道要读取多少元素,然后我们在列表中编写每个项目。在我们的例子中,我们将这些全部写为字符串。然后,我们将编写一个 more 字符串,该方法的第二个参数。
因此,我们在String表中有这些数据:
public static void serialize(SerializationStreamWriter streamWriter,
Collection instance) throws SerializationException {
int size = instance.size();
streamWriter.writeInt(size);
for (Object obj : instance) {
streamWriter.writeObject(obj);
}
}
中的每个字符串和字符串strings
,但由于我们不知道是否存在重复字符,因此我们无法知道是否存在重复字符串相同数量的字符串。在我们的有效负载中,然后我们称之为startsWith
,我们将引用1(baseurl字符串索引),2(策略强名称字符串索引),3(服务名称字符串索引),4(方法名称字符串索引),2(int表示期望的字段数),5(filterStrings(["a", "ab", "abc", "a"], "ab")
列表参数的类名称),4(列表中的项目数),6(第一项的类型)列表,字符串),7(&#34; a&#34;的内容),6(字符串类型),8(&#34; ab&#34;的内容),6(字符串类型),9(内容&#34; abc&#34;),6(字符串类型),7(&#34; a&#34;的内容),6(第二个参数的字符串类型),最后8个(&#34; ab&的内容) #34;再次)。
如何查找Action,DeclaracionJurada等类?如果不知道哪些字段以及它们发生了什么顺序,我们无法肯定地说出来。从有效载荷中重建内容没有好办法,但是如果你可以在发送有效载荷之前调试正在运行的应用程序,你可以观察要序列化的对象的结构,并用它来决定你做什么&#39 ;在溪流中找到了。我观察到样本流中有几个负数,表明负值对于用例很重要,或者有后引用,这不是简单的对象树,而是一个完整的图形,它将使事情稍微复杂一点。
RPC序列化格式并不复杂 - 我强烈建议您阅读各种strings
子类中的代码以了解它的作用。从那里,您应该能够将这两个值列表(字符串和引用/基元)解析为实际对象,以及可能通过线路发送的所有类的结构,并在任何语言或框架中重新实现它
答案 1 :(得分:0)
如果您无法更改服务层以导出更传统的REST端点,我可能会使用类似gwt-syncproxy之类的java桥接器,并导出可在php中使用的接口easyli。