Apache 서버 사용법에 대해 정리합니다.

인터넷 강의를 정리한거라 범위가 제한적일수도 있습니다.

초보 입장에서 보시고 빠진건 의견 주시면 감사하겠습니다!


1. 아파치 서버 배경지식


웹서버 히스토리

- 연구원 Tim burners Lee가 hypertext 기반 정보를 연구원들 사이어서 공유 & 업데이트 하기 위한 수단을 연구하는 프로젝트를 시작으로 네트워크와 웹 환경에 대한 연구를 진행함

- hypertext idea with TCP and DNS creates the WWW (1989)

- HTML, URI, HTTP : 웹 서버를 이해하기 위한 개념 3대장


Software for Web Server (다음의 요구사항을 만족시켜야 함)

- Web Server: HTTP Server -> Application Server

  - using HTTP Protocol

  - Static content, Dynamic content

  - Must implement the HTTP Protocol

  - example) Apache, Nginx, IIS and others


HTTP는 제일 단순하게 이렇게 작동한다.

Client ---- (HTTP Request)  ---> Web Server

            /index.html 보여줘

       <--- (HTTP Response) ----

            여기 있음, HTML 형식이고 길이는 ...


2. 아파치 구조 파악하기


(1) 아파치 프로세스 제어


Apache enable automatically (시스템 설정)

$ systemctl enable httpd

$ systemctl status httpd


Firewall http and https on (CentOS 7의 방법이므로 OS마다 다시 찾아서 적용해야 함)

$ firewall-cmd --permanent --add-service=http

$ firewall-cmd --permanent --add-service=https

$ firewall-cmd --reload


Find Apache location

$ whereis httpd


Help Apache execution

$ httpd -h


Show Loaded Apache Modules

$ httpd -M


Show Compiled Apache Modules

$ httpd -l


Check if config file location is vaild

$ httpd -t -f /etc/httpd/conf/httpd.conf


All Apache Module's location

$ cd /usr/lib64/httpd/modules


(2) Apache's main location (아파치 프로그램 구성)

$ cd /etc/httpd

- conf    : configuration file's directory

- conf.d  : custom configuration file's directory

- logs    : Apache log directory

- modules : Apache module's location (linked to /usr/lib64/httpd/modules)


3. Apache Configuration

(1) 설정 파일 작성 기초

한줄로 선언하는 설정 명령

+ ServerRoot                 : Apache's root directory

+ Listen                     : Port(+IP Addr) that Apache listen

+ Include *.conf             : 추가 설절파일 포함하여 작동, 설정파일이 존재하지 않으면 에러

+ User/Group                 : OS에서 아파치가 실행되는 계정명

+ ServerAdmin                : 관리자 이메일

+ ServerName                 : 웹 서버의 도메인 이름

+ DocumentRoot               : 웹 서버에서 사용할 resource 의 root 경로

+ ErrorLog                   : 아파치 에러로그 파일의 위치

+ LogLevel                   : 로그 출력 수준 (debug, info, notice, warn, error, crit, alert, emerg)

+ IncludeOptional            : 명시한 추가 설정파일이 실제로 있을 경우에만 포함시켜 작동함


Tag로 선언하는 설정 명령

+ <Directory addr>            : 디렉토리에 대한 접근 설정   *root에 대한 접근은 기본적으로 막혀있다.

  - AllowOverride

  - Require

  - Options

+ <IfModule mod>              : 지정한 모듈이 존재할 경우 추가 설정 정의

+ <File file>                 : 파일에 대한 접근 설정   *보통은 .htaccess와 .htpasswd 파일이 웹에 공유되는 것을 막기 위해 기본 설정 되어 있음


(2) Virtual Host Setting

name based

<VirtualHost *:80>
  DocumentRoot  "/var/www/akademia"
  ServerName  www.akademia.com
</VirtualHost>

port based

* 방화벽 포트 오픈 설정
* conf 생성
Listen 8080

<VirtualHost *:8080>
  DocumentRoot  "/var/www/akademia"
  ServerName  web.akademia.com
</VirtualHost>

Alias Setting (한줄 추가)

Alias /img /var/www/html/images

Redirect Setting (한줄 추가)

Redirect [permanent|temp] /program.html /download.html

Custom Log

https://httpd.apache.org/docs/2.2/ko/mod/mod_log_config.html 참고, 그만큼 옵션이 다양함


(3) Security Basic Settings

httpd.conf

ServerTokens OS->Prod 
ServerSignature On->Off
FileETag None
TraceEnable off

custom conf

Options -Indexes

apache 서비스 계정에 권한

$ sudo usermod -g apache apache
$ sudo chown -R apache.apache /etc/httpd/
$ ls -ld /etc/httpd/
$ sudo systemctl restart httpd

root 외에 설정 파일 접근 차단

$ ps -ef | grep httpd
$ sudo chmod 750 /etc/httpd/conf
$ sudo chmod 750 /etc/httpd/conf.d

Set up TLS/SSL for a free (도메인이 있어야 가능)

$ sudo yum install epel-release
$ sudo yum install mod_ssl python-certbot-apache
$ firewall-cmd --permanent --add-service=https
$ firewall-cmd --reload
$ certbot --apache -d <도메인 명>
$ certbot --renew   # scheduler 설정

Basic Authentication Setting

# custom 설정에 추가
<Directory "/var/www/akademia/admin">
  AuthType Basic
  AuthName "For Authenticated Users"
  AuthUserFile /etc/httpd/password/passwords_file
  Require valid-user
</Directory>

$ httpd -t
$ sudo systemctl restart httpd

(2) passwords_file 생성
$ sudo mkdir /etc/httpd/password
$ sudo htpasswd -c /etc/httpd/password/passwords_file user1

Digest Authentication Setting

# custom 설정에 추가
<Directory "/var/www/akademia/mod">
  AuthType Digest
  AuthName "Private"
  AuthUserFile /etc/httpd/password/digest
  Require valid-user
</Directory>

(2) passwords_file 생성
$ sudo htdigest -c /etc/httpd/password/digest "Private" user3

Access Control

