ScarShow

< IS >

讓 PHP 支援 PUT FileUpload

2012-08-19  /  IT  /  PHP

由於PHP對於PUT Method所傳送的資料不會做處理,所以對於使用PUT所傳送的資料必須手動作處理,簡單的方式就是去讀取php://input再做處理

<?php
// do something...

$put_data = file_get_contents('php://input');

// handle put data...


簡單的資料處理起來還OK但是遇到form-data的時候就困難了,之前在實做RESTful Server的時候就有寫過一隻PHP Function去做處理檔案上傳

但是難免會有 BUG,所以那時候就想看看到底PHP在初始化的時候哪邊是在做FileHandle的相關處理,所以就去查詢相關的文章及Trace PHP Source Code

結果發現在 main/SAPI.c 中的 sapi_activate Function 中有這麼一段Code

/* main/SAPI.c:456 */
if (SG(request_info).content_type && !strcmp(SG(request_info).request_method, "POST"))) {
    /* HTTP POST may contain form data to be processed into variables
     * depending on given content type */
    sapi_read_post_data(TSRMLS_C);
} else {
    // ...
}


它用來比較HTTP Request Method是否為POST,然後底下所呼叫的Function sapi_read_post_data 是用來處理檔案上傳的,所以就對它做一些修改

/* main/SAPI.c:456 */
if (SG(request_info).content_type && (!strcmp(SG(request_info).request_method, "POST") || !strcmp(SG(request_info).request_method, "PUT"))) {
    /* HTTP POST may contain form data to be processed into variables
     * depending on given content type */
    sapi_read_post_data(TSRMLS_C);
} else {
    // ...
}


既然有了這麼一個Sample然後在看看還有哪些程式中也有相同的處理機制,所以就用 grep 吧

$ grep -rn \"POST\" ./main


然後發現底下的程式中也有相同的判斷,所以就再多做修改,這邊

/* main/php_content_type.c:44 */
if (!strcmp(SG(request_info).request_method, "POST") || !strcmp(SG(request_info).request_method, "PUT")) {
    if (NULL == SG(request_info).post_entry) {
        /* no post handler registered, so we just swallow the data */
        sapi_read_standard_form_data(TSRMLS_C);
    }
    // ...
}


還有這邊

/* main/php_variables.c:681 */
if (PG(variables_order) &&
        (strchr(PG(variables_order),'P') || strchr(PG(variables_order),'p')) &&
    !SG(headers_sent) &&
    SG(request_info).request_method &&
    (!strcasecmp(SG(request_info).request_method, "POST") || !strcasecmp(SG(request_info).request_method, "PUT"))) {
    sapi_module.treat_data(PARSE_POST, NULL, NULL TSRMLS_CC);
    vars = PG(http_globals)[TRACK_VARS_POST];
} else {
    // ...
}


所以一共改了3個地方,都多加了PUT的判斷,接下來就開始編譯了

./configure --prefix=/home/scarwu/php \
    --with-config-file-path=/home/scarwu/php/etc \
    --with-config-file-scan-dir=/home/scarwu/php/var/db \
    --with-pear=/home/scarwu/php/lib/php \
    --disable-all \
    --enable-dom \
    --enable-exif \
    --enable-hash \
    --enable-json \
    --enable-mbregex \
    --enable-mbstring \
    --enable-phar \
    --enable-session \
    --enable-short-tags \
    --enable-libxml \
    --enable-simplexml \
    --enable-tokenizer \
    --enable-xml \
    --enable-xmlreader \
    --enable-xmlwriter \
    --with-xsl \
    --with-xmlrpc \
    --with-mhash \
    --with-pcre-regex \
    --with-zlib=/usr \
    --with-curl=/usr \
    --with-gettext=/usr \
    --with-mysql=mysqlnd \
    --with-mysqli=mysqlnd \
    --with-pdo-mysql=mysqlnd \
    --enable-pdo \
    --with-sqlite3 \
    --with-pdo-sqlite \
    --enable-fpm
make
make install


測試環境Server部份 Nginx + php-fpm 而Client是自己用 PHP + curl 寫得,至於Server設定就不多說,以下是測試用的PHP Code

<?php
    print_r($_SERVER);
    echo "\n";
    print_r($_POST);
    echo "\n";
    print_r($_FILES);


試著用PUT傳檔案及Params上去之後,回傳結果為此,PHP已經幫你處理好了,該出現的都出現了

Array
(
    ...
    [REQUEST_METHOD] => PUT
    [CONTENT_TYPE] => multipart/form-data; boundary=----------------------------a24f01ed52c0
    [CONTENT_LENGTH] => 1446
    [SCRIPT_FILENAME] => /var/www/index.php
    ...
)

Array
(
    [json] => {"key":"value"}
)

Array
(
    [file] => Array
        (
            [name] => upload.txt
            [type] => application/octet-stream
            [tmp_name] => /tmp/phpIXeGvM
            [error] => 0
            [size] => 1121
        )

)


結論

隨然這樣子對PHP做修改可以達到目的,但畢竟這不是一個正確的選擇,會造成維護上的困難,接著就是在此環境開發的Code不一定能在其他環境中使用

畢竟這不是一個標準的PHP,接下來在找時間看如何把這個寫成PHP Extension,還是說提個Issue...