http module, locathost
http 모듈이 서버를 여는 역할을 함. callback함수로 실행을 시킨다. 여기서 포트번호를 적고 실행이 제대로 되었을 때와 되지 않았을 때를 나누어서 action을 취한다.
req : 방문요청 (방문자에 대한 정보를 포함함.
res : 요청에 대한 응답, 항상 요청에 응해야 하는 건 아님.
res.wirte() :html문서 한줄씩 적어서 보내주는 것이고 res.end()는 더이상 write할 것이 없다는 것을 브라우저에게 알려주는 메소드로 마지막에 한번만 쓸 수 있다. 그리고 listen같은 경우에는 서버가 유지되도록 도와준다. 실제로 실행하면 아마 더이상 터미널에서 다른 걸 할 수 없다. 끄는 건 ctrl+c 접속은 localhost:포트번호로 접속할 수 있다.
localhost는 내 컴퓨터에서만 돌아가는 일종의 호스트라고 생각하면 됨, 개발용으로 자주 사용함.
포트
http 기본포트(:80)
https 기본포트(:443) 이 기본포트들은 URL 뒤에 생략되어있다.
만약에 포트가 다르다면 호스트가 같더라도 다른 사이트처럼 동작할 수 있다. 그래서 도메인을 사도 문제가 될수도...? ㅋㅋㅋ 그래서 포트가 있으면 그 도메인을 더 잘 활용할 수 있다. 참고로 1024 아래 포트는 다른 프로그램이 사용할 확률이 높다.
응답으로 파일 읽어 보내기
서버도 하나의 이벤트 기반으로 움직이기 때문에 이벤트 리스너를 사용할 수 있다.
ex : server.on함수를 통해 이벤트가 발생하면 메시지를 띄우도록 하는 것
근데 보통 이런식으로 res.write로 html을 한줄씩 보내는 사람은 아무도 없다. 그래서 보통은 html파일을 통째로 전송하는 방식을 주로 사용함. fs모듈을 추가한 후 fs.readFile을 이용하면 버퍼(이 함수의 파라미터에서는 data)를 통해서 파일을 받아올 수 있다. 그래서 res.end가 버퍼를 보내준다.
서버는 항상 서버가 아니라 클라이언트가 될 수도 있다. 누가 요청을 보내고 응답을 하는 관계에 따라서 달라질 수 있기에 유동적인 개념이다.
쿠키 설정하기,req.url
클라이언트와 서버간의 데이터를 주고받는 방법으로 보통 쿠키와 세션을 사용한다.
먼저 req.headers.cookie는 클라이언트에서 요청된 쿠키를 받아오는 것이고 res.writeHead에서는 첫번째 인자로는 서버 통신이 성공했다는 뜻의 상태코드인 200을, 두번째 파라미터는 쿠키의 내용을 정해주는 것인데 여기서는 그 내용을 mycookie=test로 설정하였다. 형태로 만들어놓았다. 이렇게 되면 서버에서 클라이언트로 쿠키를 보낸다.
이렇게 보내진 쿠키는 network창에서 확인이 가능하다. 저장한 쿠키는 application 창에서 찾아볼 수 있다. 요청이나 응답에는 그에 대한 정보를 담고 있는 헤더가 포함되어 있다. 이 때 res header와 req header에는 쿠키에 대한 정보가 포함된다. 만약 mycookie=test라는 문자열이 아닌 객체로 받아오고 싶다면 parseCookies라는 함수를 이용하면 객체처럼 받아올 수 있다.
그리고 req.url를 이용해서 어떤 곳에서 요청이 들어왔는지 확인할 수 있다. 이를 이용해서 다른 페이지를 보낼 수 있다.
라우터 분기 처리와 쿠키
쿠키가 사용자를 추척한다???
/login URL로 들어가면 다른 페이지가 뜨도록 분기처리를 할 수 있다.
또 url.parse로 쿼리를 파싱하고 또 그 쿼리를 파싱해서 이름을 따올 수 있다.
name : 로그인 창에서 가져오는 이름.
로그인에 있는 쿼리스트링이 파싱되서 쿠키로 저장되서 클라이언트로 넘어간다. 클라이언트에서 로그인이라는 주소로 이름을 쿼리스트링으로 붙여서 서버로 요청이 감. 서버에서는 이름을 파싱하고 클라이언트에 이름을 쿠키로 삼는다는 내용의 응답을 보냄.
Expires : 이 쿠키의 유효시간, 쿠키는 유효시간이 지나면 자동으로 사라진다. httponly : js에서 쿠키에 접근 X, path=/ : 루트 경로에서만 유효한 쿠키 참고로 path가 /면 /아래 모든 경로에서도 유효함. (ex: /post, /post/1 등등) 뭐 여튼 세미콜론으로 쿠키의 옵션을 지정해줄 수 있다.
이 뒤쪽의 주소를 지워서 root page로 이동한다.
여기서 이름을 입력한 다음 login을 누르면 다시 로그인 페이지로 간다.
그렇게 되면 쿠키의 value로 이름이 넘어가게 된다.
상태코드 : 302 임시 이동으로 응답을 할 때 브라우저에게 location에 적힌 페이지로 이동하라는 코드임
302 로 루트페이지로 설정을 하면 로그인 시 쿠키는 설정이 된 상태로 다시 돌아옴. 그리고 이제는 쿠키가 남아있을 때 로그인 후 화면에서 @@님 안녕하세요가 뜨도록 한다.
방금 있던 쿠키로 이런식으로 화면을 바꿀 수 있다.
즉 처음에는 URL이 login으로 시작하지도 않고 쿠키도 없으므로 else문에 걸려 루트페이지로 접속이 된다. 그 뒤에 로그인을 하면 그 요청은 if(req.rul.startsWite('/login')에 걸리게 된다. 거기서 같이 따라오는 쿼리를 파싱을 해서 따온 이름을 name에다가 넣어서 쿠키로 삼아라! 라능 명령을 한다. 그러면서 302 요청에 의해서 루트로 보낸다. 그렇게 302로 가면 다시 루트로 가서 루트요청을 받는데 이번엔 cookies.name에 대한 정보가 있기 때문에 @@님 안녕하세요! 라고 뜬다. 그리고 여기서는 새로고침을 해도 @@님 안녕하세요가 뜨는데 이는 쿠키가 있기에 기억되는 것이다. 그래서 보안할 때 쿠키를 지우라는 것. 이게 탈취당하면 개인정보가 유출될 수도 있다. 하지만 이를 통해서 데이터를 보내므로 유용하게 쓸 수 있다.
메모리 세션 구현해보기
보통 쿠키를 사용해서 유지하는 세션을 많이 사용하므로 둘이 밀접한 관계가 있다.
쿠키의 문제? 너무 쉽게 정보가 노출이 된다. 즉 보안에 취약하다. 그렇기에 우선 1차적인 노출을 피하자라는 의미에서 session을 사용함.
session이라는 빈 객체를 만들어주고(const session = {} ) randomInt라는 변수를 하나 만들어둔다. 그리고 아까 쿠키에 담긴 정보들은 session에 넣는다. 이런식으로 처리를 하면 이전에는 바로 name과 value가 바로 떴다면 session을 사용했을 경우에는 이름 대신 아까 생성한 randomInt가 뜨게 된다.
이번에는 로그인 이후에 화면도 쿠키 대신 session을 사용해서 구현을 한다면(코드 뒤쪽 내용은 expires를 통해 유효시간이 맞는지 확인하는 코드) 이전과 같은 동작을 쿠키 대신 session을 이용해서 할 수 있다. 이렇게 되면 우선 1차적으로 서버 정보가 유출되는 건 막을 수는 있다. 물론 지금 구조의 세션도 브라우저의 쿠키가 노출되면 정보가 유출될 수 있긴 하다.
REST API
서버의 자원들(게시글, 댓글 등등..) 을 주소를 통해서 가져오는데 이걸 어떻게 구조화할까? REST API!
보통 이 위의 명령어에 URL을 붙여서 사용함
GET www.naver.com/users/1 -> 네이버의 1번 사용자를 가져오는 것.
POST www.naver.com/users ->사용자를 등록하는 것
PUT www.naver.com/users -> 사용자를 전체수정, 통째로 대체
PATCH www.naver.com/users/5 -> 5번 사용자를 부분수정
DELETE www.naver.com/users/2 -> 2번 사용자를 삭제함
원래 POST API의 규칙으로 자원은 명사형이여야 한다는 규칙이 있다.
window.onload = getUser로 로딩 시 getUser함수를 호출하고 그 다음 함수는 HTML에서 form에서 정보를 입력하고 submit을 눌렀을 때 그 정보들을 불러오는 역할을 함. 빈 폼으로 안들어오도록 하고 그 다음에 form에서 입력한 정보를 가져온 다음 POST를 통해서 제출한 정보를 서버로 보내면서 이러한 이름(/users에) 을 가진 사람을 만들어주세요! 그 다음에 onload는 서버에 요청을 보냈으면 응답을 하는데 201을 보내주면 성공, 그게 아니라면 에러 메시즈를 띄워줌. 그다음에 성공을 했으면 getUser를 호출함.
실행시 AJAX요청을 서버로 보낸다. ( 이 함수 가장 뒤에 xhr.open('GET', /users'); xhr.send(); 가 있다.)
users라는 자원을 가져오는 요청을 보냄.
가져오면 HTML문서에서 list에 수정, 삭제 버튼과 여러가지 태그들을 랜더링하도록 했다. 이러한 것들에는 수정하고 삭제하는 이벤트 리스너들도 등록을 해주는 식으로 설계되어있다. req.url이 자원이라면 메소드들은 req.method에 담겨있다. 이를 통해서 메소드들을 통해 가져온 정보들을 처리하는 분기처리를 할 수 있다.
우선 GET method를 한번 보자. req.url의 정보에 따라서 분기를 또 해준다. 만약 루트라면 html문서를 가져와서 랜더링해주고 /users 라면 그 유저의 정보를 가져오는데 문제는 res.end()로는 users라는 객체를 가져올 수는 없다. 그렇기에 JSON.stringify로 타입을 바꿔줘서 가져와야 한다.
하지만 윗 코드에서처럼 사용한다면 실제로 필요한 정적파일들을 가져온다는 이야기가 없었으므로 정상적으로 get이 작동하지 않게끔 된다. 이를 해결하기 위해서는 else if 떡칠을 하는 방법도 있겠지만 실제로는 저런 파일들이 상당히 많을 수 있으므로 req.url에서 요청하는 것과 똑같은 파일들을 불러오는 식으로 코드를 작성한다.
이렇게 코드가 작성되고 나서 다시 한번 network 창을 한번 보자
정상적으로 모든 정보들을 GET해오는데에 성공했다고 할 수 있다. 그리고 우리가 처리했던 /users에 대한 정보들이 여기로 가져와진다. (코드상에서는 완전히 비어있는 객체기 때문에 아무 정보는 안들어있다.
아까 위의 코드에서 POST를 통해 정보를 등록하라는 요청이 들어오면 분기를 처리할 코드를 작성해보자.
역시 json문자열로 이름을 보내주고.. send에 담기는 내용은 헤더가 아닌 본문에 담긴다. 이를 처리를 해보자. 본문과 같은 경우에는 데이터를 통째로 받아서 처리를 하기 힘드므로 스트림을 chunk라는 단위로 나누어서 처리를 한다. 이런식으로 처리를 하면 body는 실질적으로 name을 json의 문자열로 받아온 것이 된다. 이를 parse를 이용해서 받아준다. 그리고 id는 사용자에게 고유한 key를 부여해준 후 db역할을 하는 users라는 객체에 넣어준다. 그다음에 요청이 성공했다고 보내준다.
이렇게 이름을 입력하고 보내면 POST 요청이 정상적으로 작동하는 것을 알 수 있다. req정보를 보면 실제로 입력한 정보가 작성되어 있다. 그리고 res를 보면 사용자 등록 성공이라는 메시지가 정상적으로 잘 나온다.
우리는 코드를 작성할 때 POST요청을 보내고 이게 200 or 201로 성공을 했다면 GET users요청을 보내도록 했으니까 반복적으로 요청이 가게 된다. 이런식으로 정보를 등록하면 수정과 삭제 또한 진행할 수 있도록 분기처리를 해보자.
우선 수정하는 코드를 보자. 역시 POST와 마찬가지로 body를 읽어야 한다. 근데 일부분을 수정하는 것이기 때문에 유저의 번호를 확인해야 하는데 이 경우에는 정규표현식을 써서 하거나 요청을 startsWith로 뽑아준다. (이 상황에서는 항상 /users/'에 걸리니까..) 그 다음에 문자열 처리를 해서 유저 번호를 뽑아준다. 그 후 수정완료된 객체를 다시 front로 보내줌.
이제 작동시켜 보고 zerocho를 zero로 수정을 하니 zerochork PUT요청을 보내고 그 내용에는 zerocho를 zero로 바꾸라는 내용을 담고 있다. 그리고 users 정보를 보면 유저도 마찬가지로 zerocho에서 zero로 바뀌어져 있다.
delete도 역시 삭제하는 유저의 번호를 가져온 후 그 유저를 지우고 삭제완료가 되면 그 정보를 front로 보내주는 동일한 매커니즘으로 작동한다.
hero를 DELETE 요청을 했더니 지워지는 걸 확인할 수 있다.
라우터 리팩토링
중복이 발생하거나 코드가 지나치게 지저분하면 리팩토링이 필요하다. 아까 짠 코드도 솔직히 말해서 5개의 분기를 나누고 그게 좀 많이 지저분한 편이다. 이제 리팩토링을 해보자! 핵심은 if문이 떡칠되어 있는 것을 router 객체로 만드는 작업이다.
이런식으로 간략하게 리팩토링을 할 수 있다. 여기서 '*' 은 와일드카드로 그 외의 모든 경우를 말한다. GET @@가 undefined일 경우로 걸러낼 수 있다.
즉, 여기서 matchedUrl이 undefined일 경우에 *을 쓰도록 해줘야 한다.
https, http2
예전에는 http를 사용했는데 요즘은 보안이 강력한 https로 넘어가는 추세이다. 그래서 node도 https를 지원한다. 그래서 https라는 모듈을 사용하면 된다. 다만 이 경우에는 인증서가 필요하다. 무료도 있고 유료도 있는데 무료 중에서는 letsencrypt라는 인증서가 있다. 그 인증서를 첫번째 옵션에다가 추가를 해주면 된다. http2라는 것도 있는데 https 기반이기 때문에 역시 인증서가 필요하지만 최신기능이므로 아직 불안정한 부분이 많아 https를 많이 사용한다.
cluster로 멀티 프로세싱 하기
노드가 싱글쓰레드다? 코어를 1개만 쓴다는 이야기다. 하지만 실제로 코어를 1개밖에 안쓰는 경우는 거의 없다고 보면 된다. 그래서 이를 해결하기 위해서 노는 코어들을 활용하는 cluster로 멀티프로세싱을 해보자!
개념상으로는 반복문으로 서버를 여러개 만드는 방식이긴 하다. cluster에는 master 프로세스와 worker프로세서가 있는데 master인 경우에는 cpu 갯수만큼 worker를 만들어내고 worker들은 실제로 서버를 돌리는 역할을 한다. 여기서 cluster.fork()로 워커를 만들어낸다. 그런데 가끔 worker가 사라지는 경우가 있는데 이 경우에는 이벤트를 받아서 다시 만들어줄 수 있다.
이런 식으로 코어의 갯수만큼 worker가 생성되고 요청을 나누어서 받으면서 코어를 돌린다.
이 강의자료들은 osam.kr의 node.js 기본부터 프로젝트 실습까지(조현영님) 라는 강의를 토대로 작성하였습니다. 사실 제가 받아들인 대로 쓰는거라서 틀린 내용이 있을 수 있으니 혹시 사실과 다른 부분이 있으면 피드백 해주시면 감사하겠습니다.
ABM 인턴 후기 (0) | 2023.03.20 |
---|---|
node.js를 공부해보자 (4 : express web server 만들기) (0) | 2021.10.08 |
node.js를 공부해보자(1, 기본적인 문법과 callback, promise, async/await) (0) | 2021.10.07 |
야구 시뮬레이션 개발일기 (1. 왜 내가 하던 게임은 망겜소리를 듣나) (0) | 2021.09.18 |
시간 날 때 할만한 TOY Project 목록들 (0) | 2020.12.01 |