(참고: https://httpd.apache.org/docs/2.4/mod/mod_authz_host.html)
(1) Directory 태그 안에 추가
<RequireAll>
  Require all granted
  Require [not] ip XX.XX.XX.XX
</RequireAll>

.htaccess 설정

(1) Directory 태그 안에 추가
AllowOverride AuthConfig #추가

(2) 지정 위치에 .htaccess 파일 작성
AuthType Digest
AuthName "Private"
AuthUserFile /etc/httpd/password/digest
Require valid-user

웹 서버 anti-virus 설치

$ yum -y install epel-release
$ yum -y install clamav-server clamav-data clamav-update clamav-filesystem clamav clamav-scanner-systemd clamav-devel clamav-lib clamav-server-systemd
(selinux 작동 되어있어야 함)
$ setsebool -P antivirus_can_scan_system 1
$ setsebool -P clamd_use_jit 1
$ vi /etc/clamd.d/scan.conf
$ vi /etc/freshclam.conf
$ freshclam
$ clamscan -r [--(remove|move)] /var/www/akademia/

DoS Attack 방지

$ yum -y install mod_evasive
$ yum -y install mailx
$ vi /etc/httpd/conf.d/mod_evasive.conf
$ tail -f /var/log/messages

4. 기타

(1) Apache MPM (Multi-Processing Modules)

- The worker MPM : multiple child processes * many threads each, Each thread handles one connection at a time
(generally good for high-traffic servers)
- The event MPM : worker with main threads, designed to allow more requests to be served simultaneously by passing off some processing work to supporting threads
- The prefork MPM : multiple child processes * 1 thread each, Each process handles one connection at a time.
(it can be used with non-thread-safe third-party modules, and it is easier to debug on platforms with poor thread debugging support.)

(2) Adjusting httpd.conf (커넥션 관련 옵션)

- Timeout               : sets the number of seconds before data sends to or receives from the client timeout
- KeepAlive             : enables persistent connections on the web server
- KeepAliveTimeout      : the number of seconds Apache wait for a subsequent request before closing the connection
- MaxKeepAliveRequests  : limits the number of requests allowed per connection when KeepAlive is on.


Posted by kevin.jeong.
,

이전 글에서 웹 요청을 위한 HTTP 요청/응답 메시지를 알아보았습니다.

그렇다면 웹 서버에서는 이러한 요청을 어떻게 처리하는 걸까요?


정적인 요청과 동적인 요청, 그 차이는?

자바 웹 개발을 처음 시작할때, Apache Tomcat을 설치하면서 WAS의 역할에 대해 간략하게 배우게 되죠.

WAS의 역할은 동적인 요청을 처리하는 거라고 하는데, 그렇다면 정적인 것과 동적인 것은 어떻게 구분을 할까요?


저도 몇년동안 이걸 어떻게 정의할 수 있을지 고민이 많았습니다만..

핵심은 "정보가 변하는가?" 입니다.

정적인 요청은 한번 위치시켜 놓으면 변하지 않는 정보에 대한 요청인 거고,

(사람의 개입없이 단순히 웹 서버가 작동중일때를 기준)

동적인 요청은 요청의 식별자는 동일해도, 부가적인 정보(HTTP 요청 헤더, 파라미터 변수 혹은 json/xml 형식의 message-body)의 유무나

시간이 지나면서 변하게 되는 데이터인지에 따라

요청할때마다 응답하는 정보가 달라지는 경우를 말하는거죠


그래서 Tomcat같은 WAS는 이런 동적인 요청을 처리하기 위해 HTTP 요청 메시지를 파싱하고,

파라미터나 헤더값, message-body에 따라 동적인 데이터의 저장소 (보통은 DB) 에서 CRUD 같은 지지고볶는 작업을 통해 

가공된 응답을 만들어서 전달하는 것인데요.

(이 부분을 back-end라고 부르죠~)


반면 정적인 요청을 전담하는 웹 서버에서는 요청의 식별자를 요청하는 자원의 상대적인 위치로 인식하고

찾아서 있으면 메시지에 포함하거나 다운로드를 할 수 있도록 응답상태를 Redirect 로 만들어서 보내고,

없으면 응답상태가 403이 되는거죠.


Apache 웹 서버는 이런 정적인 요청을 처리해서 응답하도록 설계/개발된 서버 프로그램이라고 정리할 수 있겠습니다!


정적인 요청과 동적인 요청을 웹서버는 어떻게 구분하지?

아쉽게도 웹서버 자체에서 요청을 받는 시점에 정적인지 동적인지 구분해서 처리할수 있는 방법은 없습니다.

웹 서버는 일단 HTTP 요청 메시지의 URI로 요청 자원을 찾아보고 없으면 설정에 따라 처리할 뿐이죠.


대신 대부분의 웹서버 프로그램들은 WAS와 연동이 가능하도록 플러그인 형태의 모듈을 제공합니다.

이걸 사용하면 웹서버가 먼저 요청을 처리하고 URI에 해당하는 정보 자원이 없으면

이 요청을 WAS에게 넘기는 방식으로 작동합니다.

(아니면 특정 포트를 통해 들어오는 요청은 무조건 WAS에 이관하도록 처리할 수도 있다고 합니다~)


따라서 웹 서버를 본격적으로 다룬다고 하면 설정을 이해할 수 있어야 됩니다.

저도 아직은 미생이지만.. 조만간 Apache 설정법을 정리해볼까 하네요~


번외) Tomcat이랑 다른 WAS도 정적인 요청 다 처리하는데..? 굳이 구분을 왜 하는거야?

..라고 질문하는 분들을 위해

네 맞습니다. 자바 웹 개발 처음 배울때 분명히 tomcat에서 이미지도 처리해주고 css랑 js파일 경로 다 제공해줬죠.

WAS자체만으로도 웹서버로 사용가능한건 맞죠.

다만, 정확히 알아두고 넘어가야 하는 건, WAS로 나오는 서버프로그램들은 Apache 같은 웹 서버의 경량버전을 내포한채로 공개됩니다.

즉, WAS는 사실 동적인 요청을 처리하기 위한 Servlet Container + 경량 웹 서버 모듈인거죠.


자바같이 서버 구현이 가능한 언어에서는 동적인 웹 요청을 처리하기 위한 프로세스를 컨테이너에서 관리하도록 하고,

여기에 웹 서버처럼 작동할 수 있도록 웹 서버의 필수 핵심 기능을 더 붙여서 WAS를 개발한 게 됩니다.

servlet 같은 프로그램 실행 thread의 컨테이너가 WAS의 핵심은 맞지만, WAS == 컨테이너는 엄밀하게 따지면 잘못된거라고 생각되네요.


그래서 WAS에서는 웹 서버에 대한 설정은 깊게 못합니다. 

아예 못한다고 하긴 무리가 있겠지만, 정확히는 웹서버 상의 상당히 많은 설정들이 생략 혹은 default로 박혀버려서

대표적인 몇가지 (HTTPS 및 timeout 정도)를 제외하고는 설정이 불가능 한거죠.


사실 Apache 같은 웹서버를 사용하는 이유라고 하면, 웹으로 유입되는 네트워크 트래픽을 관리하거나

동적인 영역과 정적인 영역을 분리하는게 좋다여서, SI/SM 같은 회사 전산에서는 굳이 필요하지 않을 수도 있습니다.

대신, 웹/모바일 서비스가 회사의 메인인 경우는 트래픽이 많으면 많을 수록 매출로 연결되는데,

안정적인 웹 속도와 사용감을 제공하기 위해서는 웹 서버의 설정이 꼭 필요하고,

실제로도 카카오, 네이버 등의 대형 IT회사에서는 이쪽 전문인력만 따로 뽑아서 키우기도 합니다.


결론은 인터넷을 거쳐 온 후의 서버 측 웹 요청을 중간에 컨트롤할 필요가 있으면 웹서버와 WAS를 분리해서 사용합니다~


Posted by kevin.jeong.
,

워드프레스에서 티스토리로 옮기면서 새로운 주제를 하게 되었습니다.

웹 개발자로서 업무에 대한 지식을 정리하는게 주 목적이긴 하지만, 잘 정리해서 모두에게 도움이 되었으면 하네요.

가급적 쉽게 설명하고 업무에 바로 적용할 정도로 정리하는게 목표이긴 한데... みんなさん、がんばりましょう!


Apache 웹 서버에 대해 스터디 발표를 준비하는데,

아무래도 단계별로 가야할거 같아서 나눠서 포스팅하려고 합니다.


Apache 웹 서버의 목적 - HTTP 프로토콜 처리

Apache 웹 서버는 결국 웹 요청을 처리하기 위해 웹 상에서 작동하는 서버 프로그램입니다.

그렇다면 웹 에서의 요청과 응답이 어떤 것인지 먼저 알아볼 필요가 있을거 같은데요.


HTTP는 World Wide Web 이라는 전세계의 컴퓨터를 인터넷으로 연결해서 정보를 공유하는 일종의 가상 공간 안에서, 

정보를 요청하고 응답을 주기 위한 프로토콜, 즉, 통신 양식에 대한 명세(Spec)입니다. 

이 말인 즉슨, HTTP 명세를 벗어난 요청/응답은 웹 상에서 처리될 수 없다는 것이죠.


