화,수 휴가를 제출하고 여행을 다녀왔더니 정말 많은 공부가 밀려있었다 ... 오늘 화, 수 안한만큼 해야지 !! 했는데 너무 피곤했는지 늦게 일어나서 9시 12분에 출석을 해버렸다 ... ㅜ 부랴부랴 웹 서버 기초에 대해 유어클래스를 공부해보았는데 아무래도 3일치를 하루만에 하려고 하니 뇌에 과부하가 온 거 같다. 오늘은 과제 제출도 하지 못해 반딧불반에 오게 되었고 당연한 결과인 만큼 열심히 나머지 공부를 해 볼것이당 ! ! 웹 서버에 대해서는 거의 노베이스라고 해도 될 정도로 무지해 공부하는데 어려움이 많았던 거같다.
node.js 를 사용해본적이 없어 더 헤맸었던 거같다 ..
오늘 포스트는 오늘 공부한 web server 기초와 mini node server 과제를 의사코드로 작성해 볼 것이다. 또한 node.js 의 HTTP 트랜잭션 해부 문서를 꼼꼼히 읽고 정리해볼것이다.
- cors
- mini node server 과제
CORS
SOP란 ?
Same-Origin Policy : 동일 출처 정책
같은 출처의 리소스만 공유 가능함
출처(Origin) : 프로토콜 , 호스트 , 포트의 조합
출처 중 하나라도 다르면 동일한 출처로 보지 않음
ex) https://www.codestates.com:443/course
https:// 프로토콜 www.codestates.com:443 호스트 :443 포트
SOP는 왜 생겨났을까 ?
동일 출처 정책은 잠재적으로 해로울 수 있는 문서를 분리함으로써 공격받을 수 있는 경로를 줄여줌
ex) 네이버에 로그인 후 로그아웃을 까먹음 ->브라우저에 로그인 정보가 남아있음 -> 로그인 정보를 노리는 코드가 있는 사이트에 방문 -> 해킹당함
이를 방지하기 위해 SOP는 애초에 다른 사이트와의 리소스 공유를 제한하기 때문에 로그인 정보가 타사이트 코드에 의해 새어나가는 것을 방지 할 수 있음
한마디로 SOP는 보안상의 이유로 생긴 정책임
보안에서는 이점이 있지만 다른 출처의 리소스를 사용하게 될 일이 너무 많음
-> 이러한 문제 상황에서 필요한 것이 CORS 임
CORS 란?
Cross-Origin Resource Sharing : 교차 출처 리소스 공유
추가 HTTP 헤더를 사용하여 , 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에게 알려주는 체제
즉 브라우저는 SOP에 의해 기본적으로 다른 출처의 리소스 공유를 막지만 CORS 를 사용하면 접근 권한을 얻을 수 있게 됨.
CORS 동작 방식
3가지 방법이 존재
1. 프리플라이트 요청
실제 요청을 보내기 전 , OPTION 메서드로 사전 요청을 보내 해당 출처 리소스에 접근 권한이 있는지부터 확인하는 것
접근 권한이 있을 때
클라이언트 브라우저 서버 | 실제요청 --> | 프리플라이트 요청 -->
Origin: manchoon.com| | | <-- 프리플라이트 응답
Access-Control-Allow-Origin : manchoon.com| | | 실제요청 --> | | <-- 응답 | <-- 응답 |
브라우저는 서버에 실제 요청을 보내기 전 프리플라이트로 요청을 보내고 , 응답 헤더의
Access-Control-Allow-Origin 으로 요청을 보낸 출처가 돌아오면 실제 요청을 보냄
접근 권한이 없을 때
클라이언트 브라우저 서버 | 실제요청 --> | 프리플라이트 요청 -->
Origin: manchoon.com| | <-- CORS 에러 | <-- 프리플라이트 응답
Access-Control-Allow-Origin : 응답 헤더 에 없음|
요청을 보낸 출처가 접근 건한이 없다면 브라우저에서 CORS 에러를 띄우게 되고, 실제 요청은 전달 되지 않음
프리플라이트 요청의 필요성
1. 실제 요청을 보내기 전 권한을 확인할 수 있어 실제 요청을 통째로 보내는 것 보다 리소스 측면에서 효율적
2. CORS에 대비가 되어있지 않은 서버를 보호할 수 있음 .
ex) 프리플라이트 요청을 먼저 보내게 되면 , 프리플라이트 요청에서 CORS 에러를 띄우게 됨
2. 단순 요청 ( Simple Request)
특정 조건이 만족되면 프리플라이트 요청을 생략하고 요청을 보내는 것
만족 조건
클라이언트 서버 | 실제 요청 -->
Origin : manchoon.com| 요청 수행 | <-- 응답
Access-Control-Allow-Origin : *|
1. GET, HEAD, POST 요청 중 하나여야 함
2. 자동으로 설정되는 헤더 외에, Accept, Accept-Language, Content-Language, Content-Type 헤더의 값만 수동으로 설정할 수 있음
3. Content-Type 헤더에는 application/x-www-form-urlencoded, multipart/form-data, text/plain 값만 허용됨
하지만 위의 조건들은 모두 만족시키기는 어려우므로 참고만 !
3. 인증정보를 포함한 요청 (Credentialed Request)
요청 헤더에 인증 정보를 담아 보내는 요청
출처가 다를 경우 별도의 설정을 하지 않으면 쿠키를 보낼 수 없음 . 따라서 프론트,서버 양측 모두 CORS 설정이 필요함
프론트 측 : 요청 헤더에 withCredentials : true 를 넣어줘야 함
서버 측: 응답 헤더에 Access-Control-Allow-Credentials : true 를 넣어줘야 함
(서버 측에서 Access-Control-Allow-Origin 을 설정할 때, 모든 출처를 허용한다는 뜻의 와일드카드(*)로 설정하면 에러가 발생함 . 인증 정보를 다루는 만큼 출처를 정확하게 설정해주어야 함)
CORS 설정 방법
1. Node.js 서버 / 2. Express 서버
// 1. Node.js 서버
const http = require('http');
const server = http.createServer((request, response) => {
// 모든 도메인
response.setHeader("Access-Control-Allow-Origin", "*");
// 특정 도메인
response.setHeader("Access-Control-Allow-Origin", "https://codestates.com");
// 인증 정보를 포함한 요청을 받을 경우
response.setHeader("Access-Control-Allow-Credentials", "true");
})
//2. Express 서버
const cors = require("cors");
const app = express();
//모든 도메인
app.use(cors());
//특정 도메인
const options = {
origin: "https://codestates.com", // 접근 권한을 부여하는 도메인
credentials: true, // 응답 헤더에 Access-Control-Allow-Credentials 추가
optionsSuccessStatus: 200, // 응답 상태 200으로 설정
};
app.use(cors(options));
//특정 요청
app.get("/example/:id", cors(), function (req, res, next) {
res.json({ msg: "example" });
});
Mini Node Server 의사코드
Bare minimum requirements
이번 과제에서 구현하는 웹 서버의 기능은 매우 단순합니다. 클라이언트의 액션(버튼 클릭)에 따라 각기 다른 HTTP 요청을 서버로 보내고, HTTP 요청에 담아 보낸 단어를 소문자 또는 대문자로 응답을 받아 화면에 보여 줍니다.
[그림] Mini Node Server 예시
Endpoint(URL)Method기능
/lower | POST | 문자열을 소문자로 만들어 응답해야 합니다 |
/upper | POST | 문자열을 대문자로 만들어 응답해야 합니다 |
[표] Endpoint에 따른 메서드와 기능 설명
- POST에 문자열을 담아 요청을 보낼 때는 HTTP 메시지의 body(payload)를 이용합니다.
- 서버는 요청에 따른 적절한 응답을 클라이언트로 보내야 합니다.
- CORS 관련 헤더를 OPTIONS 응답에 적용해야 합니다.
- 클라이언트의 preflight request에 대한 응답을 돌려줘야 합니다.
- preflight request에 대한 응답 헤더는 이미 작성되어 있습니다.
const http = require('http');
const server = http.createServer((request, response) => {
response.writeHead(200, defaultCorsHeader);
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
};
})
[코드] preflight request에 대한 응답 헤더
const http = require('http');
const PORT = 4999;
const ip = 'localhost';
const server = http.createServer((request, response) => {
console.log(
`http request method is ${request.method}, url is ${request.url}`
);
response.writeHead(200, defaultCorsHeader);
response.end('hello mini-server sprints');
});
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
};
기본으로 제공되는 코드는 위와 같다 . 문제를 해결해보기 전 의사코드를 작성해 볼 것이다.
const http = require('http'); //Node.js HTTP 모듈을 불러오기 위함
const PORT = 4999; // 포트번호는 4999로 설정
const ip = 'localhost'; // ip는 localhost 즉 내 컴퓨터를 의미
//node 웹 서버 애플리케이션은 웹 서버 객체를 만들어야 함 이 때 createServer를 사용함
//서버로 오는 HTTP 요청마다 createServer에 전달된 함수가 한 번씩 호출됨
//HTTP 요청이 서버에 오면 node가 트랜잭션을 다루려고 request와 response 객체를 전달하며 요청 핸들러 함수를 호출함.
const server = http.createServer((request, response) => {
console.log(
`http request method is ${request.method}, url is ${request.url}`
);
//요청을 처리할 때, 우선은 메서드와 URL을 확인한 후 이와 관련된 적절한 작업을 실행하려고 함 . 적절한 작업은 request 프로퍼티를 이용 !
const { headers, method, url } = request;
// 메소드와 url에 따라 다르게 실행되어야 함 -> if문 사용
// 1.메소드가 POST이면서 대문자로 변경할 때 즉 url이 /upper
// 2.메소드가 POST이면서 소문자로 변경할 때 즉 url이 /lower
// 분기를 한 후 조건에 맞게 요청을 실행
// POST 요청이므로 요청 body를 'data'와 'end' 이벤트로 받음
// 각 'data' 이벤트에서 발생시킨 청크는 Buffer
// 이 청크가 문자열 데이터라는 것을 알고 있다면 이 데이터를 배열에 수집한
// 다음 'end' 이벤트에서 이어 붙인 다음 문자열로 만드는 것이 가장 좋음
// 우리는 문자열 데이터로 받을 것이기 때문에 배열에 저장할 것
// 그 후 저장한 배열 데이터를 조건에 맞게 toUpperCase() ,toLowerCase() 실행
// ex) body = body.toUpperCase();
// let body = [];
// request.on('data', (chunk) => {
// body.push(chunk);
// }).on('end', () => {
// body = Buffer.concat(body).toString();
// // 여기서 `body`에 전체 요청 바디가 문자열로 담겨있습니다.
// });
// cors 헤더를 넣어서 cors 에러 해결 , defaultCorsHeader 에서는 *로 모든 오리진 허용
// 따라서 문제 없이 요청이 가능함
response.writeHead(200, defaultCorsHeader);
response.end('hello mini-server sprints');
});
//요청을 실제로 처리하려면 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
};
위의 의사코드를 바탕으로 코드를 작성해보겠다.
const http = require('http'); //Node.js HTTP 모듈을 불러오기 위함
const PORT = 4999; // 포트번호는 4999로 설정
const ip = 'localhost'; // ip는 localhost 즉 내 컴퓨터를 의미
//node 웹 서버 애플리케이션은 웹 서버 객체를 만들어야 함 이 때 createServer를 사용함
//서버로 오는 HTTP 요청마다 createServer에 전달된 함수가 한 번씩 호출됨
//HTTP 요청이 서버에 오면 node가 트랜잭션을 다루려고 request와 response 객체를 전달하며 요청 핸들러 함수를 호출함.
const server = http.createServer((request, response) => {
console.log(
`http request method is ${request.method}, url is ${request.url}`
);
//요청을 처리할 때, 우선은 메서드와 URL을 확인한 후 이와 관련된 적절한 작업을 실행하려고 함 . 적절한 작업은 request 프로퍼티를 이용 !
const { headers, method, url } = request;
// 메소드와 url에 따라 다르게 실행되어야 함 -> if문 사용
// 1.메소드가 POST이면서 대문자로 변경할 때 즉 url이 /upper
// 2.메소드가 POST이면서 소문자로 변경할 때 즉 url이 /lower
// 분기를 한 후 조건에 맞게 요청을 실행
// POST 요청이므로 요청 body를 'data'와 'end' 이벤트로 받음
// 각 'data' 이벤트에서 발생시킨 청크는 Buffer
// 이 청크가 문자열 데이터라는 것을 알고 있다면 이 데이터를 배열에 수집한
// 다음 'end' 이벤트에서 이어 붙인 다음 문자열로 만드는 것이 가장 좋음
// 우리는 문자열 데이터로 받을 것이기 때문에 배열에 저장할 것
// 그 후 저장한 배열 데이터를 조건에 맞게 toUpperCase() ,toLowerCase() 실행
// ex) body = body.toUpperCase();
// let body = [];
// request.on('data', (chunk) => {
// body.push(chunk);
// }).on('end', () => {
// body = Buffer.concat(body).toString();
// // 여기서 `body`에 전체 요청 바디가 문자열로 담겨있습니다.
// });
let body = [];
if (request.method === "POST") {
request
.on("error", (err) => {
console.error(err);
})
.on("data", (chunk) => {
// Buffer
// body = chunk;
body.push(chunk);
})
.on("end", () => {
// options가 걸릴 때
// concat []의 요소를 합쳐주었다.
// from Buffer 그 자체 => toString으로 스트링으로 변환
body = Buffer.concat(body).toString();
response.on("error", (err) => {
console.error(err);
});
response.statusCode = 200;
response.setHeader("Content-Type", "application/json");
if (url === "/upper") {
body = body.toUpperCase();
} else if (url === "/lower") {
body = body.toLowerCase();
}
// cors 헤더를 넣어서 cors 에러 해결
// 모든 오리진을 허용해줘서 문제없이 요청 가능
response.writeHead(200, defaultCorsHeader);
response.write(body);
response.end();
});
}
// OPTIONS 따로 처리해주면 좋다.
// 따로 처리하지 않으면, body가 falsy인 경우 에러
if (request.method === "OPTIONS") {
response.writeHead(200, defaultCorsHeader);
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
};
'코드스테이츠44기 프론트엔드' 카테고리의 다른 글
섹션2를 마무리하며 KTP회고 (0) | 2023.04.10 |
---|---|
Section 2 기술면접 준비 (0) | 2023.04.10 |
클라이언트 ajax 요청 (0) | 2023.04.03 |
Postman 사용하기 (0) | 2023.03.30 |
REST API 완벽 이해하기 (0) | 2023.03.29 |