2017년 10월 28일 토요일

Node.js 공부[5]

5챕터-웹 서버 만들기

노드에서는 웹 서버를 만들 때 필요한 http 모듈이 있는데, 이 모듈을 사용하면 HTTP프로토콜로 요청하는 내용과 응답을 모두 처리할 수 있다. 
그러나 쉽고 빠르게 웹 서버를 구성하려면 익스프레스(Express)를 사용하는 것이 좋다.

간단한 웹 서버 만들기

1
2
3
4
5
6
7
8
9
10
11
var http = require('http');
//웹서버 객체 만들기
var server = http.createServer();
//웹서버를 시작하여 192.168.0.5 IP와 3000번 포트에서 대기
var host = '192.168.0.2';
var port =3000;
server.listen(port, host, '50000',function(){
    console.log('웹 서버가 시작되었습니다. : %s, %d',host, port);
});
cs

웹브라우저에 요청할 때 어떤 이벤트가 발생하는 지 예제를 통해 볼 수 있다.
on()메소드는 이벤트를 처리할 때 가장 기본적인 메소드이다. 이 메소드로 connection, request, close 이벤트를 처리할 수 있는 콜백 함수를 각각 등록해 두면 상황에 맞게 호출된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var http = require('http');
var server = http.createServer();
var port = 3000
server.listen(port, function(){
    console.log('웹 서버가 시작되었습니다. : %d', port);
});
//클라이언트 연결 이벤트 처리
server.on('connection'function(socket){
    var addr = socket.address();
    console.log('클라이언트가 접속했습니다.: %s, %d', addr.address, addr.port);
});
//클라이언트 요청 이벤트 처리
server.on('request'function(req, res){
    console.log('클라이언트 요청이 들어왔습니다.');
   
    //클라이언트 요청시 페이지로 응답 보냄.
    res.writeHead(200, {"Content-Type""text/html; charset=utf-8"});
    res.write("<html>");
    res.write("<head>");
    res.write("<title>응답 페이지</title>");
    res.write("</head>");
    res.write("<body>");
    res.write("<h1>node 응답 페이지</h1>");
    res.write("</body>");
    res.write("</html>");
    //end는 응답을 모두 보냈다는 것을 의미하며 일반적으로는 end()가 호출될 때 클라이언트로 응답을 전송한다.
    res.end();  
    
});
//서버 종료 이벤트 처리
server.on('close'function(){
    console.log('서버가 종료 됩니다.');
});
cs

파일을 스트림으로 읽어 응답 보내기
파일은 스트림 객체로 읽어 들일 수 있고 웹서버의 응답 객체도 스트림으로 데이터를 전송할 수 있기 때문에 두 개의 스트림은 파이프로 서로 연결할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var http = require('http');
var fs = require('fs');
var server = http.createServer();
var port = 3000
server.listen(port, function(){
    console.log('웹 서버가 시작되었습니다. : %d', port);
});
//클라이언트 연결 이벤트 처리
server.on('connection'function(socket){
    var addr = socket.address();
    console.log('클라이언트가 접속했습니다.: %s, %d', addr.address, addr.port);
});
//클라이언트 요청 이벤트 처리
server.on('request'function(req, res){
    console.log('클라이언트 요청이 들어왔습니다.');
   
    var filename = 'aa.jpg';
    var infile = fs.createReadStream(filename, {flage: 'r'});
    
    //파이프로 연결하여 알아서 처리하도록  설정
    infile.pipe(res);
    
});
//서버 종료 이벤트 처리
server.on('close'function(){
    console.log('서버가 종료 됩니다.');
});
cs

서버에서 다른 웹사이트의 데이터를 가져와 응답하기
http모듈을 사용해 GET방식으로 다른 사이트에 데이터를 요청하는 코드이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var http = require('http');
var options = {
    host: 'www.google.com',
    port:80,
    path: '/'
};
var req = http.get(options, function(res){
    //응답처리
    resData = '';        
    res.on('data'function(chunk){
        resData += chunk;
    });
    
    res.on('end'function(){
        console.log(resData);
    });
});
req.on('error'function(err){
       console.log("오류 발생: " + err.message);
});
cs