그렇다면 HTTP 명세라는 건 어떻게 생겨먹었나 봐야죠.

우선, 가장 큰 구분은 요청이냐 응답이냐 인데요.

HTTP는 웹 상에서 일어나는 행동을 요청과 응답 두 가지로 정의합니다.

정보가 필요한 쪽에서는 정보를 얻기 위해 요청을 보내고,

정보를 가진 쪽에서는 요청을 통해 어떤 정보에 대한 요청인지 확인한 다음 알맞은 정보를 응답으로 보내는거죠.


* 사실 요청과 응답이라는 패러다임은 모든 통신의 행동 방식이죠.

전화도 마찬가지이고, TCP/UDP도 결국은 요청 - 응답의 반복입니다.

다만, 컴퓨터에서는 디지털화된 데이터가 이동하면서 요청 - 응답 메시지에 이전보다 더 많은 정보를 실을수 있게된거고,

정보를 가진 쪽에서 어떤 정보에 대한 요청인지 구분이 용이하도록 header, body 등

명세가 더 세분화되었다고 생각하시면 될거 같습니다.


그럼 요청을 어떻게 하고, 응답은 또 어떻게 받는걸까요?

정답은 메시지 입니다.

메시지에 목적지 주소와 원하는 정보의 위치, 요청하는 정보에 대한 상세 설명, 인증정보 (정보를 가진 쪽에서 요구할 경우), 요청 명세의 버전 등을 적어서 보내면

정보를 가진 쪽에서 메시지를 받고 어떤 정보인지 찾아서

있으면 정보와 상세 설명을, 없으면 왜 없는지(?)를 처리결과와 함께 새로운 메시지로 실어서 요청한 곳으로 다시 보내는거죠.


HTTP 요청/응답 메시지

HTTP 프로토콜은 인터넷 상에서의 통신 규약이므로 구체적으로 알아보려면 RFC문서를 보는게 좋습니다.

