シン・クラウド for FreeでPythonで掲示板を作ってみた!

シン・クラウド for FreeはPythonが使えます。
CGIというものによって、
クライアントからのリクエストをPythonで処理しレスポンスを返します。
設定も簡単ですが、ちょっとハマるところもあるので、
そういったところを紹介できればと思います。
しかし下に書いてあることが発生したことも留意してください。(ここでやったことが原因かは分かららない。)
掲示板のデータをcsvとsqlite3で保存するタイプを作ってみました。
イチゲブログ掲示板【シン・クラウド for Free】(CSV)
イチゲブログ掲示板【シン・クラウド for Free】(Sqlite3)
PythonでAPI出力したものも作ってみました。
じゃんけんゲーム

2023/12/9追記
原因は分からないが、以下のようなことがあったのでご注意ください。
以下抜粋です。
【シン・レンタルサーバー】■重要■お客様のサーバーアカウントにおける不正なアクセスの検知および制限の実施について
この度の負荷上昇に際してプロセスの稼働状況を確認しましたところ、以下の不正なプロセスが多数稼働しておりました。
▼稼働していた不正なプロセス——————————————————-./php

[不正プログラムと思われるファイル一覧]
/home/cf193110/cf193110.cloudfree.jp/public_html/wp/wp-admin/includes/index.php
/home/cf193110/cf193110.cloudfree.jp/public_html/wp/wp-content/uploads/2023/11/byp.php
/home/cf193110/.php

お客様のサーバーアカウントにおいて前述の不正なファイル(ウイルス、マルウェアなど)が検出されるとともに、日本国外からの不審なアクセスを確認いたしました。
▼不正アクセスの根本原因————————————————————
(1)お客様が運用中のプログラム(WordPress等)において
 [1]プログラム(WordPress等)の管理パスワードが流出し、第三者に不正ログインされた。
 [2]セキュリティ上問題のある致命的なバグ(脆弱性)が存在し、第三者に脆弱性を利用された。
(2)お客様のサーバーアカウントに関するFTP情報が流出し、第三者に不正にFTP接続をされた。

プログラム(WordPress等)の管理パスワードが流出しプログラムを悪用されたか
お客様が運用中のプログラムの脆弱性を悪用されてしまった可能性が高いものと思われます。

【3】推奨される設定について
お客様のウェブサイトにてPHPプログラムをご利用の場合、サーバーパネルの「php.ini設定」にて「allow_url_fopen」および「allow_url_include」をいずれも「無効(Off)」にすることを強くお勧めいたします。→ということなので両方OFFにした。

詳細は下の記事に書きました。

2024/2/7追記、いつからか分かりませんが(SSH接続設定したときに気づいた)
私の場合、ファイルマネージャーで初期ドメインのディレクトリが表示されなくなりました。

しくみ

CGIとは、プログラムファイルの置かれているURLにクライアント(ブラウザなど)がアクセスすると、Webサーバがオペレーティングシステム(OS)を介してプログラムを起動し、実行結果がクライアントに送信されます。(ChatGpt談)
シン・クラウド for FreeでCGIを使ってPythonを動かすには、
public_htmlにある.htaccessに1文追加して、pythonファイルを同じディレクトリまたは下層のディレクトリにアップロードしアクセス権限を変えるだけで使えました。
クライアントのアクセス先は、そのpythonファイルになります。
レスポンスはpythonファイルでprint文でhtmlを書けばレスポンスされます。

Hellow World(get)

まずはHello Worldを表示させます。
シン・クラウド for FreeはXserverがベースになっているようでXserverのやり方でうまくいきました。
シン・クラウド for Freeのファイルマネージャーで操作できます。

Hellow world!をpythonで出力できました。やり方は参考のリンク先を参照してみてください。
参考:https://hazukei.com/886/
https://xn--ccke1di9d4h.com/blog/xampp%E3%81%8B%E3%82%89-py%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B/#2Xserver%E3%81%AE%E5%A0%B4%E5%90%88
注意点
.htaccessは変更する前にバックアップを取っておいた方がいいと思います。
また.htaccessという名前、たくさんあるので間違えて他の.htaccessを変更してしまうと
大変なので、あまりやらないほうがいいと思います。
メモ帳でファイルを編集すると改行がCRLFになるのでvscodeなど専用エディターでLFにしないと500エラーが発生します。
vscodeの場合(拡張機能によって違うかも)右下にあるCRLFをクリックすればLFとCRLFが選択できるようになるのでLFにする。

ファイルマネージャーの一連の操作をまとめると

formで送る(post)

クライアントからデータを送るpostは、どうやるのか?
formでpostで送ったものはcgiライブラリに用意されている関数で受け取れます。(赤色の部分)
実際に作ったもの→formで送る(test2.py)

#!/usr/bin/python3
import sys
import io
#日本語対応
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
import cgi
# cgiはcgiプログラムに使うモジュール。
import cgitb
# cgitbはcgiプログラムデバッグに関するモジュール。うまくいかない。
cgitb.enable()