POST방식으로 데이터를 요청하는 코드이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var http = require('http');
var opts = {
    host: 'www.google.com',
    port: 80,
    method: 'POST',
    path: '/',
    headers: {}
};
var resData ='';
var req = http.request(opts, function(res){
    //응답처리
    res.on('data'function(chunk){
        resData += chunk;
    });
    
    res.on('end'function(){
        console.log(resData);
    });
});
opts.headers['Content-Type'= 'application/x-www-form-unlencoded';
req.data = "q=actor";
opts.headers['Content-Lenth'= req.data.length;
req.on('error'function(err){
    console.log("오류발생:"+ err.message);
});
//요청 전송
req.write(req.data);
req.end();
cs

익스프레스로 웹 서버 만들기

http모듈만 사용해 웹 서버를 구성하면 많은 것들을 직접 만들어야 한다. 하지만 express모듈을 사용하면 간단한 코드로 웹서버의 기능을 구현할 수 있다. 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//여기서 express모듈은 웹 서버를 위해 만들어진 것이므로 http모듈 위에서 동작한다. 따라서 함께 불러줘야함.
var express = require('express')
, http = require('http');
//익스프레스 객체 생성
var app = express();
//기본 포트를 app객체에 속성으로 지정
app.set('port', process.env.PORT || 3000);
//Express 서버 시작
http.createServer(app).listen(app.get('port'), function(){
    console.log('익스프레스 서버를 시작했습니다: ' +app.get('port'));
});
cs
전 단계에서 http모듈로 웹 서버를 만들 때 createServer() 메소드로 웹 서버 객체를 만들고, listen() 메소드를 호출하여 클라이언트의 요청을 대기하도록 설정하였다. 익스프레스를 사용한 것도 같지만 차이점으로 createServer()메소드에 전달되는 파리미터로 app객체를 전달하는 것이 있다. app객체는 익스프레스 서버 객체이다.
이 객체의 주요 메소드로는
set(name, value) : 서버 설정을 위한 속성 지정, set()메소드로 지정한 속성은 get()으로 꺼내어 확인 할 수 있음
get(name) : 서버 설정을 위해 지정한 속성을 꺼내온다.
use([path,] function[, function...]) : 미들웨어 함수를 사용한다,
get([path,] function) : 특정 패스로 요청된 정보를 처리한다.


미들웨어로 클라이언트에 응답보내기
노드에서는 미들웨어를 사용하여 필요한 기능을 순차적으로 실행할 수 있다. 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var express = require('express')
, http = require('http')
var app = express();
app.use(function(req, res, next){
    console.log('첫 번째 미들웨어에서 요청을 처리함.');
    
    req.user = 'mike';
    
    next();
});
app.use(function(req, res, next){
    console.log('두 번째 미들웨어에서 요청을 처리함.');
    
    res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
    res.end('<h1>Express 서버에서' +req.user+ '가응답한 결과입니다.</h1>');
});
http.createServer(app).listen(3000function(){
    console.log('Express 서버가 3000번 포트에서 시작됨');
})
cs
>>각각의 미들웨어 안에서 마지막에 next() 메소드를 호출하여 다음 미들웨어로 처리 결과를 넘겨준다. 

또한 익스프레스에는 send(), redirect() 등 응답객체 메소드가 있다.
1
2
3
4
5
6
7
8
9
10
app.use(function(req, res, next){
    //send()로 JSON데이터 전송하기
    res.send({name:'소녀시대', age:20});
});
app.use(function(req, res, next){
    //redirect()로 페이지 이동하기
    res.redirect('http://google.co.kr');
});
cs

익스프레스에서 요청 객체에 추가한 헤더와 파라미터 알아보기
클라이언트에서는 요청 파라미터를 함께 보낼 수 있다. 이때 GET방식으로 요청했다면 요청파라미터들은 요청 객체의 query 객체 안에 들어간다. 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var express = require('express')
, http = require('http')
var app = express();
app.use(function(req, res, next){
    console.log('첫 번째 미들웨어에서 요청을 처리함.');
    
    var userAgent = req.header('User-Agent');
    var paramName = req.query.name;
    
    res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
    res.write('<h1>Express서버에서 응답한결과입니다. </h1>');
    res.write('<div><p>User-Agent : ' + userAgent + '</p></div>');
    res.write('<div><p>Param name: '+ paramName + '</p></div>');
    res.end();
});
http.createServer(app).listen(3000function(){
    console.log('Express 서버가 3000번 포트에서 시작됨');
})
cs
요청 파라미터를 주소에 입력해주면 
http://localhost:3000/?name=mike
이러한 창이 뜬다. 

미들웨어 사용하기

지금까지 use()메소드로 설정하는 미들웨어 함수 안에 코드를 직접 넣어 클라이언트로 응답을 전달했다. 하지만 모든 기능을 직접 만들어야한다면 쉽지 않으므로, 익스프레스에는 미리 만들어 둔 여러 미들웨어를 제공한다.

static 미들웨어: 특정 폴더의 파일들을 특정 패스로 접근할 수 있도록 만들어 줌
1
2
3
var static = require('serve-static');
...
app.use(static(path.join(__dirname, 'public')));

cs
이런식으로 지정해주면 public 폴더안의 파일들을  바로 접근할 수 있는 듯
public/index.html
ex) http://localhost:3000/index.html 와 같은 주소로 바로 접근 가능

body-parser 미들웨어: POST로 요청했을 때 요청 파라미터를 확인할 수 있음.
>>클라이언트가 POST방식으로 요청할 때 본문 영역에 들어있는 요청 파라미터들을 파싱하여 요청 객체의 body속성에 넣어준다.
public/login.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>로그인 테스트</title>
    </head>
    
<body>
    <h1>로그인</h1>
    <br>
    <form method="POST">
        <table>
            <tr>
                <td><label>아이디</label></td>
                <td><input type="text" name="id"></td>
            </tr>
            
            <tr>
                <td><label>비밀번호</label></td>
                <td><input type="password" name="password"></td>
            </tr>
        </table>
        <input type="submit" value="전송" />
    </form>
</body>
</html>
cs
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//Express 기본 모듈
var express = require('express')
, http = require('http')
, path = require('path');
//Express 미들 웨어
var bodyParser = require('body-parser')
, static = require('serve-static');
//익스프레스 객체 생성
var app = express();
//기본 속성 설정
app.set('port', process.env.PORT || 3000);
//body-parser를 사용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({extended: false}));
//body-parser를 사용해 application.json 파싱
app.use(bodyParser.json());
app.use(static(path.join(__dirname, 'public')));
//미들웨어에서 파라미터 확인
app.use(function(req, res, next){
    console.log('첫 번째 미들웨어에서 요청을 처리함.');
    
    var paramId = req.body.id || req.query.id;
    var paramPassword = req.body.password || req.query.password;
    
    res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
    res.write('<h1>Express서버에서 응답한결과입니다. </h1>');
    res.write('<div><p>paramId : ' + paramId + '</p></div>');
    res.write('<div><p>Param Password: '+ paramPassword + '</p></div>');
    res.end();
});
http.createServer(app).listen(3000function(){
    console.log('Express 서버가 3000번 포트에서 시작됨');
})
cs
>>예제를 실행해 보면 login창에서 전송버튼을 누르면 입력한 파라미터들이 다음 창에서 표시된다. 

요청 라우팅하기

다른 요청이 들어왔을 때도 use()메소드로 설정한 미들웨어 함수가 항상 호출되기 때문에 요청 url이 무엇인지 일일이 확인해야 하는 번거로움이 생긴다. 이 문제를 해결하는 것이 라우터 미들웨어(router middleware)라고 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//Express 기본 모듈
var express = require('express')
, http = require('http')
, path = require('path');
//Express 미들 웨어
var bodyParser = require('body-parser')
, static = require('serve-static');
//익스프레스 객체 생성
var app = express();
//기본 속성 설정
app.set('port', process.env.PORT || 3000);
//body-parser를 사용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({extended: false}));
//body-parser를 사용해 application.json 파싱
app.use(bodyParser.json());
app.use(static(path.join(__dirname, 'public')));
//라우터 객체 참조
var router = express.Router();
//라우팅 함수 등록
router.route('/process/login').post(function(req, res){
    console.log('/process/login 처리함');
    
   var paramId = req.body.id || req.query.id;
    var paramPassword = req.body.password || req.query.password;
    
    res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
    res.write('<h1>Express서버에서 응답한결과입니다. </h1>');
    res.write('<div><p>paramId : ' + paramId + '</p></div>');
    res.write('<div><p>Param Password: '+ paramPassword + '</p></div>');
    res.write("<br><a href='/login2.html'>로그인 페이지로 돌아가기</a>")
    res.end();
})
// 라우터 객체를 app객체에 등록
app.use('/', router);
// 등록되지 않은 패스에 대해 페이지 오류 응답
app.all('*'function(req, res) {
    res.status(404).send('<h1>ERROR - 페이지를 찾을 수 없습니다.</h1>');
});
http.createServer(app).listen(3000function(){
    console.log('Express 서버가 3000번 포트에서 시작됨');
})
cs


오류를 처리할 수 있는 미들웨어도 있다.
epress-error-handler 미들웨어 이다. 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//Express 기본 모듈
var express = require('express')
, http = require('http')
, path = require('path');
//Express 미들 웨어
var bodyParser = require('body-parser')
, static = require('serve-static');
//오류 핸들러 사용
var expressErrorHandler = require('express-error-handler');
//익스프레스 객체 생성
var app = express();
//기본 속성 설정
app.set('port', process.env.PORT || 3000);
//body-parser를 사용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({extended: false}));
//body-parser를 사용해 application.json 파싱
app.use(bodyParser.json());
app.use(static(path.join(__dirname, 'public')));
//라우터 객체 참조
var router = express.Router();
//라우팅 함수 등록
router.route('/process/login').post(function(req, res){
    console.log('/process/login 처리함');
    
   var paramId = req.body.id || req.query.id;
    var paramPassword = req.body.password || req.query.password;
    
    res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
    res.write('<h1>Express서버에서 응답한결과입니다. </h1>');
    res.write('<div><p>paramId : ' + paramId + '</p></div>');
    res.write('<div><p>Param Password: '+ paramPassword + '</p></div>');
    res.write("<br><a href='/login2.html'>로그인 페이지로 돌아가기</a>")
    res.end();
})
// 라우터 객체를 app객체에 등록
app.use('/', router);
// 404 에러 페이지 처리
var errorHandler = expressErrorHandler({
    static: {
      '404''./public/404.html'
    }
});
app.use( expressErrorHandler.httpError(404) );
app.use( errorHandler );
http.createServer(app).listen(3000function(){
    console.log('Express 서버가 3000번 포트에서 시작됨');
})
cs


토큰과 함께 요청한 정보 처리하기
/get()메소드를 호출하면서 동시에 /process/users/:id 패스를 처리하는 예제이다. http://localhost:3000/process/users/2 주소를 입력하면 users 뒤에있는 2가 아이디로 접근한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Express 기본 모듈 불러오기
var express = require('express')
  , http = require('http')
  , path = require('path');
// Express의 미들웨어 불러오기
var bodyParser = require('body-parser')
  , static = require('serve-static');
// 익스프레스 객체 생성
var app = express();
// 기본 속성 설정
app.set('port', process.env.PORT || 3000);
// body-parser를 이용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({ extended: false }))
// body-parser를 이용해 application/json 파싱
app.use(bodyParser.json())
app.use('/public', static(path.join(__dirname, 'public')));
// 라우터 사용하여 라우팅 함수 등록
var router = express.Router();
router.route('/process/users/:id').get(function(req, res) {
    console.log('/process/users/:id 처리함.');
    // URL 파라미터 확인
    var paramId = req.params.id;
    
    console.log('/process/users와 토큰 %s를 이용해 처리함.', paramId);
    res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
    res.write('<h1>Express 서버에서 응답한 결과입니다.</h1>');
    res.write('<div><p>Param id : ' + paramId + '</p></div>');
    res.end();
});
app.use('/', router);
// 등록되지 않은 패스에 대해 페이지 오류 응답
app.all('*'function(req, res) {
    res.status(404).send('<h1>ERROR - 페이지를 찾을 수 없습니다.</h1>');
});
// Express 서버 시작
http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});
cs

