Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .idea/jsonSchemas.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions example/cgi/counter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
from http.cookies import SimpleCookie


def _to_int(v, default=0):
try:
return int(v)
except Exception:
return default


# 受信クッキーを読み取り
in_cookie = SimpleCookie()
raw_cookie = os.environ.get("HTTP_COOKIE", "")
if raw_cookie:
in_cookie.load(raw_cookie)

count = _to_int(in_cookie["count"].value, 0) if "count" in in_cookie else 0
count += 1

# 送信クッキー(セッション cookie。永続化したい場合は Max-Age を設定)
out_cookie = SimpleCookie()
out_cookie["count"] = str(count)
out_cookie["count"]["path"] = "/"
# 例:1年保持したい場合は次行を有効化
# out_cookie["count"]["max-age"] = "31536000"

# ==== ヘッダー(LF 区切り) ====
print("Status: 200 OK")
print("Content-Type: text/html; charset=utf-8")
for morsel in out_cookie.values():
# Morsel.OutputString() は "key=value; Path=/; ..." を返す(改行は含まない)
print("Set-Cookie: " + morsel.OutputString())
print() # 空行でヘッダー終了(LF のみ)

# ==== 本文 ====
print(f"""<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>アクセスカウンタ</title>
</head>
<body>
<p>このブラウザでのアクセスは <strong>{count}</strong> 回目です。</p>
</body>
</html>""")
15 changes: 15 additions & 0 deletions example/cgi/timeout.cgi
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

# HTTP Header
echo "Content-Type: text/html"
sleep 10
echo ""

# HTML Content
echo "<html>"
echo "<head><title>CGI Test</title></head>"
echo "<body>"
echo "<h1>Hello, CGI!</h1>"
echo "<p>This page was generated by a Shell script.</p>"
echo "</body>"
echo "</html>"
17 changes: 8 additions & 9 deletions example/conf/webserv.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,33 @@ path = '/other'
root = 'example/html'
index = 'home.html'

# upload したファイルの取得
[[server.location]]
path = '/uploads'
root = 'example'
autoindex = 'on'
path = '/'
root = 'example/uploads'
allowed_methods = ['POST']

[[server.location]]
path = '/cgi'
root = 'example'
allowed_methods = ['GET', 'POST']
cgi_extensions = ['.cgi']
cgi_extensions = ['.cgi', 'py']

# --

[[server]]
port = 8082
port = 8081
server_name = ['upload.example.com']

# upload 専用
# upload したファイルの取得
[[server.location]]
path = '/'
root = 'example/uploads'
allowed_methods = ['POST']
autoindex = 'on'

# --

[[server]]
port = 8081
port = 8082
server_name = ['redirect.example.com']

# NOTE: /foo/bar はどこにリダイレクトするべきか?
Expand Down
53 changes: 53 additions & 0 deletions example/html/form/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>ファイルアップロードフォーム(/example/uploads)</title>
<link rel="stylesheet" href="./style.css"/>
<script src="./index.js" defer></script>
</head>
<body>
<div class="card" role="region" aria-label="ファイルアップロード">
<div class="header">
<div class="title">ファイルアップロード(POST /example/uploads)</div>
<p class="desc">標準のフォーム送信と、JavaScript による非同期アップロード(進捗表示)に対応したサンプルです。JavaScript
が無効でも通常送信できます。</p>
</div>

<form id="uploadForm" action="/example/uploads" method="post" enctype="multipart/form-data" novalidate>
<div class="field">
<label for="fileInput">ファイル</label>
<div id="dropzone" class="dropzone" tabindex="0"
aria-label="ここにファイルをドラッグ&ドロップ、またはクリックして選択">
<div>
<strong>ドラッグ&ドロップ</strong> または <strong>クリック</strong> してファイルを選択
</div>
<div class="small">複数選択可。ファイルサイズや拡張子の制限は必要に応じてサーバ側で検証してください。
</div>
</div>
<input id="fileInput" name="files" type="file" multiple hidden/>
<div id="fileList" class="filelist" aria-live="polite"></div>
</div>

<div class="controls">
<button id="submitBtn" type="submit">アップロード</button>
<div class="small">送信先: <code>/example/uploads</code>(POST, <code>multipart/form-data</code>)</div>
</div>

<div class="progress" aria-hidden="true" title="アップロード進捗">
<div id="bar" class="bar"></div>
</div>

<noscript>
<p class="small">JavaScript が無効のため、通常のフォーム送信でアップロードします(進捗表示はありません)。</p>
</noscript>
</form>

<div class="result">
<div style="font-weight:600; margin-bottom: 6px;">サーバ応答</div>
<pre id="response" class="response" aria-live="polite">—</pre>
</div>
</div>
</body>
</html>
93 changes: 93 additions & 0 deletions example/html/form/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
(function () {
const form = document.getElementById('uploadForm');
const input = document.getElementById('fileInput');
const dropzone = document.getElementById('dropzone');
const fileList = document.getElementById('fileList');
const bar = document.getElementById('bar');
const resp = document.getElementById('response');
const submitBtn = document.getElementById('submitBtn');

function listFiles(files) {
if (!files || files.length === 0) {
fileList.textContent = '';
return;
}
const items = Array.from(files).map(f => `${f.name} (${(f.size / 1024).toFixed(1)} KB)`);
fileList.textContent = items.join('\n');
}

// ドロップゾーンの操作
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}

['dragenter', 'dragover', 'dragleave', 'drop'].forEach(ev => {
dropzone.addEventListener(ev, preventDefaults, false);
});
['dragenter', 'dragover'].forEach(ev => {
dropzone.addEventListener(ev, () => dropzone.classList.add('dragover'), false);
});
['dragleave', 'drop'].forEach(ev => {
dropzone.addEventListener(ev, () => dropzone.classList.remove('dragover'), false);
});
dropzone.addEventListener('click', () => input.click());
dropzone.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
input.click();
}
});
dropzone.addEventListener('drop', (e) => {
const dt = e.dataTransfer;
if (dt && dt.files && dt.files.length) {
input.files = dt.files; // 一括選択
listFiles(input.files);
}
});
input.addEventListener('change', () => listFiles(input.files));

// 非同期アップロード(XHR: 進捗取得用)
form.addEventListener('submit', function (e) {
// JavaScript 有効時は XHR で送信(進捗表示)
e.preventDefault();
resp.textContent = '送信中…';
bar.style.width = '0%';
submitBtn.disabled = true;

const fd = new FormData();
// CSRF など他のフィールドを含めたい場合は form.elements を走査
// ここではファイルのみ送信
if (input.files && input.files.length) {
// バックエンドの期待に合わせて name を調整
// ここでは同じ name("files") を複数回 append
Array.from(input.files).forEach(file => fd.append('files', file));
}

const xhr = new XMLHttpRequest();
xhr.open('POST', form.action, true);
xhr.upload.onprogress = function (evt) {
if (evt.lengthComputable) {
const percent = Math.round((evt.loaded / evt.total) * 100);
bar.style.width = percent + '%';
}
};
xhr.onload = function () {
submitBtn.disabled = false;
if (xhr.status >= 200 && xhr.status < 300) {
resp.classList.remove('error');
resp.textContent = xhr.responseText || 'アップロードが完了しました。';
bar.style.width = '100%';
} else {
resp.classList.add('error');
resp.textContent = `エラー: ${xhr.status} ${xhr.statusText}\n` + (xhr.responseText || '');
}
};
xhr.onerror = function () {
submitBtn.disabled = false;
resp.classList.add('error');
resp.textContent = 'ネットワークエラーにより送信に失敗しました。';
};
xhr.send(fd);
});
})();
Loading
Loading