コンテンツにスキップ

PythonでCGI可能なローカルWebサーバー

[質問]

ローカルPCで、自分だけ使うGUIプログラムを作ります。 バックエンドはPythonによるローカルWEBサーバーと、そこで実行されるPython-CGIとします。 フロントエンドはHTML+JavaScriptです。localhostでの利用が前提です。
次の構成で、サンプルプログラムを作ってください。
(1) Pythonを利用した、CGIも実行可能なWebサーバー。
(2) 簡単なCGIで、ドキュメントルート外のファイルを読み書きするサンプル。
(3) CGIにアクセスするHTML+JavaScript。

[回答] Grok

以下は、ご要望通りの完全動作するサンプルプログラムです。
localhost(127.0.0.1)限定で、自分だけが使うGUIプログラムとして最適化しています。

ディレクトリ構成(これを作成してください)

project/                  ← 任意のフォルダ名でOK
├── server.py             ← Webサーバー起動スクリプト
├── www/
│   ├── index.html
│   └── script.js
├── cgi-bin/
│   └── file_handler.py   ← CGI(ドキュメントルート外のファイルを読み書き)
└── data/                 ← 自動作成されます(ノート保存用)

(1) Python Webサーバー(server.py)

import os
from functools import partial
from http.server import HTTPServer, CGIHTTPRequestHandler

def main():
    # ドキュメントルートを www フォルダに固定
    web_dir = os.path.join(os.path.dirname(__file__), 'www')

    # CGI対応ハンドラ(directory指定)
    Handler = partial(CGIHTTPRequestHandler, directory=web_dir)

    # localhost:8000 で起動
    server_address = ('127.0.0.1', 8000)
    httpd = HTTPServer(server_address, Handler)

    print("=== ローカルGUIサーバー起動 ===")
    print(f"http://{server_address[0]}:{server_address[1]}")
    print("ブラウザで上記URLを開いてください。")
    print("終了は Ctrl+C")

    httpd.serve_forever()

if __name__ == "__main__":
    main()

(2) CGIサンプル(cgi-bin/file_handler.py)

ドキュメントルート外(../data/notes.txt)を自由に読み書きします。

#!/usr/bin/env python3
import cgi
import cgitb
import os
import json

cgitb.enable()  # エラー時にブラウザに詳細表示(開発用)

# =============================================
# ドキュメントルート外のファイルパス
# =============================================
script_dir = os.path.dirname(__file__)           # cgi-bin/
project_dir = os.path.dirname(script_dir)        # project/
data_dir = os.path.join(project_dir, 'data')
os.makedirs(data_dir, exist_ok=True)

DATA_FILE = os.path.join(data_dir, 'notes.txt')

# =============================================
# メイン処理
# =============================================
form = cgi.FieldStorage()
action = form.getfirst('action')

if action == 'load':
    content = ''
    if os.path.exists(DATA_FILE):
        with open(DATA_FILE, 'r', encoding='utf-8') as f:
            content = f.read()

    print('Content-Type: application/json')
    print()
    print(json.dumps({'status': 'ok', 'content': content}))

elif action == 'save':
    content = form.getfirst('content', '')
    with open(DATA_FILE, 'w', encoding='utf-8') as f:
        f.write(content)

    print('Content-Type: application/json')
    print()
    print(json.dumps({'status': 'ok'}))

else:
    print('Content-Type: text/plain')
    print()
    print('Unknown action')

Windowsの場合
shebang行(1行目)はそのままでも動きますが、動かない場合は
#!C:\Python312\python.exe(ご自身のPythonパス)に変更してください。


(3) フロントエンド(HTML + JavaScript)

www/index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>ローカルGUIサンプル</title>
    <script src="script.js" defer></script>
    <style>
        body { font-family: sans-serif; margin: 20px; }
        textarea { width: 100%; max-width: 800px; font-size: 16px; }
        button { padding: 10px 20px; font-size: 16px; margin-right: 10px; }
        #status { font-weight: bold; }
    </style>
</head>
<body>
    <h1>ローカルGUIサンプル(Python CGI + HTML+JS)</h1>
    <p>このテキストエリアの内容は <code>../data/notes.txt</code> に保存されます。</p>

    <textarea id="note" rows="20" placeholder="ここに自由に書いてください..."></textarea>
    <br><br>
    <button onclick="loadNote()">📂 読み込み</button>
    <button onclick="saveNote()">💾 保存</button>
    <span id="status"></span>
</body>
</html>

www/script.js

async function loadNote() {
    try {
        const res = await fetch('/cgi-bin/file_handler.py?action=load', { method: 'GET' });
        if (!res.ok) throw new Error('読み込み失敗');
        const data = await res.json();

        document.getElementById('note').value = data.content || '';
        showStatus('✅ 読み込み完了', 'green');
    } catch (e) {
        showStatus('❌ 読み込みエラー: ' + e.message, 'red');
    }
}

async function saveNote() {
    const content = document.getElementById('note').value;
    try {
        const res = await fetch('/cgi-bin/file_handler.py', {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({ action: 'save', content: content })
        });
        if (!res.ok) throw new Error('保存失敗');
        await res.json();
        showStatus('✅ 保存完了', 'green');
    } catch (e) {
        showStatus('❌ 保存エラー: ' + e.message, 'red');
    }
}

function showStatus(msg, color) {
    const status = document.getElementById('status');
    status.textContent = msg;
    status.style.color = color;
    setTimeout(() => status.textContent = '', 3000);
}

// ページを開いたときに自動で読み込み
window.onload = loadNote;


実行手順

  1. 上記のフォルダ・ファイルを作成(コピペでOK)
  2. ターミナル / コマンドプロンプトで project フォルダに移動
  3. 実行:
    python server.py
    
  4. ブラウザで http://127.0.0.1:8000/ を開く

→ テキストを入力 → 「保存」→ ブラウザを閉じても、次に開いたときに「読み込み」で復元されます!

これで完全にローカル完結のGUIプログラムが完成です。
必要に応じてボタンや機能を増やしたり、他のCGIを追加したりしてください。
質問があればいつでもどうぞ!

[管理者注] 修正版

Python 3.13 から cgi モジュールが使えなくなったので、 サードパーティ製の legacy-cgi をインストールすること。

pip install legacy-cgi

あとディレクトリ構成は、次のようにwwwの中にcgi-binを入れないとだめだった。

project/
├── server.py
├── www/
│   ├── cgi-bin/
│   │    └── file_handler.py
│   ├── index.html
│   └── script.js
└── data/

これにともない、file_handler.py の次の1行を修正。

# =============================================
# ドキュメントルート外のファイルパス
# =============================================
script_dir = os.path.dirname(__file__)           # cgi-bin/
project_dir = os.path.dirname(os.path.dirname(script_dir)) # ★ここを修正
data_dir = os.path.join(project_dir, 'data')
os.makedirs(data_dir, exist_ok=True)

なにか別のバグを見つけたらここに追加します。