(HTTP 프로토콜의 RFC 문서 링크 - https://tools.ietf.org/html/rfc2616)

다만 개발자 입장에서 당장 필요한 것은 HTTP를 처리하는 웹 서버에 요청을 어떻게 주는지,

응답은 어떤 모양으로 오는지 이해하는 거겠죠.


요청 메시지는 다음과 같은 모양입니다.

GET /restapi/v1.0 HTTP/1.1
Accept: application/json
Authorization: Bearer UExBMDFUMDRQV1MwMnzpdvtYYNWMSJ7CL8h0zM6q6a9ntw

(출처 : 위키백과, https://ko.wikipedia.org/wiki/HTTP)


첫번째 줄은 start-line으로 정의 되어 있는데, 요청 메시지일 경우는 request-line으로 부르고, METHOD / URI / HTTP버전 으로 구성됩니다.

정보를 요청하고 응답받는 입장에서만 보면 URI가 정보 자원의 위치를 가리키는 것이므로 가장 중요하죠.

HTTP METHOD는 정보 자원이 처리되는 방식을 가리키는 걸로 GET/POST/PUT/PATCH/DELETE/OPTION 등등을 RFC문서에서 정의하고 있지만,

웹 브라우저에서는 GET으로 지정해서 요청 자원을 그대로 받는 형태로 쓰고 있습니다.

이 부분은 다음 포스트에서 다시 설명드리겠습니다.

HTTP버전은 말 그대로 프로토콜의 버전인데, HTTP의 경우 아직은 1.1로 지정해서 사용합니다.


두번째 줄 부터는 헤더라고 해서 요청이나 클라이언트 자신에 대한 부가적인 정보를 나타내는 역할로 사용합니다.

예제 같은 경우는 응답을 JSON형식으로 전달하라는 것이고, 요청하는 정보 자원의 유형을 미리 알고 있으면 저렇게 기재하는 것입니다.

헤더 부분의 Authorization 필드는 인증정보를 요구하는 것이고, 저렇게 자기자신의 개인키를 넣어서 자기자신을 알리는 것이죠.

헤더부분은 저렇게 두번째 줄부터 CRLF(HTTP 메시지 상에서의 개행문자)가 두번 연속으로 들어간 곳 까지입니다.


응답 메시지의 예시는 다음과 같습니다.

HTTP/1.1 200 OK
Date: Mon, 23 May 2005 22:38:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Encoding: UTF-8
Content-Length: 138
Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)
ETag: "3f80f-1b6-3e1cb03b"
Accept-Ranges: bytes
Connection: close

<html>
<head>
  <title>An Example Page</title>
</head>
<body>
  Hello World, this is a very simple HTML document.
</body>
</html>

(출처 : 위키백과, https://ko.wikipedia.org/wiki/HTTP)


첫번째 줄은 요청과 마찬가지로 start-line으로 시작하지만, 응답 메시지일 경우는 status-line으로 다르게 부릅니다.

구성은 요청받은 HTTP버전 / 처리상태 로 이루어집니다.

처리상태는 상태코드와 상태에 대한 설명으로 이루어지는데, 종류가 좀 많습니다.

크게 100 ~ 500 클래스로 구분하죠.

1XX - 요청을 계속 처리중임, 일반적으로는 사용되지 않음

2XX - 요청이 잘 처리되었고, 처리된 정보의 종류를 설명하지만, 보통은 200 코드만 사용됨

3XX -  요청을 처리하기 위한 다음 Action을 나타냄 (즉, Redirection)

4XX - Client Error, 즉 요청 메시지에 오류가 있음을 나타냄

5XX - Server Error, 즉 서버가 어떤 이유로 요청을 처리할 수 없는 상태인지를 나타냄


세부적인 코드는 검색해보시길 바랍니다.

예를 들어, 응답받은 상태코드가 403이면 요청을 서버가 이해하지 못한다는 것인데, 보통은 헤더나 파라미터 같은 필수값이 일부 빠진 상태로 요청을 보내서 그렇죠.

404는 URI가 가리키는 정보가 유효하지 않다는 거니까, 경로가 잘못된게 되는거고요.

반대로 500 클래스가 응답으로 나오면 운영팀에 비상등이 켜졌다고 보면 됩니다. 빼박 서버 문제니까요.


두번째 줄 부터 헤더영역인 것 까지는 동일한데, 이때 헤더의 내용은 응답에 대한 부가정보로 요청때보다 내용이 많아지게 됩니다.

그리고 개행문자를 두번쳐서 헤더를 끝낸다음, 응답하는 정보(데이터)가 이어서 응답 메시지에 들어갑니다.

이때 정보가 들어가는 영역을 message-body라고 부릅니다.

요청때도 이 message-body가 들어갈 수는 있으나, REST 방식 같은 특수 케이스에만 쓰이죠.

일반적인 HTTP 요청 메시지에는 message-body가 들어가지 않는다고 생각하시면 됩니다.


URL과 URI의 차이는?

브라우저에서는 웹 요청을 보낼때는 URL만 이용했습니다.

그럼 저 HTTP 요청/응답 메시지는 어떻게 만들어지고 처리되는 걸까요?

브라우저에서의 처리 절차는 다음과 같습니다.

  1. URL을 입력받으면, 파싱해서 HTTP 요청 메시지를 만든 다음 URL에 명시된 도메인(혹은 IP주소)로 보냄
  2. 응답을 기다림
  3. 응답 상태가 정상이면 메시지에서 message-body 부분을 뽑아서 브라우저 창에 보여줍니다.


그럼 파싱은 어떻게 하는 걸까요?

일반적으로 사용하는 URL의 예를 들어 보면

https://www.sample.com/resource1/sample1.html?param1=xx

이런 식인데,

'://' 을 기준으로 왼쪽은 프로토콜, 오른쪽은 도메인(혹은 IP:PORT)이 됩니다.

도메인 바로 오른쪽의 / 다음 부터는 요청하는 자원의 위치가 되는 거죠.

(물음표 다음부터는 파라미터로 활용하고요)


여기서 URL과 URI을 구별할수 있는데,

URL은 도메인을 포함한 웹 상에서 정보자원의 위치이고, URI는 정보자원의 식별자가 되는겁니다.

즉 URL안에서 프로토콜과 도메인을 제거한 'resource1/sample1.html?param1=xx' 가 자원의 식별자인 URI인거죠.


요청 메시지의 request-line을 구성할때 URI와 프로토콜 버전을 URL로부터 이렇게 뽑아냅니다.

그럼 나머지는?

HTTP METHOD 같은 경우 웹 브라우저에서는 GET으로만 보냅니다.

자원을 받아서 화면에 보여줘야 되니까요.

당연히 헤더 부분도 HTML이나 파일 같은 정적 자원을 요청하는다는 걸로 채워지는 거죠.


Posted by kevin.jeong.
,

Java로 웹 개발을 할때 Spring 프레임워크를 빼놓고 얘기하면 거의 빈 껍데기 혹은 어불성설 같은 느낌일 겁니다. 그만큼 보편화 되어 있는데, 이걸 논할때 트랜잭션 개념을 빼면 겉핧기식으로 공부했다고 보는 것 같아요. 그래서 정리하려고 합니다.

 

1.  트랜잭션 개념

Spring MVC - 트랜잭션이란 도데체 뭐란 말인가!

자세한 설명은 위 블로그 글에 잘 나와 있네요. 구글링을 좀 했는데, 다들 보시면 좋을 거 같습니다.

대신, 요약을 하면

하나의 쿼리에 대한 commit/rollback을 넘어서, 업무처리같이 단위는 하나지만 그 안에 insert/update/delete (+select) 쿼리 여러개가 순차적으로 모두 에러없이 실행되어야 처리되었다고 말할 수 있을때, 이렇게 안에 세부 순서를 가진 업무 단위 혹은 이를 처리하기 위한 기술을 트랜잭션이라고 부릅니다.

 

2. Java에서 트랜잭션을 처리하는 방법 (혹은 이것의 변화과정)

(1) Procedure

Spring이 등장하기 이전에, 혹은 MVC 개념이 발전하기 전엔 DB에서 프로시저를 작성하고 그 안에 쿼리들을 넣어서 트랜잭션을 처리하게 했다고 합니다. 단점은 DB를 바꿀때, 예를 들어 mysql에서 oracle로 바꾼다 그러면 DBA는 죽어나는거죠. 아마 이때쯤에 DB 튜닝 혹은 쿼리 튜닝 일하시는 분들 단가가 엄청 올라갔던거 같습니다. 그리고 오라클 같은 경우는 패키지라고 해서 프로시저를 묶은 개념이 있더라고요. 업무차 두세번 정도 작업 했었는데, 문법 차이만 있지 작성 방법은 비슷했었습니다.

(2) 메소드 내 try ~ catch ~ finally 구문에서 여러 쿼리 호출

개발자 입장에서 트랜잭션을 구현한다고 했을때 쉽게 떠오르는 방식일거 같네요. 우선은 connection 객체에서 setAutoCommit(false) 로 자동 커밋을 막고, 쿼리 여러개를 쭉 수행한 다음 try ~ catch 사이 마지막에 commit() 을 호출하는 겁니다. 문제는 소스코드에서 중복되는 부분이 너무 많다는 거겠죠. 개발자들에게도 스트레스가 될거고요.

(3) Spring 에서 처리하기

스프링에서는 크게 AOP와 어노테이션으로 트랜잭션을 구현할 수 있습니다.

우선 AOP를 이용한 방식입니다.

// mvc-config.xml
<context:component-scan base-package="com.kevins.web" use-default-filters="false">
  <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

// application-config.xml
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"    // 추가할 부분
       xmlns:tx="http://www.springframework.org/schema/tx"      // 추가할 부분
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop                       // 추가할 부분
                           http://www.springframework.org/schema/aop/spring-aop.xsd        // 추가할 부분
                           http://www.springframework.org/schema/tx                        // 추가할 부분
                           http://www.springframework.org/schema/tx/spring-tx.xsd"         // 추가할 부분
>

...

//추가할 부분
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>

<aop:config proxy-target-class="true">
  <aop:pointcut id="serviceOperation" expression="execution(public * com.kevins.web..service.*Service.*(..))" />
  <aop:advisor id="transactionAdvisor" pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
</aop:config>

<tx:advice id="txAdvice" transaction-manager="transactionManager">
  <tx:attributes>
    <tx:method name="save*" rollback-for="Exception"/>
    <tx:method name="update*" rollback-for="Exception"/>
    <tx:method name="remove*" rollback-for="Exception"/>
  </tx:attributes>
</tx:advice>

...

</beans>

이렇게 Spring 설정을 해놓으면 Controller/Service/DAO를 구현하고, DAO내 메소드 명을 <tx:method> 태그 부분에 선언한대로만 맞추면 자동으로 실행되는거 같습니다. 대신 DAO 선언시 @Repository 어노태이션을 꼭 붙일것과, AOP 설정을 잘 이해해야겠죠.

어노테이션으로 구현하는 것은 AOP보다 쉽다고 합니다. Service\DAO 구현체나 그 메소드 앞에 @Transactional 이라는 어노테이션을 붙이면 되네요. 대신에 dataSource 설정하는 스프링 설정파일에 transactionManager 까지는 선언되어있어야 합니다.

그리고 프로그램적 선언 방식도 있다고 하는데 설정파일에 선언한 transactionManager를 @Resource 어노테이션으로 직접 호출해서 commit/rollback을 제어하는 방식입니다.

 

3. 마무으리

급하게 정리하다보니 다른 블로그를 많이 참고했습니다. 참고한 블로그 목록 입니다. 정리가 훨씬 잘 되어 있으므로 가급적 참고하시길 바랍니다.

Outsider's Dev Story - [Spring 레퍼런스] 11장 트랜잭션 관리 #1

가리사니 - Spring @Transactional Method 적용범위 : rollback 주의

박철우의 블로그 - 스프링의 트랜잭션 관리

성일만 - [Spring] 스프링 트랜잭션 적용하기 (Spring + MyBatis + MySQL)

Full Stack Web Developer Syaku - #5 스프링 트랜잭션 - 스프링 프레임워크 게시판 : Spring Framework Transaction

 

Posted by kevin.jeong.
,

자바스크립트 어느 정도 공부해보시거나 써보신 분들은 콜백지옥이라는 말 심심치 않게 들으셨을 겁니다. 이런 걸 말하는거죠.

setTimeout(function(){
  console.log('ddok');
  setTimeout(function(){
    console.log('ddak');
    setTimeout(function(){
      console.log('end!!!');
    }, 100);
  }, 100);
}, 100);

피라미드를 보는 거같은 이 복잡함.. 다들 상당히 거슬리실거에요. 이런 상황이 안올거란 법은 없지만, 그래도 기왕이면 가독성을 해결하는게 좋겠죠.

ES 6 문법 중에 Promise 라는 것이 있는데, 이런 상황을 어느정도 해결할 수 있다고 해요.

 

1. Promise

영어로는 이렇게 설명하고 있습니다.

"Promises are a first class representation of a value that may be made available in the future."

직역하면, 미래에 만들어 질 수 있는 값에 대한 클래스 표현이다..?

예제 코드를 보면서 설명하죠.

// ES6 문법 with Promise
function msgAfterTimeout (msg, who, timeout) {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(`${msg} Hello ${who}!`), timeout)
    })
}
msgAfterTimeout("", "Foo", 100).then((msg) =>
    msgAfterTimeout(msg, "Bar", 200)
).then((msg) => {
    console.log(`done after 300ms:${msg}`)
})

