stream_context_create с опцией HTTP на основе имени домена

#php #xml #xslt #cookies

#php #xml #xslt #файлы cookie

Вопрос:

Как я могу указать streamContext, созданному с stream_context_create() помощью, использовать разные HTTP-заголовки в соответствии с доменным именем запрошенного URI?

Я использую XSL для преобразования XML на стороне сервера (PHP).

Для этого я сначала создаю контекст потока с некоторыми параметрами и применяю этот поток к libxml :

 $streamContextOptions = array(
    'http' =>array(
        'header'=>$clientHeadersString /* HTTP header */
    )
);
$streamContext = stream_context_create($streamContextOptions);
libxml_set_streams_context($streamContext);
  

Затем я загружаю XML, XSL и выполняю преобразование

 // Load the XML
$xmlDocument = new DOMDocument();
$xmlDocument->load($xmlURI);

// Load the XSL into
$xslDocument = new DOMDocument();
$xslDocument->load($xslURI);
$xslProcessor = new XSLTProcessor();
$xslProcessor->importStylesheet($xslDocument);

// Apply the XSL
$htmlDocument = $xslProcessor->transformToDoc($xmlDocument);
  

В XSL у меня есть несколько document() вызовов, поэтому сервер PHP вызывает указанные URI, используя контекст потока. Но у меня внутри этих document() вызовов разные домены (допустим, у меня есть www.foo.com и www.bar.com ).

 <?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" encoding="utf-8" indent="yes"/>

    <xsl:template match="/">
        <xsl:value-of select="document('http://www.foo.com/with-user-agent.xml.php')/root"/>
        <xsl:value-of select="document('http://www.bar.com/with-accept-language.php')/root"/>
    </xsl:template>
</xsl:stylesheet>    
  

Я хочу изменить HTTP-заголовки, используемые libxml при выполнении document() вызовов внутри XSL, чтобы я мог отправлять [cookies user-agent] в foo.com , и [другие файлы cookie accept-language] для bar.com когда они запрашиваются внутри document() вызова XSL.

Я не хочу отправлять заголовок «too many» в foo.com (иначе говоря, я не хочу посылать Accept-Language в foo.com ) ни к bar.com , и я не хочу, чтобы печенье смешивалось (печенье отправляется на foo.com не должны быть отправлены в bar.com ).

Каким-то образом, как:

 $streamContextOptions = array(
    'http-for-foo' =>array(
        'header'=>$clientHeadersStringForFoo /* HTTP header only when document() calls for foo.com*/
    ),
    'http-for-bar' =>array(
        'header'=>$clientHeadersStringForBar /* HTTP header only when document() calls for bar.com*/
    )
);
  

Есть ли способ сообщить XSLTProcessor изменить параметры streamOptions в зависимости от домена, запрошенного document() функцией?

Комментарии:

1. Разве истинная проблема вашего внешнего ресурса не зависит от заголовков запроса? Имхо ресурс (ы) должен быть статическим и кэшируемым.

2. @RolandFranssen Нет, потому что ресурсом XML могут быть данные профиля пользователя (имя пользователя, аватар, почта …). Таким образом, пользователь должен войти в систему, прежде чем сможет получить этот вид XML. Возвращаемый XML также может быть основан Accept-Language на данных локализации.

Ответ №1:

Вы не можете указать разные хосты для параметров контекста потока, потому что при потоковой операции ввода-вывода контекст был назначен — нет возможности libxml_set_streams_context отличаться в зависимости от имени хоста (также для потоков в PHP это тоже невозможно. По крайней мере, AFAIK).

Поэтому вместо этого вам нужно назначить URI с соответствующими параметрами потока.

В вашем сценарии мне кажется, что это проще всего сделать после настройки DOM, но перед преобразованием, создав свой собственный загрузчик внешних объектов, который может различать параметры контекста на основе имени хоста:

 ...

$xslProcessor->importStylesheet($xslDocument);

$httpOptions       = [
    'timeout' => 1,
    'header'  => "User-Agent: Godzilla Gabba Gandalf Client 42.4 - Lord of the XSLT Weed Edition"
];
$httpOptionsByHost = [
    'www.foo.com' => [
        'header' => "X-Secret-Debug-Request-Flag: verbose-verbose-verbose"
    ],
    'www.bar.com' => [
        'header' => "User-Agent: 1' OR TRUE"
    ]
];

libxml_set_external_entity_loader(
    function ($public, $system, $context) use ($httpOptions, $httpOptionsByHost) {

        $url = new Net_URL2($system);
        $url->normalize();
        $host = $url->getHost();

        $options['http'] = [];

        if (isset($httpOptionsByHost[$host])) {
            $options['http'] = $httpOptionsByHost[$host];
        }

        $options['http']  = $httpOptions;

        $context = stream_context_create($options);

        return fopen($url, 'r', false, $context);
    }
);

// Apply the XSL
$htmlDocument = $xslProcessor->transformToDoc($xmlDocument);

...
  

Вы не указали XML в своем вопросе, и внешние URI не работают, поэтому я мог протестировать только частично, но, насколько я мог проверить, это должно сработать.

Возможно, вам потребуется больше отличаться от функции внешнего загрузчика объектов, поскольку у вас могут быть дополнительные другие загружаемые объекты, которые не должны обрабатываться одинаково. Но я предполагаю, что вы можете это понять.

Комментарии:

1. libxml_set_external_entity_loader() Работает ли для document() вызовов внутри XSL? Или я должен переключаться и использовать entities внутри XML вместо document() внутри XSL?

2. Это работает для document() вызовов внутри XSL, иначе я бы этого не предложил. В моем тесте я мог бы создать два разных контекста потока для двух разных имен хостов, которые у вас есть в двух разных URI, которые находятся document() внутри файла XSL.

Ответ №2:

Возможно, вы могли бы прокси-запрос? Т.е. document('//mydomain/?doc=origin-doc')

Таким образом, вы можете изменить запрос (например, с помощью cURL) в соответствии с вашими потребностями. Параметр doc можно использовать для условного изменения запроса.

Комментарии:

1. Дело в том, что я хочу иметь возможность обрабатывать XSL на стороне клиента: процесс на стороне сервера является резервным вариантом на случай, если браузер не поддерживает XSL. Итак, используя прокси, когда XSL анализируется на стороне клиента, браузер отправляет mydomain учетные данные (и прокси-сервер otherDomain ) вместо otherDomain прямого вызова с правильными учетными данными. Я знаю, что на стороне сервера mydomain не будет otherDomain учетных данных, но я хочу otherDomain , чтобы меня называли без учетных данных, когда на стороне сервера, и с полными учетными данными, когда на стороне клиента.

2. На самом деле, поскольку Chrome (webkit?) не принимает CORS для документов XML / XSL, я буду использовать такой способ и придерживаться локальных URI (с одним контекстом потока для всего XSL), загружаемых сервером, если мне нужно.