スマホからPCにUSBを介さずにファイルを送りたい
USBって便利ですよね。でも繋ぐのが面倒だったり、ケーブルがないときもあります。クラウドストレージも便利ですが、セキュリティやプライバシーの観点から使いたくない人もいるでしょう。そんなとき、自分で簡単なファイル転送サーバーを立てる方法があります。
import http.server
import cgi
class UploadHandler(http.server.SimpleHTTPRequestHandler):
def do_POST(self):
# フォームデータの解析
form = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD': 'POST'}
)
# 'file' という名前で送られてきたデータを保存
if "file" in form:
file_item = form["file"]
with open(file_item.filename, "wb") as f:
f.write(file_item.file.read())
# 送信後のレスポンス
self.send_response(200)
self.end_headers()
self.wfile.write(b"Upload Successful!")
# ブラウザに表示するHTML(簡易的なアップロードボタン)
def do_GET(self):
if self.path == '/':
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
html = """
<html><body>
<form method="POST" enctype="multipart/form-data">
<input type="file" name="file" style="font-size: 2em;"><br><br>
<input type="submit" value="Upload to PC" style="font-size: 2em;">
</form>
</body></html>
"""
self.wfile.write(html.encode())
else:
super().do_GET()
if __name__ == "__main__":
http.server.test(HandlerClass=UploadHandler, port=8000)
このコードをPCで実行すると、ローカルホストの8000番ポートでファイル転送サーバーが立ち上がります。スマホのブラウザから http://<PCのIPアドレス>:8000/ にアクセスすると、ファイルを選択してアップロードできるフォームが表示されます。アップロードされたファイルは、PCの同じディレクトリに保存されるはずでしたが、Python 3.13以降では、cgi モジュールが、ついに**「完全に削除(Removed)」**されてしまったそうです。
Traceback (most recent call last):
File "/home/miru/tools/server.py", line 2, in <module>
import cgi
ModuleNotFoundError: No module named 'cgi'
それでも代わりの方法はあります。標準の email モジュール(マルチパート解析用)を使うのが、今の「枯れた」モダンなやり方です。
--- /home/miru/tools/server.py 2026-02-28 20:04:37.811910926 +0900
+++ /home/miru/tools/server2.py 2026-02-28 20:08:51.009089661 +0900
@@ -1,27 +1,34 @@
import http.server
-import cgi
+import email.message
+import io
class UploadHandler(http.server.SimpleHTTPRequestHandler):
def do_POST(self):
- # フォームデータの解析
- form = cgi.FieldStorage(
- fp=self.rfile,
- headers=self.headers,
- environ={'REQUEST_METHOD': 'POST'}
- )
+ # ヘッダーから境界文字列(boundary)を取得
+ content_type = self.headers.get('Content-Type')
+ if not content_type or 'multipart/form-data' not in content_type:
+ self.send_error(400, "Expected multipart/form-data")
+ return
+
+ # ボディを解析してファイルを取り出す
+ length = int(self.headers.get('content-length'))
+ body = self.rfile.read(length)
- # 'file' という名前で送られてきたデータを保存
- if "file" in form:
- file_item = form["file"]
- with open(file_item.filename, "wb") as f:
- f.write(file_item.file.read())
-
- # 送信後のレスポンス
- self.send_response(200)
- self.end_headers()
- self.wfile.write(b"Upload Successful!")
+ # メッセージオブジェクトとしてパース
+ msg = email.message_from_bytes(b'Content-Type: ' + content_type.encode() + b'\r\n\r\n' + body)
+
+ for part in msg.walk():
+ if part.get_filename():
+ filename = part.get_filename()
+ payload = part.get_payload(decode=True)
+ with open(filename, 'wb') as f:
+ f.write(payload)
+ print(f"Saved: {filename}")
+
+ self.send_response(200)
+ self.end_headers()
+ self.wfile.write(b"Upload Successful!")
- # ブラウザに表示するHTML(簡易的なアップロードボタン)
def do_GET(self):
if self.path == '/':
self.send_response(200)
@@ -29,9 +36,10 @@
self.end_headers()
html = """
<html><body>
+ <h2>Upload to My PC</h2>
<form method="POST" enctype="multipart/form-data">
- <input type="file" name="file" style="font-size: 2em;"><br><br>
- <input type="submit" value="Upload to PC" style="font-size: 2em;">
+ <input type="file" name="file" style="font-size: 1.5em;"><br><br>
+ <input type="submit" value="Upload" style="font-size: 1.5em;">
</form>
</body></html>
"""
でもこれでは一回に一つのファイルしか送れません。複数ファイルを同時に送るには、HTMLフォームを少し変更して、複数選択を許可する必要があります。
--- /home/miru/tools/server2.py 2026-02-28 20:08:51.009089661 +0900
+++ /home/miru/tools/server3.py 2026-02-28 20:25:15.813981867 +0900
@@ -1,51 +1,51 @@
import http.server
import email.message
-import io
class UploadHandler(http.server.SimpleHTTPRequestHandler):
def do_POST(self):
- # ヘッダーから境界文字列(boundary)を取得
content_type = self.headers.get('Content-Type')
- if not content_type or 'multipart/form-data' not in content_type:
- self.send_error(400, "Expected multipart/form-data")
- return
-
- # ボディを解析してファイルを取り出す
length = int(self.headers.get('content-length'))
body = self.rfile.read(length)
- # メッセージオブジェクトとしてパース
+ # マルチパートデータをパース
msg = email.message_from_bytes(b'Content-Type: ' + content_type.encode() + b'\r\n\r\n' + body)
+ count = 0
for part in msg.walk():
- if part.get_filename():
- filename = part.get_filename()
+ filename = part.get_filename()
+ if filename:
payload = part.get_payload(decode=True)
with open(filename, 'wb') as f:
f.write(payload)
print(f"Saved: {filename}")
+ count += 1
self.send_response(200)
self.end_headers()
- self.wfile.write(b"Upload Successful!")
+ self.wfile.write(f"{count} files uploaded successfully!".encode())
def do_GET(self):
if self.path == '/':
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
+ # 'multiple' 属性を追加し、少しスマホで押しやすくUIを調整
html = """
- <html><body>
- <h2>Upload to My PC</h2>
- <form method="POST" enctype="multipart/form-data">
- <input type="file" name="file" style="font-size: 1.5em;"><br><br>
- <input type="submit" value="Upload" style="font-size: 1.5em;">
- </form>
- </body></html>
+ <html>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <body style="font-family: sans-serif; text-align: center; padding-top: 50px;">
+ <h2>File Uploader</h2>
+ <form method="POST" enctype="multipart/form-data">
+ <input type="file" name="file" multiple style="font-size: 1.2em;"><br><br>
+ <input type="submit" value="Upload All" style="font-size: 1.2em; padding: 10px 20px;">
+ </form>
+ </body>
+ </html>
"""
self.wfile.write(html.encode())
else:
super().do_GET()
if __name__ == "__main__":
+ print("Server started at http://0.0.0.0:8000")
http.server.test(HandlerClass=UploadHandler, port=8000)
で、最終的にこうなりました。
import http.server
import email.message
class UploadHandler(http.server.SimpleHTTPRequestHandler):
def do_POST(self):
content_type = self.headers.get('Content-Type')
length = int(self.headers.get('content-length'))
body = self.rfile.read(length)
# マルチパートデータをパース
msg = email.message_from_bytes(b'Content-Type: ' + content_type.encode() + b'\r\n\r\n' + body)
count = 0
for part in msg.walk():
filename = part.get_filename()
if filename:
payload = part.get_payload(decode=True)
with open(filename, 'wb') as f:
f.write(payload)
print(f"Saved: {filename}")
count += 1
self.send_response(200)
self.end_headers()
self.wfile.write(f"{count} files uploaded successfully!".encode())
def do_GET(self):
if self.path == '/':
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
# 'multiple' 属性を追加し、少しスマホで押しやすくUIを調整
html = """
<html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<body style="font-family: sans-serif; text-align: center; padding-top: 50px;">
<h2>File Uploader</h2>
<form method="POST" enctype="multipart/form-data">
<input type="file" name="file" multiple style="font-size: 1.2em;"><br><br>
<input type="submit" value="Upload All" style="font-size: 1.2em; padding: 10px 20px;">
</form>
</body>
</html>
"""
self.wfile.write(html.encode())
else:
super().do_GET()
if __name__ == "__main__":
print("Server started at http://0.0.0.0:8000")
http.server.test(HandlerClass=UploadHandler, port=8000)
aliasを作っておくと最高に「楽」になります。
# .bashrc や NixOSの shellAliases に
alias share-on='nix-shell -p python3 --run "python3 ~/bin/upload-server.py"'
これで、PCの前で「あ、写真送りたい」と思った瞬間に share-on と打つだけ。スマホからPCにファイルを送るのが、USBもクラウドも使わずに、超簡単になります。セキュリティ的には、同じネットワーク内でしかアクセスできないので、そこまで心配する必要はないでしょう。ただし、公共のWi-Fiなど不特定多数がアクセスできるネットワークではドキドキしながら使うことをおすすめします。