HHVM 上的 SoapServer 抛出 "Bad Request" SoapFault

SoapServer on HHVM throws a "Bad Request" SoapFault

我正在 Hacklang 中创建一个 Soap Web 服务,在进行一些测试时,我注意到在某些情况下我会收到 SoapFault - Bad Request。为了找出问题所在,我在php中写了简化的class,并准备了适当的wsdl文件。经过一些测试后,我发现在纯非 hhvm php 中它运行良好,但是当我 运行 它在 hhvm 上时它无法按预期工作 - 对于某些请求它可以工作,而对于其他 (只是有点不同)它会抛出 SoapFault。

soap.php:

<?php

if (isset($_GET['wsdl'])) {
    header('Content-Type:application/xml');
    echo file_get_contents('soap.wsdl');
    exit(0);
}

class WebService {

    public function addContacts($addContactsRequest) {
        return new \SoapVar(
            array(
                'status'        => 0,
                'contactsCount' => 4,
            ),
            constant('SOAP_ENC_OBJECT'),
            'AddContactsResponse',
            'https://example.com/api/soap/'
        );
    }

}

$server = new \SoapServer('soap.wsdl');
$server->setClass('WebService');
$server->handle();

soap.wsdl:

<wsdl:definitions xmlns:tns="https://example.com/api/soap/" targetNamespace="https://example.com/api/soap/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
    <wsdl:types>
        <s:schema targetNamespace="https://example.com/api/soap/">
            <s:complexType name="AddContactsRequest">
                <s:sequence>
                    <s:element minOccurs="1" maxOccurs="1" nillable="false" name="contacts" type="tns:ContactArray">
                    </s:element>
                    <s:element minOccurs="1" maxOccurs="1" nillable="true" name="groups" type="tns:stringArray">
                    </s:element>
                    <s:element minOccurs="1" maxOccurs="1" nillable="true" name="createGroupsIfNotExist" type="s:boolean">
                    </s:element>
                </s:sequence>
            </s:complexType>
            <s:complexType name="ContactArray">
                <s:annotation>
                    <s:documentation>
                        Array of Contact
                    </s:documentation>
                </s:annotation>
                <s:complexContent>
                    <s:restriction base="soapenc:Array">
                        <s:attribute ref="soapenc:arrayType" wsdl:arrayType="tns:Contact[]">
                        </s:attribute>
                    </s:restriction>
                </s:complexContent>
            </s:complexType>
            <s:complexType name="AddContactsResponse">
                <s:sequence>
                    <s:element minOccurs="1" maxOccurs="1" nillable="false" name="status" type="s:integer">
                    </s:element>
                    <s:element minOccurs="1" maxOccurs="1" nillable="false" name="contactsCount" type="s:integer">
                    </s:element>
                </s:sequence>
            </s:complexType>
            <s:complexType name="Contact">
                <s:sequence>
                    <s:element minOccurs="1" maxOccurs="1" nillable="false" name="phone" type="s:string">
                    </s:element>
                    <s:element minOccurs="1" maxOccurs="1" nillable="true" name="name" type="s:string">
                    </s:element>
                    <s:element minOccurs="1" maxOccurs="1" nillable="true" name="firstName" type="s:string">
                    </s:element>
                    <s:element minOccurs="1" maxOccurs="1" nillable="true" name="lastName" type="s:string">
                    </s:element>
                    <s:element minOccurs="1" maxOccurs="1" nillable="true" name="customField1" type="s:string">
                    </s:element>
                    <s:element minOccurs="1" maxOccurs="1" nillable="true" name="customField2" type="s:string">
                    </s:element>
                    <s:element minOccurs="1" maxOccurs="1" nillable="true" name="customField3" type="s:string">
                    </s:element>
                    <s:element minOccurs="1" maxOccurs="1" nillable="true" name="customField4" type="s:string">
                    </s:element>
                    <s:element minOccurs="1" maxOccurs="1" nillable="true" name="customField5" type="s:string">
                    </s:element>
                    <s:element minOccurs="1" maxOccurs="1" nillable="true" name="customField6" type="s:string">
                    </s:element>
                </s:sequence>
            </s:complexType>
        </s:schema>
    </wsdl:types>
    <wsdl:message name="addContactsSoapIn">
        <wsdl:part name="request" type="tns:AddContactsRequest">
        </wsdl:part>
    </wsdl:message>
    <wsdl:message name="addContactsSoapOut">
        <wsdl:part name="return" type="tns:AddContactsResponse">
        </wsdl:part>
    </wsdl:message>
    <wsdl:portType name="WebServiceSoap">
        <wsdl:operation name="addContacts">
            <wsdl:input message="tns:addContactsSoapIn">
            </wsdl:input>
            <wsdl:output message="tns:addContactsSoapOut">
            </wsdl:output>
        </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="WebServiceSoap" type="tns:WebServiceSoap">
        <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc">
        </soap:binding>
        <wsdl:operation name="addContacts">
            <soap:operation soapAction="https://example.com/api/soap/addContacts">
            </soap:operation>
            <wsdl:input>
                <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="https://example.com/api/soap/" parts="request">
                </soap:body>
            </wsdl:input>
            <wsdl:output>
                <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="https://example.com/api/soap/" parts="return">
                </soap:body>
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="WebService">
        <wsdl:port name="WebServiceSoap" binding="tns:WebServiceSoap">
            <soap:address location="https://example.com/api/soap/soap.php">
            </soap:address>
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