// ES5 (기존 문법)
function msgAfterTimeout (msg, who, timeout, onDone) {
    setTimeout(function () {
        onDone(msg + " Hello " + who + "!");
    }, timeout);
}
msgAfterTimeout("", "Foo", 100, function (msg) {
    msgAfterTimeout(msg, "Bar", 200, function (msg) {
        console.log("done after 300ms:" + msg);
    });
});

//setTimeout 함수 동작 예상 - 절대 정확하지 않습니다. 그냥 단순하게 코드화한거 뿐입니다.
function setTimeout(callback, time){
    wait(time);
    callback();
}

기존과 비교해보면, Promise는 콜백을 담고 있는 상태처럼 보입니다. 그래서 함수 리턴으로 Promise를 던지면 함수 실행 이후 then() 메소드로 콜백 호출을 이어가는거죠.

그럼 위의 예제를 돌려보겠습니다.

Promise 선언의 setTimeout에서 콜백부분이 resolve를 실행하게 되어있습니다. 여기서 보기에 저 resolve는 메시지를 이어서 나올 then 메소드에 첫번째 인자로 넘겨주는 역할인거 같습니다. 그리고 Promise안의 콜백이 제대로 실행되지 않으면 reject를 실행하도록 setTimeout 다음줄에 추가해야 합니다.

이걸로 볼때 Promise는 콜백 + 상태를 자바스크립트 클래스로 구체화한거라고 생각하시면 되겠습니다.

그리고 Promise는 Angular나 React같이 front-end를 위한 자바스트립트 프레임워크에서 많이 쓰이는 문법입니다. 프론트엔드쪽 일하시려면 이 정도는 최소한으로 아시는게 좋을거에요.


2. 레퍼런스

Promise 상태도 구분해서 부르는거 같습니다. 다만, 제가 설명하기에는 한계가 있을거 같고, 대신 참고한 자료를 남깁니다.

ES6 - Promises

ES6 - Promise Usage (es6-features.org)

JS promise 패턴 구현해보기

 

Posted by kevin.jeong.
,

간만에 블로그 올립니다.

개인적으로 Socket.IO 를 많이 궁금해했었는데, 더 늦기 전에 정리를 해보려고 합니다.

(1) 개요

채팅이나 메신저 어플리케이션을 구현한다고 했을때, 웹서버 (혹은 WAS 를 포함한) 에서만 기능을 구현하는 것은 상당히 비효율적일 겁니다. 트래픽 양을 생각하면 웹 서버에서 전부 처리하는 것은 로드가 많이 걸리죠. Node.Js로 비동기 처리방식의 구현이 용이해진 만큼, 이를 이용해서 비동기 방식으로 가벼운 메시지 등을 처리하면 운영에 있어서도 유연하겠죠. 아마 이전에는 소켓 프로그램을 구현했겠지만, Node.js 에 Socket.IO 모듈을 설치하면 URL 및 HTTP 전송 방식으로도 이처럼 light한 요청들을 처리하는데 상당히 유용하다고 합니다.

 

(2) 설치

Node.Js 를 설치하고 프로젝트 폴더에 Socket.IO 모듈을 추가하면 바로 사용할 수 있다고 합니다. 터미널에서 npm install 명령어로 추가합니다.

npm install --save socket.io

 

(3) Node 서버 메인

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket){
  socket.on('chat message', function(msg){
    io.emit('chat message', msg);
  });
});

http.listen(3000, function(){ console.log('listening on *:3000'); });

express로 웹 서버 환경을 구현하고 . Socket.IO 모듈은 io 변수가 접근자가 되어서 해당 요청을 처리합니다. io.on() 함수 안의 콜백에서 관련 기능을 구현하면 될거 같네요.

socket.on 메소드의 첫번째 파라미터를 기능을 정의하는 키워드 정도로 생각하시면 될거 같아요. socket.on 메소드는 기능을 구현하는 단위가 되겠네요.

(4) 클라이언트 페이지 처리

<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>

    <script src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-1.11.1.js"></script>
    <script>
      $(function () {
        var socket = io();
        $('form').submit(function(){
          socket.emit('chat message', $('#m').val());
          $('#m').val('');
          return false;
        });
        socket.on('chat message', function(msg){
          $('#messages').append($('<ul><li>').text(msg));
        });
      });
</script>
</body>
</html>

페이지에서 socket.io.js 모듈을 추가하고 기능을 정의하였습니다. 그리고 form에 이벤트를 걸었네요.

socket.emit 메소드는 첫번째 파라미터에 정의한 기능으로 메시지를 보내는 액션을 수행하고, socket.on 메소드는 소켓으로부터 데이터가 전송된 후의 액션을 정의하네요.

 

(5) 레퍼런스

이번글은 Socket.IO 의 getting started 페이지인 https://socket.io/get-started/chat/ 를 읽고 풀어보았습니다. 생략한게 많아서 API 목록이나 기타 더 궁금하신 분들은 여기를 직접 참고해서 들어가시는게 좋겠습니다.

- 서버 API : https://socket.io/docs/server-api/

- 클라이언트 API : https://socket.io/docs/client-api/

 

 

Posted by kevin.jeong.
,

자바스크립트 3대장 (React, Angular, Vue)을 익히다 보면 Vanilla JS라는 말이 자주 등장합니다. 그러나 어디에서도 그 실체는 드러나지가 않죠. 도데체 뭐가 Vanilla JS 라는겨?? 저도 처음엔 이랬습니다만.. 더 이상 바보가 되지 않도록 정리 한 해보죠~

 

※ vanilla 의 어원

이거 하나로 정리가 되죠? 형용사로 특별한 점이 없는, 일반적인 이라는 뜻입니다. 명사로서의 뜻도 있지만, 바닐라 열매에서 추출한 원료라는 뜻이어서 큰 특징은 없습니다.

곰곰이 생각해보면 아이스크림에서 바닐라 맛은 미각적인 특징이 들어가지 않은 기본적인 맛이잖아요? 이걸 따와서 특별한게 없는 것의 특징으로 vanilla 라고 부르기 시작한게 아닐까 싶어요. 실제로 영어에서 vanilla는 별 특징 없는 걸 표현할때 쓰는 slang이라고 하네요~

 

※ Vanilla JS

즉, Vanilla JS는 라이브러리나 프레임워크 등의 특징을 타지 않는 순수 자바스크립트 문법을 가리키는 또 하나의 slang입니다. 아마 프로그래밍 기본기의 중요성을 망각하고, 무조건 프레임워크나 라이브러리 같이 포장되서 나오는 것들만 좋아하는 사람들을 골려주기 위해 나온 말 같은데요..ㅋㅋㅋ 아래 동영상에 자세히 설명이 나옵니다~

 

ps. 근데 한편으로는 씁쓸한게... 뭔가 우리나라 현실을 너무 잘 비꼬는 거 같은...?

Posted by kevin.jeong.
,

이번에 다룰 내용은 React.js, Vue.js 등의 최신 자바스크립트 라이브러리 및 프레임워크에서 꼭 보게되는 내용입니다. 잘 이해하면 최신 자바스크립트 트렌드를 따라가는데 수월해지죠.


※ Template

template 문법은 기존 자바스크립트에서 문자열 사이에 값을 넣을때 .. + yourName + .. 같은 처리로 코드가 지저분해지는걸 막고 가독성을 높여주는 효과가 있습니다. 간단하므로 예제로 설명드리겠습니다.