name=''
toukou=''
# パラメータを取得するための関数
# get、post区分なしでデータを持ち込む。
form = cgi.FieldStorage()
# パラメータを取得する。
if len(form) > 0:
  name = form.getvalue('name','')
  toukou = form.getvalue('toukou','')

# 画面応答するhtmlドキュメント
html = f"""	
<!DOCTYPE html>	
<html>	
  <head><title>formで送る</title></head>	
  <body>
  <h1>formで送る</h1>
    <form action='' method='post'>
    <label for="user_name">名前:</label>
    <input type='text' id="user_name" name='name' value=''><br><br>
    <label for="toukou">投稿:</label>
    <input type='text' id="toukou" name='toukou' value=''><br><br>	
    <input type="submit" value="送信">
    </form>	
    <br />	

    <h2>投稿内容</h2>
    名前 - {name} <br />	
    投稿 - {toukou} <br />
""";	
# ヘッダータイプ設定
print("Content-type: text/html; charset=UTF-8")
# httpプロトコールでheaderとbodyの区分は改行なので必ず入れる。なければエラーに発生する。(bodyがないhttpファイルなので)
print('')
# バーディーを出力
print(html)
print("</body></html>")

test2.pyと同じ階層にhtmlでformを作ってtest2.pyにpostしてみてもうまくいきます。
実際につくったもの→form.html
ただ、このformを別のサーバーにおいて同じことをやるとCorsエラーになると思ったけど
ローカルのパソコンで下のファイルを開いて送信したら送られた。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>formで送る(html分離)</title>
</head>
<body>
    <form action="https://cf193110.cloudfree.jp/python/test2.py/" method="post">
        <label for="user_name">名前:</label>
        <input type='text' id="user_name" name='name' value=''><br><br>
        <label for="toukou">投稿:</label>
        <input type='text' id="toukou" name='toukou' value=''><br><br>	
        <input type="submit" value="送信">
      </form>
</body>
</html>

Javascriptのfetchで送ったらCorsエラーになったのでJavascriptだとダメなのか?

出たエラー(開発ツールのコンソールで確認)
Access to fetch at 'https://cf193110.cloudfree.jp/python/test2.py/' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Javascript利用時のCorsエラーの問題は結構やっかいで
他のサーバーから利用するときに問題になります。
もしかしたPythonで何かすればオリジン無視できるようになるかもしれませんが分かりません。
まあ同じところにおいて同一オリジンにすれば送れます。
下の「APIも作れる」のところの「じゃんけんゲーム」はfetchで送っています。

【Django入門】Djangoのセキュリティ実験してみた!XSS、CSRF、CORSなど
Djangoが備えているセキュリティ機能を理解するため簡単な例で実験しました。入力して表示するだけのサンプルプログラムでXSS、CSRF、CORSに対する実験です。その他のセキュリティ項目もご紹介します。この記事はDjangoセキュリティの

掲示板

気休めのセキュリティ対策としてパーミションを少し工夫してみた。
データを保存するディレクトリを最下層にして書き込み可能な7にした。

public_html/
 ├ python/ 505
    ├ hozon/ 707
      │   ├toukou_test.csv 606
      │   └main.db 646(main.dbはsqlite3によりグループが4で作られたのでそのままにした)
      ├keijisfcsv.py 505
      └keijifsq.py 505

csv保存タイプ

CSVに投稿データを保存するタイプとsqlite3に保存するタイプを作りました。
イチゲブログ掲示板【シン・クラウド for Free】(CSV)
サニタイジング(投稿欄にJavascriptコードを書かれても文字列として扱うようにする)は
html.escape(squote=True)を利用しています。
以下の処理はpostしたときにリダイレクトする処理だが、なくても基本的には動く、
しかし、これを入れることによってF5(更新)を押したき
formを再送しますかというダイアログが出なくなる。
またこの処理を入れる前、初回の投稿のときに、前のpostが残っていて勝手に投稿されてしまう不具合が起きていたが、これによりなくなった。

redirect=''
略
redirect='<meta http-equiv="refresh" content="0;url=keijisfcsv.py">'
略
{redirect}
#!/usr/bin/python3
import cgi
# cgiはcgiプログラムに使うモジュール
import cgitb
# cgitbはcgiプログラムデバッグに関するモジュールだ。うまくいかない
cgitb.enable()
import sys
import io
#日本語対応
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
import csv
import html
user_name=''
toukou=''
last_name=''
last_toukou=''
# 最終ID
with open('hozon/toukou_test.csv', encoding='utf-8', newline='') as f:
    reader = csv.DictReader(f)
    last_row = None
    for row in reader:
        last_row = row
    if last_row is not None:
        last_id = last_row['ID']
        last_name=last_row['名前']
        last_toukou=last_row['投稿']
    else:
        last_id=0

# パラメータを取得するための関数
# get、post区分なしでデータを持ち込む。
form = cgi.FieldStorage()
# パラメータuser_nameを取得する。
user_name = form.getvalue('user_name','')
# パラメータのtoukouを取得する。
toukou = form.getvalue('toukou','')
# writerを使用
redirect=''
if (toukou!=last_toukou) and (user_name!='') and (toukou!=''):
    redirect='<meta http-equiv="refresh" content="0;url=keijisfcsv.py">'
    append_data = [[int(last_id)+1, user_name,toukou]]
    with open('hozon/toukou_test.csv', 'a', encoding='utf-8', newline='') as f:
        writer = csv.writer(f)
        writer.writerows(append_data)
