发送 XML 请求后捕获 EPP 服务器响应

Catch EPP server response after sending XML request

目前我们正在开发域名注册商API。

    $options = [
        'ssl' => [
            'verify_peer' => true,
            'local_cert' => __DIR__ . '/Domain.pem',
            'local_pk' => __DIR__ . '/Domain.pem',
            'allow_self_signed' => true,
        ]
    ];
    $context = stream_context_create($options);
    $ch = stream_socket_client($serverPath.':'.$parentClass->port, $errorNumber, $errorString, 60, STREAM_CLIENT_CONNECT, $context);
    stream_set_timeout($ch, 60);

    fwrite($ch, $command);

    $data = '';

    while (!feof($ch)) {
        $data .= fread($ch, 1024);
    }

    fclose($ch);

写XML请求后

<epp xsi:schemaLocation="urn:ietf:params:xml:ns:epp-1.0 epp-1.0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<login>
<clID>User</clID>
<pw>Password</pw>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>urn:ietf:params:xml:ns:obj1</objURI>
<objURI>urn:ietf:params:xml:ns:obj2</objURI>
<objURI>urn:ietf:params:xml:ns:obj3</objURI>
<svcExtension>
<extURI>http://custom/obj1ext-1.0</extURI>
</svcExtension>
</svcs>
</login>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

fread 给出了问候消息,而不是有关登录请求的状态代码。 这种读取响应的方法是否正确?没有从服务器获得正确答案的原因是什么?谢谢

EPP 并不像您想象的那样工作。作为开始,我建议您花一些时间详细阅读 RFC5730 和 5734,它们将在下面讨论。完成这项工作后,您将需要全面掌握 RFC 5731、5732 和 5733 中的所有 EPP 详细信息。不要指望能够在未阅读所有内容的情况下编写成功的 EPP 客户端。

现在回到您的问题,以及您不遵守 EPP 规范的原因。

参见 RFC5730 的第 2 节,转载于此:

          |
          V
  +-----------------+                  +-----------------+
  |   Waiting for   |     Connected    |     Prepare     |
  |      Client     |----------------->|     Greeting    |
  +-----------------+    or <hello>    +-----------------+
     ^                                           |
     | Close Connection                     Send |
     |     or Idle                      Greeting |
  +-----------------+                            V
  |       End       |     Timeout      +-----------------+
  |     Session     |<-----------------|   Waiting for   |
  +-----------------+                  |      Client     |
     ^    ^    ^        Send +-------->|  Authentication |
     |    |    |    Response |         +-----------------+
     |    |    |     +--------------+            |
     |    |    |     | Prepare Fail |            | <login>
     |    |    +-----|   Response   |            | Received
     |    |    Send  +--------------+            V
     |    |    2501          ^         +-----------------+
     |    |   Response       |         |   Processing    |
     |    |                  +---------|     <login>     |
     |    |                  Auth Fail +-----------------+
     |    |       Timeout                         |
     |    +-------------------------------+       | Auth OK
     |                                    |       V
     |   +-----------------+  <hello>  +-----------------+
     |   |     Prepare     |<----------|   Waiting for   |
     |   |     Greeting    |---------->|   Command or    |
     |   +-----------------+   Send    |     <hello>     |
     | Send x5xx             Greeting  +-----------------+
     | Response  +-----------------+  Send    ^  |
     +-----------|     Prepare     | Response |  | Command
                 |     Response    |----------+  | Received
                 +-----------------+             V
                            ^          +-----------------+
                    Command |          |   Processing    |
                  Processed +----------|     Command     |
                                       +-----------------+

换句话说,您的客户应该如何表现:

  • 建立TLS连接(一定要验证远程证书)
  • 服务器首先说话,带有问候消息(请参阅同一 RFC 的第 2.4 节)
  • 您需要阅读此消息,并从中提取各个部分,特别是 objURI 和 extURI 部分
  • 现在客户端发送其登录消息(第 2.9.1.1 节)
  • 与其他命令一样,此命令将使服务器回复一个结果,告诉您它是成功(代码 1000)还是失败(任何以 2 开头的代码)

你程序中的错误是你的客户先说话,向服务器发送一些东西。根据上述状态模式,这是不允许的,因此请先阅读服务器回复。

所以你的问题不在于发送XML(虽然你也有这方面的问题,见下文),首先它不尊重谁先发言的顺序。

也不要这样做:

    while (!feof($ch)) {
        $data .= fread($ch, 1024);
    }

请参阅解释 EPP 传输方式的 RFC 5734。总结并回复上面关于你的问题的一些评论:

  • EPP 使用 TLS 作为传输方式,没有 HTTPS
  • 700 是标准的 IANA 分配端口,除非注册管理机构另有说明
  • 每个 XML 消息(在 UTF-8 序列化等之后)在线路上以 4 个字节为前缀,表示完整 EPP 帧的长度(帧 = 4 个字节 header长度 + 完整消息)。

因此,当您希望收到来自服务器的消息时,您可以这样做:

  • 你读取前 4 个字节(这甚至可能不是那么简单,你可能需要一个一个地读取它们并整理它们,这在很大程度上取决于网络使用的工具 I/O )
  • 现在你确切地知道你期望下一个到达的大小,它是上面 4 个字节的长度(正确解码)减去 4 以从全长中删除 4 个字节本身。

但请注意,您向服务器发送内容时也需要这样做! 不要只发送 XML 内容,您需要形成一个适当的 EPP 帧,这意味着:

  • 计算消息的大小(以字节为单位,而不是以字符为单位,因此在 UTF-8 序列化等之后)
  • 准备 4 个字节来存储该大小作为网络字节顺序
  • 立即发送这 4 个字节,然后是 XML 消息。

此外,作为业内的 "veteran"(从业 20 年,编写了 EPP 服务器和客户端,并参与了定义它的 RFC),请根据经验采纳此建议:如果您的工作只是连接到一个注册表,那么生活很简单;您甚至可以使用注册表提供的工具包,只要它是您选择的语言。

但是,一旦需要编写一个能够正确连接到多个注册中心的客户端,就会很痛苦。即使它是一个标准,您也会发现注册管理机构之间在许多主题上存在很多差异,例如如何报告扩展错误数据、存在哪些 EPP 扩展及其工作方式、domain:check 的响应内容等等对了,细小差异的清单在这里一一列举。

出于以上所有原因,您可能也不想重新发明轮子。 有些图书馆可以为您完成所有 EPP 工作,例如 https://github.com/centralnic/php-epp 我不以任何方式认可它,因为我不知道它,因为我不使用 PHP,但也许它可以帮助你,要么按原样重用,这样你就没有代码可写,或者在至少看看他们是如何解决具体问题的,这样你才能受到启发。

例如上面关于长度和每个EPP帧开头的4个字节的问题,参见https://github.com/centralnic/php-epp/blob/master/Net/EPP/Protocol.php

中的getFramesendFrame