サーバー間でファイルを連携・転送する方法として、SFTPやFTPSは長年のデファクトスタンダードです。これらは非常に堅牢で信頼性の高いプロコルですが、時には「このためだけにSFTPサーバーを立てたり、ファイアウォールのポートを新しく開けたりするのは面倒だ…」と感じることもあります。
もし連携先のサーバーが既にWebサーバー(HTTPS)を公開しているなら、その仕組みを流用してファイルをアップロードするAPIを実装するのはいかがでしょうか。
この記事では、PythonのWebフレームワークであるFlaskを使ってファイルアップロードAPIサーバーを構築し、クライアントからはシンプルなバッチファイルでファイルを送信する方法を紹介します。
- Flaskを使ってファイルアップロードAPIの作成方法がわかります。
- FlaskのHTTPS設定、APIキー利用といったセキュリティー対策がわかります。
- 本API向けのcurlによるファイルアップロード方法がわかります。
この記事のプログラムは、以下の環境で開発および動作確認を行っています
・OS: Windows11
・言語: Python 3.13
・Webフレームワーク: Flask 3.1.1
なぜAPI経由のファイルアップロードなのか?
SFTPの代わりにAPIを利用するメリットはいくつかあります。
- ポート/サービス管理の簡素化: Webサーバーが使うHTTPS(443)ポートさえ開いていればよく、追加のポート開放やSFTPデーモンの設定・管理が不要になります。
- 認証・認可の柔軟性: 既存のWebアプリケーションの認証基盤(OAuth, JWTなど)に乗せることが容易です。今回は簡易なAPIキー認証を採用しますが、より高度なアクセス制御も実現できます。
- 後続処理との連携: ファイルを受け取った後に、データベースにメタ情報を保存したり、別のサービスに通知を送ったりといった追加処理を、同じアプリケーション内でシームレスに実装できます。
- クライアントの実装容易性: cURLのような標準的なHTTPクライアントが多くの環境で利用できるため、クライアント側に特別なライブラリやソフトウェアを導入する必要がありません。
表にまとめると、次の通りです。
サーバー側の実装 (Python Flask)
まずはファイルを受け取るサーバー側のコードです。Pythonの軽量WebフレームワークであるFlaskを使用します。
# -*- coding: utf-8 -*-
import os
from flask import Flask, request, jsonify
from werkzeug.utils import secure_filename
# --- 設定項目 ---
UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'uploads')
API_KEY = 'your-secret-api-key'
ALLOWED_EXTENSIONS = {'txt', 'pdf'}
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 20 * 1024 * 1024
# app.config['JSON_AS_ASCII'] = False # jsonifyで日本語を正しく表示するために必要です
app.json.ensure_ascii = False # 日本語文字化け対応
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
def allowed_file(filename):
"""許可された拡張子かチェックする"""
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['POST'])
def upload_file():
# --- 1. APIキーのチェック ---
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return jsonify({'error': 'Authorizationヘッダーが存在しないか、形式が不正です'}), 403
token = auth_header.split(' ')[1]
if token != API_KEY:
return jsonify({'error': 'APIキーが無効です'}), 403
# --- 2. ファイル存在チェック ---
if 'file' not in request.files:
return jsonify({'error': 'リクエストにファイルパートが含まれていません'}), 400
file = request.files['file']
# --- 3. ファイル名とファイル形式のチェック ---
if file.filename == '':
return jsonify({'error': 'ファイルが選択されていません'}), 400
if not allowed_file(file.filename):
return jsonify({'error': f"許可されていないファイル形式です。許可されている形式: {list(ALLOWED_EXTENSIONS)}"}), 400
# --- 4. ファイルの保存 ---
if file:
filename = secure_filename(file.filename)
save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(save_path)
print(f"ファイルが保存されました: {save_path}")
payload = {
'message': 'ファイルのアップロードに成功しました',
'filepath': save_path
}
return jsonify(payload), 200
return jsonify({'error': '不明なエラーが発生しました'}), 500
if __name__ == '__main__':
app.run(port=5002, debug=True, ssl_context='adhoc')
サーバーコード解説
このサーバーコードはシンプルに見えますが、安全なファイルアップロード機能を実現するための重要なセキュリティ対策がいくつも施されています。一つずつ詳しく見ていきましょう。
1. 認証:APIキーによるアクセス制御
このAPIの最初の関門はAPIキーのチェックです。
# --- 1. APIキーのチェック ---
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return jsonify({'error': 'Authorizationヘッダーが存在しないか、形式が不正です'}), 403
token = auth_header.split(' ')[1]
if token != API_KEY:
return jsonify({'error': 'APIキーが無効です'}), 403
- 目的: 許可されたクライアントだけがファイルをアップロードできるように制限します。
- 仕組み: クライアントはリクエストヘッダーに
Authorization: Bearer <あなたのAPIキー>という形式で秘密のキーを含める必要があります。サーバー側では、このヘッダーが存在すること、形式が正しいこと、そしてAPI_KEYとして設定された値と一致することを検証します。一致しなければ処理を中断し、403 Forbidden(アクセス拒否)エラーを返します。 - 本番環境での注意点: サンプルコードの
'your-secret-api-key'はあくまで例です。本番環境では、UUID生成ツールなどを利用して、推測困難な長く複雑な文字列(例:f47ac10b-58cc-4372-a567-0e02b2c3d479)を使用してください。また、このキーをコード内に直接書き込むのではなく、環境変数や設定ファイル、シークレット管理サービスから読み込むようにすることが強く推奨されます。
2. ファイル名のサニタイズ:ディレクトリトラバーサル攻撃の防止
最も重要なセキュリティ対策の一つが、ファイル名の無害化(サニタイズ)です。
# --- 4. ファイルの保存 ---
if file:
filename = secure_filename(file.filename)
# ...
file.save(save_path)
- 目的: 悪意のあるファイル名によって、意図しない場所にファイルを保存される「ディレクトリトラバーサル攻撃」を防ぎます。
- 仕組み: 例えば、攻撃者がファイル名として
../../etc/passwdのような文字列を送信してきたとします。もしファイル名をチェックせずにそのままパスとして結合してしまうと、ウェブサーバーの管理外の重要なシステムファイルが上書きされてしまう危険性があります。 Flaskのwerkzeug.utilsに含まれるsecure_filename()関数は、このような危険な文字列(../や/など)をファイル名から取り除き、安全なファイル名(例:etc_passwd)に変換してくれます。これにより、ファイルは必ずUPLOAD_FOLDER内に保存されることが保証されます。
3. 拡張子フィルタリング:意図しないファイルのアップロード防止
受け付けるファイルの種類を限定することも、サーバーを守る上で不可欠です。
ALLOWED_EXTENSIONS = {'txt', 'pdf'}
# ...
if not allowed_file(file.filename):
return jsonify({'error': f"..."}), 400
- 目的: 実行可能なスクリプトファイル(例:
.php,.py,.sh)やウイルスなどがアップロードされるのを防ぎます。 - 仕組み:
ALLOWED_EXTENSIONSで許可する拡張子を「ホワイトリスト」として定義します。アップロードされたファイル名から拡張子を取り出し、このリストに含まれているかを確認します。リストにない拡張子のファイルは拒否することで、サーバー上で予期しないプログラムが実行されるリスクを大幅に低減できます。
4. ファイルサイズの制限:DoS攻撃の緩和
巨大なファイルによる攻撃からサーバーリソースを守ります。
app.config['MAX_CONTENT_LENGTH'] = 20 * 1024 * 1024 # 20MB
- 目的: 悪意のあるユーザーが非常に巨大なファイルを送りつけて、サーバーのディスクスペースやメモリを枯渇させるサービス妨害(DoS)攻撃を防ぎます。
- 仕組み: この一行を設定するだけで、Flaskは指定されたサイズ(この例では20MB)を超えるリクエストボディを受け取った時点で、自動的に接続を中断し
413 Payload Too Largeエラーを返してくれます。これにより、アプリケーション本体が巨大なデータを処理する前に攻撃を遮断できます。
5. 通信の暗号化:HTTPSの利用
APIキーやファイルデータがネットワーク上で盗聴されるのを防ぎます。
if __name__ == '__main__':
app.run(..., ssl_context='adhoc')
- 目的: クライアントとサーバー間の通信を暗号化し、第三者によるデータの盗聴や改ざん(中間者攻撃)を防ぎます。
- 仕組み:
ssl_context='adhoc'は、開発を手軽にするためにFlaskが一時的な自己署名証明書を生成してHTTPS通信を有効にする機能です。これはあくまで開発用です。 本番環境では、Let’s Encryptなどの認証局から発行された正規のSSL/TLS証明書を設定したNginxやApacheのようなリバースプロキシサーバーの後ろでFlaskアプリケーションを動作させるのが一般的です。
その他セキュリティー対応
今回はプログラム内における簡単なセキュリティー対応策を記載しましたが、本番環境ではインフラレイヤーの対応策も講じてください。サーバー間のAPI利用ですから、IPフィルタリングが最も重要で効果的な対応でしょう。他にも、ファイアーウォールやWAFの導入、mTLS等、あなたが求めるセキュリティーレベルまで対応を行いましょう。
クライアント側の実装 (Windowsバッチファイル版)
クライアント側は、Windows標準の機能でHTTP通信を行えるcurlコマンドを利用したバッチファイルで実装します。
@echo off
rem The API key must match the one in fileup.py
set API_KEY=your-secret-api-key
rem -k is added to allow self-signed certificates
curl -k -X POST -H "Authorization: Bearer %API_KEY%" -F "file=@%~dp0sample.txt" https://127.0.0.1:5002/upload
pause
バッチファイル解説
set API_KEY=...:API_KEYという名前の環境変数を定義しています。サーバー側で設定したキーと一致させる必要があります。curlコマンドの各オプション:-kまたは--insecure: サーバーが自己署名証明書など、信頼された認証局によって署名されていない証明書を使っている場合に、証明書の検証エラーを無視して通信を許可します。テスト用のadhoc証明書を使っているため、このオプションが必要です。-X POST: HTTPリクエストのメソッドとしてPOSTを指定します。-H "Authorization: Bearer %API_KEY%": HTTPヘッダーを追加します。ここではAuthorizationヘッダーに、環境変数%API_KEY%の値を埋め込んだBearerトークンを設定しています。-F "file=@%~dp0sample.txt":multipart/form-data形式でデータを送信します。これはファイルアップロードで使われる形式です。file=: サーバー側がrequest.files['file']で受け取るためのキー名です。@:@に続く文字列をファイルパスとみなし、そのファイルの内容をデータとして送信することを示します。この@が非常に重要です。%~dp0: これは特殊な変数で、実行しているバッチファイル自身が置かれているディレクトリのパス(例:C:\Users\YourName\Desktop\)に展開されます。これにより、バッチファイルと同じ場所にsample.txtを置いておけば、どこからバッチを実行しても正しくファイルを見つけることができます。
まとめ
この記事では、SFTPのような従来のファイル転送サービスに代わるアプローチとして、既存のWebサーバー(HTTPS)の仕組みを利用したAPI経由でのファイルアップロード方法を紹介しました。
Python Flaskを使えば、セキュリティチェックを組み込んだ堅牢なアップロードサーバーを驚くほど簡単に構築できます。また、クライアント側も特別なツールを必要とせず、curlコマンドを記述したシンプルなバッチファイルだけでファイル送信を実現できました。
ファイアウォールの設定変更や新たなサービスの管理といった手間を省けるこの方法は、特に既存のWebサービスにファイル連携機能を追加したい場合に非常に有効です。ぜひ、ご自身のプロジェクトでもこのモダンなファイル連携手法を試してみてください。