쿠키와 세션 관리하기

사용자가 로그인 상태인지 아닌지 확인하고 싶을 때에는 쿠키나 세션을 사용한다.  쿠키는 클라이언트 웹 브라우저에 저장되는 정보이며, 세션은 웹 서버에 저장되는 정보이다.
익스프레스에서는 cookie-parser 미들웨어를 사용하면 쿠키를 설정하거나 확인할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// Express 기본 모듈 불러오기
var express = require('express')
  , http = require('http')
  , path = require('path');
// Express의 미들웨어 불러오기
var bodyParser = require('body-parser')
  , static = require('serve-static');
var cookieParser = require('cookie-parser')
// 익스프레스 객체 생성
var app = express();
// 기본 속성 설정
app.set('port', process.env.PORT || 3000);
// body-parser를 이용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({ extended: false }))
// body-parser를 이용해 application/json 파싱
app.use(bodyParser.json())
app.use('/public', static(path.join(__dirname, 'public')));
// 라우터 사용하여 라우팅 함수 등록
var router = express.Router();
router.route('/process/showCookie').get(function(req, res) {
    console.log('/process/showCookie 호출됨');
    
    res.send(req.cookies);
});
router.route('/process/setUserCookie').get(function(req, res) {
    console.log('/process/setUserCookie 호출됨');
    
    //쿠키 설정
    res.cookie('user', {
        id: 'mike',
        name'소녀시대',
        authorized: true
    });
    
    //redirect로 응답
    res.redirect('/process/showCookie');
});
app.use('/', router);
// 등록되지 않은 패스에 대해 페이지 오류 응답
app.all('*'function(req, res) {
    res.status(404).send('<h1>ERROR - 페이지를 찾을 수 없습니다.</h1>');
});
// Express 서버 시작
http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});
cs
크롬의 개발자 도구에서 어플리케시연 - cookies 항목을 클릭하면 쿠기 정보가 보인다.

이번엔 세션이다. 세션도 상태정보를 저장하는 역할을 하지만 쿠키와 달리 서버쪽에 저장된다. 대표적인 예로는 로그인했을때 저장되는 세션을 들 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Express 기본 모듈 불러오기
var express = require('express')
  , http = require('http')
  , path = require('path');
// Express의 미들웨어 불러오기
var bodyParser = require('body-parser')
  , cookieParser = require('cookie-parser')
  , static = require('serve-static')
  , errorHandler = require('errorhandler');
// 에러 핸들러 모듈 사용
var expressErrorHandler = require('express-error-handler');
// Session 미들웨어 불러오기
var expressSession = require('express-session');
// 익스프레스 객체 생성
var app = express();
// 기본 속성 설정
app.set('port', process.env.PORT || 3000);
// body-parser를 이용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({ extended: false }))
// body-parser를 이용해 application/json 파싱
app.use(bodyParser.json())
app.use('/public', static(path.join(__dirname, 'public')));
// cookie-parser 설정
app.use(cookieParser());
// 세션 설정
app.use(expressSession({
    secret:'my key',
    resave:true,
    saveUninitialized:true
}));
// 라우터 사용하여 라우팅 함수 등록
var router = express.Router();
// 로그인 라우팅 함수 - 로그인 후 세션 저장함
router.route('/process/login').post(function(req, res) {
    console.log('/process/login 호출됨.');
    var paramId = req.body.id || req.query.id;
    var paramPassword = req.body.password || req.query.password;
    
    if (req.session.user) {
        // 이미 로그인된 상태
        console.log('이미 로그인되어 상품 페이지로 이동합니다.');
        
        res.redirect('/public/product.html');
    } else {
        // 세션 저장
        req.session.user = {
            id: paramId,
            name: '소녀시대',
            authorized: true
        };
        
        res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
        res.write('<h1>로그인 성공</h1>');
        res.write('<div><p>Param id : ' + paramId + '</p></div>');
        res.write('<div><p>Param password : ' + paramPassword + '</p></div>');
        res.write("<br><br><a href='/process/product'>상품 페이지로 이동하기</a>");
        res.end();
    }
});
// 로그아웃 라우팅 함수 - 로그아웃 후 세션 삭제함
router.route('/process/logout').get(function(req, res) {
    console.log('/process/logout 호출됨.');
    
    if (req.session.user) {
        // 로그인된 상태
        console.log('로그아웃합니다.');
        
        req.session.destroy(function(err) {
            if (err) {throw err;}
            
            console.log('세션을 삭제하고 로그아웃되었습니다.');
            res.redirect('/public/login2.html');
        });
    } else {
        // 로그인 안된 상태
        console.log('아직 로그인되어있지 않습니다.');
        
        res.redirect('/public/login2.html');
    }
});
// 상품정보 라우팅 함수
router.route('/process/product').get(function(req, res) {
    console.log('/process/product 호출됨.');
    
    if (req.session.user) {
        res.redirect('/public/product.html');
    } else {
        res.redirect('/public/login2.html');
    }
});
app.use('/', router);
// 404 에러 페이지 처리
var errorHandler = expressErrorHandler({
    static: {
      '404': './public/404.html'
    }
});
app.use( expressErrorHandler.httpError(404) );
app.use( errorHandler );
// Express 서버 시작
http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});
cs
세션이 만들어지면 connect.sid 쿠기가 브라우저에 저장된다.

파일 업로드 기능 만들기

파일을 업로드할 때는 멀티파트(multipart)포맷으로 된 파일 업로드 기능을 사용한다.

여기에선 multer 미들웨어로 파일을 업로드하는 방법을 알아본다. 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Express 기본 모듈 불러오기
var express = require('express')
  , http = require('http')
  , path = require('path');
// Express의 미들웨어 불러오기
var bodyParser = require('body-parser')
  , cookieParser = require('cookie-parser')
  , static = require('serve-static')
  , errorHandler = require('errorhandler');
// 에러 핸들러 모듈 사용
var expressErrorHandler = require('express-error-handler');
// Session 미들웨어 불러오기
var expressSession = require('express-session');
// 파일 업로드용 미들웨어
var multer = require('multer');
var fs = require('fs');
// 클라이언트에서 ajax로 요청했을 때 CORS(다중 서버 접속) 지원
var cors = require('cors');
// 익스프레스 객체 생성
var app = express();
// 기본 속성 설정
app.set('port', process.env.PORT || 3000);
// body-parser를 이용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({ extended: false }))
// body-parser를 이용해 application/json 파싱
app.use(bodyParser.json())
// public, uploads 폴더 오픈
app.use('/public', static(path.join(__dirname, 'public')));
app.use('/uploads', static(path.join(__dirname, 'uploads')));
        