请求 1(成功):

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://example.com/api/soap/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <SOAP-ENV:Body>
        <ns1:addContacts>
            <request xsi:type="ns1:AddContactsRequest">
                <contacts SOAP-ENC:arrayType="ns1:Contact[3]" xsi:type="ns1:ContactArray">
                    <item xsi:type="ns1:Contact">
                        <phone xsi:type="xsd:string">500555444</phone>
                        <name xsi:nil="true"/>
                        <firstName xsi:nil="true"/>
                        <lastName xsi:nil="true"/>
                        <customField1 xsi:nil="true"/>
                        <customField2 xsi:nil="true"/>
                        <customField3 xsi:nil="true"/>
                        <customField4 xsi:nil="true"/>
                        <customField5 xsi:nil="true"/>
                        <customField6 xsi:nil="true"/>
                    </item>
                    <item xsi:type="ns1:Contact">
                        <phone xsi:type="xsd:string">500555777</phone>
                        <name xsi:type="xsd:string">Test 1</name>
                        <firstName xsi:nil="true"/>
                        <lastName xsi:nil="true"/>
                        <customField1 xsi:nil="true"/>
                        <customField2 xsi:nil="true"/>
                        <customField3 xsi:nil="true"/>
                        <customField4 xsi:nil="true"/>
                        <customField5 xsi:nil="true"/>
                        <customField6 xsi:nil="true"/>
                    </item>
                    <item xsi:type="ns1:Contact">
                        <phone xsi:type="xsd:string">500123456</phone>
                        <name xsi:type="xsd:string">testName</name>
                        <firstName xsi:type="xsd:string">testFirstName</firstName>
                        <lastName xsi:type="xsd:string">testLastName</lastName>
                        <customField1 xsi:type="xsd:string">testCustomField1</customField1>
                        <customField2 xsi:type="xsd:string">testCustomField2</customField2>
                        <customField3 xsi:type="xsd:string">testCustomField3</customField3>
                        <customField4 xsi:type="xsd:string">testCustomField4</customField4>
                        <customField5 xsi:type="xsd:string">testCustomField5</customField5>
                        <customField6 xsi:type="xsd:string">testCustomField6</customField6>
                    </item>
                </contacts>
                <groups xsi:nil="true"/>
                <createGroupsIfNotExist xsi:nil="true"/>
            </request>
        </ns1:addContacts>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

请求 2(SoapFault 错误请求)

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://example.com/api/soap/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <SOAP-ENV:Body>
        <ns1:addContacts>
            <request xsi:type="ns1:AddContactsRequest">
                <contacts SOAP-ENC:arrayType="ns1:Contact[4]" xsi:type="ns1:ContactArray">
                    <item xsi:type="ns1:Contact">
                        <phone xsi:type="xsd:string">500555444</phone>
                        <name xsi:nil="true"/>
                        <firstName xsi:nil="true"/>
                        <lastName xsi:nil="true"/>
                        <customField1 xsi:nil="true"/>
                        <customField2 xsi:nil="true"/>
                        <customField3 xsi:nil="true"/>
                        <customField4 xsi:nil="true"/>
                        <customField5 xsi:nil="true"/>
                        <customField6 xsi:nil="true"/>
                    </item>
                    <item xsi:type="ns1:Contact">
                        <phone xsi:type="xsd:string">500555666</phone>
                        <name xsi:nil="true"/>
                        <firstName xsi:nil="true"/>
                        <lastName xsi:nil="true"/>
                        <customField1 xsi:nil="true"/>
                        <customField2 xsi:nil="true"/>
                        <customField3 xsi:nil="true"/>
                        <customField4 xsi:nil="true"/>
                        <customField5 xsi:nil="true"/>
                        <customField6 xsi:nil="true"/>
                    </item>
                    <item xsi:type="ns1:Contact">
                        <phone xsi:type="xsd:string">500555777</phone>
                        <name xsi:type="xsd:string">Test 1</name>
                        <firstName xsi:nil="true"/>
                        <lastName xsi:nil="true"/>
                        <customField1 xsi:nil="true"/>
                        <customField2 xsi:nil="true"/>
                        <customField3 xsi:nil="true"/>
                        <customField4 xsi:nil="true"/>
                        <customField5 xsi:nil="true"/>
                        <customField6 xsi:nil="true"/>
                    </item>
                    <item xsi:type="ns1:Contact">
                        <phone xsi:type="xsd:string">500123456</phone>
                        <name xsi:type="xsd:string">testName</name>
                        <firstName xsi:type="xsd:string">testFirstName</firstName>
                        <lastName xsi:type="xsd:string">testLastName</lastName>
                        <customField1 xsi:type="xsd:string">testCustomField1</customField1>
                        <customField2 xsi:type="xsd:string">testCustomField2</customField2>
                        <customField3 xsi:type="xsd:string">testCustomField3</customField3>
                        <customField4 xsi:type="xsd:string">testCustomField4</customField4>
                        <customField5 xsi:type="xsd:string">testCustomField5</customField5>
                        <customField6 xsi:type="xsd:string">testCustomField6</customField6>
                    </item>
                </contacts>
                <groups xsi:nil="true"/>
                <createGroupsIfNotExist xsi:nil="true"/>
            </request>
        </ns1:addContacts>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

这些请求之间的唯一区别是第二个请求中多了一个 <item xsi:type="ns1:Contact">...</item> 元素。

我发现这是 \SoapServer::handle 方法在不带参数调用时的问题 ($soap_request)。如果我通过 $HTTP_RAW_POST_DATA:

它工作正常
global $HTTP_RAW_POST_DATA;
$server->handle($HTTP_RAW_POST_DATA);