В Python, как установить _soapheaders для Zeep, используя словари?

Во время недавнего опыта написания JS-переводчика я много боролся с внутренней работой дат ECMA / JS. Итак, я полагаю, что я брошу свои 2 цента здесь. Надеемся, что совместное использование этого материала поможет другим с любыми вопросами о различиях между браузерами в том, как они обрабатывают даты.

Сторона входа

Все реализации хранят свои значения даты внутри себя как 64-разрядные номера которые представляют собой миллисекунды с 01.01.1970 UTC (GMT - это то же самое, что и UTC). Даты, возникающие после 1/1/1970 00:00:00, являются положительными числами, а даты предшествуют отрицательным.

Поэтому следующий код дает точный результат во всех браузерах.

Date.parse('1/1/1970');

В моей временной зоне ( EST), результат составляет 18000000, потому что это то, сколько мс составляет 5 часов (это всего 4 часа в летние месяцы). Значение будет отличаться в разных часовых поясах. Все основные браузеры делают это одинаково.

Вот и все. Несмотря на то, что в форматах входных строк есть некоторые отклонения, которые основные браузеры будут анализировать как даты, они по существу интерпретируют их одинаково с часовыми поясами и летней экономией. Один из них - формат ISO 8601. Это единственный формат, специально описанный в спецификации ECMA-262 v.5. Для всех других строковых форматов интерпретация зависит от реализации. По иронии судьбы, это формат, в котором браузеры могут отличаться. Ниже приведен сравнительный вывод Chrome vs Firefox для 1/1/1970 на моей машине с использованием формата строки ISO 8601.

Date.parse('1970-01-01T00:00:00Z');       // Chrome: 0         FF: 0
Date.parse('1970-01-01T00:00:00-0500');   // Chrome: 18000000  FF: 18000000
Date.parse('1970-01-01T00:00:00');        // Chrome: 0         FF: 18000000
  • Спецификатор «Z» указывает, что вход уже находится в UTC и не требует смещения перед хранением.
  • Спецификатор «-0500» указывает, что вход находится в GMT-05: 00, поэтому оба браузера интерпретируют ввод как находящийся в моем локальном часовом поясе. Это означает, что значение будет преобразовано в UTC перед сохранением. В моем случае это означает добавление 18000000 мс к внутреннему значению даты, что требует сдвига -18000000 мс (-05: 00), чтобы вернуть меня в локальное время.
  • Однако, когда спецификатор отсутствует, FF обрабатывает ввод в качестве локального времени, в то время как Chrome рассматривает его как время UTC. Для меня это создает 5-часовую разницу в сохраненной стоимости, что является проблематичным. В моей реализации я оказался на стороне FF здесь, потому что мне нравится вывод toString для соответствия моему входному значению, если я не укажу альтернативный часовой пояс, который я никогда не делаю. Отсутствие спецификатора должно предполагать местный вход времени.