// cookie-parser 설정
app.use(cookieParser());
// 세션 설정
app.use(expressSession({
    secret:'my key',
    resave:true,
    saveUninitialized:true
}));
// 클라이언트에서 ajax로 요청했을 때 CORS(다중 서버 접속) 지원
app.use(cors());
//multer 미들웨어 사용: 미들 웨어 사용 순서 중요 body-parser > multer > router
// 파일제한: 10개, 1G
var storage = multer.diskStorage({
    //destination: 업로드한 파일이 저장될 폴더를 지정
    destination: function(req, file, callback){
        callback(null'uploads')    
    },
    filename: function(rqe, file, callback){
        callback(null, file.originalname + Date.now());
    }
});
var upload = multer({
    storage: storage,
    limits: {
        files:10,
        fileSize: 1024 * 1024 * 1024
    }
});
// 라우터 사용하여 라우팅 함수 등록
var router = express.Router();
router.route('/process/photo').post(upload.array('photo',1),function(req,res){
    console.log('photo 호출됨');
    
    try{
        var files = req.files;
        
        console.dir('#=== 업로드된 첫번째 파일 정보=====#')
        console.dir(req.files[0]);
        console.dir('#======#')
        
        //현재의 파일 정보를 저장할 변수 선언
        var originalname = '',
            filename = '',
            mimetype ='',
            size =0;
        
            if(Array.isArray(files)){ //배열에 들어가 있는 경우(설정에서 1개의 파일도 배열에 넣게 했음)
                console.log("배열에 들어있는 파일 갯수 :%d", files.length);
                
                for(var index =0; index < files.length; index++){
                    originalname = files[index].originalname;
                    filename = files[index].filename;
                    mimetype = files[index].mimetype;
                    size = files[index].size;
                    
                }
            }else//배열에 들어가 있지 않는 경우(현재 설정에서는 해당 없음)
                console.log("파일 갯수: 1");
                
                originalname = files[index].originalname;
                filename = files[index].filename;
                mimetype = files[index].mimetype;
                size = files[index].size;
            }
        
        console.log('현재 파일 정보:'+originalname+','+ filename+','+mimetype+',' +size);
        
        //클라이언트에 응답 전송
        res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
        res.write('<h1>파일 업로드  성공</h1>');
        res.write('<hr/>');
        res.write('<p>원본 파일이름:' +originalname+'-> 저장 파일명:'+filename +'</p>');
        res.write('<p>MIME TYPE:' +mimetype+ '</p>');
        res.write('<p>파일 크기:' +size+ '</p>');
        res.end();
        
    }catch(err){
        console.dir(err.stack);
    }
});
app.use('/', router);
// 404 에러 페이지 처리
var errorHandler = expressErrorHandler({
    static: {
      '404''./public/404.html'
    }
});
app.use( expressErrorHandler.httpError(404) );
app.use( errorHandler );
// Express 서버 시작
http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

cs
파일을 업로드했을 때 업로드한 파일의 정보는 배열 객체로 저장된다. 여기서는 for문으로 배열 객체의 요소들인 파일 이름이나 크기를 하나씩 확인한다. 

업로드버튼을 눌러 웹서버로 요청하면 업로드 기능을 담당하는 multer 미들웨어를 거쳐 라우팅 함수쪽으로 전달된다. 요기선 파일이름을 변경하여 uploads폴도어 저장된다. 이 과정에서 확인된 정보는 응답으로 보내저 업로드 결과를 볼 수 있다. 

2017년 10월 24일 화요일

MySQL JOIN 정리

JOIN

다른 테이블 간의 데이터를 연결시켜 사용하는 것 

INNER 조인

일반적인 용도에 사용되며, 조건에 맞는 데이터를 두 테이블에서 모두 추출해낸다.

예) 
1
2
3
4
select board.bnum, board.title, reply.rnum
from free_board as board join
free_reply as reply 
on board.bnum = reply.bnum
cs
결과를 보면 게시물의 bnum과 댓글의 bnum이 같은 결과만 가져오는걸 알 수 있다. 

LEFT/RIGHT 조인

기준 테이블을 설정해 준다. 
FROM A LEFT/RIGHT JOIN B ON ~ 형태이며 레프트 조인은 A가 기준 라이트 조인은 B가 기준이 된다.  

예)
1
2
3
4
select board.bnum, board.title, reply.rnum
from free_board as board left join
free_reply as reply 
on board.bnum = reply.bnum
cs
결과를 보면 board가 기준의 되었기 때문에 조건에 맞지 않아 rnum이 없더라도 board의 bnum은 모두 불러오고 rnum이 없더라도 null 값으로 표기함을 알 수 있다.

INNER JOIN = JOIN
LEFT OUTER JOIN = LEFT JOIN
RIGHT OUTER JOIN = RIGHT JOIN 


CROSS JOIN 조인

집합에서의 곱 개념이며, A={a, b, c} B={1,2,3,4} 이면
A CROSS JOIN은 (a,1)(a,2)(a,3)(a,4)(b,1)(b,2).....(c,3)(c,4)가 된다. 


