HttpRequest 封裝底層 Socket 通訊,提供統一介面發送三種常見 HTTP 請求,支援表單欄位與檔案上傳。
<?php
class HttpRequest {
private $_ip = '';
private $_host = '';
private $_url = '';
private $_port = '';
private $_errno = '';
private $_errstr = '';
private $_timeout = 15;
private $_fp = null;
private $_formdata = [];
private $_filedata = [];
// 設定連線參數
public function setConfig($config) {
$this->_ip = $config['ip'] ?? '';
$this->_host = $config['host'] ?? '';
$this->_url = $config['url'] ?? '';
$this->_port = $config['port'] ?? '';
$this->_errno = $config['errno'] ?? '';
$this->_errstr = $config['errstr'] ?? '';
$this->_timeout = $config['timeout'] ?? 15;
if ($this->_ip === '') $this->_ip = $this->_host;
}
public function setFormData(array $formdata) { $this->_formdata = $formdata; }
public function setFileData(array $filedata) { $this->_filedata = $filedata; }
// 發送請求總入口
public function send($type = 'get') {
$type = strtolower($type);
if (!in_array($type, ['get','post','multipart'])) return false;
if (!$this->connect()) return false;
switch ($type) {
case 'get': $out = $this->sendGet(); break;
case 'post': $out = $this->sendPost(); break;
case 'multipart': $out = $this->sendMultipart(); break;
}
if (!$out) return false;
fputs($this->_fp, $out);
$response = '';
while ($row = fread($this->_fp, 4096)) $response .= $row;
$this->disconnect();
$pos = strpos($response, "\r\n\r\n");
return $pos !== false ? substr($response, $pos + 4) : $response;
}
private function connect() {
$this->_fp = @fsockopen($this->_ip, $this->_port, $this->_errno, $this->_errstr, $this->_timeout);
return $this->_fp !== false;
}
private function disconnect() { if ($this->_fp) fclose($this->_fp); }
// GET:查詢字串附加於 URL
private function sendGet() {
$query = http_build_query($this->_formdata);
$url = $this->_url . ($query ? '?' . $query : '');
return "GET $url HTTP/1.1\r\nHost: {$this->_host}\r\nConnection: close\r\n\r\n";
}
// POST:application/x-www-form-urlencoded
private function sendPost() {
$body = http_build_query($this->_formdata);
return "POST {$this->_url} HTTP/1.1\r\n"
. "Host: {$this->_host}\r\n"
. "Content-Type: application/x-www-form-urlencoded\r\n"
. "Content-Length: " . strlen($body) . "\r\n"
. "Connection: close\r\n\r\n$body";
}
// Multipart:支援檔案上傳
private function sendMultipart() {
$boundary = '----HttpRequestBoundary' . md5(microtime());
$body = '';
foreach ($this->_formdata as $k => $v) {
$body .= "--$boundary\r\n";
$body .= "Content-Disposition: form-data; name=\"$k\"\r\n\r\n$v\r\n";
}
foreach ($this->_filedata as $k => $file) {
// $file = ['path' => '', 'name' => '', 'type' => '']
$content = file_get_contents($file['path']);
$body .= "--$boundary\r\n";
$body .= "Content-Disposition: form-data; name=\"$k\"; filename=\"{$file['name']}\"\r\n";
$body .= "Content-Type: {$file['type']}\r\n\r\n";
$body .= $content . "\r\n";
}
$body .= "--$boundary--\r\n";
return "POST {$this->_url} HTTP/1.1\r\n"
. "Host: {$this->_host}\r\n"
. "Content-Type: multipart/form-data; boundary=$boundary\r\n"
. "Content-Length: " . strlen($body) . "\r\n"
. "Connection: close\r\n\r\n$body";
}
}
$http = new HttpRequest();
$http->setConfig(['host' => 'api.example.com', 'url' => '/upload', 'port' => 80]);
$http->setFormData(['user_id' => 123]);
$http->setFileData(['avatar' => ['path' => '/tmp/photo.jpg', 'name' => 'photo.jpg', 'type' => 'image/jpeg']]);
$response = $http->send('multipart');
send() 參數。md5(microtime()) 確保唯一性。fsockopen 後必定 fclose。cURL 或 Guzzle,支援 HTTPS、重試、超時控制、並發等企業級特性。file_get_contents 會佔用大量記憶體,建議分塊讀取寫入 Socket。HttpRequest 類別展示了 PHP 底層 Socket 通訊的核心邏輯,是理解 HTTP 協議、打造自定義 HTTP 客戶端的優秀教學範例。實務專案中請優先採用成熟函式庫,專注於業務邏輯而非協議細節。