블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


빠르게 훝어보는 node.js

#3 - Express 1/2

조대협 (http://bcho.tistory.com

Express

node.js는 여러 종류의 웹 개발 프레임웍을 제공한다.얼마전에 Paypal이 내부 시스템을 대규모로 node.js로 전환하면서 오픈소스화한 KarkenJS Meteo 등 여러가지 프레임웍이 있는데그중에서 가장 많이 사용되는 프레임웍 중하나인 Express에 대해서 설명하고자 한다.

Express는 웹 페이지 개발 및 REST API 개발에 최적화된 프레임웍으로 매우 사용하기가 쉽다.

프로젝트 생성

먼저 express module npm을 이용해서 설치한 다음 express 프로젝트를 생성해보자.

% express --session --ejs --css stylus myapp

명령을 이용하면 아래와 같이 express 프로젝트가 생성되고, 기본 디렉토리 및 파일이 생성된다.



Ÿ   --session HTTP session을 사용하겠다는 것이다.

Ÿ   --ejs는 템플릿 렌더링 엔진으로 ejb를 사용하겠다는 것이다. (자세한 내용은 나중에 template 엔진에서 설명한다.)

Ÿ   그리고 --css CSS 엔진을 stylus로 사용한다는 것이다.

위와 같이 프로젝트가 생성되었으면, 의존성이 있는 모듈을 설치하기 위해서

%cd myapp

%npm install

을 실행해보자. 앞에서 정의한 옵션에 따라서, ejb,stylus 등의 모듈과 기타 필요한 모듈들이 서리되는 것을 확인할 수 있을 것이다.

디렉토리 구조



그러면 생성된 디렉토리 구조를 살펴보도록 하자

Ÿ   ./node_module : express앱에 필요한 module을 저장한다.

Ÿ   ./public : html, image, javascript,css와 같은 정적 파일들을 저장한다.

Ÿ   ./routes : URL 별로 수행되는 로직을 저장한다.

Ÿ   ./views : HTML view 템플릿을 저장한다. (여기서는 ejs로 지정하였기 때문에, ejs 템플릿들이 여기 저장된다.)

Ÿ   app.js : 이 웹프로젝트의 메인 소스 코드

Ÿ   package.json : module package.json 파일로, 의존성 및 npm script 명령어를 정의한다.

예제 코드

다음은 자동으로 생성된 app.js 코드의 전문이다. 주요 부분에 대한 내용을 살펴보도록 하자.

var express = require('express');

var routes = require('./routes');

var user = require('./routes/user');

var http = require('http');

var path = require('path');

 

var app = express();

 

// all environments

app.set('port', process.env.PORT || 3000);

app.set('views', path.join(__dirname, 'views'));

app.set('view engine', 'ejs');

app.use(express.favicon());

app.use(express.logger('dev'));

app.use(express.json());

app.use(express.urlencoded());

app.use(express.methodOverride());

app.use(app.router);

app.use(require('stylus').middleware(path.join(__dirname, 'public')));

app.use(express.static(path.join(__dirname, 'public')));

 

// development only

if ('development' == app.get('env')) {

  app.use(express.errorHandler());

}

 

app.get('/', routes.index);

app.get('/users', user.list);

 

http.createServer(app).listen(app.get('port'), function(){

  console.log('Express server listening on port ' + app.get('port'));

});

먼저 require 부분에서 필요한 module 들을 로드한다.


환경 설정

다음으로, var app = express(); express 객체를 설정한 후에, express 애플리케이션에 대한 환경을 설정한다. 몇몇 주요 내용만 살펴보면,

Ÿ   app.set('port', process.env.PORT || 3000);

를 이용해서, node.js의 포트를 설정한다. 여기서는 default 3000 포트로 사용하고, 환경변수에 PORT라는 이름으로 포트명을 지정했을 경우에는 그 포트명을 사용하도록 한다.(Linux의 경우 export PORT=80 이런식으로 환경 변수를 지정한다.)

Ÿ   app.set('views', path.join(__dirname, 'views'));

Ÿ   app.set('view engine', 'ejs');

다음으로, 템플릿 엔진의 종류와 템플릿 파일의 위치를 정의한다 템플릿 파일의 위치를 path.join(__dirname, 'views') 로 정의 하였는데, __dirname은 현재 수행중인 파일의 위치 즉, app.js가 위치한 위치를 의미하며, path.join을 이용하면, ${현재디렉토리}/views 라는 경로로 지정한 것이다.

Ÿ   app.use(require('stylus').middleware(path.join(__dirname, 'public')));

Ÿ   app.use(express.static(path.join(__dirname, 'public')));

css 엔진의 종류를 stylus로 지정하고, 엔진이 렌더링할 static 파일들의 위치를 지정한다. 그리고, express static 파일의 위치를 지정한다. “./public” 디렉토리로 지정

Ÿ   app.use(express.json());

는 들어오는 http request body json 일때도 파싱할 수 있도록 지원한다.

이 밖에도, urlencoded request multipart request(파일 업로드)를 지원하려면 아래 부분을 추가하면 된다

Ÿ   app.use(express.urlencoded());

Ÿ   app.use(express.multipart());

 

이밖에도 다양한 지원 설정들이 있는데, 자세한 사항은 http://expressjs.com/3x/api.html#middleware를 참고하기 바란다.


router 처리

다음으로 특정 URL로 들어오는 http request에 대한 handler (node에서는 router라고 한다.)를 지정한다.

Ÿ   app.get('/', routes.index); : HTTP GET / 에 대해서 /router/routes.js 에 있는 index 함수를 실행하도록 한다.

Ÿ   app.get('/users', user.list); : HTTP GET /users 요청에 대해서 /router/user.js에 있는 list() 함수를 이용하도록 한다.

user.js routes.js 코드 초기 부분에서 require 이용하여 import 처리가 되어 있다.

Ÿ   var routes = require('./routes');

Ÿ   var user = require('./routes/user');

 

http server의 기동

마지막으로 위의 설정값을 기반으로 http server를 기동 시킨다.

http.createServer(app).listen(app.get('port'), function(){

  console.log('Express server listening on port ' + app.get('port'));

});


Router

Router는 특정 URL로 들어오는 HTTP Request를 처리하는 패턴이다. 앞에서 간단하게 살펴본것과 같이 app.{HTTP_METHOD}(“URL”,{Callback_function}); 으로 정의한다.

express에서는 보통 각 URL Method에 맵핑되는 function /routes/ 디렉토리 아래에 모듈로 만들어 놓고 require를 사용해서 불러서 사용하게 된다.

아래 그림을 보면, app.js에서 /routes/index.js (디렉토리 경로만 지정해놓으면 디폴트로 index.js를 부른다.) /route/user.js 모듈을 부른후에

HTTP/GET “/” route 모듈의 index 메서드를 통해서 처리하게 하고,
HTTP/GET “/users”
route 모듈의 list 메서드를 통해서 처리하도록 한다.



 

HTML Parameter Passing

웹서버를 기동하였으면, 그러면 어떻게 HTML에서 parameter를 넘기는지 보자, HTML에서는 HTTP URL의 일부 또는 Query String 또는 HTML body form value로 값을 넘길 수 있다.

각각의 방법을 알아보면

URL Param

Ÿ   app.get(“URI/:{parameter name}”,callbackfunction…);

식으로 정의하면 URL 내의 경로를 변수로 사용할 수 있다. 예를 들어서

app.get('/tweeter/:name',function(req,res){

          console.log(req.params.name);

      });

의 코드는 /tweeter/{경로} 로 들어오는 HTTP GET 요청에 대해서 {경로} 부분을 변수 처리 한다. /tweeter/terry라고 하면, req.params.name으로 해서 “terry”라는 값을 받을 수 있다.

Query Param

다음으로는 HTTP request로 들어오는 Query String의 값을 추출하는 방법인데, request.query.{query string 키 이름} 으로 추출할 수 있다.

예를 들어 HTTP GET /search?q=조대협 으로 요청이 들어왔을때

Ÿ   var q = request.query.q  로 하면 조대협이라는 value를 추출할 수 있다.

Form Param

마지막으로 HTML <form>을 통해서 들어오는 값을 추출하는 방법이다.

다음과 같은 HTML이 있다고 가정할때,

<input name=”username” …/>

form 값을 읽어 올려면

Ÿ   var q = request.body.username  으로 하면 HTML form에서 name“username”으로 정해진 element의 값을 읽어올 수 있다.

Rendering & Template

HTTP response로 응답을 보내는 방법을 rendering이라고 하는데, 간단한 문자열을 경우, response.send(“문자열”); 을 이용해서 보낼 수 도 있다. 또는 response code를 싫어서 보낼때는 response.send(404,”페이지를 찾을 수 없습니다.”); 와 같은 식으로 첫번째 인자에 HTT response code를 실어서 보내는 것도 가능하다.

그러나 복잡한 HTML을 경우 문자열 처리가 어렵기 때문에, Template이라는 것을 사용할 수 있다.

아래는 Express가 지원하는 템플릿중의 하나인 ejs 템플릿으로, JSP,ASP 와 유사한 형태를 갖는다.

먼저 ejs 모듈을 npm을 이용해서 설치 한 후에,



Express 프로젝트 생성시에 다음과 같이 “—ejs”옵션을 줘서, EJB를 템플릿 엔진으로 사용하도록 지정한다.

% express --session --ejs --css stylus myapp

ejs에 대한 module 의존성을 정의하기 위해서 package.json을 정의한다.

{

  "name": "application-name",

  "version": "0.0.1",

  "private": true,

  "scripts": {

    "start": "node app.js"

  },

  "dependencies": {

    "express": "3.4.8",

    "ejs": "*"

  }

다음으로 생성된 app.js 파일에서의 설정 부분을 보면, 템플릿 엔진이 ejs로 지정되어 있는 것을 확인할 수 있다.그리고 ejs 템플릿 파일을 저장할 위치를 __dirname/views 로 지정한 것을 확인할 수 있다.

app.set('views', path.join(__dirname, 'views'));

app.set('view engine', 'ejs');

 

이제 ejs를 사용할 준비가 되었다. 템플릿을 직접 만들어 보자. 앞에서 지정한것과 같이 “/views”디렉토리에 생성하면 된다. /views/index.ejs 파일을 만들어 보자

<!DOCTYPE html>

<html>

<head>

    <title><%= title %></title>

</head>

<body>

<h1><%= title %></h1>

<p>Welcome to <%= name %></p>

</body>

</html>

일반적인 HTML과 거의 유사하다. Parameter를 사용하고자 할때는 ASP JSP처럼 <%=변수%> 로 사용하면 된다. 마찬가지로, for,while,if등 간단한 스크립트 로직도 작성할 수 있다.

ejs에 대한 스크립트는 http://embeddedjs.com/ 를 참고하면 된다.

템플릿을 만들었으면, router를 정의해서, 이 템플릿을 부르도록 해보자

다음과 같은 코드로 /routes/index.js 파일을 생성한다.

exports.index = function(req, res){

          res.render('index', { title: 'Express',name:'Terry' });

        };

       

이 파일은 이 request에 대해서 rendering을 할 때, index라는 템플릿을 부르고 (앞서 엔진과 view 디렉토리를 지정했기 때문에, __dirname/index.ejs 파일을 부르게 된다.), 이때 인자로 title=”Express”, name=”Terry” 두 변수를 각각의 값으로 넘기게 된다.

이렇게 넘겨진 값은 앞서 정의한 template 파일 내의 <%=변수%> 부분에 의해서 HTML 로 렌더링이 되서 출력되게 된다.



Express ejs이외에도 jadeHogan.js와 같은 다른 template 엔진도 지원한다.

https://github.com/Deathspike/template-benchmark 를 보면, 각 템플릿 엔진의 성능 비교가 나와 있는데, Hogan,ejs,jade 순으로 빠른 것을 볼 수 있다. 100,000 템플릿을 렌더링 했을 시에 Hogan 4257 ms, ejs 5283 ms, jades 13068ms 가 소요됨을 볼 수 있다.

Jade html을 사용하지 않고, 고유의 태그 언어를 이용하여, 템플릿을 정의하는데, 이는 실행시에 자동으로 HTML로 변경이 되게 된다. 아래 그림 참조 (jade 스크립트가 렌더링 후 우측의 HTML처럼 변경이 된다.). Jade 스크립트를 사용하면, HTML 보다 더 구조화 되고 깔끔한 템플릿을 만들 수 있다는 장점이 있지만. HTML Publisher(디자이너)가 직접 Jade를 만들어서 페이지를 만들어 주기가 어려우니 (디자이너가 HTML이외의 jade 스크립트를 다시 배워야 함). 분업이 쉽지 않다.



Jade의 장점은 HTML Layout을 지원한다는 것인데, Layout은 하나의 페이지를 Header,Footer,Left Menu 와 같이 별도의 구역으로 나누어서,개별 템플릿으로 렌더링 할 수 있는 기능이다. EJS의 경우에도 https://github.com/RandomEtc/ejs-locals 확장 모듈을 이용하여 Layout과 유사한 기능을 제공할 수 있다.
ejs에서 layout을 지원하는 모듈들이 있기는 하지만 근래에 들어서 잘 maintenance가 안되고 있으니, 다운로드 횟수나 최종 업데이트 시간등을 확인하고 사용하는 것을 권장한다.

필자의 경우 ejs를 선호하는데, 디자이너로부터 받은 HTML을 작업해서 그대로 템플릿으로 변환하기가 쉽다.

HTTP Header 정보의 처리

HTTP에서 Request/Response Header에 대한 처리 방법은 다음과 같다. 먼저 request에서 Header를 읽는 방법은 간단하다.

var ua = req.headers['user-agent'];

 

와 같이 request 객체에서 headers array에 들어 있는 value를 위와 같이 읽으면 된다.

반대로 response message header 정보를 쓰는 방법은 

response.writeHead({HTTP Response Code},{“key”:”value”});

 

식으로 Http response code와 함께, header 정보를 key,value pair array로 넘겨주면 response message에 같이 실어서 리턴한다. 다음은 사용 예이다.

response.writeHead(200,{‘Content-Type”:’text/html’,’Server’:’terry’ });

 

HTTP Cookie 처리

HTTP에서 사용되는 Cookie에 대한 사용법은 아래 예를 통해서 살펴보자. 아래 예는 Cookie를 쓰고 읽는 부분이다.

App.js 파일 일부이다. 먼저 express cookie를 사용함을 알려줘야 한다.

app.use(express.cookieParser());

 

app.get('/writecookie', routes.writecookie);

app.get('/readcookie', routes.readcookie);

 

다음, request객체에서 cookie 메서드를 이용해서 cookie 값을 쓴다. 이때 인자는 cookie 이름, cookie 값 그리고, 배열로 Cookie에 대한 옵션 (Expire time )을 기술한다.

/routes/index.js 파일

exports.writecookie = function(req, res){

    res.cookie('name','terry',{ expires: new Date(Date.now() + 900000), httpOnly: true });

    res.end();

};

 

exports.readcookie = function(req, res){

    var name = req.cookies.name;

    console.log("name cookie is :"+name);

    res.end();

}

 

아래는 위의 코드를 호출하여, cookie를 읽고 쓴 결과를 console로 출력한 내용이다.



Signed Cookie의 사용

위와 같은 방식을 사용하면, Cookie가 네트워크를 통해서 전송 및 브라우져에 저장될 때 암호화 되지 않은 형태로 전송 및 저장 되기 때문에, 악의 적인 공격에 Cookie값이 노출될 우려가 있다.

아래는 실제로 Cookie read하는 HTTP/GET 프로토콜을 Fiddler를 이용해서 Capture한 케이스이다. 아래 내용을 보면 name이라는 쿠키 값이 암호화 되지 않고 올라오는 것을 확인할 수 있다.


 

이를 방지 하기 위해서 Express에서는 Secure Cookie를 지원한다.

app.use에서 express.cookieParser를 정의할때, 암호화 키를 넣을 수 있다.

app.js에서 아래와 같이 cookieParser안에 쿠키에 대한 암호화 키를 지정한후에

app.use(express.cookieParser('mykey'));

 

쿠키를 쓸때는 쿠키 옵션에 “signed:true”옵션을 주고, 쓰면 암호화 된 형태로 쿠키를 쓸 수 있으며

exports.writecookie = function(req, res){

    res.cookie('name','terry',{ expires: new Date(Date.now() + 900000), httpOnly: true ,signed:true});

    res.end();

};

 

쿠키를 읽을때는 request.Cookies가 아니라 request.signedCookie에서 값을 읽으면 암호화된 쿠키를 읽을 수 있다.

exports.readcookie = function(req, res){

    var name = req.signedCookies.name;

    console.log("name cookie is :"+name);

    res.end();

}


(http://expressjs.com/api.html#req.signedCookies
내용 참조).

쿠키를 쓸 때 Packet을 잡아보면, 아래와 같이 Set-Cookie에서 name 쿠키가 암호화된 형태로 전송되는 것을 확인할 수 있으며



읽을 때도 아래와 같이 암호화된 형태로 쿠키가 전송되는 것을 확인할 수 있다.



HTTP Session 처리

HTTP Session 을 사용하는 방법도 Cookie와 크게 다르지 않다. 다만,HTTP Session의 경우 Signed Cookie를 사용하기 때문에, 아래와 같이 express app.use cookieParser 정의시 secret 키를 반드시 정해주고, HTTP Session을 사용함을 express.session use처리 함으로써 명시적으로 알려줘야 한다.

app.use(express.cookieParser('your secret here'));

app.use(express.session());

 

다음으로 session을 쓸때는 request.session.{key name}={value} 식으로 저장하고

exports.writesession = function(req, res){

  req.session.name='terry';

  console.log('write session = name:'+req.session.name);

  res.end();

};

 

값을 읽을 때는 마찬가지로 request.session.name으로 값을 빼낼 수 있다.

exports.readsession = function(req, res){

    console.log('write session = name:'+req.session.name);

  res.end();

};

 

아래는 브라우져를 연 후에 session read/write/read 순서로 테스트한 결과이다. Session write전에는 session에 값이 없다가 write 후에는 정상적으로 값을 읽을 수 있음을 확인할 수 있다.



클러스터에서의 Session 처리

HTTP Session 사용시에 주의할 점은 여러 개의 node.js 인스턴스를 시용할 시, 특히 클러스터링을 사용할 경우에는 인스턴스간에 Session 정보가 공유가 되지 않는다. 이 경우 앞단에 L4 Reverse Proxy같은 로드 밸런스를 둘 경우, 사용자가 항상 같은 서버로 붙지 않기 때문에 세션 정보가 유실 될 수 있다. (처음에는 1번 서버로 연결되었다가 두번째 request L4 Round Robin 정책에 의해서 2번 서버로 연결되는 케이스)

이런 문제를 해결하기 위해서 각 node.js 인스턴스간의 Session 정보를 공유 스토리지에 저장해놓고 서로 공유할 수 있는데, Redis가 많이 사용된다.

방식은 connect-redis 모듈을 이용하여 redis node와 연결하고, app.use에서 session 설정시에 아래 설정과 같이 RedisStore Session Store로 지정하여, Redis를 통해서 Session 정보를 공유할 수 있다.

var express = require('express');

var RedisStore = require('connect-redis')(express);

var ports = require('./classes/ports.js');

var config = require('./config/config.js');

var routes = require('./routes');

 

var app = express();

app.use(express.cookieParser());

app.use(express.session({

  store: new RedisStore({

    port: config.redisPort,

    host: config.redisHost,

    db: config.redisDatabase,

    pass: config.redisPassword

  }),

  secret: 'Your secret here',

  proxy: true,

  cookie: { secure: true }

}));

 

  자세한 설정은 clustering 부분에서 다시 설명한다.


#1 – node.js의 소개와 내부 구조 http://bcho.tistory.com/881

#2 - 설치와 개발환경 구축 http://bcho.tistory.com/884

#3 - Event,Module,NPM  http://bcho.tistory.com/885

#4 - 웹 개발 프레임웍 Express 1/2 - http://bcho.tistory.com/887


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. dolove9 2014.04.02 17:44  댓글주소  수정/삭제  댓글쓰기

    좋은 정보 감사합니다. 클러스터링 세션 공유 강좌가 무척 기대되네요..다시한번 감사합니다.

  2. 2014.04.28 16:50  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  3. 알리스 2014.05.02 09:52  댓글주소  수정/삭제  댓글쓰기

    node-v0.10.26-x64.msi 설치후에 npm install -g express 로 설치후 (C:\Users\mrkim\AppData\Roaming\npm\node_modules\express 에 설치되었어요)

    cmd 창에서 e:\dev 경로로 이동 후 express myapp 하면
    'express'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는 배치 파일이 아닙니다.
    라고 뜨는데 도대체 어디를 어떻게 확인 해야 할까요?제발 부디...알려 주세요...

    • 조대협 2014.05.02 10:23 신고  댓글주소  수정/삭제

      %PATH%상에 global module이 설치되는 경로가 들어가 있어야 합니다.
      제 경우 윈도우즈 사용자명이 bw.cho인데
      C:\Users\bw.cho\AppData\Roaming\npm 를 PATH에 추가해놨습니다. 해당 디렉토리를 보면 express.cmd 명령이 있습니다.

  4. 알리스 2014.05.02 13:27  댓글주소  수정/삭제  댓글쓰기

    답변 감사합니다~ 그런데..저도 PATH에 C:\Users\mrkim\AppData\Roaming\npm이 잡혀 있기는 하거든요.
    근데 해당 디렉토리 보면 express.cmd 가 없었어요..
    대신 C:\Users\mrkim\AppData\Roaming\npm\node_modules\express\Readme.md에 보니까
    install -g express-generator@3 이렇게 하라는 내용이 있어서..그렇게 했더니 설치가 되었습니다~^^아까 그 경로에 express.cmd 개 생겼더라구요..
    근데 뒤에 -generator@3는 무슨 의미인지 전혀 모르겠네요...익스프레서 버전이 4임에도 불구하고 3으로 설치하라는 소리인가?...

    어째뜬 좋은 정보 너무 감사합니다. 블러그 많은 도움이 되고 있어요.
    그런데 산넘어 산이네요..
    npm install -g oracle 도 설치안되는데..조만간에...윈도우에서 오라클 접속하는 내용도 올려 주심 안될까요?^^ please

  5. happygrammer 2014.06.02 14:37 신고  댓글주소  수정/삭제  댓글쓰기

    https://github.com/expressjs/session
    5월 10일 경에, 세션을 처리하는 방법이 바뀌었습니다. 문서가 업데이트 되면 더 좋을것 같습니다. :)

  6. 김재민 2014.06.23 13:15  댓글주소  수정/삭제  댓글쓰기

    user = require('./routes/user') 부분에서 Error: Cannot find module './routes/user' 와 같은 에러가 계속 해서 발생합니다. 혹시 해결방안을 알고계시다면 조언 부탁드리겠습니다.

    • 조대협 2014.06.24 09:17 신고  댓글주소  수정/삭제

      route 디렉토리 아래 user.js 라는 파일이 있으면 됩니다.
      아니면 예제 테스트 하시는 거라면 user 관련 모듈을 모두 코드에서 빼버리시고, require('user') 부분도 빼버리시면 됩니다.

  7. phistpop2 2014.10.16 13:27  댓글주소  수정/삭제  댓글쓰기

    맥에서 개발중인데 express 프로젝트생성할때 express 명령어를 찾지 못했습니다.
    찾아보니 express-generator를 설치하라고 하더라고요. 이렇게 해서 되긴했는데
    그냥 express만 설치해서 되는게 맞는건가요?

  8. 박성우 2015.11.02 11:51  댓글주소  수정/삭제  댓글쓰기

    강좌 잘보고 있습니다. 감사합니다.
    express를 설치하고 express --session --ejs --css stylus myapp를 입력해 보았는데요.
    'express'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는 배치 파일이 아닙니다. 메세지가 나옵니다.
    환경변수 path는 이렇게 설정되어 있습니다. C:\Users\LGe\AppData\Roaming\npm 근데 안되네요. 왜 이런걸까요?

  9. ㅇㅇ 2015.11.23 08:36  댓글주소  수정/삭제  댓글쓰기

    Signed Cookie는 쿠키를 암호화 하는게 아니라
    위변조를 방지하기 위해 HMAC을 추가하는 기능 아닌가요?

  10. 행인 2016.11.01 17:29  댓글주소  수정/삭제  댓글쓰기

    좋은 강의 감사합니다. 한가지, Signed Cookie 의 경우, 첨부하신 이미지에서 terry 라는 문자열이 그대로 드러나는데(name=%3A terrey...), 암호화 기능이 잘못된 것 같습니다.