Но здесь ситуация ухудшается, FF обрабатывает краткую форму формата ISO 8601 («YYYY -MM-DD ") по-разному, чем обрабатывает длинную форму (« YYYY-MM-DDTHH: mm: ss: sssZ ») без каких-либо логических причин. Вот результат из FF с длинными и короткими форматами даты ISO без спецификатора часового пояса.

Date.parse('1970-01-01T00:00:00');       // 18000000
Date.parse('1970-01-01');                // 0

Итак, чтобы ответить на вопрос первоначального вопросчика, "YYYY-MM-DD" - это короткая форма ISO 8601 "YYYY-MM-DDTHH:mm:ss:sssZ". Таким образом, это интерпретируется как время UTC, тогда как другое интерпретируется как локальное. Вот почему,

Это не jive:

console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08")).toString());

Это делает:

console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08T00:00:00")).toString());

Нижняя строка - это строка синтаксического анализа. Единственная строка ISO 8601, которую вы можете безопасно анализировать в браузерах, - это длинная форма. И ВСЕГДА используйте спецификатор «Z». Если вы это сделаете, вы можете спокойно перемещаться между локальным и UTC.

Это работает в браузерах (после IE9):

console.log(new Date(Date.parse("2005-07-08T00:00:00Z")).toString());

К счастью, большинство современных браузеров другие форматы ввода одинаково, включая наиболее часто используемые форматы «1/1/1970» и «1/1/1970 00:00:00 AM». Все следующие форматы (и другие) рассматриваются как локальный ввод времени во всех браузерах и конвертируются в UTC перед хранением. Таким образом, они совместимы с кросс-браузером. Выходной код этого кода во всех браузерах в моем часовом поясе одинаковый.

console.log(Date.parse("1/1/1970"));
console.log(Date.parse("1/1/1970 12:00:00 AM"));
console.log(Date.parse("Thu Jan 01 1970"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00 GMT-0500"));

Сторона выхода

. На стороне вывода все браузеры переводили часовые пояса одинаково, но они обрабатывать строковые форматы по-разному. Вот функции toString и то, что они выводят. Обратите внимание на то, что функции toUTCString и toISOString выводят на моей машине 5:00 AM.

Конвертирует с UTC в локальное время перед печатью

 - toString
 - toDateString
 - toTimeString
 - toLocaleString
 - toLocaleDateString
 - toLocaleTimeString

Прямая печать сохраненного времени UTC

 - toUTCString
 - toISOString 

In Chrome
toString            Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString        Thu Jan 01 1970
toTimeString        00:00:00 GMT-05:00 (Eastern Standard Time)
toLocaleString      1/1/1970 12:00:00 AM
toLocaleDateString  1/1/1970
toLocaleTimeString  00:00:00 AM

toUTCString         Thu, 01 Jan 1970 05:00:00 GMT
toISOString         1970-01-01T05:00:00.000Z

In Firefox
toString            Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString        Thu Jan 01 1970
toTimeString        00:00:00 GMT-0500 (Eastern Standard Time)
toLocaleString      Thursday, January 01, 1970 12:00:00 AM
toLocaleDateString  Thursday, January 01, 1970
toLocaleTimeString  12:00:00 AM

toUTCString         Thu, 01 Jan 1970 05:00:00 GMT
toISOString         1970-01-01T05:00:00.000Z

Я обычно не используйте формат ISO для ввода строки. Единственный раз, когда использование этого формата полезно для меня, - это когда даты нужно сортировать как строки. Формат ISO можно сортировать как есть, а другие - нет. Если у вас есть совместимость с несколькими браузерами, укажите либо часовой пояс, либо используйте совместимый строковый формат.

Код new Date('12/4/2013').toString() проходит через следующее внутреннее псевдопреобразование:

  "12/4/2013" -> toUCT -> [storage] -> toLocal -> print "12/4/2013"

Надеюсь, этот ответ был полезен.

1
задан Liquidgenius 18 January 2019 в 14:36
поделиться

1 ответ

Я считаю, что с Zeep легче работать, если у нас есть действительный и полный WSDL.

Простой API-сервис WSDL, который ожидает элемент без пространства имен, импортирует схему без пространства имен, например:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://tempuri.org/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://tempuri.org/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" >
  <wsdl:types>
    <s:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/">
      <s:import schemaLocation="namespaceLessElement.xsd"/>
      <s:element name="Api" minOccurs="0" maxOccurs="1">
      </s:element>
      <s:element name="ApiResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="1" maxOccurs="1" name="ApiResult" type="s:int"/>
          </s:sequence>
        </s:complexType>
      </s:element>
      <s:element name="apiKey">
        <s:complexType>
          <s:sequence>
            <s:element name="api_key" type="s:string"></s:element>
          </s:sequence>
        </s:complexType>
      </s:element>
    </s:schema>
  </wsdl:types>
  <wsdl:message name="ApiSoapIn">
    <wsdl:part name="parameters" element="tns:Api"/>
  </wsdl:message>
  <wsdl:message name="ApiSoapOut">
    <wsdl:part name="parameters" element="tns:ApiResponse"/>
  </wsdl:message>
  <wsdl:message name="ApiKeyHeader">
    <wsdl:part name="ApiKeyHeaderParam" element="tns:apiKey"/>
  </wsdl:message>
  <wsdl:message name="PagerHeader">
    <wsdl:part name="PagerHeaderParam" ref="pager"/>
  </wsdl:message>
  <wsdl:portType name="ApiSoap">
    <wsdl:operation name="Api">
      <wsdl:documentation>This is a test WebService. Returns a number</wsdl:documentation>
      <wsdl:input message="tns:ApiSoapIn"/>
      <wsdl:output message="tns:ApiSoapOut"/>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="ApiSoap" type="tns:ApiSoap">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="Api">
      <soap:operation soapAction="http://tempuri.org/Api" style="document"/>
      <wsdl:input>
        <soap:header message="tns:ApiKeyHeader" part="ApiKeyHeaderParam" use="literal"/>
        <soap:header message="tns:PagerHeader" part="PagerHeaderParam" use="literal"/>
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="ApiTest">
    <wsdl:port name="ApiSoap" binding="tns:ApiSoap">
      <soap:address location="http://superpc:8082/"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

С namespaceLessElement.xsd:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<s:schema xmlns:s="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified">
  <s:element name="pager">
    <s:complexType>
      <s:sequence>
        <s:element name="page" type="s:int"></s:element>
        <s:element name="per_page" type="s:int"></s:element>
      </s:sequence>
    </s:complexType>
  </s:element>
</s:schema>

Обратите внимание, как Определение операции, которое ожидает, что значения заголовка указывают на правильные сообщения:

<wsdl:operation name="Api">
  <soap:operation soapAction="http://tempuri.org/Api" style="document"/>
  <wsdl:input>
    <soap:header message="tns:ApiKeyHeader" part="ApiKeyHeaderParam" use="literal"/>
    <soap:header message="tns:PagerHeader" part="PagerHeaderParam" use="literal"/>
    <soap:body use="literal"/>
  </wsdl:input>
  <wsdl:output>
    <soap:body use="literal"/>
  </wsdl:output>
</wsdl:operation>

, и они, в свою очередь, ссылаются на правильные элементы:

<wsdl:message name="ApiKeyHeader">
  <wsdl:part name="ApiKeyHeaderParam" element="tns:apiKey"/>
</wsdl:message>
<wsdl:message name="PagerHeader">
  <wsdl:part name="PagerHeaderParam" ref="pager"/>
</wsdl:message>

Вы должны проверить в WSDL вашего веб-сервиса, что операция описывает оба заголовка и включает в себя определение схемы для обоих элементов. В примере WSDL пространство имен службы - targetNamespace="http://tempuri.org/", но оно должно указывать на URL-адрес вашей веб-службы.

Таким образом, предполагая, что ваш WSDL действителен и завершен, нам нужно определить Client, указывающий на WSDL, а затем установить значения заголовка с помощью параметра _soapheaders, аналогично методу, который я использовал здесь , но построение ссылка на содержание. Zeep может позаботиться о разных пространствах имен, но я обнаружил проблемы с пустыми:

transport = Transport(cache=SqliteCache())
self.Test = Client(wsdl='http://my-endpoint.com/production.svc?wsdl', transport=transport)

# Header objects
apiKey_header = xsd.Element(
    '{http://tempuri.org/}apiKey',
    xsd.ComplexType([
        xsd.Element(
            'api_key', xsd.String()
        )
    ])
)

pager_header = xsd.Element(
    'pager',
    xsd.ComplexType([
        xsd.Element(
            'page', xsd.Integer()
        ),
        xsd.Element(
            'per_page', xsd.Integer()
        )
    ])
)

apiKey_header_value = apiKey_header( api_key=key)
pager_header_value = pager_header( page=page, per_page=perpage)

# Request
response = self.Test.service.Api( _soapheaders=[apiKey_header_value, pager_header_value] )

logger.debug("Result={1}".format(response))

# Prints: Result=2 (or whatever value the test API sends)

РЕДАКТИРОВАТЬ: Пример сгенерированного запроса XML:

<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
   <soap-env:Header>
      <ns0:apiKey xmlns:ns0="http://tempuri.org/">
         <api_key>1230011</api_key>
      </ns0:apiKey>
      <pager>
         <page>2</page>
         <per_page>10</per_page>
      </pager>
   </soap-env:Header>
   <soap-env:Body>
      <ns0:Api xmlns:ns0="http://tempuri.org/"/>
   </soap-env:Body>
</soap-env:Envelope>

Убедитесь, что заголовок, который имеет пространство имен определено с правильным URL.

Если у вас все еще есть проблемы, это может означать, что ваш WSDL не определяет все элементы или что он неправильно связывается с внешними XSD. В этих случаях один из вариантов - сохранить локальную копию WSDL и связанных XSD, затем отредактировать файлы, чтобы исправить ссылки, а затем вместо этого указать Zeep на этот локальный файл.

0
ответ дан Markoan 18 January 2019 в 14:36
поделиться
Другие вопросы по тегам:

Похожие вопросы: