Получение измененных данных PUT с PHP на IIS 7

У меня IIS 7 работает с веб-службой PHP 5.3.8. Я создаю обработчик для запросов PUT, и когда PUT пытается, обработчик называется штрафом.

Чтобы получить доступ к данным PUT, отправленным клиентом, я использую код как

if (($stream = fopen('php://input', "r")) !== FALSE) {
 fputs($logfile, "stream reading begin\r\n");
 stream_set_timeout($stream, 5);
 $newcontent = "";
 while ($chunk = fread($stream, 32)) {
 $newcontent .= $chunk;
 fputs($logfile, "stream read chunk >>$chunk<<\r\n");
 }
 // $newcontent = (stream_get_contents($stream));
 fputs($logfile, "stream read\r\n");

Проблема, с которой я столкнулся, заключается в том, что скрипт PHP, обрабатывающий запрос PUT, зависает при чтении данных. Я начал с "$ newcontent = (stream_get_contents ($ stream)); который висит. Затем я попытался установить тайм-аут для потока, без изменений. Затем я попробовал цикл while, который считывает данные POST в небольших кусках и записывает их в журнал. Таким образом, я вижу, что почти все данные PUT считываются, но одно из запросов чтения к концу (несколько сотен байт до конца данных) никогда не возвращается.

После некоторых сообщений, которые я нашел в stackoverflow, я пробовал разные типы пула приложений, такие как "classic.net" - без изменений.

Я запускаю PHP с помощью FastCGI-

В приложении IIS appcmd я вижу, что эти запросы зависают вечно:

C:\Windows\System32\inetsrv>appcmd list requests /elapsed:3000
REQUEST "c400000080000e95" (url:PUT /.../.../....php?mac=009033280075&proc=&mode=flashdir&tag=phone-calls, time:729928 msec, client:172.16.10.131, stage:ExecuteRequestHandler, module:FastCgiModule)
REQUEST "6900000180000d81" (url:PUT /.../.../u....php?mac=0090333000af&proc=reset&mode=xml&tag=phone-regs, time:453588 msec, client:172.16.4.59, stage:ExecuteRequestHandler, module:FastCgiModule)

Когда я подключаю HTTP-соединение, я вижу, что он простаивает с регулярными правами на сохранение TCP.

Излишне говорить, что код работает отлично с Apache.

Мне кажется, что PHP не распознает конец входящих данных и ждет больше данных. Однако, даже если это так, я не понимаю, почему тайм-аут потока не вступает в силу.

Любая идея, как это исправить?

Благодарю вас, Кристоф

PS: Я должен добавить, что клиент использует закодированную кодировку для данных PUT:

PUT /moodle-debug/.../....php?mac=IP111-28-00-75... HTTP/1.1
User-Agent: innovaphone-IP111/110792
Transfer-Encoding: chunked

23
vars check 09c4c4ec6778cb3eb4aa63

ac0
# 11r1 dvl IP111[11.0792], 
...

Я нашел примечание в https://bugs.php.net/bug.php?id=60826

В случае "Transfer-Encoding: chunked" IIS поставляет Content-length: -1 в данные запроса, который отбрасывается в **** в sapi_cgi_read_post. Причинение подпрограммы читать данные до тех пор, пока не прочитает полный 4G (2 ^ 32 -1).

Который объяснил бы это поведение. Тогда вопрос будет: есть ли исправление?

1 ответ

Так что мне потребовалось 2 дня, чтобы понять. Вот что я придумал:

  • PHP 5.3.8 с FastCGI на IIS7.x не может обрабатывать запросы PUT с данными кодированных данных
  • По-видимому, IIS указывает на отрицательный размер контента для PHP (-1), который затем пытается прочитать большое количество байтов (-1, приложенных к ****) из потока запросов HTTP
  • поскольку клиент не отправляет столько байтов, чтение не выполняется
  • поскольку клиент, однако, также не закрывает поток HTTP-запросов, PHP бесконечно ждет бесконечных входных данных
  • после очень долгого времени (60 минут на моей машине) IIS завершает выполнение запроса PHP

Чтобы обойти эту проблему (которая должна быть исправлена в PHP, на мой взгляд), я создал обработчик HTTP-запроса для IIS, закодированный на С#. Вот:

using System.Web;
using System.IO;
using System.Text;
using System;

namespace handler
{
 public class PutHandler : IHttpHandler
 {
 string path = "C:\\inetpub\\wwwroot\\put\\log\\mylog.txt";

 private void log(string line)
 {
 FileStream log;
 StreamWriter stream;
 log = File.Open(path, FileMode.Append);
 stream = new StreamWriter(log);
 stream.WriteLine(line);
 stream.Flush();
 stream.Close();
 }

 public static byte[] Combine(byte[] first, byte[] second)
 {
 byte[] ret = new byte[first.Length + second.Length];
 Buffer.BlockCopy(first, 0, ret, 0, first.Length);
 Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
 return ret;
 }

 public PutHandler()
 {
 log("PutHandler()");
 }
 ~PutHandler()
 {
 log("~PutHandler()");
 }

 // This handler is called whenever a file ending 
 // in .put is requested. A file with that extension
 // does not need to exist.
 public void ProcessRequest(HttpContext context)
 {
 log("ProcessRequest()");

 HttpRequest Request = context.Request;
 HttpResponse Response = context.Response;


 log("size " + context.Request.ContentLength);
 log("size " + context.Request.HttpMethod);
 byte[] allbytes = new byte[0];
 // context.Request.ContentLength is 0 for chunked encodings :-(
 while (true)
 {
 byte[] bytes = context.Request.BinaryRead(1024);
 if (bytes.Length <= 0) break;
 allbytes = PutHandler.Combine(allbytes, bytes);
 }
 log("Total " + allbytes.Length + " bytes in request");
 Response.StatusCode = 200;

 string uri = "http://" + Request.ServerVariables["SERVER_NAME"] + Request.RawUrl.Replace(".put", ".php");
 log("pushing back to " + uri);

 // now pushback to original target URL
 var client = new System.Net.WebClient();
 Response.BinaryWrite(client.UploadData(uri, "PUT", allbytes));

 log("RequestHandler done");
 }
 public bool IsReusable
 {
 // To enable pooling, return true here.
 // This keeps the handler in memory.
 get { return false; }
 }
 }
}

Обработчик должен быть известен IIS. Для этого вам необходимо предоставить файл web.config в соответствующем каталоге приложений следующим образом:

<!--?xml version="1.0" encoding="utf-8"?-->
<configuration>
 <system.webserver>
 <modules>
 <remove name="WebDAVModule">
 </remove></modules>
 <handlers>
 <remove name="WebDAV">
 <add name="PutHandler" path="*.put" verb="PUT" type="handler.PutHandler, PutHandler" requireaccess="Script">
 </add></remove></handlers>
 </system.webserver>
</configuration>

Это удаление стандартных модулей/обработчиков WebDav, поскольку мы не хотим их здесь. Затем он добавляет новый обработчик (двоичные файлы которого необходимо установить в папке bin веб-приложения). Этот обработчик вызывается для всех URI с суффиксом.put и только для запросов PUT. Чтобы это сработало, пул приложений-приложений должен находиться в "интегрированном" режиме.

То, что делает обработчик, состоит в сборе чанкированных данных (что отлично работает на С#), а затем для повторной отправки всех данных (тела и относительного URI, включая все аргументы запроса), на тот же виртуальный сервер с суффиксом ".put", измененным на ".php". Поскольку это сейчас отправляется за один шаг, не используется кодировка с кодировкой, и получающий PHP-код работает нормально.

licensed under cc by-sa 3.0 with attribution.