シンプルなサーバの作成
シンプルなサーバの作成
サーバを作成しましょう
ここではhttpメソッドのpost
メソッドを使用して、データを追加するサーバを作成します.
プロセスはこうです.
post
メソッドを使用して、データを文字列形式でサーバに送信する.post
方式でデータを転送すると大量のデータがある可能性がある.この場合、大量のデータを一度に処理すると、プログラミングがオフになったり、コンピュータに負担がかかったりする可能性があります.Nodejsでは、
post
方式で伝送されるデータが多い場合に対応するため、受信時にセグメント受信が行われる.このように、スライス受信のたびに、「data」の後にコールバック関数が呼び出されることが約束される.
req.on('data', function() {
//조각조각 나눠서 데이터를 수신할때마다 호출되는 콜백함수
//데이터를처리하는 기능을 정의
});
이처럼 정보가 조각으로 들어오다가 더이상 들어올 정보가 없으면 'end'뒤에있는 콜백함수를 호출하도록 약속되있다.
즉 이 콜백함수가 호출되있을때는 정보 수신이 끝났다고 생각하면된다.
req.end('end',()=>{
//더이상 수신할 정보가 없으면 호출되는 콜백함수
//데이털 처리를 마무리하는 기능을 정의
})
2.req.on
のプロセスにおいて、受信する情報がない場合に呼び出される部分は、後で実行されるコールバック関数によって、JSON.parse
を使用して入力されたデータがオブジェクトとして受信されることが分かる.この部分はパソコンで約束した定義なので、このように従うとしましょう.
そして、受信したデータは、
goodArray
の空の配列に格納される.整理されたデータは
JSON.stringify
を再利用し、json
形態のStringタイプでデータに応答してデータを格納することができる.最後に
server.Listenは、コンソールにサーバ応答の情報を表示するために書かれています.
入力するデータをpostmanに送信すると、
以下に示すように、データはよく格納されていることがわかります.
このサーバの問題点
-以前のサーバでは、もちろん内部にデータを格納できます.
データベースがより静的に格納されるのは安定しています.
-セカンダリ・サーバにデータを配列またはオブジェクトとして格納します.エラーによってサーバが停止すると、すべてのデータが失われます.
-サーバが停止しているかどうかにかかわらず、存在するデータベースを作成する必要があります.
解決策
データベースを作成することにしました.
-次の順序で
-POSTで受信したデータをサーバ上のファイルに保存します.
-PUT、DELETEなどの修正ファイルを使用します.
-GET呼び出し時応答ファイルに格納されているデータ
const http = require("http");
const hostname = "127.0.0.1";
const fs = require("fs");
const port = 3000;
const server = http.createServer((req, res) => {
const url = require("url").parse(req.url, true);
console.log("url : ", url);
if (url.pathname == "/product") {
if (req.method == "POST") {
let body = "";
req.on("data", function (data) {
body += data;
console.log("body: --- ", body);
});
req.on("end", function () {
const json = JSON.parse(body);
console.log("json : --", json);
const product = new Product(json.name, json.price);
console.log("product : ---", product);
let productArray = [product];
let newDb;
console.log("newDb1 : ---", newDb);
// client(postman) 1번째 api 호출
// API 로직
// - fs.readfile -> data 없음 -> 신규 db에 아이템 1 추가 -> fs.writeFile 실행 -> database.json 생성
// client(postman) 2번째 api 호출
// API 로직
// - fs.readfile -> data 있음 -> 기존 db 아이템 1 추가 -> fs.writeFile 실행 -> database.json 생성
fs.readFile("database.json", "utf8", function (err, data) {
if (data) {
// 2번째 입력부터
let originDb = JSON.parse(data);
console.log("originDb ---: ", originDb);
let originProductObc = originDb.products ? originDb.products : [];
console.log("originProduc ---", originProductObc);
let arrayDb = Array.from(originProductObc);
arrayDb.push(product);
console.log("arraydb : ---", arrayDb);
originDb.products = arrayDb;
console.log("origindb2 : ----", originDb);
console.log("originDb : ", originDb);
newDb = JSON.stringify(originDb);
console.log("newdb1 : ----", newDb);
} else {
// 1번째 입력
newDb = JSON.stringify({ products: productArray });
console.log("newdb11 : ----", newDb);
}
// res.end(JSON.stringify({result : true,list : newDb}))
fs.writeFile("database.json", newDb, "utf8", function (err) {
res.statusCode = 201;
res.end(JSON.stringify({ result: true, list: newDb }));
});
});
});
} else if (req.method == "PUT") {
//post end
let body = "";
req.on("data", (data) => {
body += data;
});
req.on("end", () => {
const json = JSON.parse(body);
console.log("json: --- : ", json);
const product = new Product(json.name, json.price);
console.log("product : ---", product);
fs.readFile("database.json", "utf8", (err, data) => {
let originDb = JSON.parse(data);
console.log("originDb : ---", originDb);
let originProductObc = originDb.products ? originDb.products : [];
console.log("originPro : ----", originProductObc);
let arrayDb = Array.from(originProductObc);
console.log("arraydb : ----", arrayDb);
arrayDb[json.indexx] = product;
console.log("arraydb22 : ----", arrayDb);
originDb.products = arrayDb;
console.log("origindb222 : ---", originDb);
let newDb = JSON.stringify(originDb);
fs.writeFile("database.json", newDb, "utf8", function (err) {
res.statusCode = 201;
res.end(JSON.stringify({ result: true, list: newDb }));
});
});
});
} else if (req.method == "DELETE") {
// put end
let body = "";
req.on("data", function (data) {
body += data;
});
req.on("end", function () {
const json = JSON.parse(body);
fs.readFile("database.json", "utf8", function (err, data) {
let originDb = JSON.parse(data);
let originProductObc = originDb.products;
let arrayDb = Array.from(originProductObc);
delete arrayDb[json.index];
arrayDb = arrayDb.filter((x) => x != null);
originDb.products = arrayDb;
let newDb = JSON.stringify(originDb);
fs.writeFile("database.json", newDb, "utf8", (err) => {
res.statusCode = 201;
res.end(JSON.stringify({ result: true, list: arrayDb }));
});
});
});
} else {
// DELETE end ,get
res.statusCode = 200;
fs.readFile("database.json", "utf8", (err, data) => {
res.end(JSON.stringify({ result: true, list: data }));
});
}
}
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
}
class User {
constructor(name, id, pwd) {
this.name = name;
this.id = id;
this.pwd = pwd;
}
}
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
上記のコードはnodejsのfs
モジュールを使用してdbを作成し、データを格納するように動作します.では、HTTPメソッドがデータを格納する方法を見てみましょう.
poroduct
POST
方法はこうです.まず、
fs
モジュールを宣言し、readFile
、WriteFile
メソッドを使用して、顧客からデータを受信します. class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
}
class User {
constructor(name, id, pwd) {
this.name = name;
this.id = id;
this.pwd = pwd;
}
}
postmanから受信したクライアントを使用してサーバに送信された情報を使用し、受信したデータを最初の方法で文字列として保存します.req.on
のセクションでは、const product = new Product(json.name, json.price);
の方法で新しいインスタンスを使用してデータを格納する.次に、
readFile
の方法を使用して、データを2つの方法で格納することを考慮する.1つ目はAにデータがないとき、2つ目はデータがあるとき、この2つの状況を考慮してデータを格納することです.
まずデータがない場合を考えてみましょう
else {
// 1번째 입력
newDb = JSON.stringify({ products: productArray });
console.log("newdb11 : ----", newDb);
}
上部では、let newDb;
を使用してdbに格納されたデータ空間を提供する.したがって、
Stringify
を使用してクライアントが格納したデータをjson
形式に変換し、newDb
空間にデータを格納して出力します.post
にデータを保存すると、上のデータが表示されます.PUT
else if (req.method == "PUT") {
//post end
let body = "";
req.on("data", (data) => {
body += data;
});
req.on("end", () => {
const json = JSON.parse(body);
const product = new Product(json.name, json.price);
fs.readFile("database.json", "utf8", (err, data) => {
let originDb = JSON.parse(data);
let originProductObc = originDb.products ? originDb.products : [];
let arrayDb = Array.from(originProductObc);
arrayDb[json.index] = product;
originDb.products = arrayDb;
let newDb = JSON.stringify(originDb);
fs.writeFile("database.json", newDb, "utf8", function (err) {
res.statusCode = 201;
res.end(JSON.stringify({ result: true, list: newDb }));
});
});
});
今回は、ストレージデータを修正するPUT
の方法について説明します.put
メソッドの場合、クライアントは変更するデータをサーバに送信し、サーバは対応するデータを変更するだけです.このプロセス中のコードは、変更するデータのインデックスを指定することによって、クライアントから
arrayDb[json.index] = product;
を送信するconst product = new Product(json.name, json.price);
を使用する.機能は、情報に従ってデータを修正することです.
変更するインデックスと変更するコードを指定することで、変更するデータ情報を知ることができます.
DELETE
次に、保存したdbにデータを消去する方法を見てみましょう.
データを削除する方法は次のとおりです.
else if (req.method == "DELETE") {
// put end
let body = "";
req.on("data", function (data) {
body += data;
});
req.on("end", function () {
const json = JSON.parse(body);
fs.readFile("database.json", "utf8", function (err, data) {
let originDb = JSON.parse(data);
let originProductObc = originDb.products;
let arrayDb = Array.from(originProductObc);
delete arrayDb[json.index];
arrayDb = arrayDb.filter((x) => x != null);
originDb.products = arrayDb;
let newDb = JSON.stringify(originDb);
fs.writeFile("database.json", newDb, "utf8", (err) => {
res.statusCode = 201;
res.end(JSON.stringify({ result: true, list: arrayDb }));
});
});
});
}
上のコード.delete arrayDb[json.index];
arrayDb = arrayDb.filter((x) => x != null);
上のコードで消すことができます.このコードはclientから消去するデータのインデックスを取得し、データを削除します.
filter
メソッドを使用して、削除されたデータ(null)以外の残りの値をarrayyDb
に格納し、データの再作成の逆アクティビティを行う.実際に削除するindexを設定し、post manで確認すると、上の削除が正しいことを確認できます.
GET
最後にgetメソッドです.
実際,getメソッドは上記の過程で最も基本的な部分である.
else {
res.statusCode = 200;
fs.readFile("database.json", "utf8", (err, data) => {
res.end(JSON.stringify({ result: true, list: data }));
});
}
既存に格納されているデータを読み取るだけなので、readFile
方法で格納されているdbをチェックするだけでよい.User
else if (url.pathname.includes("/user")) {
if (req.method == "POST") {
if (url.pathname == "/user/signup") {
let body = "";
req.on("data", function (data) {
body += data;
});
req.on("end", () => {
const json = JSON.parse(body);
const user = new User(json.name, json.id, json.pwd);
//입력하려는 user 객체를 class를 이용해서 만들었음
let userArray = [];
let newDb;
fs.readFile("database.json", "utf8", (err, data) => {
if (data) {
let originDb = JSON.parse(data);
let originUserObc = originDb.user;
//기존에 user database가 존재하는지 확인함
console.log("originDb : ", originDb);
if (originUserObc) {
console.log("originUserObc : ", originUserObc);
userArray = Array.from(originUserObc);
//user가 이미 가입 되어 있는지 확인합니다
for (let i = 0; i < userArray.length; i++) {
let originUser = userArray[i];
if (user.id == originUser.id) {
res.end(JSON.stringify("user exist"));
return;
} else {
console.log("User not exist");
}
} // user가 이미 가입 안되어 있으면 가입을 합니다.
userArray.push(user);
originDb.user = userArray;
newDb = JSON.stringify(originDb);
fs.writeFile("database.json", newDb, "utf8", (err) => {
res.statusCode = 201;
res.end(JSON.stringify({ result: true, list: newDb }));
});
} else {
// database : o, user: x
console.log("userArray : ", userArray);
userArray.push(user);
originDb.user = userArray;
console.log("userArray : ", userArray);
newDb = JSON.stringify(originDb);
console.log("newDb : ", newDb);
fs.writeFile("database.json", newDb, "utf8", (err) => {
res.statusCode = 201;
res.end(JSON.stringify({ result: true, list: newDb }));
});
}
} else {
// database: x
newDb = JSON.stringify({ user: [user] });
fs.writeFile("database.json", newDb, "utf8", (err) => {
res.statusCode = 201;
res.end(JSON.stringify({ reuslt: true, list: newDb }));
});
}
});
});
} else if (url.pathname == "/user/signin") {
let body = "";
req.on("data", (data) => {
body += data;
});
req.on("end", () => {
fs.readFile("database.json", "utf8", (err, data) => {
if (data) {
const json = JSON.parse(body);
const user = new User(json.name, json.id, json.pwd);
let originDb = JSON.parse(data);
let originUserDb = originDb.user;
//우선 기존에 유저 데이터가 있는 지를 if문으로 체크
if (originUserDb) {
let arrayDb = Array.from(originUserDb);
for (let i = 0; i < arrayDb.length; i++) {
let originUser = arrayDb[i];
if (user.id == originUser.id && user.pwd == originUser.pwd) {
res.end(JSON.stringify({ login: true }));
console.log("return");
return;
}
}
res.end(JSON.stringify({ login: false })); //end는 서버에서 한번안된다고나왔으면 그게끝이기떄문에반복문안에 넣으면안됨 왜냐하면 반복문안에있으면 한번잘못됐다나온후에는 계쏙안된다고나오기떄문
} else {
//유저 데이터가 없다면 로그인에 실패
//no user exists
res.end(JSON.stringify({ login: false }));
}
} else {
// data가 없을때
res.end(JSON.stringify({ login: false }));
}
});
});
} // signin end
} else if (req.method == "PUT") {
let body = "";
req.on("data", (data) => {
body += data;
});
req.on("end", () => {
const json = JSON.parse(body);
const user = new User(json.name, json.id, json.pwd);
fs.readFile("database.json", "utf8", (err, data) => {
//기존데이터 읽음
let originDb = JSON.parse(data); // parse는 우리가원하는 데이터타입으로바꾸는걸의미 여기서는 문자열을 json으로 바꿈
let originUserDb = originDb.user;
let arrayDb = Array.from(originUserDb);
// 읽어온 데이터를 수정
arrayDb[json.index] = user;
originDb.user = arrayDb;
let newDb = JSON.stringify(originDb);
//수정된 데이터를 저장
fs.writeFile("database.json", newDb, "utf8", function (err) {
res.statusCode = 201;
res.end(JSON.stringify({ result: true, list: arrayDb }));
});
});
});
} else if (req.method == "DELETE") {
let body = "";
req.on("data", function (data) {
body += data;
});
req.on("end", function () {
const json = JSON.parse(body);
fs.readFile("database.json", "utf8", function (err, data) {
let originDb = JSON.parse(data);
let originUserDb = originDb.user;
let arrayDb = Array.from(originUserDb);
delete arrayDb[json.index];
arrayDb = arrayDb.filter((x) => x != null);
originDb.user = arrayDb;
let newDb = JSON.stringify(originDb);
fs.writeFile("database.json", newDb, "utf8", (err) => {
res.statusCode = 201;
res.end(JSON.stringify({ result: true, list: arrayDb }));
});
});
});
} else {
//get
let user_name = url.query.user_name;
res.statusCode = 200;
fs.readFile("database.json", "utf8", (err, data) => {
let originDb = JSON.parse(data);
let originUserDb = originDb.user;
if (originUserDb) {
let userArray = Array.from(originUserDb);
for (let i = 0; i < userArray.length; i++) {
if (user_name == userArray[i].name) {
res.end(JSON.stringify({ result: true, users: userArray[i] }));
return;
}
}
res.end(JSON.stringify("User Not exist"));
}
});
}
}
ログイン、登録、変更、削除を作成します.上記のコースは特定のユーザーに関するものなので、urlのpathnameが
user
を含むかどうかによって決定します.singup
会員登録を作成しましょう
else if (url.pathname.includes("/user")) {
if (req.method == "POST") {
if (url.pathname == "/user/signup") {
let body = "";
req.on("data", function (data) {
body += data;
});
req.on("end", () => {
const json = JSON.parse(body);
const user = new User(json.name, json.id, json.pwd);
//입력하려는 user 객체를 class를 이용해서 만들었음
let userArray = [];
let newDb;
fs.readFile("database.json", "utf8", (err, data) => {
if (data) {
let originDb = JSON.parse(data);
let originUserObc = originDb.user;
//기존에 user database가 존재하는지 확인함
console.log("originDb : ", originDb);
if (originUserObc) {
console.log("originUserObc : ", originUserObc);
userArray = Array.from(originUserObc);
//user가 이미 가입 되어 있는지 확인합니다
for (let i = 0; i < userArray.length; i++) {
let originUser = userArray[i];
if (user.id == originUser.id) {
res.end(JSON.stringify("user exist"));
return;
} else {
console.log("User not exist");
}
} // user가 이미 가입 안되어 있으면 가입을 합니다.
userArray.push(user);
originDb.user = userArray;
newDb = JSON.stringify(originDb);
fs.writeFile("database.json", newDb, "utf8", (err) => {
res.statusCode = 201;
res.end(JSON.stringify({ result: true, list: newDb }));
});
} else {
// database : o, user: x
console.log("userArray : ", userArray);
userArray.push(user);
originDb.user = userArray;
console.log("userArray : ", userArray);
newDb = JSON.stringify(originDb);
console.log("newDb : ", newDb);
fs.writeFile("database.json", newDb, "utf8", (err) => {
res.statusCode = 201;
res.end(JSON.stringify({ result: true, list: newDb }));
});
}
} else {
// database: x
newDb = JSON.stringify({ user: [user] });
fs.writeFile("database.json", newDb, "utf8", (err) => {
res.statusCode = 201;
res.end(JSON.stringify({ reuslt: true, list: newDb }));
});
}
});
});
}
まずelse {
// database : o, user: x
console.log("userArray : ", userArray);
userArray.push(user);
originDb.user = userArray;
console.log("userArray : ", userArray);
newDb = JSON.stringify(originDb);
console.log("newDb : ", newDb);
fs.writeFile("database.json", newDb, "utf8", (err) => {
res.statusCode = 201;
res.end(JSON.stringify({ result: 2, list: newDb }));
});
}
このコードは、user
データがない場合に最初に実行されるコードです.すぐに確認すると.
その後
user
が存在する場合、もう一度会員登録をすると存在するプレイヤーを表示します.
次に、新しいユーザーを再登録するときに実行されるコードは、次のとおりです.
if (originUserObc) {
console.log("originUserObc : ", originUserObc);
userArray = Array.from(originUserObc);
//user가 이미 가입 되어 있는지 확인합니다
for (let i = 0; i < userArray.length; i++) {
let originUser = userArray[i];
if (user.id == originUser.id) {
res.end(JSON.stringify("user exist"));
return;
} else {
console.log("User not exist");
}
} // user가 이미 가입 안되어 있으면 가입을 합니다.
このコードは実行されます.
その後、上記の手順を繰り返し、会員加入を行います.
signin
else if (url.pathname == "/user/signin") {
let body = "";
req.on("data", (data) => {
body += data;
});
req.on("end", () => {
fs.readFile("database.json", "utf8", (err, data) => {
if (data) {
const json = JSON.parse(body);
const user = new User(json.name, json.id, json.pwd);
let originDb = JSON.parse(data);
let originUserDb = originDb.user;
//우선 기존에 유저 데이터가 있는 지를 if문으로 체크
if (originUserDb) {
let arrayDb = Array.from(originUserDb);
for (let i = 0; i < arrayDb.length; i++) {
let originUser = arrayDb[i];
if (user.id == originUser.id && user.pwd == originUser.pwd) {
res.end(JSON.stringify({ login: true }));
console.log("return");
return;
}
}
res.end(JSON.stringify({ login: false })); //end는 서버에서 한번안된다고나왔으면 그게끝이기떄문에반복문안에 넣으면안됨 왜냐하면 반복문안에있으면 한번잘못됐다나온후에는 계쏙안된다고나오기떄문
} else {
//유저 데이터가 없다면 로그인에 실패
//no user exists
res.end(JSON.stringify({ login: false }));
}
} else {
// data가 없을때
res.end(JSON.stringify({ login: false }));
}
});
});
} // signin end
}
ログインの場合、httpメソッドはpost
入力として指定されます.データ入力方式は
signup
と同じである. if (originUserDb) {
let arrayDb = Array.from(originUserDb);
for (let i = 0; i < arrayDb.length; i++) {
let originUser = arrayDb[i];
if (user.id == originUser.id && user.pwd == originUser.pwd) {
res.end(JSON.stringify({ login: true }));
console.log("return");
return;
}
}
res.end(JSON.stringify({ login: false })); //end는 서버에서 한번안된다고나왔으면 그게끝이기떄문에반복문안에 넣으면안됨 왜냐하면 반복문안에있으면 한번잘못됐다나온후에는 계쏙안된다고나오기떄문
} else {
//유저 데이터가 없다면 로그인에 실패
//no user exists
res.end(JSON.stringify({ login: false }));
}
} else {
// data가 없을때
res.end(JSON.stringify({ login: false }));
}
このコードでは,会員口から作成されたid,pwdを確認する.正しい場合はloginエラー時falseでコードを記述します.
上のコードを自分でチェックすると
会員口で作るコードについてはtrueです.
会員が入力していないコードについてfalseが表示されます.
PUT(修正)
会員情報の修正について説明します.
else if (req.method == "PUT") {
let body = "";
req.on("data", (data) => {
body += data;
});
req.on("end", () => {
const json = JSON.parse(body);
const user = new User(json.name, json.id, json.pwd);
fs.readFile("database.json", "utf8", (err, data) => {
//기존데이터 읽음
let originDb = JSON.parse(data); // parse는 우리가원하는 데이터타입으로바꾸는걸의미 여기서는 문자열을 json으로 바꿈
let originUserDb = originDb.user;
let arrayDb = Array.from(originUserDb);
// 읽어온 데이터를 수정
arrayDb[json.index] = user;
originDb.user = arrayDb;
let newDb = JSON.stringify(originDb);
//수정된 데이터를 저장
fs.writeFile("database.json", newDb, "utf8", function (err) {
res.statusCode = 201;
res.end(JSON.stringify({ result: true, list: arrayDb }));
});
});
});
}
上記のコードとよく似ています.arrayDb[json.index] = user;
originDb.user = arrayDb;
この部分には違いがあります.この部分は、clientで変更したいインデックスを選択し、上に入力したclient変更後の情報を既存のdbに保存するコードに置き換えることを意味します.上の写真を見ればよく修正されていることがわかります.
DELETE(メンバーの削除)
以下は会員の削除です.
else if (req.method == "DELETE") {
let body = "";
req.on("data", function (data) {
body += data;
});
req.on("end", function () {
const json = JSON.parse(body);
fs.readFile("database.json", "utf8", function (err, data) {
let originDb = JSON.parse(data);
let originUserDb = originDb.user;
let arrayDb = Array.from(originUserDb);
delete arrayDb[json.index];
arrayDb = arrayDb.filter((x) => x != null);
originDb.user = arrayDb;
let newDb = JSON.stringify(originDb);
fs.writeFile("database.json", newDb, "utf8", (err) => {
res.statusCode = 201;
res.end(JSON.stringify({ result: true, list: arrayDb }));
});
});
});
}
上記のコードで注意すべきコードは次のとおりです. delete arrayDb[json.index];
arrayDb = arrayDb.filter((x) => x != null);
originDb.user = arrayDb;
これは、クライアントから削除したい会員情報のインデックスを受信して削除し、削除した会員情報から生成したnullを除去し、残りのデータのみを選択し、既存のデータdbに再保存するコードである.正常に動作していることがわかります.
get(特定のメンバー情報を表示)
最後はgetです.
else {
//get
let user_name = url.query.user_name;
res.statusCode = 200;
fs.readFile("database.json", "utf8", (err, data) => {
let originDb = JSON.parse(data);
let originUserDb = originDb.user;
if (originUserDb) {
let userArray = Array.from(originUserDb);
for (let i = 0; i < userArray.length; i++) {
if (user_name == userArray[i].name) {
res.end(JSON.stringify({ result: true, users: userArray[i] }));
return;
}
}
res.end(JSON.stringify("User Not exist"));
}
});
}
このセクションは、特定の会員情報を表示するコードです.まず、
let user_name = url.query.user_name;
を使用して、特定のクエリに関する情報を取得する.これは
user_name == userArray[i].name
を利用して特定の情報があるかどうかを知る会員のコードです.すぐに確認しよう
会員が加入する会員は特定のqueryで特定の会員しか見られない.
Reference
この問題について(シンプルなサーバの作成), 我々は、より多くの情報をここで見つけました https://velog.io/@kyle-shk/간단한-서버만들기テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol