2022.06.16(Thurs)
[TIL] Day36
[SEB FE] Day36
Cross-Origin Resource Sharing
(๊ต์ฐจ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ )
์ถ๊ฐ HTTP ํค๋๋ฅผ ์ฌ์ฉํ์ฌ ํ ์ถ์ฒ์์ ์คํ ์ค์ธ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ค๋ฅธ ์ถ์ฒ์ ์ ํํ ์์์ ์ ๊ทผํ ์ ์๋ ๊ถํ์ ๋ถ์ฌํ๋๋ก ๋ธ๋ผ์ฐ์ ์ ์๋ ค์ฃผ๋ ์ฒด์
๐ย SOP์ ์ํด ๋ค๋ฅธ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ ๋ฅผ ๋ง์ ๋ธ๋ผ์ฐ์ ๋ฅผ CORS๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์ ์ ๊ทผ ๊ถํ์ ์ป๊ฒ ๋๋ ๊ฒ!
Same-Origin Policy
, ๋์ผ ์ถ์ฒ ์ ์ฑ
๐ย ๊ฐ์ ์ถ์ฒ์ ๋ฆฌ์์ค๋ง ๊ณต์ ๊ฐ๋ฅ
๐ซถ ์ถ์ฒ(Origin
):protocol
,host
,port
์กฐํฉ (ex.https://www.google.com:443/
)
๐คทโโ๏ธย Why SOP
์ฌ์ฉ?
๐ย ์ ์ฌ์ ์ผ๋ก ํด๋ก์ธ ์ ์๋ ๋ฌธ์๋ฅผ ๋ถ๋ฆฌํจ์ผ๋ก์จ ๊ณต๊ฒฉ๋ฐ์ ์ ์๋ ๊ฒฝ๋ก๋ฅผ ์ค์ฌ์ฃผ๋ฏ๋ก
โ
ย ๋ค๋ฅธ ์ถ์ฒ ๋ฆฌ์์ค๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ถ์ง๋ง SOP ๋๋ฌธ์ ์ ๊ทผ โ
โ CORS
์ค์ ์ผ๋ก ์๋ฒ ์๋ต ํค๋์ โAccess-Control-Allow-Origin
โ์ ์์ฑํ๋ฉด ์ ๊ทผ ๊ถํ ํ๋ ๊ฐ๋ฅ
์ ์ Three.js ํ ์ด ํ๋ก์ ํธ๋ฅผ ์งํํ์ ๋ ์๋์ ๊ฐ์ ์๋ฌ๊ฐ ๋ ์ ๋ธ๋ผ์ฐ์ ์์ ๋ฐ๋ก ์คํ์ํค์ง ๋ชปํ๊ณ , http-server๋ฅผ npm์ผ๋ก ์ค์นํ ํ์ ์ด๋ฅผ ์ด์ฉํ์๋๋ฐ ์ด๋ฒ ์๊ฐ์ ํตํด CORS๊ฐ ๋ฌด์์ด๊ณ , ์ ์ด๋ฐ error๊ฐ ๋ฐ์ํ๋์ง ์กฐ๊ธ์ด๋๋ง ์ ์ ์์๋ค.
Preflight Request
์ค์ ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ ,
OPTIONS
๋ฉ์๋๋ก ์ฌ์ ์์ฒญ์ ๋ณด๋ด, ํด๋น ์ถ์ฒ ๋ฆฌ์์ค์ ์ ๊ทผ ๊ถํ์ด ์๋์ง๋ถํฐ ํ์ธ
Access-Control-Allow-Origin
์ผ๋ก ์์ฒญ์ ๋ณด๋ธ ์ถ์ฒ๊ฐ ๋์์ค๋ฉด ์ค์ ์์ฒญ์ ๋ณด๋ด๊ฒ ๋จ๐ย ์ค์ ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ ๋ฏธ๋ฆฌ ๊ถํ ํ์ธ ๊ฐ๋ฅ โ ๊ถํ ์๋ ์์ฒญ์ ๋ฏธ๋ฆฌ ๊ฑฐ๋ฅผ ์ ์๋ค๋ ์ฅ์
if (request.method === "OPTIONS") {
response.writeHead(200, defaultCorsHeader);
response.end();
}
Simple Request
ํน์ ์กฐ๊ฑด์ด ๋ง์กฑ๋๋ฉด Preflight ์์ฒญ์ ์๋ตํ๊ณ ์์ฒญ์ ๋ณด๋
a. GET
, HEAD
, POST
์์ฒญ ์ค ํ๋์ฌ์ผ ํจ
b. Accept
, Accept-Language
, Content-Language
, Content-Type
ํค๋ ๊ฐ๋ง ์๋์ผ๋ก ์ค์ ๊ฐ๋ฅ
ย ย ย ย i. Content-Type
ํค๋์ application/x-www-form-urlencoded
, multipart/form-data
, text/plain
๊ฐ๋ง ํ์ฉ
Credentialed Request
(์ธ์ฆ์ ๋ณด๋ฅผ ํฌํจํ ์์ฒญ)์์ฒญ ํค๋์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ด์ ๋ณด๋ด๋ ์์ฒญ
๋ณ๋์ ์ค์ ์ ํ์ง ์์ผ๋ฉด ์ฟ ํค๋ฅผ ๋ณด๋ผ ์ ์์ (๐คทโโ๏ธwhy? ๋ฏผ๊ฐํ ์ ๋ณด๋ผ์)
โ Front, Server ๋ชจ๋CORS
์ค์ ํ์
withCredentials: true
์ฝ์
Access-Control-Allow-Credentials: true
์ฝ์
Access-Control-Allow-Origin
์ค์ ์,*
)๋ก ์ค์ ํ๋ฉด Error ๋ฐ์ ๐ย ์ถ์ฒ ์ ํํ ์ค์ !Node.js Server
const http = require('http');
const server = http.createServer((request, response) => {
// all domain
response.setHeader("Access-Control-Allow-Origin", "*");
// ํน์ domain
response.setHeader("Access-Control-Allow-Origin", "https://google.com");
// ์ธ์ฆ ์ ๋ณด๋ฅผ ํฌํจํ ์์ฒญ์ ๋ฐ์ ๊ฒฝ์ฐ
response.setHeader("Access-Control-Allow-Credentials", "true");
})
Express Server
// cors ๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํด์ ๋ ๊ฐ๋จํ CORS ์ค์ ๊ฐ๋ฅ
const cors = require("cors");
const app = express();
// all domain
app.use(cors());
// ํน์ domain
const options = {
origin: "https://google.com", // ์ ๊ทผ ๊ถํ์ ๋ถ์ฌํ๋ domain
credentials: true, // ์๋ต ํค๋์ Access-Control-Allow-Credentials ์ถ๊ฐ
optionsSuccessStatus: 200, // ์๋ต ์ํ 200 ์ค์
};
app.use(cors(options));
// ํน์ Request
app.get("/example/:id", cors(), function (req, res, next) {
res.json({ msg: "example" });
});
Node.js์ http module์ ์ด์ฉํ์ฌ Web Server ๋ง๋ค๊ธฐ
- Client Action(button click)์ ๋ฐ๋ผ ๋ค๋ฅธ HTTP ์์ฒญ์ ์๋ฒ๋ก ๋ณด๋ด๊ณ ,
HTTP ์์ฒญ์ ๋ด์ ๋ณด๋ธ ๋จ์ด๋ฅผ ์๋ฌธ์/๋๋ฌธ์๋ก ์๋ต ๋ฐ์ ํ๋ฉด์ ๋ณด์ฌ์ฃผ๋ ๊ธฐ๋ฅ ๊ตฌํ
โฐย Web Server
: HTTP ์์ฒญ์ ์ฒ๋ฆฌํ๊ณ ์๋ต์ ๋ณด๋ด์ฃผ๋ ํ๋ก๊ทธ๋จ
// Server ์คํ
node [folder name]/[file name]
// nodemon ์ฌ์ฉ์, ์๋ฒ๋ฅผ ๋งค๋ฒ ์คํ์ํฌ ํ์์์
// 1-1. nodemon ์ค์น
npm install nodemon
// 1-2. package.json์ 'scripts' ์ฝ๋ ์ถ๊ฐ
"start": "nodemon [folder name]/[file name]"
// 2. nodemon์ ๋ฐ๋ก ์ค์นํ์ง ์๊ณ ์คํ
npx nodemon [folder name]/[file name]
// Client ์คํ
// 1. web browser์์ html ํ์ผ ์คํ
// 2. ํน์ port๋ก client ์คํํ๊ณ ์ถ๋ค๋ฉด, serve ์ด์ฉ
npx serve -l [port number] [folder name]/
// server/basic-server.js
const http = require("http");
const PORT = 4999;
const ip = "localhost";
// ์๋ฒ ์์ฑ
const server = http.createServer((request, response) => {
// preflight request
if (request.method === "OPTIONS") {
// ๋ช
์์ ์ผ๋ก response stream์ Header ์์ฑ
// writeHead() ๋ฉ์๋์ status code, Header ์์ฑ
// ํค๋๋ฅผ ์ค์ ํ๊ณ ๋๋ฉด ์๋ต ๋ฐ์ดํฐ๋ฅผ ์ ์กํ ์ค๋น ์๋ฃ!
response.writeHead(200, defaultCorsHeader);
response.end();
return; // ์กฐ๊ฑด๋ฌธ๋ง๋ค return; ์ ํด์ฃผ๋ฉด app crushed ์ด์ฉ๊ตฌ ์๋ฌ ๋ฐ์
}
let body = [];
// ๋ฌธ์์ด์ ์๋ฌธ์๋ก ๋ง๋ค์ด ์๋ต
// ์์ฒญ ๋ฉ์๋๊ฐ 'POST'์ด๋ฉด์ Endpoint(URL)๊ฐ /lower์ธ ๊ฒฝ์ฐ
if (request.method === "POST" && request.url === "/lower") {
request
// ๊ฐ 'data' event์์ ๋ฐ์์ํจ chunk๋ 'Buffer'.
// chunk๊ฐ ๋ฌธ์์ด ๋ฐ์ดํฐ๋ผ๋ฉด ์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐฐ์ด์ ์์งํ ํ,
// 'end' ์ด๋ฒคํธ์ ์ด์ด ๋ถ์ฌ ๋ค์ ๋ฌธ์์ด๋ก ๋ง๋๋ ๊ฒ์ด good!
.on("data", (chunk) => {
body.push(chunk);
})
.on("end", () => {
// 'body'์ ์ ์ฒด ์์ฒญ ๋ฐ๋๊ฐ ๋ฌธ์์ด๋ก ๋ด๊ฒจ ์์
body = Buffer.concat(body).toString();
response.writeHead(200, defaultCorsHeader);
response.end(body.toLowerCase());
return;
});
// ๋ฌธ์์ด์ ๋๋ฌธ์๋ก ๋ง๋ค์ด ์๋ต
// ์์ฒญ ๋ฉ์๋๊ฐ 'POST'์ด๋ฉด์ Endpoint(URL)๊ฐ /upper์ธ ๊ฒฝ์ฐ
} else if (request.method === "POST" && request.url === "/upper") {
request
.on("data", (chunk) => {
body.push(chunk);
})
.on("end", () => {
body = Buffer.concat(body).toString();
response.writeHead(200, defaultCorsHeader);
response.end(body.toUpperCase());
return;
});
// ์ ์กฐ๊ฑด์ด ์๋ ๊ฒฝ์ฐ 404 ์๋ต
} else {
response.statusCode = 404;
response.end();
}
});
// listen ๋ฉ์๋๋ฅผ server ๊ฐ์ฒด์์ ํธ์ถํจ์ผ๋ก์จ ์์ฒญ์ ์ค์ ๋ก ์ฒ๋ฆฌ
// ๋๋ถ๋ถ ์๋ฒ๊ฐ ์ฌ์ฉํ๊ณ ์ ํ๋ ํฌํธ ๋ฒํธ๋ฅผ listen์ ์ ๋ฌ
server.listen(PORT, ip, () => {
console.log(`http server listen on ${ip}:${PORT}`);
});
const defaultCorsHeader = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Accept",
"Access-Control-Max-Age": 10,
};
โ npm run start
๋ก ์๋ฒ๋ ์คํ์ํค๊ณ , ํด๋ผ์ด์ธํธ๋ ๋ธ๋ผ์ฐ์ ์ ํจ๊ป ๋๋ ค์ผ ์๋ ๊ฐ๋ฅํ๋ค.
๐ย ์ฐธ๊ณ -Node ๋ฌธ์
https://nodejs.org/ko/docs/guides/anatomy-of-an-http-transaction/