참고

2017년 10월 23일 월요일

Node.js 공부[4]

4챕터

노드의 기본 기능 알아보기 이다.
이 챕터에서는 서버를 만들기 전에 알아야 할 기본 내용을 다루고 있다.

주소 문자열과 요청 파라미터 다루기

url 모듈을 사용하여 일반 주소 문자열을 URL객채로 만들거나 URL객체서 일반 문자열로 변환하는 것을 쉽게 할 수 있다.

URL객체의 속성 중 query속성은 요청 파라미터의 정보를 가지고 있다. 요청 파라미터는 & 기호로 구분되는데 querystring모듈을 통해 쉽게 분리할 수 있다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
주소 문자열
*/
var url = require('url');
//주소 문자열을 URL 객체로 만들기
//parse(): 주소 문자열으 ㄹ파싱하여 URL 객체로 만들어준다.
var curURL = url.parse('https://www.google.co.kr/search?q=%E3%85%8E&oq=%E3%85%8E&aqs=chrome..69i57j69i61l3j0l2.1980j0j7&sourceid=chrome&ie=UTF-8');
//URL 객체를 주소 문자열로 만들기
//format(): URL객체를 주소 문자열로 반환한다.
var curStr = url.format(curURL);
console.log('주소 문자열: %s', curStr);
console.dir(curURL);
/*
요청 파라미터 확인하기
*/
var querystring = require('querystring');
var param = querystring.parse(curURL.query);
console.log('요청 파라미터 중 query의 값: %s', param.query);
//stringify() : 요청 파라미터 객체를 문자열로 
console.log('원본 요청 파라미터 : %s', querystring.stringify(param));
cs

이벤트

노드는 대부분 이벤트를 기반으로 하는 비동기 방식으로 처리한다.  그리고 비동기 방식으로 처리하기 위해서는 서로 이벤트를 전달한다. 이벤트는 한쪽에서 다른 쪽으로 알림 메시지를 보내는 것과 비슷하다. 노드는 이런 이벤트를 보내고 받을 수 있도록 EventEmitter라는 것을 만들었다.
노드의 객체는 EventEmitter를 상속받을 수 있으며, 상송 받은 후에는 EventEmitter 객체의 on()과 emit()메소드를 사용할 수 있다.

EventEmitter의 주요 메소드이다
on(event, listener): 지정한 이벤트의 리스터를 추가한다.
once(event, listener): 지정한 이벤트의 리스터를 추가하지만 한 번 실행한 후에는 자동으로 리스너가 제거된다.
removeListener(event, listener): 지정한 이벤트에 대한 리스너를 제거한다.
1
2
3
4
5
6
7
8
9
10
11
process.on('tick'function(count){
    console.log('tick 이벤트 발생함 : %s ', count);
});
//setTimeout을 이용하여 2초 후에 동작 설정
setTimeout(function(){
    console.log('2초 후에 tick 이벤트 전달 시도');
    
    //emit를 이용해 tick이벤트를 process객체로 전달
    process.emit('tick''2');
},2000);
cs


파일 다루기

노드는 동기식 IO와 비동기식 IO 기능을 함께 제공한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
동기식 읽기
*/
var fs = require('fs');
//readFileSync: 동기식 파일 읽기
var data = fs.readFileSync('./package.json''utf-8');
console.log(data);
/*
비동기식 읽기
*/
//raedFile 메소드를 실행하면서 세번째 파라미터로 전달된 함수는 파일을 읽어들이는 작업이 끝났을때 호출됨
//이때, 2개의 파라미터 err와 data를 전달 받아 오류가 발생했는지 아니면 제대로 실행했는지 알 수 있음
fs.readFile('./package.json','utf-8'function(err, data2){
    console.log(data2);
});
console.log('package 파일을 읽도록 요청');
/*
파일에 데이터 쓰기 예제
*/
fs.writeFile('./output.txt''Hello'function(err){
    if(err){
        console.log('Error: ' + err);
    }
    
    console.log('output.txt 쓰기 완료');
})
cs


버퍼

