Upload ด้วย cURL

บทความนี้จะเป็นการเขียนตัวอย่างโค้ดสำหรับอัปโหลดไฟล์ด้วย cURL ไปยัง server ปลายทาง.

ก่อนที่จะเริ่มส่วนของ cURL นั้น ให้ทำหน้าฟอร์มปกติสำหรับผู้ใช้ และหน้ารับคำสั่งอัปโหลดแบบปกติเสียก่อน เพื่อตรวจสอบการทำงานได้จริง.

กระบวนการอัปโหลดตามปกติ

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Form upload</title>
</head>
<body>
    <form action="upload.php" method="post" enctype="multipart/form-data">
        <input type="hidden" name="hidden-input" value="hidden value">
        <p>
            <label for="inputfile">Select image:</label><br>
            <input id="inputfile" type="file" name="image" accept="image/jpeg,image/gif,image/png">
        </p>
        <p>
            <button type="submit">Submit</button>
        </p>
    </form>
</body>
</html>

form.html

โค้ดด้านบนนี้จะเป็นหน้าฟอร์มธรรมดา โดยจะส่งค่าไปยังหน้า upload.php ซึ่งจากตัวอย่างจะมี input hidden อยู่ 1 ช่อง และ input file ที่จะรับเฉพาะรูปภาพเท่านั้น.

<?php

header('Content-type: application/json');

$output = [];


if (empty($_FILES['image']['name']) || !isset($_FILES['image']['error'])) {
    $_FILES['image']['error'] = 4;
}

if ($_POST && $_FILES) {
    // @link https://www.php.net/manual/en/features.file-upload.errors.php#115746
    $phpFileUploadErrors = array(
        0 => 'There is no error, the file uploaded with success',
        1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
        2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
        3 => 'The uploaded file was only partially uploaded',
        4 => 'No file was uploaded',
        6 => 'Missing a temporary folder',
        7 => 'Failed to write file to disk.',
        8 => 'A PHP extension stopped the file upload.',
    );
    $uploadFolder = __DIR__ . DIRECTORY_SEPARATOR . 'uploaded';
    $allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif'];

    if (isset($_FILES['image']['error']) && $_FILES['image']['error'] !== UPLOAD_ERR_OK) {
        $output['error'] = true;
        $output['message'] = $phpFileUploadErrors[$_FILES['image']['error']];
    } else {
        if (!is_dir($uploadFolder)) {
            $oldumask = umask(0);
            mkdir($uploadFolder, 0777);
            umask($oldumask);
            unset($oldumask);
        }

        $moveToFileName = $uploadFolder . DIRECTORY_SEPARATOR . $_FILES['image']['name'];
        if (move_uploaded_file($_FILES['image']['tmp_name'], $moveToFileName)) {
            $Finfo = new finfo();
            $output['uploadedMimeType'] = $Finfo->file($moveToFileName, FILEINFO_MIME_TYPE);
            unset($Finfo);
            if (in_array(strtolower($output['uploadedMimeType']), $allowedMimeTypes)) {
                http_response_code(201);
                $output['uploadSuccess'] = true;
                $output['message'] = 'Uploaded completed.';
            } else {
                http_response_code(400);
                $output['uploadSuccess'] = false;
                $output['error'] = true;
                $output['message'] = 'You have uploaded disallowed file types.';

                @unlink($moveToFileName);
            }
        } else {
            $output['error'] = true;
            $output['message'] = 'Unable to move uploaded file.';
        }

        unset($moveToFileName);
    }

    unset($uploadFolder);
}



$output['debug']['_POST'] = $_POST;
$output['debug']['_FILES'] = $_FILES;

echo json_encode($output);

upload.php

โค้ดด้านบนนี้จะเป็นการรับค่ามาจากหน้า form.html แล้วตรวจสอบข้อผิดพลาดต่างๆ รวมทั้งตรวจสอบ mime type ที่แท้จริงจากตัวไฟล์ว่าจะต้องเป็นไฟล์ภาพเท่านั้น. เมื่อทุกอย่างผ่านหมดแล้วจะแสดงข้อความอัปโหลดสำเร็จ หรือถ้าไม่ผ่านจะแสดงข้อความผิดพลาดให้ได้รู้. หน้านี้จะแสดงข้อมูลออกมาในรูปแบบ JSON.

เมื่อทำการทดลองตามปกติ ถ้าอัปโหลดสำเร็จก็ถือว่าโค้ดอัพโหลดนี้พร้อมใช้งานแล้ว.

อัปโหลดด้วย cURL

ตัวอย่างต่อไปนี้ จะเป็นการใช้ไฟล์รูปที่มีพร้อมใช้งานอยู่แล้ว โดยขอให้เตรียมไฟล์รูปขนาดเล็กไม่เกิน 2 MB จำนวน 1 รูป เป็นประเภทรูป jpeg โดยตั้งชื่อไฟล์ image.jpg ไว้ในที่เดียวกันกับโค้ดทั้งหมดด้านบน.

ในส่วนของการอัปโหลดนั้น จะใช้คลาส CURLFile() ซึ่งมีรองรับตั้งแต่ PHP 5.5 เรื่อยมา. โดยจะสั่งให้คลาสนี้อ่านข้อมูลไฟล์แล้วแทรกใน POST fields เพื่อส่งข้อมูลเป็น method POST ไปยังเป้าหมายคือ upload.php.