with open('hozon/toukou_test.csv', encoding='utf-8', newline='') as f:
    reader = csv.DictReader(f)
    rows = list(reader)
    users_table = '<table border=1 width="100%"><th width="10%">ID</th><th width="20%">名前</th><th width="70%">メッセージ</th>'
    for row in reversed(rows):
    # for row in reader:
    #サニタイジング
        nameout=html.escape(row['名前'],quote=True)
        toukouout=html.escape(row['投稿'],quote=True)
        users_table += '<tr><td>'+str(row['ID'])+'</td><td>'+str(nameout)+'</td><td>'+str(toukouout)+'</td></tr>'
    users_table += '</table>'
# 画面い応答するhtmlドキュメント
html = f"""	
<!DOCTYPE html>	
<html>	
  <head>
    {redirect}
    <title>シン・クラウド for Free掲示板(csv)</title>
  </head>	
  <body style="background-color:lightblue">
    <h1>イチゲブログ掲示板【シン・クラウド for Free】(CSV)</h1>	
    <form action='' method='post'>
    <label for="user_name">名前:</label>
    <input type='text' id="user_name" name='user_name' value=''><br><br>
    <label for="toukou">投稿:</label>
    <input type='text' id="toukou" name='toukou' value=''><br><br>	
    <input type="submit" value="送信">
    </form>	
    <br />	
    <h2>投稿内容</h2>
    <hr/>
    {users_table}
""";	
# ヘッダータイプ設定
print("Content-type: text/html; charset=UTF-8")
# httpプロトコールでheaderとbodyの区分は改行なので必ず入れる。なければエラーに発生する。(bodyがないhttpファイルなので)
print('')
# バーディーを出力
print(html)
print("</body></html>")

keijisfcsv.pyと同階層に,toukou_test.csvというファイル名で
以下の項目列だけ入れたファイルをアップロードしてください。

ID,名前,投稿

sqlite3保存タイプ

sqlite3に保存するタイプは【Python】CGIで掲示板を作成するを、
ほとんどそのまま使わせていただきました。
ファイル名index.pyを参照している部分は自分で使ったkeijisfsq.pyに変えています。
イチゲブログ掲示板【シン・クラウド for Free】(Sqlite3)
サニタイジングについては特に追加しませんでしたが
Javascriptは500エラーになって実行できませんでした。
<hr>線を引くは実行できました。
保存されるデータはmain.dbに保存されます。そのファイルを削除すると初期化できます。

先頭に以下1行追加が必要です。
#!/usr/bin/python3

投稿を降順にするには
for x in db.read_data():
→for x in reversed(db.read_data()):

APIも作れる

APIも作れます。
以下のようにURLの最後に?でparam1、param2に任意の値を入力してアクセスするとそのままレスポンスするコードを作ってアップロードしてあるので、試してみてください。

https://cf193110.cloudfree.jp/python/testapi.py?param1=english&param2=日本語
#!/usr/bin/python3

import cgi
import json

# ヘッダーを設定してJSON形式の出力を返す
def print_json_response(data):
    print("Content-Type: application/json; charset=utf-8")
    print()
    print(json.dumps(data))

form = cgi.FieldStorage()

# パラメーターの取得
param1 = form.getfirst("param1")
param2 = form.getfirst("param2")

# ここで必要な処理を実行し、APIレスポンスデータを生成
response_data = {
    "param1": param1,
    "param2": param2,
    "message": "APIリクエストが正常に処理されました。",
}

# JSON形式でレスポンスを出力
print_json_response(response_data)

fetchで出した手をPythonに送って、apiで相手の手の画像と勝敗を送るようにして作りました。
じゃんけんゲーム

End of script output before headers

VsコードでSSH接続していたら500エラーが出るようになった。
ログを見るとEnd of script output before headers。
リソース制限?VsコードでSSH接続がリソース使ってるかも。
リソースが指すものは恐らくメモリのことでメモリ使いすぎなのかも。
半日したら自然に治ったので可能性は高いと思います。
参考:https://prog.morisakimikiya.com/internal-server-error/
参考:https://i-say.net/note/862/
無料で使わせていただいてるので、制限されても仕方ないです。
下のような経験をしているのでメモリ不足で何が起こっても不思議ではない気はしています。

https://kikuichige.com/24820

まとめ

1番よくハマることは、ネットのコードをコピペしたときに改行がCRLFになってしまい、そのままアップロードすると500エラーになります。
なのでLFにするように気を付けましょう。
あと、日本語対応の以下を入れないで日本語使うと500エラーになります。

#日本語対応
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

イチゲをOFUSEで応援する(御質問でもOKです)Vプリカでのお支払いがおすすめです。
MENTAやってます(ichige)

タイトルとURLをコピーしました