1. はじめに
前回の記事では、SocketDebuggerを使ってTCP通信でJSON形式のデータを返すサーバーを構築しました。
ところが、読者の方から「HTTPによるREST APIサーバーを作ったのかと思った」というコメントをいただきました。コメントありがとうございます🙇
確かに「JSONを返す」「サーバーを構築する」といったキーワードは、HTTPベースのWeb APIを連想させるかもしれません🤔
でも実際には、HTTPは使っておらず、純粋なTCP通信でした。
そこで今回は、SocketDebuggerのLuaスクリプト機能を使って、本当にHTTPを模擬し、REST APIサーバーとして動作させてみることに挑戦してみました!
「SocketDebuggerってそんなことまでできるの?」と思った方、ぜひ最後まで読んでみてください👍
2. そもそもREST APIって?
イラスト:Copilot(Microsoft)による生成画像
REST(Representational State Transfer)APIとは、HTTPを使ってクライアントとサーバーがデータをやり取りするための設計スタイルです。主にWebサービスやスマホアプリなどで使われており、以下のような特徴があります:
- HTTPメソッドを使う:GET、POST、PUT、DELETEなど
-
URLでリソースを指定する:例)
GET /users/123
- レスポンスはJSON形式が一般的
- ステートレス:リクエストごとに完結していて、サーバー側に状態を持たない
たとえば、以下のようなリクエストがREST APIの典型です:
GET /status HTTP/1.1
Host: example.com
これに対して、サーバーはこんなレスポンスを返します
HTTP/1.1 200 OK
Content-Type: application/json
{"status": "ok"}
このようなやり取りが、SocketDebuggerのLuaスクリプトでTCPレベルから模擬できるというのが今回の挑戦です。
3. 構築するサーバーとクライアントの仕様
今回は、インターネットに繋がっていなくても、PCの中だけでREST APIを試せる模擬サーバーを作ります。
サーバー(情報を返す係)とクライアント(情報をお願いする係)は、同じPCの中で動きます。
PCの自分自身を指す特別な住所「127.0.0.1(localhost)」を使って通信します。
このとき使うポート番号は8080です。
アクセス先(URI)は http://127.0.0.1/api/user です。
/api/user という部分は、模擬用に決めた名前で、特別な決まりや意味はありません。
実際のREST APIでは、開発者が自由にわかりやすい名前を付けます。
💻サーバー仕様
サーバーはREST APIリクエストに対して以下のようなレスポンスをするように実装します。
HTTPメソッド | レスポンスヘッダ(HTTPステータス) | レスポンスボディ(JSON) |
---|---|---|
GET | HTTP/1.1 200 OK | {"method":"GET","status":"OK","received_body":受信したJSON} |
POST | HTTP/1.1 200 OK | {"method":"POST","status":"OK","received_body":受信したJSON} |
PUT | HTTP/1.1 200 OK | {"method":"PUT","status":"OK","received_body":受信したJSON} |
DELETE | HTTP/1.1 200 OK | {"method":"DELETE","status":"OK","received_body":受信したJSON} |
OPTIONS | HTTP/1.1 204 No Content | なし |
上記以外 | HTTP/1.1 405 Method Not Allowed | {"error":"Method Not Allowed"} |
💻クライアント仕様
クライアントはJavaScriptのfetchメソッドを使って非同期にREST APIのリクエストをサーバーに対して行います。送信するデータはブラウザ上で設定可能にします。
4. SocketDebuggerの設定とLuaスクリプトでREST APIサーバーを構築!
4-1. SocketDebuggerを起動後、[設定]→[通信設定]を選択し、設定画面を表示します。[接続]→[ポート1]を選択し、以下を設定します。
項目 | 設定値 |
---|---|
このポートを使用する | チェック有 |
通信タイプ | TCPサーバ |
localポート番号 | 8080 |
4-2. そのままの画面で[動作]→[ポート1]を選択し、「スクリプト制御を行う」にチェックを入れ設定します。最後に「OK」をクリックします。
4-3. スクリプトエディタ(スクリプト1)内の受信通知関数を、下記の処理に置き換えてください。(コピペOK!)
---------------------------------------------
-- 受信通知
---------------------------------------------
function OnReceive(recv)
Logput(1, 'OnReceive')
-- バイト配列→文字列変換
local data = string.char(unpack(recv))
-- HTTPリクエスト形式の判定
local method, full_path, version = string.match(data, "^([A-Z]+)%s+([^%s]+)%s+(HTTP/[12]%.[0-9])")
if not method then
return nil -- HTTPリクエストではない
end
Logput(1, string.format('[HTTP] method=%s path=%s', method, full_path))
-- プリフライトリクエストの特別処理(OPTIONS)
if method == "OPTIONS" then
local preflight_headers = {
"HTTP/1.1 204 No Content",
"Access-Control-Allow-Origin: *",
"Access-Control-Allow-Headers: Content-Type",
"Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Max-Age: 86400",
"Content-Length: 0",
"Connection: close",
"",
""
}
SendData(table.concat(preflight_headers, "\r\n"))
return 0
end
local result = "HTTP/1.1 400 Bad Request"
local json_body = ""
local check_flg = true
-- 有効なHTTPメソッドをチェック
local valid_methods = {GET=1, POST=1, PUT=1, DELETE=1}
if not valid_methods[method] then
result = "HTTP/1.1 405 Method Not Allowed"
json_body = '{"error":"Method Not Allowed"}'
check_flg = false
end
-- パスとクエリに分割
local path, query = full_path:match("([^?]+)%??(.*)")
-- RESTfulパス構造のバリデーション("//"や不正な文字列を防ぐ)
if check_flg and (not string.match(path, "^/[a-zA-Z0-9/_%-]*$") or string.match(path, "//")) then
result = "HTTP/1.1 400 Bad Request"
json_body = '{"error":"Invalid path format: must start with / and no double slashes."}'
check_flg = false
end
-- Hostヘッダーの存在チェック
if check_flg and not string.match(data, "\r?\nHost:%s*[^\r\n]+") then
result = "HTTP/1.1 400 Bad Request"
json_body = '{"error":"Host header missing or invalid."}'
check_flg = false
end
-- リクエストボディの抽出
local body = string.match(data, "\r\n\r\n(.*)")
if not body then body = "" end
-- 正常な場合のレスポンス生成
if check_flg then
result = "HTTP/1.1 200 OK"
json_body = string.format('{"method":"%s","status":"OK","received_body":%s}', method, body)
end
-- レスポンスヘッダー作成(405時も Allow を返す)
local response = {
result,
"Content-Type: application/json; charset=utf-8",
"Access-Control-Allow-Origin: *",
"Allow: GET, POST, PUT, DELETE", -- メソッド制限の通知用
"Content-Length: " .. tostring(#json_body),
"Connection: close",
"",
json_body
}
-- レスポンス送信
SendData(table.concat(response, "\r\n"))
return 0
end
4-4. ポート1の通信を開始し、REST APIのリクエストを待ちます。
5. ブラウザで動作確認してみよう!
SocketDebuggerでREST APIサーバーを構築したら、次はクライアントを実装して動作確認してみましょう。
ここでは、ブラウザ上で実行できるJavaScriptを使って、GET・POST・PUT・DELETEの各メソッドをテストできる簡易UIを用意しました。
以下のコードをHTMLファイルとして保存し、ブラウザで開いてください。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Compact REST API Tester</title>
<style>
body{font-family:sans-serif;margin:20px}
.container{display:grid;grid-template-columns:repeat(2,570px);gap:12px;justify-content:start}
.card{border:1px solid #ccc;border-radius:8px;padding:10px;width:550px;flex-shrink:0;display:flex;flex-direction:column}
h3{margin:2px 0 4px}
input,textarea{width:100%;box-sizing:border-box;margin-bottom:4px;padding:4px;font-size:.9em}
textarea{height:30px}
.readonly{background:#eee;color:#666}
.response{background:#f9f9f9;border:1px solid #ddd;padding:4px;font-size:.85em;white-space:pre-wrap;word-break:break-word;min-height:80px;max-height:80px;overflow:auto}
button{margin-bottom:4px;padding:4px}
</style>
</head>
<body>
<h2>Compact REST API Tester</h2>
<div class="container">
<!-- GET -->
<div class="card">
<h3>GET</h3>
<input type="text" id="get-url" value="http://127.0.0.1:8080/api/user?id=1">
<textarea id="get-body" class="readonly" readonly>GETメソッドにBodyは不要です</textarea>
<button onclick="sendRequest('GET')">Send GET</button>
<div id="get-response" class="response"></div>
</div>
<!-- POST -->
<div class="card">
<h3>POST</h3>
<input type="text" id="post-url" value="http://127.0.0.1:8080/api/user">
<textarea id="post-body">{ "name": "Alice", "email": "alice@example.com" }</textarea>
<button onclick="sendRequest('POST')">Send POST</button>
<div id="post-response" class="response"></div>
</div>
<!-- PUT -->
<div class="card">
<h3>PUT</h3>
<input type="text" id="put-url" value="http://127.0.0.1:8080/api/user/1">
<textarea id="put-body">{ "name": "Updated Alice" }</textarea>
<button onclick="sendRequest('PUT')">Send PUT</button>
<div id="put-response" class="response"></div>
</div>
<!-- DELETE -->
<div class="card">
<h3>DELETE</h3>
<input type="text" id="delete-url" value="http://127.0.0.1:8080/api/user/1">
<textarea id="delete-body">{}</textarea>
<button onclick="sendRequest('DELETE')">Send DELETE</button>
<div id="delete-response" class="response"></div>
</div>
</div>
<script>
function sendRequest(method) {
const url = document.getElementById(`${method.toLowerCase()}-url`).value;
const body = document.getElementById(`${method.toLowerCase()}-body`).value;
const responseEl = document.getElementById(`${method.toLowerCase()}-response`);
fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: method === 'GET' ? undefined : body
})
.then(res => res.text().then(text => {
responseEl.textContent = `Status: ${res.status}\n\n${text}`;
}))
.catch(err => {
responseEl.textContent = `Error: ${err}`;
});
}
</script>
</body>
</html>
🔛動作確認
ブラウザで実行すると以下のように表示されます。
GET・POST・PUT・DELETEの各メソッドのSendボタンをクリックし、HTTPによるREST APIリクエストを実行すると…
REST APIサーバー(SocketDebugger)から、正常なレスポンスがきています!😄
🔎ログの確認
REST APIサーバー(SocketDebugger)のログも確認します。(例としてPOST)
クライアントからのリクエストが以下です。
HTTPヘッダの一部はJavaScriptとブラウザにより自動で作られています。
POST /api/user HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 49
sec-ch-ua-platform: "Windows"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0
sec-ch-ua: "Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"
Content-Type: application/json
sec-ch-ua-mobile: ?0
Accept: */*
Origin: null
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: ja,en;q=0.9,en-GB;q=0.8,en-US;q=0.7
{ "name": "Alice", "email": "alice@example.com" }
サーバーはリクエストに対して以下のレスポンスをしています。
Luaスクリプトでの実装がHTTPとREST APIに準拠していることが確認できます。
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: *
Allow: GET, POST, PUT, DELETE
Content-Length: 97
Connection: close
{"method":"POST","status":"OK","received_body":{ "name": "Alice", "email": "alice@example.com" }}
SocketDebuggerが正しくHTTPによるREST APIを模擬していることが確認できました🎉
6. まとめ
今回は、SocketDebuggerを使用してHTTPによるREST APIを模擬するサーバーを構築しました。
TCP/UDPだけじゃなく、上位プロトコルのHTTPも模擬することで、アプリケーション開発における通信確認の手間を大幅に削減できます👍
HTTPによるREST APIの模擬サーバーはこんな時に便利!
- HTTPが実際にどうなっているか学習したい📝
- ローカル環境でREST APIによる通信のテストをしたい🛜
- HTTPやREST APIの異常ケースを実現したい🤔
記事リンク
株式会社ユードム
株式会社ユードムはITと人間力で社会に貢献します。
SocketDebuggerのご購入はこちら(期間限定の試用版もあります)
https://www.udom.co.jp/sdg/index.html