PHPデモ/「チャット:SPA(Single Page Application)」:「AJAX、タイマー、ファイルのアップ、ファイル・ロック」(Ver.18)
デモなのでイロイロ手抜きをしています。
インターネット上で公開する「フォルダー名、ファイル名」には使ってはダメな文字があります(非常に制限が厳しい)、全角も使ってはダメ。
このデモではインターネット上に関してはencodeURI()で%エスケープしているので、一応インターネット上でも全角文字のファイル名もおkにしてあります。
ただし、このデモではアップロードするファイル名を流用してサーバーに保存しているので、全角に対応してない(UNIX系の)サーバーの場合は、ファイル名に全角が含まれているファイルはアップしないように、ご注意下さい。
対応策としては、オリジナルの全角のファイル名とは無関係の英数字のファイル名に変更するなど。
下記を参照すれば分かる通り、英数字のファイル名に変換するにしても、変な記号とかは使ったらダメと言う事です。
【WEB】URLで使用できる文字の規定 RFC3986 Section2.3
https://ses-blog.net/archives/web/983.html
>チルダはユーザディレクトリ、ピリオドはファイルの拡張子で使われるため、実際は以下と考えること。
>・半角大文字アルファベット A~Z
>・半角小文字アルファベット a~z
>・半角数字 0~9
>・ハイフン –
>・アンダースコア _
ザックリと言うと、ブラウザ側に表示されてない差分のデータをサーバー側から持って来ています。
そうすれば効率は良いですが、プログラムとしては、見通しが可成り悪くなってしまうデメリットもありますが(つまり他人が読みずらいと言うこと)。
レスポンスの遅延を想定して、タイマー処理はsetInterval()では無く、setTimeout()にする、(レスポンスの遅延を想定して)レスポンスが来てからsetTimeout()を設定する。
つまりsetTimeout()を設定する場所はどこでも良いと言う訳では無い。
サーバー・サイドの場合、多数のユーザーが同時にアクセスした場合を考慮する必要があります。
具体的にはサーバー・サイドの場合は、ファイル出力時にファイルをロックしないとデータ破壊される危険性があります。
また ファイルの書き換え(ファイル入力してファイル出力する)は、ファイル入出力を一体としてロックしないとデータ破壊される危険性があります、つまり ファイル入力とファイル出力でファイル・ロックを2つに分けるとファイル・データが破壊される危険性があります。
下記デモでもファイル入力とファイル出力でファイル・ロックは1つだけだと言うことに注意して下さい。
なお (データの削除などで)ファイル・サイズが前より小さくなる場合は「rewind(~)、ftruncate(~,0)」で一旦ファイル・サイズをゼロにする必要があります。
【index.php】
<?php
$fnHTMLSpecialcChars = "htmlspecialchars";
$fnHTMLEntities = "htmlentities";
$fnRawURLEncode = "rawurlencode";
$fnFile_Article = "File_Article";
$fnFRemoveInner = "FRemoveInner";
$fnFile_Template = "File_Template";
$fnPrintOut = "PrintOut";
// Debug用 Switch : {1, 2}
$nDebugSW = 0;
$sEncode = "UTF-8";
$sASC_LF = "\x0A"; // Line Feed.
$sASC_CR = "\x0D"; // Carriage Return.
$sIndention = $sASC_CR;
$sUE_Indention = rawurlencode($sIndention);
$sDelimiter = "<>"; // "\t"; //
// $Delimiterは実際は"\t"タブ文字などが推奨されますが、
// デバッグ用表示には"<>"の方が分かりやすいでしょう。
$sUE_Delimiter = rawurlencode($sDelimiter);
$sFName_Article = "Article.txt";
$sFName_Template = "Template.html";
$sFFirst_Print = "@Print_";
$sFExt_Print = ".txt";
$nArticleMax = 100;
$nSerialSurp = 0x10000000;
/*
// Debug用
$nArticleMax = 3;
$nSerialSurp = 4;
*/
$Article_Add = false; // true; //
$nLastSerial = -1;
$sPT_Name = "";
if (isset($_POST['Name'])) {
$sPT_Name = $_POST['Name'];
// PrintOut("sPT_Name: {$sPT_Name}");
}
$FName_Print = $sFFirst_Print . $sPT_Name . $sFExt_Print;
if ($nDebugSW) {
touch($FName_Print);
($FHW_Print = fopen($FName_Print, "w"))
|| die("die: fopen w: {$FName_Print}.");
flock($FHW_Print, LOCK_EX); // ファイル排他ロック
// LOCK_SH : ファイル共有ロック
// LOCK_EX : ファイル排他ロック
}
$sPT_SubmitMode = "";
if (isset($_POST['SubmitMode'])) {
$sPT_SubmitMode = $_POST['SubmitMode'];
PrintOut("sPT_SubmitMode: {$sPT_SubmitMode}");
}
PrintOut("Delimiter: {$sUE_Delimiter}");
$sPT_LineMarker = "";
if (isset($_POST['LineMarker'])) {
$sPT_LineMarker = $_POST['LineMarker'];
PrintOut("sPT_LineMarker: {$sPT_LineMarker}");
}
// PrintOut("sPT_LineMarker: {$sPT_LineMarker}");
$sPT_Name = "";
if (isset($_POST['Name'])) {
$sPT_Name = $_POST['Name'];
PrintOut("sPT_Name: {$sPT_Name}");
}
$sPT_Comment = "";
if (isset($_POST['Comment'])) {
$sPT_Comment = $_POST['Comment'];
PrintOut("sPT_Comment: {$sPT_Comment}");
}
// PrintOut("POST['name']: {$_POST['name']}";
// PrintOut("POST['comment']: {$_POST['comment']}";
$sHE_Name = $sPT_Name;
// $sHE_Name = htmlspecialchars($sPT_Name, ENT_QUOTES);
$sHE_Comment = "";
$Article_Dsp = true; // false; //
if ($sPT_SubmitMode == "AJAX") {
// $AJAX = true; // false; //
}
if ($sPT_SubmitMode == "Article_Add" && "" < $sPT_Name && "" < $sPT_Comment) {
PrintOut("if (sPT_Name && sPT_Comment &&)");
$Article_Add = true; // false; //
PrintOut("Article_Add: {$Article_Add}");
} else {
PrintOut("else (sPT_Name && sPT_Comment &&)");
// $sHE_Comment = $sPT_Comment;
}
$sResponse = "";
if (!$sPT_SubmitMode) {
$sResponse = File_Template();
} else {
$sResponse = File_Article($sPT_LineMarker);
}
if ($FHW_Print) {
// ファイルclose // ファイル・ロックも開放
fclose($FHW_Print)
|| die("die: fclose: {$FName_Print}.");
}
echo $sResponse;
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
function PrintOut($msg)
{
global $FHW_Print, $FName_Print, $sPT_SubmitMode;
if ($FHW_Print) {
$msg = $msg ? $msg : "";
fwrite($FHW_Print, $msg . PHP_EOL)
|| die("die: fwrite: {$FName_Print}.");
}
}
function File_Template()
{
global $sFName_Template, $nDebugSW, $sUE_Delimiter, $sUE_Indention,
$sIndention, $nArticleMax;
// touch($sFName_Template);
($FHR_Template = fopen($sFName_Template, "r"))
|| die("die: fopen r: {$sFName_Template}.");
// flock($FHW_Template, LOCK_EX); // ファイル排他ロック
// LOCK_SH : ファイル共有ロック
// LOCK_EX : ファイル排他ロック
// $a1sTemplate = [];
$sTemplate = "";
while ($line = fgets($FHR_Template)) {
$sTemplate .= $line . $sIndention;
}
$sTemplate = str_replace('<&$DebugSW>', $nDebugSW, $sTemplate);
$sTemplate = str_replace('<&$UE_Delimiter>', $sUE_Delimiter, $sTemplate);
$sTemplate = str_replace('<&$UE_Indention>', $sUE_Indention, $sTemplate);
$sTemplate = str_replace('<&$ArticleMax>', $nArticleMax, $sTemplate);
// ファイルclose // ファイル・ロックも開放
fclose($FHR_Template)
|| die("die: fclose: {$sFName_Template}.");
return $sTemplate;
}
function File_Article($sMarker)
{
PrintOut("");
PrintOut("function File_Article($sMarker)");
global $sFName_Article, $sDelimiter, $AJAX,
$sPT_Name, $sPT_Comment, $Article_Add,
$nArticleMax, $sIndention, $nSerialSurp;
$oTimeStamp = new DateTime();
$sSummary = "";
touch($sFName_Article);
($FHRP_Article = fopen($sFName_Article, "r+"))
|| die("die: fopen r+: {$sFName_Article}.");
flock($FHRP_Article, LOCK_EX); // ファイル排他ロック
// LOCK_SH : ファイル共有ロック
// LOCK_EX : ファイル排他ロック
$serial = PHP_INT_MIN;
$begin = PHP_INT_MIN;
$line = null;
$a1sArticle = [];
while ($line = fgets($FHRP_Article)) {
$line = str_replace(PHP_EOL, '', $line);
$a1sArticle[] = $line;
$col = explode($sDelimiter, $line);
}
if (count($a1sArticle)) {
$col = explode($sDelimiter, $a1sArticle[0]);
$serial = (int)$col[0];
}
if ($Article_Add) {
$line = "{$oTimeStamp->format('Y/m/d H:i:s v')}{$sDelimiter}{$sPT_Name}{$sDelimiter}{$sPT_Comment}";
if ($serial < 0) {
$serial = 0;
} else {
$serial++;
$serial = $serial % $nSerialSurp;
}
$line = $serial . $sDelimiter . $line;
if ($_FILES) {
$i = 0;
// foreach ($_FILES['FUpload'] as $k => $v) {
/*
画像におけるMimeTypeと拡張子の一覧リスト
| 拡張子 | MimeType |
|--------|------------|
| .bmp | image/bmp |
| .gif | image/gif |
| .ico | image/vnd.microsoft.icon |
| .jpeg | image/jpeg |
| .jpg | image/jpeg |
| .png | image/png |
| .svg | image/svg+xml |
| .tif | image/tiff |
| .tiff | image/tiff |
*/
// ↑jpgはjpegの省略形、tifはtiffの省略形なので同義
if (count($_FILES['FUpload']['name'])) {
}
$dname = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://') . $_SERVER['HTTP_HOST'] . dirname($_SERVER['SCRIPT_NAME']);
for ($i = 0; $i < count($_FILES['FUpload']['name']); $i++) {
PrintOut(
"" .
"_FILES['FUpload'][name]: {$_FILES['FUpload']['name'][$i]}, " .
"_FILES['FUpload'][type]: {$_FILES['FUpload']['type'][$i]}, " .
"_FILES['FUpload'][tmp_name]: {$_FILES['FUpload']['tmp_name'][$i]}, " .
"_FILES['FUpload'][size]: {$_FILES['FUpload']['size'][$i]}, " .
"_FILES['FUpload'][error]: {$_FILES['FUpload']['error'][$i]}, " .
""
);
$fname0 = "{$_FILES['FUpload']['name'][$i]}";
// ↑インターネット上で公開する「フォルダー名、ファイル名」には使ってはダメな文字があります(非常に制限が厳しい)、全角も使ってはダメ
if ($fname0) {
if (!file_exists($serial)) {
mkdir($serial);
// ↑インターネット上で公開する「フォルダー名、ファイル名」には使ってはダメな文字があります(非常に制限が厳しい)、全角も使ってはダメ
}
$fname1 = "{$i}.{$fname0}";
// $fname2 = rawurlencode("{$i}.{$fname0}");
move_uploaded_file($_FILES['FUpload']['tmp_name'][$i], "{$serial}/{$fname1}");
$line .= $sDelimiter . "{$dname}/{$serial}/{$fname1}";
}
}
}
array_unshift($a1sArticle, $line);
// $a1sArticle = array_slice($a1sArticle, 0, $nArticleMax);
}
// $nLastSerial = $serial;
$iMarker = $sMarker == "" ? -1 : (int)$sMarker;
PrintOut("sMarker : {$sMarker}; iMarker : {$iMarker}; ");
$a1sNewArticle = [];
$article = "";
$summary = true; // false; //
PrintOut("");
PrintOut("for (i = 0; i < count(a1sArticle); i++)");
for ($i = 0; $i < count($a1sArticle); $i++) {
$line = $a1sArticle[$i];
$col = explode($sDelimiter, $line);
$serial = (int)$col[0];
PrintOut("serial : {$serial}, ");
PrintOut("if ($i < nArticleMax)");
if ($i < $nArticleMax) {
PrintOut("if ($i < $nArticleMax)");
PrintOut("line : {$line}, ");
if ($iMarker == $serial) {
PrintOut("if (iMarker == serial)");
PrintOut("serial: $serial, ");
$summary = false; // true; //
// break;
}
if ($summary) {
$sSummary .= $line . $sIndention;
}
$a1sNewArticle[] = $line;
$article .= ($i != 0 ? $sIndention : "") . $line;
$nArticleSize = count($a1sNewArticle);
PrintOut("count(a1sNewArticle) : {$nArticleSize}, ");
PrintOut("article : {$article}, ");
} else {
$nArticleSize = count($a1sArticle);
$col = explode($sDelimiter, $a1sArticle[$i]);
$serial = (int)$col[0];
if (file_exists($serial)) {
PrintOut("if (file_exists($serial))");
FRemoveInner($serial);
rmdir($serial);
// ↑インターネット上で公開する「フォルダー名、ファイル名」には使ってはダメな文字があります(非常に制限が厳しい)、全角も使ってはダメ
}
}
}
if ($Article_Add) {
$a1sArticle = $a1sNewArticle;
fseek($FHRP_Article, 0);
rewind($FHRP_Article)
|| die("die: fseek: {$sFName_Article}.");
ftruncate($FHRP_Article, 0)
|| die("die: ftruncate: {$sFName_Article}.");
foreach ($a1sArticle as $key => $line) {
PrintOut($line);
fwrite($FHRP_Article, $line . PHP_EOL)
|| die("die: fwrite: {$sFName_Article}.");
}
}
// ファイルclose // ファイル・ロックも開放
fclose($FHRP_Article)
|| die("die: fclose: {$sFName_Article}.");
PrintOut($sSummary);
return $sSummary;
}
function FRemoveInner($path)
{
if (!is_dir($path)) {
return;
}
$files = glob($path . '/*');
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
} else {
FRemoveInner($file);
}
}
}
【Template.html】
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
</head>
<style>
p,
section {
border: thin solid #000;
min-height: 1.5em;
}
#idDebug,
#idDebugControl {
display: none;
}
</style>
<body>
<form method="POST" id="idForm" enctype="multipart/form-data" action="">
<input type="text" id="idName" name="Name" placeholder="Name"><br>
<input type="text" id="idComment" name="Comment" placeholder="Comment"><br>
<input type="file" name="FUpload[]" placeholder="file"><br>
<input type="file" name="FUpload[]" placeholder="file"><br>
<input type="file" name="FUpload[]" placeholder="file"><br>
<!-- ↑PHPの場合、一意のnameで複数のデータに対応する場合は「[]」の記述が必要(PHPの独自仕様なので、普通は そんな事はしない) -->
<!-- ↑1つのinputタグで複数のfileをアップしたい場合は設定が必要 -->
<button type="button" onclick="sArticleMode='Article_Add';">Submit</button><br>
<input type="hidden" id="idLineMarker" name="LineMarker">
<input type="hidden" id="idSubmitMode" name="SubmitMode" value="AJAX">
</form>
<main id="idMain">
<div id="idDebugControl">
<button type="button" onclick="AJAXComm();">AJAXComm</button><br>
</div>
<section id="idDebug">
</section>
<section id="idMessage">
</section>
<section id="idArticle">
</section>
</main>
</body>
<script>
nDebugSW = Number("<&$DebugSW>");
sIndention = decodeURI("<&$UE_Indention>");
sDelimiter = decodeURI("<&$UE_Delimiter>");
nArticleMax = Number("<&$ArticleMax>");
nTimeout = 500;
sArticleMode = "";
LineMarker = "";
// wDummy = document.createElement('p');
wDebug = document.getElementById("idDebug");
wDebugControl = document.getElementById("idDebugControl");
wMessage = document.getElementById("idMessage");
wForm = document.getElementById("idForm");
wName = document.getElementById("idName");
wComment = document.getElementById("idComment");
a1wFUpload = document.getElementsByName("FUpload[]");
wSubmitMode = document.getElementById("idSubmitMode");
wLineMarker = document.getElementById("idLineMarker");
wArticle = document.getElementById("idArticle");
ConsoleOut(`nDebugSW: ${nDebugSW}`);
ConsoleOut(`sDelimiter: ${sDelimiter}`);
if (2 <= nDebugSW) {
wDebug.style.display = "block";
wDebugControl.style.display = "block";
}
function AJAXComm() {
ConsoleOut();
ConsoleOut("function AJAXComm()");
var sAJAX_Charset = "UTF-8";
var sAJAX_Server = "";
var sQueryParameter = `Submit=AJAX&LineMarker=${LineMarker}&`;
sQueryParameter += `Name=${encodeURI(wName.value)}&`;
wLineMarker.value = LineMarker;
ConsoleOut(`sArticleMode: ${sArticleMode}, `);
var err = false; // true; //
if (sArticleMode == 'Article_Add') {
ConsoleOut("if (sArticleMode == 'Article_Add')");
ConsoleOut(`wName.value: ${wName.value}, `);
ConsoleOut(`wComment.value: ${wComment.value}, `);
ConsoleOut(`wSubmitMode.value: ${wSubmitMode.value}, `);
wMessage.innerText = "";
if (!wName.value) {
err = true; // false; //
wMessage.innerText += "★Nameが入力されていません\n";
}
if (!wComment.value) {
err = true; // false; //
wMessage.innerText += "★Commentが入力されていません\n";
}
if (!err) {
wSubmitMode.value = sArticleMode;
}
sArticleMode = "";
}
ConsoleOut(`sQueryParameter: ${sQueryParameter}, `);
var sAJAX_Parameter = "";
var oAJAX = new XMLHttpRequest();
oAJAX.onload = function () {
if (wSubmitMode.value == 'Article_Add') {
sArticleMode = "";
wComment.value = "";
for (var i = 0; i < a1wFUpload.length; i++) {
a1wFUpload[i].value = "";
}
}
wSubmitMode.value = "AJAX";
ConsoleOut();
ConsoleOut("oAJAX.onload");
if (this.readyState === 4 && this.status === 200) {
ConsoleOut("this.readyState: " + this.readyState);
ConsoleOut("this.status: " + this.status);
var res = oAJAX.responseText;
ConsoleOut(`res: ${res}, `);
wDebug.innerText = res;
if (res) {
var a1sArticle = res.split(sIndention);
ConsoleOut("a1sArticle");
ConsoleOut(a1sArticle);
var col = null;
var nImgStart = 4;
for (var i = a1sArticle.length - 1; 0 <= i; i--) {
var line = a1sArticle[i];
if (" " <= line) {
var wP = document.createElement('p');
// wP.innerText = line;
wArticle.prepend(wP);
col = line.split(sDelimiter);
ConsoleOut("");
ConsoleOut("for (var j = 0; j < col.length; j++)");
for (var j = 0; j < col.length; j++) {
ConsoleOut(
`j: ${j}, ` +
`col[${j}]: ${col[j]}, ` +
"");
if (j < nImgStart) {
// ConsoleOut("if (j < nImgStart)");
// wDummy.innerText = col[j];
wP.innerHTML += KillTag(col[j]);
} else {
ConsoleOut("if (j < nImgStart)else");
wP.innerHTML += `<a target="_blank" href="${encodeURI(col[j])}">${col[j]}</a>`;
ConsoleOut(`wP.innerHTML: ${wP.innerHTML}, `);
}
// wDummy.innerText = sDelimiter;
wP.innerHTML += KillTag(sDelimiter);
ConsoleOut(`wP.innerHTML: ${wP.innerHTML}, `);
}
}
}
/*
var wFirst = wArticle.firstElementChild;
if (wFirst) {
col = wFirst.innerText.split(sDelimiter);
}
*/
if (col) {
LineMarker = Number(col[0]);
}
ConsoleOut(`LineMarker: ${LineMarker}, `);
while (nArticleMax < wArticle.childElementCount) {
wArticle.lastElementChild.remove();
}
}
}
if (nDebugSW < 2) {
setTimeout(AJAXComm(), nTimeout);
}
}
oForm = new FormData(wForm);
ConsoleOut(`wForm: ${wForm}, `);
ConsoleOut(wForm);
ConsoleOut(`oForm: ${oForm}, `);
for (var d of oForm.entries()) {
ConsoleOut(`${d[0]}: ${d[1]}`);
}
// oAJAX.overrideMimeType('text/plain; charset=' + sAJAX_Charset);
oAJAX.open('POST', sAJAX_Server);
// oAJAX.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// oAJAX.setRequestHeader("Content-Type", "multipart/form-data");
oAJAX.send(oForm);
ConsoleOut(`oAJAX.send(oForm)`);
// ConsoleOut(`sQueryParameter: ${sQueryParameter}, `);
}
if (nDebugSW < 2) {
setTimeout(AJAXComm(), nTimeout);
}
function KillTag(txt) {
return txt
.replace(/&/gs, '&')
.replace(/</gs, '<')
.replace(/>/gs, '>')
.replace(/"/gs, '"')
.replace(/'/gs, ''');
}
function ConsoleOut(msg) {
if (nDebugSW) {
msg = msg ? msg : "";
console.log(msg);
}
}
</script>
</html>
コメント
コメントを投稿