douxuanling6523 2014-12-12 20:49
浏览 40
已采纳

在IIS 7上使用PHP检索分块的PUT数据

I have IIS 7 running with a PHP 5.3.8 web service. I have create a handler for PUT requests and when a PUT is attempted, the handler is called fine.

To access the PUT data sent by the client, I use code like

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

The issue i ran into is, that the PHP script handling the PUT request hangs when reading the data. I started with "$newcontent = (stream_get_contents($stream));" which hangs. I then tried to set a timeout for the stream, no change. I then tried the while loop which reads the POST data in small chunks and logs them. This way I can see that almost the whole PUT data is read, but one of the read-requests towards the end (a few hundred bytes before the end of the data) never returns.

Following some posts I found on stackoverflow, I tried different application pool types, such as "classic .net" - no change.

I run PHP with FastCGI-

In IIS appcmd, I can see that these requests hang forever:

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)

When I wireshark the HTTP connection, I see that it is idle with regular TCP keep alives.

Needless to say, the code runs just fine with Apache.

Looks to me as if PHP does not recognize the end of the incoming data and waits for more data. However, even if so I do not understand why the stream timeout does not take effect.

Any idea how to fix that?

Thank you, Christoph

PS: I should add that the client is using chunked encoding for the PUT data:

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], 
...

I found a note in https://bugs.php.net/bug.php?id=60826

In case of "Transfer-Encoding: chunked" IIS supplies Content-length:-1 in the request data, which is casted to uint in sapi_cgi_read_post. Causing the routine to read data until it has read the full 4G (2^32-1).

Which would explain this behaviour. The question then would be: is there a fix?

  • 写回答

1条回答 默认 最新

  • duangou1953 2014-12-15 20:02
    关注

    So this took me 2 days to figure out. Here is what I came up with:

    • PHP 5.3.8 with FastCGI on IIS7.x can not handle PUT requests with chunked encoding data
    • Apparently, IIS indicates a negative content size to PHP (-1), which then tries to read a large number of bytes (-1 casted to uint) from the HTTP request stream
    • as the client does not send that many bytes, the read does not succeed
    • as the client however does not close the HTTP request stream either, PHP waits endlessly for input never to come
    • after a very long time (60 minutes on my machine), IIS terminates the PHP request execution

    To work around this issue (which should be fixed in PHP in my opinion), I created an HTTP request handler for IIS, coded in C#. Here it is:

    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; }
            }
        }
    }
    

    The handler needs to be made known to IIS. To do this, you need to supply a web.config in the appropriate application directory as follows:

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

    This is removing the standard WebDav modules/handlers, as we do not want them here. Then it adds a new handler (the binaries of which need to be installed in the web application's bin folder). This handler is called for all URIs with the .put suffix and only for PUT requests. For this to work, the applications application-pool must be in the "integrated" mode.

    What the handler does is to collect the chunked data (which works fine in C#) and then to re-send all the data (body and relative URI including all query args) to the same virtual server with the ".put" suffix changed to ".php". As this is now sent in a single step, no chunked encoding is used and the receiving PHP code works fine.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 (标签-UDP|关键词-client)
  • ¥15 关于库卡officelite无法与虚拟机通讯的问题
  • ¥15 qgcomp混合物线性模型分析的代码出现错误:Model aliasing occurred
  • ¥100 已有python代码,要求做成可执行程序,程序设计内容不多
  • ¥15 目标检测项目无法读取视频
  • ¥15 GEO datasets中基因芯片数据仅仅提供了normalized signal如何进行差异分析
  • ¥15 小红薯封设备能解决的来
  • ¥100 求采集电商背景音乐的方法
  • ¥15 数学建模竞赛求指导帮助
  • ¥15 STM32控制MAX7219问题求解答