버퍼 객체는 바이너리 데이터를 읽고 쓰는데 사용한다. new연산자를 사용해 새로운 바퍼 개체를 만들 수 있으며, 바이트 데이터 크기만 지정해주면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
버퍼*/
//버퍼 객체를 크기만 지정하여 만든 후 문자열 작성
var output = '안녕1';
var buffer1= new Buffer(10);
var len = buffer1.write(output, 'utf8');
console.log('첫 번째 버퍼의 문자열: %s', buffer1.toString());
//버퍼 객체를 문자열을 이용해 만들기
var buffer2 = new Buffer('안녕2''utf8');
console.log('두 번째 버퍼 객체의 문자열: %s', buffer2.toString());
//타입 확인
console.log('버퍼 객체의 타입: %s', Buffer.isBuffer(buffer1));
//버퍼 객체에 들어 있는 문자열 데이터를 문자열 변수로 만들기
var byteLen = Buffer.byteLength(output);
var str1 = buffer1.toString('utf8'0, byteLen);
var str2 = buffer2.toString('utf8');
//첫 번째 버퍼 객체의 문자열을 두 번째 버퍼 객체로 복사
buffer1.copy(buffer2, 0,0 , len);
console.log('두 번째 버퍼에 복한 후의 문자열: %s', buffer2.toString('utf8'));
//두 개의 버퍼 붙이기
var buffer3 = Buffer.concat([buffer1,buffer2]);
console.log('두 개의 버퍼를 붙인 후의 문자열: %s', buffer3.toString('utf8'));
cs


스트림 단위로 파일 읽고 쓰기 

파일을 읽거나 쓸 때는 데이터 단위가 아닌 스트림 단위로 처리할 수 있다. 스트림은 데이터가 전달되는 통로와 같은 개념..?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var fs = require('fs');
//createReadStream(path[,options]) - 파일을 읽기 위한 스트림 객체 만들기
var infile = fs.createReadStream('./output.txt', {flags:'r'});
//createWriteStream(path[,options]) - 파일을 쓰기 위한 스트림 객체 만들기
var outfile = fs.createWriteStream('./output2.txt', {flags:'w'});
infile.on('data', function(data){
    console.log('읽어 들인 데이터', data);
    outfile.write(data);
});
infile.on('end', function(){
    console.log('파일 읽기 종료');
    outfile.end(function(){
        console.log('파일 쓰기 종료');
    });
});
var outfile2 = fs.createWriteStream('./output3.txt',{flags:'w'});
//pipe를 이용해 두 개의 스트림을 붙일 수 있다.
infile.pipe(outfile2);
console.log('파일복사 방법2')
cs

fs모듈로 디렉터리 만들기

1
2
3
4
5
6
7
8
9
10
11
var fs = require('fs');
fs.mkdir('./docs'0666, function(err){
    if(err) throw err;
    console.log('새로운 docs 폴더를 만들었습니다.');
    
    fs.rmdir('./docs', function(err){
        if(err) throw err;
        console.log('docs 폴더를 삭제했습니다.');
    });
});
cs

로그 파일 남기기

프로그램의 크기가 커질 수록 로그의 양도 많아지고 로그를 보고간했다가 나중에 확인해야하는 경우도 생긴다. 
책에선 winston 모듈을 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
var winston = require('winston');                    // 로그 처리 모듈
var winstonDaily = require('winston-daily-rotate-file');        // 로그 일별 처리 모듈
var moment = require('moment');                    // 시간 처리 모듈
function timeStampFormat() {
    return moment().format('YYYY-MM-DD HH:mm:ss.SSS ZZ'); 
};
var logger = new (winston.Logger)({
    transports: [
        new (winstonDaily)({
            name: 'info-file',
            filename: './log/server',
            datePattern: '_yyyy-MM-dd.log',
            colorize: false,
            maxsize: 50000000,
            maxFiles: 1000,
            level: 'info',
            showLevel: true,
            json: false,
            timestamp: timeStampFormat
        }),
        new (winston.transports.Console)({
            name: 'debug-console',
            colorize: true,
            level: 'debug',
            showLevel: true,
            json: false,
            timestamp: timeStampFormat
        })
    ],
    exceptionHandlers: [
        new (winstonDaily)({
            name: 'exception-file',
            filename: './log/exception',
            datePattern: '_yyyy-MM-dd.log',
            colorize: false,
            maxsize: 50000000,
            maxFiles: 1000,
            level: 'error',
            showLevel: true,
            json: false,
            timestamp: timeStampFormat
        }),
        new (winston.transports.Console)({
            name: 'exception-console',
            colorize: true,
            level: 'debug',
            showLevel: true,
            json: false,
            timestamp: timeStampFormat
        })
    ]
});
//로그 테스트
var fs = require('fs');
var inname = './output.txt';
var outname = './output2.txt';
fs.exists(outname, function (exists) {
    if (exists) {
        fs.unlink(outname, function (err) {
            if (err) throw err;
            logger.info('기존 파일 [' + outname +'] 삭제함.');
        });
    }
    
    var infile = fs.createReadStream(inname, {flags: 'r'} );
    var outfile = fs.createWriteStream(outname, {flags: 'w'});
    infile.pipe(outfile);
    logger.info('파일 복사 [' + inname + '] -> [' + outname + ']');
});
cs