const bodyTag = document.getElementsByTagName('body')[0]
const myName = 'kevin'
bodyTag.innerHTML = `<div id="myDiv">${myName}'s Home</div>`

template 문법을 지정하기 위해서는 기존에 문자열을 나타내는 따옴표나 쌍따옴표 대신 보통 ESC 키 옆에 있는 ` 문자를 사용해야 합니다. 문자열을 ` ... `로 감싸면 자바스크립트가 템플릿으로 처리할 준비를 합니다. 그 다음에 '..' + 문자열변수명 + '..' 대신 ${문자열변수명 혹은 삽입할 객체의 프로퍼티명} 으로 템플릿에 삽입할 변수와 위치를 지정해주고 실행하면, 자바스크립트는 템플릿 문법을 처리해서 변수의 문자열 값을 지정된 위치에 넣은 문자열을 반환해주죠. 실행 결과는 다음과 같습니다.

크롬에서 새탭 열고 바로 개발자 도구 열어서 실행했습니다. 크롬 고유의 홈 화면은 온데간데 없이 사라지고 예제로 지정한 템플릿만 나오네요. 이처럼 템플릿 문법은 최신 자바스크립트 프레임워크에서 웹페이지 뷰를 동적으로 로드할때 많이 씁니다.


※ Tagged Template literals

템플릿으로 문자열을 동적으로 할당해주는건 좋지만, 만약 JSON 객체로 반복되는 템플릿을 실행하다가 중간에 비어서 선언이 안되어 있는 프로퍼티가 있으면 어떻게 해야 할까요? 대상이 없다고 안내하는 대체 문구가 있으면 좋겠죠? 그런걸 처리하는 방법이 있습니다. 템플릿에서 삽입할 값을 검사하는 함수를 만드는 건데요. 감이 잘 안오실테니 예제로 보여드리겠습니다.

let jsonData = [
  {
    title: "ES6 문법 정리 (1) – let, const 키워드"
    ,author: "kevini"
    ,category: "IT관련, Javascript"
    ,keywords: "const, ES6, let, 자바스크립트"
    ,publishDate: "2018-03-02"
  }
  ,{
    title: "ES6 문법 정리 개요"
    ,author: "kevini"
    ,category: "IT관련, Javascript"
    ,keywords: "ECMAScript, ES6"
    ,publishDate: "2018-03-01"
  }
  ,{
    title: "자바스크립트 개념 정리 (3) – prototype 3탄 (자바스크립트 내장 메소드, 상속구조 구현을 위한 ES6 문법)"
    ,author: "kevini"
    ,category: "IT관련, Javascript"
    ,keywords: "ES6 class, Object.스태틱메소드, 자바스크립트"
    ,publishDate: "2018-03-01"
  }
  ,{
    title: "자바스크립트 개념 정리 (3) – prototype 2탄 (prototype chaining)"
    ,author: "kevini"
    ,category: "IT관련, Javascript"
    ,keywords: "prototype chaining, 자바스크립트, 클로저"
    ,publishDate: "2018-03-01"
  }
  ,{
    title: "자바스크립트 개념 정리 (3) – prototype 1탄"
    ,author: "kevini"
    ,category: "IT관련, Javascript"
    ,keywords: "prototype, 생성자함수, 자바스크립트"
    ,publishDate: "2018-02-25"
  }
  ,{
    title: "자바스크립트 개념 정리 (2) – context & scope"
    ,author: "kevini"
    ,category: "Javascript"
    ,keywords: "context, javascript, this"
    ,publishDate: "2018-02-24"
  }
  ,{
    title: "자바스크립트 개념 정리 (1) – 변수 타입"
    ,author: "kevini"
    ,category: "Javascript"
    ,keywords: "==, ===, 변수타입, 자바스크립트"
    ,publishDate: "2018-02-03"
  }
  ,{
    title: "Atom 에디터에서 오류 발생할때 대처방법"
    ,author: "kevini"
    ,category: "개발툴관련"
    ,publishDate: "2018-01-27"
  }
  ,{
    title: "자바스크립트 표준 ES6 문법 체킹을 위한 JSHint"
    ,author: "kevini"
    ,category: "Javascript"
    ,keywords: "ES6, JSHint, 자바스크립트"
    ,publishDate: "2018-01-27"
  }
];

const bodyTag = document.getElementsByTagName('body')[0];
bodyTag.innerHTML = `<h2>블로그 목록</h2><div id="myDiv" style="text-align:left"></div>`;
const divTag = document.querySelector('#myDiv');

function tagFn(tags, title, author, category, keywords){
  if(typeof category === 'undefined') category = '카테고리 미지정';
  if(typeof keywords === 'undefined') keywords = '태그 미지정';

  return (tags[0] + title + tags[1] + author + tags[2] + category + tags[3] + keywords + tags[4]);
}

jsonData.forEach((v) => {
  const liTags = tagFn`
  <li>제목 : ${v.title}</li>
  <li>작성자 : ${v.author}</li>
  <li>카테고리 : ${v.category}</li>
  <li>태그 : ${v.keywords}</li>`;

  divTag.innerHTML += `
    <ul>
      ${liTags}
    </ul>
  `;
});

역시 크롬에서 새 탭 띄우신 다음 개발자 도구 열고 콘솔에서 예제 돌리면 아래와 같이 페이지가 바뀝니다.

자세히 보시면 밑에서 두번째 목록의 태그는 '태그 미지정' 으로 입력되어 있네요. 그 부분의 JSON 데이터를 보면 keywords 프로퍼티가 비어있습니다. 즉 비어있는 값들에 대해 처리를 할수 있도록 tagFn이라는 템플릿 검사 함수를 만든것입니다.


※ Arrow function

Arrow function은 함수를 정의하는 새로운 방식입니다. 보통 콜백으로 넘기는 함수를 정의할때 많이 쓰입니다. 물론 일반적인 함수 정의를 대체할수도 있습니다.

//일반적인 함수 정의
function(value1, value2, ...) {
  ...
}

//Arrow function 정의
let fn1 = (value1, value2, ...) => {
  ...
}

//Arrow function 사용 예
setTimeout(() => {
  console.log("check!!");
}, 200)

소스를 보면 function() {} 에서 () => {} 로 바뀐게 전부입니다. () 부분은 똑같이 파라미터를 정의해주고, {} 부분에서 함수의 기능을 정의하는 거죠. 다만 function 키워드로 함수를 선언할때와 arrow function으로 선언할때 실행컨텍스트가 다르다는 차이가 있습니다. 예제를 더 보도록 하죠.

const myObj = {
  timeoutTest1(){
    setTimeout(function(){
      console.log(this);
    }, 200);
  },
  timeoutTest2(){
    setTimeout(() => {
      console.log(this);
    }, 200);
  }
}

myObj.timeoutTest1();
myObj.timeoutTest2();

실행 결과를 보겠습니다.

myObj 에서 timeoutTest1 메소드로 setTimeout을 실행하면서 실행컨텍스트를 확인해봤습니다. 예전에 함수의 실행컨텍스트는 global 객체가 된다고 했었죠? 메소드로 실행해도 출력하는 부분은 function으로 둘러싸여있어서 Window 객체가 출력되었네요.

하지만 timeoutTest2 메소드로 setTimeout을 실행할때는 실행컨텍스트가 myObj 객체가 되네요. 즉, arrow function이 실행될때 실행컨텍스트는 자기를 부르는 메소드를 포함하는 객체가 됩니다. 이 차이만 주의하시면 arrow function은 그렇게 두려워하실 필요는 없으실거 같습니다.


※ module 정의

module을 구성하려면 우선 자바스크립트 소스를 여러 파일로 정의해야 합니다. 순서대로 방법을 적어보면,

(1) 자바스크립트 파일마다 모듈화할 객체를 구현한다.

(2) 파일마다 export 혹은 export default 키워드로 외부에서 접근 가능한 객체를 지정한다.

(3) 최종적으로 html에서 <script> 태그로 로드할 파일에서 import 문으로 다른 파일에서 정의한 객체를 로드해서 사용한다.

정도가 되겠습니다.

module을 테스트 하려면 웹 서버가 구성되있어야 하므로 저는 nodeJS 기반의 lite-server 에서 테스트 하겠습니다.

우선 예제 코드 올립니다.

/* CalendarObj.js */
class CalendarObj{
  constructor(year, month){
    this.year = year
    this.month = month;
  }

  checkParams(){
    if(this.year * 1 === NaN){
      console.log("년도의 입력 값이 올바르지 않습니다.", this.year);
      return false;
    }
    if(this.month < 1 || this.month > 12){
      console.log("월의 입력 값이 올바르지 않습니다.", this.month);
      return false;
    }
    return true;
  }

  getCalendarForm(){
    if(!this.checkParams()){
      alert("입력 값을 다시 확인하세요.");
      return false;
    }

    let month_str = this.month > 9 ? this.month : '0' + this.month;
    let max_day = 31;
    switch(this.month){
      case 4:
      case 6:
      case 9:
      case 11:
        max_day = 30;
        break;
      case 2:
        max_day = 28;
        break;
    }

    let day_of_week = new Date(`${this.year}-${month_str}-01`).getDay();

    let in_tr_arr = [];
    let tmp_tr_arr = '';

    for(let k = 0; k < day_of_week; k++){
      tmp_tr_arr += '<td></td>';
    }

    for(let i  = 1; i <= max_day; i++){
      if(day_of_week == 0){
        in_tr_arr.push(tmp_tr_arr);
        tmp_tr_arr = '';
      }

      let color_style = day_of_week === 0 || day_of_week === 6 ? 'style="color:red;"' : '';
      tmp_tr_arr += `<td ${color_style}>${i}</td>`;

      day_of_week = (day_of_week + 1) % 7;
    }

    in_tr_arr.push(tmp_tr_arr);
    let in_template = in_tr_arr.join('</tr><tr>');
    return `
      <table>
        <tr>
          <th>일</th>
          <th>월</th>
          <th>화</th>
          <th>수</th>
          <th>목</th>
          <th>금</th>
          <th>토</th>
        </tr>
        <tr>
          ${in_template}
        </tr>
      </table>
    `;
  }
}

export default CalendarObj;
/* app.js */
import CalendarObj from './CalendarObj.js'

document.querySelector("input[type=button]").addEventListener("click", (evt)=>{
  let year_input = document.querySelector("#year_input");
  let month_input = document.querySelector("#month_input");

  let cal_obj = new CalendarObj(year_input.value * 1, month_input.value * 1);
  if(!cal_obj.checkParams()){
    alert("입력값을 다시 확인하세요!");
  } else {
    console.dir(document.querySelector("#calFrame"));
    document.querySelector("#calFrame").innerHTML = cal_obj.getCalendarForm();
  }
})
<!-- index.html -->
<!doctype html>
<html>
  <head>
    <title>module test page</title>
  </head>
  <body>
    <div id="appContents">
      <input type="text" id="year_input" /><br />
      <input type="text" id="month_input" /><br />
      <input type="button" value="달력보기" />
      <div id="calFrame">

      </div>
    </div>
    <script type="module" src="./scripts/app.js"></script>
  </body>
</html>

js 파일은 scripts 디렉토리 밑에 두었습니다. 주의하실 점은 html에서 script 태그를 붙일때 type="module" 로 꼭 되어있어야 한다는 점입니다. 이렇게 안하면 module import 를 못합니다.

실행 결과는 다음과 같습니다.

이 부분은 비슷하게 예제 만들어서 lite-server 에서 테스트 해보시면 이해가 빠르실겁니다. 당장 이해하려고 하지 않으셔도 React.js, Angular.io, Vue.js 같은데서 항상 등장하는 거라서 지금은 가볍게 이해만 하고 넘어가셔도 되고요~


※ 레퍼런스

이 글은 인프런 사이트의 ‘모던자바스크립트(javascript) 개발을 위한 ES6 강좌’ 강좌의 내용을 토대로 예제코드를 작성하여 설명하였습니다. ES6 자바스크립트의 기능을 좀 더 알기를 원하시면 해당 강좌를 수강하시는 것을 권장합니다. 비용 결재 후 수강 가능합니다만 인프런 사이트 내 상당수의 강좌가 무료이며, 유료강좌도 비용이 강좌당 몇만원 수준이어서 크게 부담은 안되실겁니다. 그리고 인프런과 협약을 맺은 대학교의 학생들은 이 유로강좌도 무료로 들을 수 있으니 인프런 사이트를 참고하시기 바랍니다.

 

 

 

'Web 개발 (업데이트 없음)' 카테고리의 다른 글

Socket.IO 이해하기  (0) 2018.10.28
Vanilla JS .. ?  (0) 2018.10.28
ES6 문법 정리 (2) – Destructuring 기법  (0) 2018.10.28
ES6 문법 정리 (1) - let, const 키워드  (0) 2018.10.28
ES6 문법 정리 개요  (0) 2018.10.28
Posted by kevin.jeong.
,

Destructuring은 ES6에서 도입된 방법으로 배열 및 객체에서 원하는 정보만을 뽑아내는 새로운 방법입니다. 보통 배열에서 정보를 뽑을때 index 번호로 접근하셨는데요. 이 방법을 쓰면 index 번호를 굳이 지정하지 않아도 됩니다.


※ Destructuring Array

예제로 보겠습니다.

[선언할변수명, ...] 처럼 배열 인덱스 대신 배열 인덱스에 해당하는 위치에 변수명을 넣었는데요. 이렇게 하면 우측에 선언한 배열에서 변수명을 지정한 위치의 값을 추출해서 각각의 변수로 생성해줍니다. 그 다음은 일반적인 변수처럼 사용하면 되고, 실제로도 변수입니다.


※ Destructuring Object

역시 예제로 보겠습니다.

객체를 생성한 후 객체에서 특정 프로퍼티의 값들을 뽑아내고 싶을때 let {프로퍼티명, ...} = 객체  문법으로 작성하면 각각의 프로퍼티가 변수로 추출됩니다. 추출될때 변수명을 다르게 하고 싶은 경우는 예제의 5번째처럼 {프로퍼티명:지정할변수명, ...} 으로 대체하면 되죠. 생각보다 단순한거 같죠?

※ Destructuring 응용 #1 - JSON 파싱

Destructuring은 ajax등으로 수신받은 데이터를 집합한 객체에서 원하는 데이터만 추출할때 유용합니다. 만약 JSON 형태로 데이터를 받았다면, 그 변수는 자바스크립트 객체이므로 Destructuring을 바로 적용할 수 있죠. 역시 예제를 만들어 보겠습니다. json 객체는 ajax 통신으로 다음과 같이 전달받았다고 가정합니다.

let jsonData = [
  {
    title: "ES6 문법 정리 (1) – let, const 키워드"
    ,author: "kevini"
    ,category: "IT관련, Javascript"
    ,tags: "const, ES6, let, 자바스크립트"
    ,publishDate: "2018-03-02"
  }
  ,{
    title: "ES6 문법 정리 개요"
    ,author: "kevini"
    ,category: "IT관련, Javascript"
    ,tags: "ECMAScript, ES6"
    ,publishDate: "2018-03-01"
  }
  ,{
    title: "자바스크립트 개념 정리 (3) – prototype 3탄 (자바스크립트 내장 메소드, 상속구조 구현을 위한 ES6 문법)"
    ,author: "kevini"
    ,category: "IT관련, Javascript"
    ,tags: "ES6 class, Object.스태틱메소드, 자바스크립트"
    ,publishDate: "2018-03-01"
  }
  ,{
    title: "자바스크립트 개념 정리 (3) – prototype 2탄 (prototype chaining)"
    ,author: "kevini"
    ,category: "IT관련, Javascript"
    ,tags: "prototype chaining, 자바스크립트, 클로저"
    ,publishDate: "2018-03-01"
  }
  ,{
    title: "자바스크립트 개념 정리 (3) – prototype 1탄"
    ,author: "kevini"
    ,category: "IT관련, Javascript"
    ,tags: "prototype, 생성자함수, 자바스크립트"
    ,publishDate: "2018-02-25"
  }
  ,{
    title: "자바스크립트 개념 정리 (2) – context & scope"
    ,author: "kevini"
    ,category: "Javascript"
    ,tags: "context, javascript, this"
    ,publishDate: "2018-02-24"
  }
  ,{
    title: "자바스크립트 개념 정리 (1) – 변수 타입"
    ,author: "kevini"
    ,category: "Javascript"
    ,tags: "==, ===, 변수타입, 자바스크립트"
    ,publishDate: "2018-02-03"
  }
  ,{
    title: "Atom 에디터에서 오류 발생할때 대처방법"
    ,author: "kevini"
    ,category: "개발툴관련"
    ,tags: ""
    ,publishDate: "2018-01-27"
  }
  ,{
    title: "자바스크립트 표준 ES6 문법 체킹을 위한 JSHint"
    ,author: "kevini"
    ,category: "Javascript"
    ,tags: "ES6, JSHint, 자바스크립트"
    ,publishDate: "2018-01-27"
  }
]

이 데이터를 바탕으로 Destructuring을 해봤습니다.

JSON 배열의 3번째 값 객체에서 title을 aTitle로, author를 aAuthor로 추출하였고 그 결과는 예제 결과와 같습니다. 이런식으로 JSON에서 Destructuring을 유용하게 사용할 수 있겠네요.


※ Destructuring 응용 #2 - Event 객체 전달

웹 페이지에서 자바스크립트를 구현할때 보통 클릭 등의 이벤트가 발생할때 수행할 기능들을 리스너에 등록합니다. 그때 이벤트 객체가 파라미터로 전달되는데, 보통 우리는 여기서 클릭한 DOM 객체에 있는 값 혹은 입력 텍스트를 원할 때가 많습니다. 이런 처리에서 Destructuring을 활용하면 불필요한 변수선언이나 프로퍼티를 .으로 찾아가는일 없이 간편하게 필요한 것만 추려낼 수 있죠. 예제로 작성해보았습니다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
  <script type="javascript">
    document.querySelector("#forClick").addEventListener("click", function({target}){
      alert(target.innerText);
    });
  </script>
</head>
<body>
  <div id="forClick">
    abcdefg
  </div>
</body>
</html>

실행결과 입니다. (jsbin.com 에서 테스트했습니다.)

Destructuring으로 id로 지정한 div 태그만 가져오도록 했고, 거기에서 innerText만 경고창으로 뿌려주도록 했습니다. 잘 돌아가네요.


※ 레퍼런스

이 글은 인프런 사이트의 '모던자바스크립트(javascript) 개발을 위한 ES6 강좌' 강좌의 내용을 토대로 예제코드를 작성하여 설명하였습니다. ES6 자바스크립트의 기능을 좀 더 알기를 원하시면 해당 강좌를 수강하시는 것을 권장합니다. 비용 결재 후 수강 가능합니다만 인프런 사이트 내 상당수의 강좌가 무료이며, 유료강좌도 비용이 강좌당 몇만원 수준이어서 크게 부담은 안되실겁니다. 그리고 인프런과 협약을 맺은 대학교의 학생들은 이 유로강좌도 무료로 들을 수 있으니 인프런 사이트를 참고하시기 바랍니다.

다음에는 template, arrow function, module export & import를 정리하겠습니다~

Posted by kevin.jeong.
,

ES6로 오면서 새롭게 등장하는 키워드로 let과 const가 있습니다.

const 같은 경우는 C 계열의 언어에서 많이 보셨을 거라고 생각하고, 변수의 값을 수정하지 못하도록 하는 것 역시 비슷합니다. 다만 let이 익숙치가 않을텐데요. 이건 자바스크립트의 scope와 관계가 있기 때문에 정리가 필요할거 같습니다.

그럼 시작합니다~


※ let 키워드

예제를 먼저 보겠습니다.

var name1 = "kevin";

function fn1(){
  for(var h = 0; h < 100; h++){

  }
  console.log(h);

  for(let i = 0; i < 100; i++){

  }
  console.log(i);

  if(1===1){
    var name2 = "name is ..";
    let name3 = "case if";
  }

  console.log(name2);
  console.log(name3);
}

fn1();

실행결과는 다음과 같습니다.

똑같은 for문인데 변수를 var로 선언했을때는 for문 밖에서도 변수가 유효한 반면에 let으로 선언하니 for문 밖에서 변수가 유효하지 않다고 나오네요. 이번엔 for문 부분을 주석처리해서 다시 돌려보겠습니다.

if문 안에서 변수를 선언하고 밖에서 변수값을 출력해봤는데요. 여기서도 var 키워드와 let 키워드의 차이가 분명하게 나타나네요. var로 선언한 변수는 if문 밖에서도 유효한 반면, let은 그렇지 않죠.

let으로 선언된 변수는 block 단위의 scope를 가지기 때문에 선언된 block 밖에서는 유효하지 않습니다. 반면 var로 선언됬었던 기존의 자바스크립트 변수들은 함수 단위의 scope를 가지기 때문에 함수 안에 선언되면 그 함수 안에서는 얼마든지 접근할수 있는거죠. 잘 살펴보면 let 키워드가 다른 프로그래밍 언어에서의 변수 선언 방식과 상당히 유사하죠? 그렇기 때문에 ES6 에서의 권장방식은 var 키워드 대신 let이나 const를 활용하는 것입니다. var 키워드는 구 버전 자바스크립트 소스의 호환성을 위해 작동은 하지만 가급적 지양하라는 거죠.  기존의 자바스크립트 소스를 애매모호하게 만들던 요소들을 최대한 줄이려는걸로 생각됩니다.


※ const 키워드

const 키워드로 선언된 변수는 값의 수정이 안된다고 했습니다만, 해당 변수에 객체를 할당했다면 얘기가 조금 달라집니다. 예제를 보면서 비교해보죠.

변수에서 바로 값을 변경하거나 재할당 하는 건 안되지만, 변수를 배열객체로 만든다음 메소드로 값을 넣고 빼는건 가능합니다. const로 객체를 변수에 선언한 경우 변수는 객체의 주소값을 계속 유지하므로 객체가 갑자기 바뀌는 것을 막는 효과를 가집니다.  그리고 const 역시 let과 동일한 block 단위의 scope를 가지게 합니다.


※ 정리

자바스크립트에서 var 키워드는 scope가 지정되는게 다른 프로그래밍 언어와 달랐고, 이런 부분이 자바스크립트를 ambigious(애매한) 하게 만드는 요인이었습니다. ES6는 자바스크립트를 더욱 유연하게 만들면서도 ambigious한 요인들은 없애기 위해 많은 변화를 주었고, 그 중에 하나가 let과 const 키워드의 도입이 아닐까 생각합니다.

다음에는 Destructuring으로 찾아뵙겠습니다~

 

 

Posted by kevin.jeong.
,