<?php
$imageFile = __DIR__ . DIRECTORY_SEPARATOR . 'image.jpg';
$targetURL = (strtolower($_SERVER['REQUEST_SCHEME']) === 'https' ? 'https://' : 'http://')
    . ($_SERVER['HTTP_HOST'] ?? 'localhost')
    . (isset($_SERVER['REQUEST_URI']) ? dirname($_SERVER['REQUEST_URI']) : '')
    . '/upload.php';


if (!is_file($imageFile)) {
    http_response_code(404);
    echo '<p>Image file was not found. Please prepare image file at this location: <strong>' . $imageFile . '</strong></p>';
    exit();
}


$headers = [];
$allHeaders = [];
$postFields = [];


// create post fields
$postFields['hidden-input'] = 'hidden value (from cURL).';
// create upload file to post fields.
$Finfo = new finfo();
$fileMimeType = $Finfo->file($imageFile, FILEINFO_MIME_TYPE);
unset($Finfo);
$CurlFile = new CURLFile($imageFile, $fileMimeType, 'curl-upload-' . basename($imageFile));
$postFields['image'] = $CurlFile;
unset($CurlFile, $fileMimeType);


// start cURL.
$ch = curl_init($targetURL);
// don't echo out immediately.
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// method POST.
curl_setopt($ch, CURLOPT_POST, true);
// disable SSL verify. this is for test with self signed (local host) only. remove this on production site.
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
// response headers work.
curl_setopt($ch, CURLOPT_HEADERFUNCTION, 'headerFunction');
// POST data with file upload.
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);

// execute cURL.
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if (curl_errno($ch)) {
    http_response_code(500);
    echo '<p>cURL error: ' . curl_error($ch) . '</p>';
    curl_close($ch);
    exit();
}

// close cURL.
curl_close($ch);
unset($ch, $postFields);


http_response_code($httpCode);
if (isset($headers['content-type'])) {
    header('Content-type: ' . $headers['content-type']);
}
unset($headers);


// add headers to `$response` to see what we have here.
$responseJson = json_decode($response);
unset($response);
$responseJson->debug->allHeaders = $allHeaders;
$responseJson->debug->httpCode = $httpCode;
// echo out cURL response.
echo json_encode($responseJson);
unset($responseJson);


/**
 * cURL header manipulatement function.
 *
 * @param resource $ch cURL resource
 * @param string $header A header line.
 * @return int Return length of header line.
 */
function headerFunction($ch, $header)
{
    global $allHeaders, $headers;
    if (!empty(trim($header))) {
        $headerExp = explode(':', $header, 2);
        if (count($headerExp) === 2) {
            $headers[strtolower(trim($headerExp[0]))] = trim($headerExp[1]);
        }
        $allHeaders[] = $header;
    }
    return (int) mb_strlen($header);
}// headerFunction

curl-upload.php

สำหรับออปชั่น CURLOPT_SSL_VERIFYPEER, CURLOPT_SSL_VERIFYHOST นั้นแนะนำให้เอาออกถ้าหากทำงานอยู่บน server จริง โดยออปชั่นเหล่านี้เหมาะสำหรับทดสอบบนเครื่องที่ใช้ self signed SSL เท่านั้น.

จากโค้ดด้านบน ค่าในฟอร์มทั้งหมดรวมถึงไฟล์ที่อัปโหลดจะถูกกำหนดลงในออปชั่น CURLOPT_POSTFIELDS เพียงรายการเดียว. เมื่อทำงานเสร็จแล้วจะมีการดึงค่า header มาใช้งาน ซึ่งคุณผู้อ่านอาจจะไม่ต้องใช้ก็ได้ แต่เขียนไว้เพื่อเป็นตัวอย่างเผื่อนำไปใช้ได้ต่อไป.

เมื่อทดลองเรียกไฟล์ curl-upload.php ดู หากทำการอัปโหลดสำเร็จ ไฟล์รูปที่เรากำหนดไว้จะไปอยู่ที่ปลายทางในชื่อ curl-upload-image.jpg.

สรุป

โดยสรุปแล้วการอัปโหลดด้วย cURL นั้นไม่ได้ยุ่งยากมากอย่างที่คิด. หลายๆตัวอย่างจะมีการกำหนดอะไรมากมายนั้น อาจจะเป็นเพราะโค้ดเหล่านั้นใช้กับ PHP รุ่นที่เก่ามากๆ ก็เลยยังไม่ค่อยมีอะไรรองรับมากนัก เลยต้องเขียนมาก. ส่วนที่จำเป็นจริงๆก็คือ กำหนดค่าในการเรียก URL เป็น method POST, อ่านไฟล์ที่มีอยู่บน server เข้าไปในคลาส CURLFile() แล้วนำค่านั้นใส่ลงใน post fields, นำค่าใน post fields ทั้งหมดใส่ลงในออปชั่น CURLOPT_POSTFIELDS แล้วสั่งให้ cURL ทำงานเป็นอันเสร็จสิ้น.

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *

คุณอาจใช้แท็กHTMLและแอททริบิวต์เหล่านี้: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>