http 모듈을 이용해서 javascript로 서버를 만드는 실습을 먼저 진행해 보겠습니다.
다음과 같은 파일을 만들어 주세요.
const http = require('http');
http.createServer((req, res)=>{
console.log('get message')
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('Hello nodejs');
res.end();
}).listen(3000);
http 모듈에는 createSever라는 함수가 있습니다. 여기서
(req, res)=>{ ... }
req는 http request에 대한 정보, 클라이언트가 서버에 요청한 정보를 가지고 있습니다.
res는 http response로 클라이언트에 응답할 내용에 대한 객체입니다.
.listen(3000);
서버가 동작할 때 몇 번 포트에서 동작할 건지 지정해줍니다. 이 예제에서는 3000번 포트로 지정해서 서버을 올렸습니다.
익스프레스(Express.js)는 노드(NodeJS) 상에서 동작하는 웹 개발 프레임워크입니다.
sudo npm install -g express express-generator
express [project name]
위 명령어를 실행하면 아래와 같은 프로젝트 구조가 나옵니다.
Express의 구조는 2차 세미나에서 다뤄볼 예정입니다.
궁금한 사람은 여기를 클릭해주세요.
app.js는 node를 실행하면 가정 먼저 실행되는 파일이며 express의 대한 설정파일이 담겨져 있는 핵심 코드입니다.
그 중에서 라우팅에 관련된 소스 코드 두 부분만 먼저 확인 하겠습니다.
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
위 두 코드는 index.js 파일과 user.js파일을 가져오는 작업을 하고 있습니다.
app.use('/', indexRouter);
app.use('/users', usersRouter);
코드를 아래로 내려보면 app.use()
라는 함수가 나타납니다.
app.use()
는 특정 조건이 맞으면 다른 라우트로 전달하는 기능을 합니다. router.use() 함수 문서 보기
예시)
서비스의 주소가 192.168.0.1 라고 가정을 해보자. 만약 요청이192.168.0.1
로 요청된다면app.use('/', ...)
에서 요청을 인식하고 이를indexRouter
로 넘겨지게 됩니다.
이와 다르게 요청이192.168.0.1/user
로 요청된다면 app.use('/', ...) 에서는 인식 되지 않고app.use('/user', ...)
에서 인식되어userRouter로 넘겨지게 됩니다.
app.js에 대해서 궁금한 사람은 여기를 클릭해주세요.
router.use()를 사용하지 않고 하나의 파일에서 모든 라우팅 경로를 지정할 수 도 있습니다. 하지만 그렇게 되면 파일의 모든 라우팅정보가 들어가면서 거대해지기 때문에 app.use를 이용하여 라우팅 하는 것을 권장합니다.
express로 프로젝트를 생성하면 routes 폴더가 생긴것을 알 수 있습니다.
routes 폴더에서는 라우팅 로직을 구현합니다.
라우팅은 URI(또는 경로) 및 특정한 HTTP 요청 메소드(GET, POST 등)인 특정 엔드포인트에 대한 클라이언트 요청에 애플리케이션이 응답하는 방법을 결정하는 것을 말합니다.
참고 자료 : 기본 라우팅
서버는 하나의 주소값을 가지고 있습니다. 하지만 다양한 목적에 따라서 주소값을 나눌 수 있습니다.
192.168.0.1/login
192.168.0.1/signin
192.168.0.1/signup
위 예시와 같이 하나의 주소를 가지지만 /
이후에 나오는 정보에 따라서 다른 처리를 할 수 있습니다.
이렇듯 하나의 서버 주소를 요청 URI에 따라서 다양한 처리를 할 수 있도록 만든 것이 라우팅입니다.
var express = require('express');
var router = express.Router();
router.use('path', ...);
module.exports = router;
Express 프레임워크에서 라우팅을 구현할 때 위의 코드를 활용하여 라우팅 모듈을 만듭니다.
위 코드를 상세히 분석 해 보겠습니다.
var express = require('express');
var router = express.Router();
위 코드는 express 모듈을 가져오는 작업입니다. 또한 express에서 제공하는 Router객체를 가져옵니다.
router.use('path', ...);
router객체에는 use(), get(), post()등의 함수가 있습니다.
크게 두가지로 나눌 수 있습니다.
- router.use(): 특정 조건이 맞으면 다른 라우팅 모듈로 넘겨줍니다.
- router.method(): 현재 path에 특정 METHOD로 요청이 오면 이를 인식하고 처리합니다.
※ method에는 get, post등이 있습니다.
router.use() 함수는 미들웨어로써 사용할 수 있습니다.
그리고 module.exports = router;
는 현재 router객체를 외부에서 사용할 수 있는 라우터 모듈로 만들어 줍니다.
이러한 라우터 모듈을 연결해서 routes에 라우팅 로직을 구현할 수 있습니다.
라우팅 모듈을 이용해서 아래의 구조의 라우팅을 실습해볼 수 있습니다.
https://localhost/api/auth/signin
https://localhost/api/auth/signup
https://localhost/api/board/
signin은 로그인, signup은 회원가입, board는 게시판과 관련된 로직을 담당하도록 라우팅을 구성해보겠습니다.
먼저 다음과 같이 파일 구조를 만들어 주세요.
여기서 눈에 띄는 점이 하나 있는데 바로 index.js파일입니다. index.js파일은 현재 파일 경로를 인식했을 때 기본으로 인식되는 파일입니다. 만약 localhost/auth 경로를 요청한 경우 local/auth/index.js가 요청한 것과 같은 결과를 얻습니다.
그러면 이제 가장 밖에 있는 index.js부터 작성하겠습니다.
file path: routes/index.js
var express = require('express');
var router = express.Router();
console.log('trace: index.js');
router.use('/api', require('./api'));
module.exports = router;
file path: routes/api/index.js
var express = require('express');
var router = express.Router();
console.log('trace: /api/index.js');
router.use('/board', require('./board'));
router.use('/auth', require('./auth'));
module.exports = router;
file path: routes/api/board.js
var express = require('express');
var router = express.Router();
console.log('trace: /api/board.js');
router.get('/', (req, res)=>{
res.status(200).send({
message: "this is /api/board"
})
})
module.exports = router;
file path: routes/api/auth/index.js
var express = require('express');
var router = express.Router();
console.log('trace: /api/auth/index.js');
router.use('/signin', require('./signin'));
router.use('/signup', require('./signup'));
module.exports = router;
file path: routes/api/auth/signin.js
var express = require('express');
var router = express.Router();
console.log('trace: /api/auth/signin.js');
router.get('/', (req, res)=>{
res.status(200).send({
message: "this is /api/signin"
})
})
module.exports = router;
file path: routes/api/auth/signup.js
var express = require('express');
var router = express.Router();
console.log('trace: /api/auth/signup.js');
router.get('/', (req, res)=>{
res.status(200).send({
message: "this is /api/signup"
})
})
module.exports = router;
express 프로젝트 최상위 위치로 이동한 이후 npm start
를 실행하면 서버를 작동 시킬 수 있습니다.
처음 실행하는 경우 모듈이 설치가 안되어서 에러가 날 수 있습니다.
npm install
을 실행 시켜주시면 됩니다.
서버를 작동하는 순간 아래와 같은 결과가 나오는 것을 알 수 있습니다.
이는 라우터 모듈들이 서버가 시작되는 시점에 생성되고 연결되는 것을 알 수 있습니다.
이제 정상적으로 동작하는지 확인해 보겠습니다.
http://localhost:3000/api/board
http://localhost:3000/api/auth/signin
http://localhost:3000/api/auth/sugnup
위와 같이 브라우저에서 결과가 나타며 URI에 따라서 다른 라우팅 로직이 동작하는 것을 확인 할 수 있습니다.
공유 문서 프로젝트에 참여하기 위해서 먼저 WITH-SOPT-SERVER git organization에 가입을 하셔야 합니다. 이는 파트장인 저에게 연락 주시면 초대해드리겠습니다.
https://github.com/WITH-SOPT-SERVER/SHARED-LEARNING
링크를 들어가면 아래와 같은 화면이 나옵니다.
가장 먼저 알아야 하는 것은 READEME.md 파일입니다. 해당 경로에서 README.md파일은 기본으로 보여지게 됩니다.
먼저 프로젝트를 참여하기 위해서 WITH-SOPT-SERVER Repository 에서 SHARED-LEARNING 페이지에서 fork버튼을 눌러주세요. 그러면 개인 Repository 에 복제됩니다.
개인 원격 Repository를 Local에서 연결하겠습니다. 이때 Git bash를 이용하는 방법과 그외 Tool을 이용하는 방법이 있는데 여기서는 SourceTree를 이용한 방법으로 진행하겠습니다.
개인 Repository에서 clone or download
를 클릭하면 아래와 같은 화면이 나옵니다.
여기서 URL를 복사를 해준 이후 source tree를 켜줍니다.
소스트리에서 [새로만들기] -> [URL에서 복제]를 클릭해줍니다.
원본 URL에 복사한 주소를 입력해주면 자동으로 목적지 경로가 입력이 됩니다. 이는 local 컴퓨터에 복사할 폴거의 위치이며 자유롭게 변경하시면 됩니다.
지정한 폴더로 가면 위와 같이 파일이 생긴 것을 확인 할 수 있습니다.
이는 개인 Remote Repository와 똑같은 파일이라는 것을 알 수 있습니다.
visual Studio Code에서 해당 폴더를 workspace에 추가해주고 READEME.md파일의 참가자
항목에 이름을 추가하겠습니다.
...
# 참가자
- 윤희성
- [본인 이름]
source tree로 돌아오게 되면 히스토리에 아래와 같은 내용이 추가되었습니다.
uncommitted changes
는 변경된 사항이 커밋되지 않았다는 뜻을 의미합니다.
이제 파일 상태를 클릭해보겠습니다.
위 화면을 보면 변경된 파일 목록이 나타나고 오른쪽에는 어떤 점이 변경됬는지 나타나게 됩니다. 여기서 붉은색 영역은 삭제 된 것을 의미하고 초록색 영역은 새루 추가된 코드를 의미합니다. 위 스크린샷은 "서버파트장"이라는 코드가 삭제되고 "윤희성"이라는 코드가 추가되었다는 변경사항을 나타냅니다.
하단에 commit 메세지를 지정할 수 있습니다.
참가자 항목에 윤희성이라는 이름을 추가했다는 메세지를 남기로 커밋을 합니다.
커밋을 하면 히스토리 가장 상단에 위의 메세지가 추가된 것을 볼 수 있습니다. 이를 클릭하면 하단에 상세 정보를 볼 수 있습니다.
READEME.md 파일에 위와 같은 정보가 수정되었다는 것을 나타냅니다.
소스코드를 수정하면 change list에 추가가 됩니다.
수정된 부분 중에서 공통된 역할을 갖는 change item의 집합을 하나의 commit으로 묶을 수 있습니다.
즉 commit는 하나의 작업을 위한 코드 변경의 집합으로 생각하면 좋습니다.
지금 까지 변경사항은 오직 local에서만 적용되었습니다. 이를 확인할 수 있는 방법이 자신의 git repository에 가보면 참가자 항목에 추가가 안되어있는 것을 확인 할 수 있습니다.
Local에서 작업한 내용을 개인(Remote) Repository에도 적용을 시키는 방법이 git push입니다.
좌측 상단에 푸시 버튼에 1
이 추가가 된 것을 볼 수 있습니다. 여기서 1은 push할 수 있는 commit의 개수를 나타냅니다.
push를 하게 되면 local에서 만든 commit들을 개인(원격) Repository에 적용시킬 수 있습니다.
이 부분은 commit을 어떤 Repository에 어떤 branch로 올릴지 지정하는 부분입니다.
공유 문서 프로젝트에서는 branch관리는 하지 않기 때문에 master로 올리면 됩니다.
push를 하게 되면 origin/mater가 가장 최근에 추가한 커밋으로 이동한 것을 확인 하면 push가 정상적으로 동작한 것을 알 수 있습니다.
이제 github 페이지를 들어가면 개인 Repository에 적용된 것을 볼 수 있습니다. 하지만 fork를 한 대상인 WITH-SOPT-SERVER/SHARED-LEARNING에는 변경사항이 적용되어 있지 않습니다.
여기서 WITH-SOPT-SERVER/SHARED-LEARNING를 upstream으로 git아이디/SHARED-LEARNING를 origin으로 명칭을 정하겠습니다.
지금까지 실습은 origin에는 변경사항이 적용이 되었지만 upstream에는 적용이 되어있지 않습니다.
왜냐하면 지금 실습은 fork를 통해서 진행이 되었으며 이 과정은 upstream의 내용을 origin으로 복사한 것에 불과합니다. 이 경우 변경사항을 upstream에 올리고 싶다면 pull request
를 이용해서 진행을 하게 됩니다.
origin Repository에서 New Pull Request
를 클릭해봅시다. 그러면 아래와 같은 화면이 나타납니다.
오른쪽 Repository/branch에서 왼쪽 Repository/branch로 변경사항을 업로드 하도록 지정 할 수 있습니다. 또한 이때 업로드 되는 변경사랑은 아래 나타나게 됩니다.
이부분에서 unable to merge가 나거나 위 화면가 다르면 파트장에게 연락을 주시기 바랍니다.
create pull request를 눌러주면 아래와 같은 화면이 나옵니다.
메세지는 Pull request를 보낼때 어떠한 내용을 변경했는지 메세지로 남길 수 있습니다.
또한 오른쪽에 보면 Reviewers, Assignees..등이 있습니다. 여기서 사용자를 추가할 수 있습니다. Reviewers는 이 pull request를 검토해주기 바라는 사람을 추가하는 곳이고 Assignees는 해당 pull request를 승인하는 사람을 지정하는 곳입니다.
이번 실습에서 reviewers는 주변 지인에 github id를 입력해서 등록하고, assignees에는 heesung6701
를 검색해서 등록해주시 바랍니다.
여기까지만 진행을 하면 이제 제가 수정된 코드를 확인하고 Merge pull request를 진행 하게 됩니다. 또한 상황에 따라서 리뷰를 달 수 있는데 이는 나중에 다시 설명하겠습니다.
세미나 중 과제는 README.md파일의 참가자 항목의 자신의 이름을 추가해서 Pull request를 날리는 것입니다.
url 정보를 받아서 객체로 반환하는 함수이다.
var url = require('url');
var parsedObject = url.parse('http://user:[email protected]:8080/p/a/t/h?query=string#hash');
console.log(parsedObject);
{ protocol: 'http:',
slashes: true,
auth: 'user:pass',
host: 'host.com:8080',
port: '8080',
hostname: 'host.com',
hash: '#hash',
search: '?query=string',
query: 'query=string',
pathname: '/p/a/t/h',
path: '/p/a/t/h?query=string',
href: 'http://user:[email protected]:8080/p/a/t/h?query=string#hash' }
url객체를 url String으로 변환해주는 함수이다.
var url = require('url');
var parsedObject = url.parse('http://user:[email protected]:8080/p/a/t/h?query=string#hash');
console.log(url.format(parsedObject));
http://user:[email protected]:8080/p/a/t/h?query=string#hash'
query string을 객체로 변환해 준다.
var qStr = 'where=nexearch&query=querystring&sm=top_hty&fbm=1&ie=utf8';
var qObj = querystring.parse(qStr);
console.log(qObj);
{ where: 'nexearch',
query: 'querystring',
sm: 'top_hty',
fbm: '1',
ie: 'utf8' }
object를 query string으로 변환해주는 함수이다.
var qObj = { where: 'nexearch',
query: 'querystring',
sm: 'top_hty',
fbm: '1',
ie: 'utf8' };
console.log(querystring.stringify(qObj));
where=nexearch&query=querystring&sm=top_hty&fbm=1&ie=utf8
javascirpt에서 console.log는 화면에 출력하는 역할을 하지만 util.format은 string형태로 출력하는 역할을 합니다.
%s - String %d - Number %j - JSON 공식문서
var util = require('util');
var data = util.format('%d, %s, %j', 25, 'sopt', { name: 'heesung youn'});
console.log(data);
25, sopt, {"name": "heesung youn"}
※ 이와 비슷하게 JSON객체를 string으로 변환하는 방법에는 JSON.stringify도 있습니다.
var obj = {name: 'heesung youn'};
console.log(JSON.stringify(obj));
{"name": "heesung youn"}
crypto.createHash() 메소드를 통해서 매게변수를 알고리즘으로 갖는 Hash 클래스를 가져올 수 있습니다.
var crypto = require('crypto');
var shasum = crypto.createHash('sha512');
shasum.update('암호화 할 문자열');
var output = shasum.digest('hex');
console.log(output);
9dba3465a53d423b7fb2d29a9204f9330610f0ab6973870ab7b1f8725d6530e273f864f3bbb09bcb7107407b1d74bc7da6a6a174af76b43471b831b0eda81f30
Cipher Class는 암호화 알고리즘과 암호키를 매개변수로 createCipheriv 또는 createDecipheriv를 만들 수 있다. 각각 모두 암호화, 복화화를 담당하는 인스턴스를 만든다. 이는 update함수를 통해서 결과를 얻을 수 있으며 final을 통해서 원하는 형태로 결과 값을 얻을 수 있다.
기존에 사용하던 createCipher와 createDecipher는 deprecated 되었습니다. 관련 링크
var crypto = require('crypto');
var algorithm = 'aes-192-cbc'
var password = 'Password used to generate key'
var secretKey = crypto.scryptSync(password, 'salt', 24);
var input = '암화화할 문자열';
const iv = Buffer.alloc(16, 0);
var cipher = crypto.createCipheriv(algorithm, secretKey, iv);
var cipheredOutput = cipher.update(input, 'utf8', 'base64');
cipheredOutput += cipher.final('base64');
var decipher = crypto.createDecipheriv(algorithm, secretKey, iv);
let decrypted = decipher.update(cipheredOutput, 'base64', 'utf8');
decrypted += decipher.final('utf8');
var decipheredOutput = decrypted;
console.log('기존 문자열: ' + input);
console.log('암호화된 문자열: ' + cipheredOutput);
console.log('복호화된 문자열: ' + decipheredOutput);
crypto의 비동기 처리는 2주차에서 다루겠습니다.
var fs = require('fs');
var text = fs.readFileSync('text.txt', 'utf8');
console.log(text);
[text.txt 파일 내부 내용입니다.]
var fs = require('fs');
var data = 'Hello FileSystem';
fs.writeFileSync('text2.txt', data, 'utf8');
console.log('동기적 파일 쓰기 완료');
file system의 비동기 처리는 2주차에서 다루겠습니다.