728x90

자바스크립트 개발환경에서 Ajax로 백엔드와 통신을 테스트 할때 걸리적 거리는 것 중 하나가 바로 CORS이다. 

CORS는 웹 브라우저의 기본 보안 모델로,
자바스크립트로 원격 리소스에 접근하고자 할 때, 자바스크립트를 내려 준 원천(Origin)이 아니면 Cross-Origin HTTP 요청으로 판단하여 요청을 허가하지 않게끔 한다.

그래서 웹 서버 설정이나 프로그래밍 방식으로 CORS 설정(HTTP 헤더 설정)을 통해 상호 통신이 가능케 한다.

이는 프로덕션 환경에서는 당연하겠지만, 개발 환경에서는 여간 불편한게 아니다.

개발 환경에서 서버 측을 직접 제어 할 수 있으면 별로 문제 될 것이 없으나, 그렇지 않을 경우 서버 측 담당자에게 CORS 설정을 요청해야 한다. 

만일 서버 측 환경이 다수의 서비스와 통신을 해야 하는 상황이라면 더욱 귀찮아 진다.

그래서 프론트엔드 단에서 Cross-Origin HTTP 요청이 가능하게끔 하는 방법이 자주 사용된다.

흔히 사용되는 방법으로는 Node.js의 http-proxy 관련 모듈을 사용해서 중간에 프록시 계층을 하나 두는 것인데, 이는 프록시 만을 위한 Node 응용프로그램을 별도로 띄워야 하는 점이 마음에 들지 않는다.


따라서 이 글에서는 프론트엔드 테스트 도구와 프록시 설정을 한방에 해결 할 수 있는 편리한 환경에 대해 소개하고자 한다.

몇 가지 선택지가 있으나 여기서는 Browser-Sync를 이용해 보자

1. 설치
 > npm install -g browser-sync

2. 프록시 설정 없이 테스트 돌려 보기
 > browser-sync start --server --files “*.*”

기본포트인 3000번 포트로 프론트엔드 파일들을 마치 웹 서버에서 실행하듯 테스트 해 볼 수 있다.
물론 Browser-Sync가 제공하는 각종 브라우저 동기화, 라이브로딩 등의 장점을 만끽하면서 말이다.
그런데 이렇게만 하면, CORS 통신을 하지 못하게 된다.

따라서 아래와 같이 실행.

3. 프록시 모드로 돌려보기

  > browser-sync start --proxy "localhost:8080" --serverStatic "public" --files "public"

정적리소스는 "public" 폴더에 있다고 가정하고 나머지 요청은 8080포트로 서비스 중인 서버 측으로 요청을 프록시하도록 한다.

이렇게 함으로써 정적파일은 정적파일대로 테스트 할 수 있고, 다른 도메인(포트)로 서비스 되고 있는 원격 API도 CORS상에서 호출이 가능해 진다.

음.. 아주 편하군...

끝!!!

그런데 이런 환경을 만들어 주는 게 비단, Browser-Sync만 있는 것이 아니다. 다음은 이와 같은 개발 환경을 가능케 해 주는 다른 모듈을 안내한다.

1) lite-server
- AngularJS 계의 거장(?)인 존파파께서 만들어준 로컬 서버 환경. Broswer-Sync를 기반으로 하고 있기 때문에 동일하게 동작한다. 물론 Angular 환경이 아니더라도 얼마던지 사용가능하다.

2) webpack-dev-server
- Angular2가 처음 출시될 때는 System.js에 lite-server를 사용했는데, 이후 버전 업이 되면서 최근 Angular-CLI는 webpack을 빌드도구를 채용하고 로컬 서버를 webpack-dev-server로 교체하였다.

역시나 Angular 환경이 아니더라도 webpack-dev-server를 사용할 수 있으며, 이 환경에서 CROS 설정이 가능하다.

다음 URL에서 아주 잘 설명하고 있다.

> https://webpack.github.io/docs/webpack-dev-server.html#proxy 


3) Angular환경 
- 만일 최신 Angular 프레임워크로 작업하고 있다면 ng serve 실행 시 프록시 설정을 추가 할 수 있다. 역시 다음 URL에서 아주 잘 설명하고 있다.

https://www.npmjs.com/package/angular-cli#proxy-to-backend



'모바일 > Javascript' 카테고리의 다른 글

Javascript Compiler, Babel  (0) 2017.03.16
[Angular] Angular Modules(NgModule)  (0) 2017.01.25
Isomorphic Javascript  (0) 2017.01.23
[Node] npm(node package manager)  (0) 2017.01.20
[Angular] Lazy Loading  (0) 2017.01.18

Javascript Compiler, Babel

Posted in 모바일/Javascript // Posted at 2017. 3. 16. 10:49
728x90

* ECMAScript 6 브라우저 지원 현황

https://kangax.github.io/compat-table/es6/ 


'모바일 > Javascript' 카테고리의 다른 글

Proxy to Back-end(프론트엔드단 CORS 통신설정)  (1) 2017.07.28
[Angular] Angular Modules(NgModule)  (0) 2017.01.25
Isomorphic Javascript  (0) 2017.01.23
[Node] npm(node package manager)  (0) 2017.01.20
[Angular] Lazy Loading  (0) 2017.01.18

[Angular] Angular Modules(NgModule)

Posted in 모바일/Javascript // Posted at 2017. 1. 25. 19:30
728x90

본 아티클에서는 Angular 2.4.4 버전에서 테스트 되었습니다.

Angular 응용프로그램은 컴포넌트와 디렉티브, 파이프, 서비스 등의 조합으로 이루어진다.

응용프로그램의 규모가 커질 수록, 이러한 구성요소들을 특정 기준에 따라 그룹화시켜 관리하는 것이 좋은데 이를 위해 Angular에서는 @NgModule이라는 decorator를 통해 모듈화 구성을 지원한다.

Angular의 모듈 구성을 사용하면, 컴포넌트들을 논리적으로 그룹핑하여 관리할 수 있으며
라우팅과 연계하면 모듈 단위 Lazy Loading을 구현할 수도 있다.

(참고: 논리적 그룹이라는 측면에서 보면 닷넷의 namespace, 자바의 package와 유사할 것이다.)

Angular의 모듈화 구성에 대해 살펴보자

@NgModule Decorator
Angular 모듈구성을 위한 메타데이터 객체정보를 전달받는 데코레이터 함수이다.
전달된 메타데이터 정보에 기반해서 현재 모듈로 구성해야할 컴포넌트와 디렉티브, 파이브 등의 정보를 식별하고 외부로 노출할 컴포넌트를 결정한다.

전달되는 메타데이터 속성은 다음과 같다.

1) declarations
- 현재 모듈에 포함시킬 뷰 클래스들을 지정.(뷰 클래스 종류: 컴포넌트/디렉티브/파이프)

2) exports
- declarations에서 정의한 현재 모듈의 구성요소를 다른 모듈의 템플릿에서 사용할 수 있도록 노출시킬 뷰 클래스들을 지정.(여기서 지정하지 않은 뷰 클래스들은 다른 모듈에서 사용 불가)

3) imports
- 다른 모듈에서 노출(exports)한 뷰 클래스들를 사용하기 위해 해당 모듈 임포트.

4) providers
- 공통 서비스의 등록. 등록된 서비스는 앱의 모든 부분에서 사용 가능.

5) bootstrap
- 메인 뷰로 사용될 루트 컴포넌트 지정. 루트모듈에서만 지정가능.

(https://angular.io/docs/ts/latest/guide/architecture.html#!#modules 참고)


이제 @NgModule을 사용해서 모듈화 구성의 초간단 샘플을 작성해보자.
두 가지 형태로 테스트를 해볼텐데, 첫번째는 규모가 작은 응용프로그램이라는 가정 하에 단일 모듈(루트모듈) 구성을 두번째는 서브모듈을 하나 더 만들어 볼 것이다.

1. 루트 모듈만 사용
Angular는 최소한 하나 이상의 모듈로 구성해야 하는데, 가장 기본이 되는 모듈이 바로, 
루트모듈(Root Module)이다. 
angular-cli로 프로젝트를 생성하면 루트모듈이 자동으로 생성된다.

기본 생성된 구조에서, 컴포넌트와 서비스를 추가해보자.
추가된 컴포넌트는 sub.component와 sub.service이다. 폴더구조와 소스는 다음과 같다.

* 폴더구조


* 소스(sub.service.ts)


* 소스(sub.component.ts)


새로 추가한 SubComponent는 서비스를 주입 받아서 id/name 데이터를 획득해서 자신의 멤버변수에 할당하는 간단한 코드이다.

이렇게 추가한 SubComponent를 루트 컴포넌트에 해당하는 AppComponent에서 사용하기 위해 AppComponent의 HTML에 SubComponent 태그를 추가시킨다.

* 소스(app.component.html)

 그런데 이렇게 사용하려면 루트 모듈에 SubComponent와 SubService에 대한 참조가 정의되어 있어야 한다.

* 소스(app.module.ts)

이로써 결과를 확인할 수 있게 되었다.

* 결과화면


2. 모듈 분리하기.
앞서 루트모듈만 사용했을 경우 문제는, 프로그램 규모가 복잡하고 커질 경우 상당량이 컴포넌트/서비스/파이프/디렉티브가 생기게 되는데 이것들을 모두 하나의 모듈로 관리해야 하기 때문에 구조가 비직관적으로 복잡해지고 모듈단위 관리(라우팅 및 비동기모듈로딩 등)가 용이하지 않다는 점이다.

이에 데모를 조금 변경하여 모듈을 분리해 보자.

SubComponent와 SubService를 묶어서, 별도의 다른 모듈로 그룹화 시키기 위해 SubModule을 생성한다.

* 소스(sub.module.ts)

여기서 주의 할 점은 이 모듈의 컴포넌트를 외부 모듈에서도 사용하게끔 하려면 exports로 명시를 해줘야 한다는 것이다.

이렇게 함으로써 Sub집합(여기서는 하나의 컴포넌트와 하나의 서비스)을 별도의 Angular 모듈로 만들었으며 이 모듈을 루트모듈에서 사용하려면 다음과 루트모듈을 수정하면 된다.

* 소스(app.module.ts)


앞서 루트모듈만 사용했을 때에는, 컴포넌트와 서비스를 모두 참조해야 했지만 모듈을 분리하고 나서는 해당 모듈(여기서는 SubModule)만 참조하면 된다.

그리고 결과화면은 (볼 것도 없이) 앞의 예와 동일한다.

다음 아티클에서는 모듈과 연관되는 라우팅(Routing)에 대해 정리할 예정.


* 참고자료
- Angular 공식 가이드
- Angular Architecture Overview
- Angular Tutorial


'모바일 > Javascript' 카테고리의 다른 글

Proxy to Back-end(프론트엔드단 CORS 통신설정)  (1) 2017.07.28
Javascript Compiler, Babel  (0) 2017.03.16
Isomorphic Javascript  (0) 2017.01.23
[Node] npm(node package manager)  (0) 2017.01.20
[Angular] Lazy Loading  (0) 2017.01.18

Isomorphic Javascript

Posted in 모바일/Javascript // Posted at 2017. 1. 23. 14:45
728x90

서버와 클라이언트 사이드에서 모두 동작하는 자바스크립트 어플리케이션을 Isomorphic JavaScript(동형 자바스크립트)라고 한다.

굳이... 새로운 용어에 피로감이 들긴 하지만.

그간, 클라이언트.. 그것도 웹 브라우저에서만 주로 동작하던 자바스크립트가 명실상부! 그 영역을 점차 확대하고 있는 획기적인 시대에 이러한 패러다임을 공유하기 위한 개념적 용어는 필요해 보인다.

Isomorphic JavaScript 또는 Universal JavaScript라고들 하는데... 용어가 중요한 것이 아니라 서로 공감하고 이해되는 개념이 중요한 것이다.

Isomorphic JavaScript의 개념을 조금 더 확장시켜서, 서버-클라이언트의 동형은 물론이고 데스크톱, 가상현실/증강현실 분야에서의 자바스크립트와 하나의 어플리케이션 개발 생태계에 일조하는 개발 지원환경등을 모아서 다음과 같이 정리해 보았다

자바스크립트는 더 이상 웹만을 위하지 않는다. 그는 데스크톱 영역을 침범하기 시작했으며 가상현실 영역으로부터도 러브콜(?)을 받고 있다. 그의 생태계는 점점 넓어지고 깊어지고 있는 것이다.

순전히 개인적으로 좋아서, 틈틈히 해 오던 자바스크립트 공부에 활력소가 되고 있다. 음.. 좋아~~~




'모바일 > Javascript' 카테고리의 다른 글

Javascript Compiler, Babel  (0) 2017.03.16
[Angular] Angular Modules(NgModule)  (0) 2017.01.25
[Node] npm(node package manager)  (0) 2017.01.20
[Angular] Lazy Loading  (0) 2017.01.18
SPA 단점에 대한 단상  (1) 2017.01.17

[Node] npm(node package manager)

Posted in 모바일/Javascript // Posted at 2017. 1. 20. 10:55
728x90

간혹 헷갈리는 npm 옵션들.

각 명령어들의 상세한 옵션 등을 확인하려면 npm 문서 참조

https://docs.npmjs.com/cli/{명령어}


차차 추가하기로 함...

'모바일 > Javascript' 카테고리의 다른 글

[Angular] Angular Modules(NgModule)  (0) 2017.01.25
Isomorphic Javascript  (0) 2017.01.23
[Angular] Lazy Loading  (0) 2017.01.18
SPA 단점에 대한 단상  (1) 2017.01.17
[Webpack] 비동기 번들 로딩  (0) 2017.01.14

[Angular] Lazy Loading

Posted in 모바일/Javascript // Posted at 2017. 1. 18. 14:29
728x90

앞서 'SPA의 단점에 대한 단상'이라는 글에서 초기 구동속도 문제에 대해 다루었었다.

SPA의 초기 구동속도 문제를 완화하기 위해, 모듈을 청크단위로 분리해서 해당 모듈이 필요한 시점이 되었을 때 관련 리소스들이 다운로드 될 수 있도록 하는 Lazy Loading 기법을 사용할 수 있다.

Angular2에서는 모듈 단위로 컴포넌트와 서비스, 파이프 등 각종 구성요소들을 논리적으로 그룹화 시키고 해당 모듈단위로 Lazy Loading을 적용할 수 있는데, 이의 구현에 대해 알아본다.

1. 시나리오는 대략 이렇다

1) 총 3개의 모듈(일반적인 웹 페이지 개념으로는 3개의 웹 페이지라고 생각하자)

2) 첫 구동 페이지(메인페이지에 해당)에서는 메인 구성을 위한 리소스만 다운로드

3) 나머지 두 개의 페이지도 그 페이지에 요청이 있을 경우에만 관련 리소스 다운로드

4) 이후에는 모든 페이지를 재 방문해도, 새로고침 없이 다운로드 된 리소스 사용

2. Angular 개발 환경은 다음과 같다.

참고로 부트스트랩 3.3.7도 사용.


3. 데모 프로젝트 디렉터리 구조는 다음과 같다.
앞서 시나리오와 같이 총 3개의 페이지를 위한 3개의 Angular 모듈 구성(one/two/three)


4. 각 모듈의 라우팅 구성은 다음과 같다.
1) 루트 라우팅 구성

2) 하위 모듈 라우팅 구성


5. 각 모듈 구성은 다음과 같다.
1) 루트 모듈


2) 하위 모듈 구성


컴포넌트 구성은 중요한 것이 아니라서, 생략하고 프로젝트를 빌드 한다


6. 청크(Chunk)단위 모듈 로딩 확인하기
1) 메인 페이지 요청

Angular 구동을 위한 번들 파일과 메인페이지의 리소스들을 다운로드 받는다.


2) 두번째 페이지 요청
두번째 페이지에서 사용하는 모듈이 번들링된 청크파일과 리소스만 로딩



3) 세번째 페이지 요청
세번째 페이지에서 사용하는 모듈이 번들링된 청크파일과 리소스만 로딩


이제 모든 리소스가 다운로드 되었고, 이후부터는 해당 페이지들을 다시 방문해도 (비동기 통신 데이터를 제외하고는) 이미 다운로드 된 리소스들을 사용해서 반응성이 빨라지게 된다.

추가로 이 데모에서는 초기 페이지의 컴포넌트 재생성을 방지하기 위해 사용자 정의 RouteReuseStrategy를 적용해서 매번 컴포넌트 생성을 하지 않게끔(싱글톤) 하였다.

아래 이미지를 확인해 보면, 세 개의 페이지를 왔다갔다 할 때 첫번째 페이지의 컴포넌트의 경우 더 이상 객체를 생성하지 않는 것을 알 수 있다.(해당 로그는 객체의 생성자에서 기록하도록 했음)





'모바일 > Javascript' 카테고리의 다른 글

Isomorphic Javascript  (0) 2017.01.23
[Node] npm(node package manager)  (0) 2017.01.20
SPA 단점에 대한 단상  (1) 2017.01.17
[Webpack] 비동기 번들 로딩  (0) 2017.01.14
[Webpack] 자바스크립 모듈 번들러, 웹팩(Webpack)  (0) 2017.01.12

SPA 단점에 대한 단상

Posted in 모바일/Javascript // Posted at 2017. 1. 17. 16:33
728x90

모바일 시대로 접어들면서 웹 환경에도 많은 변화가 동반되었다.

그 중 SPA(Single Page Application)도 단단히 한몫을 차지하고 있는데, 항간에 들리는 SPA의 단점들을 나열해 보면 크게 세 가지로 압축되는 듯 하다.

1. 초기 구동 속도

2. 검색엔진 최적화(SEO)

3. 보안

4. IE 8 이하 지원 (추가)

사실 이것들은 SPA의 단점이라기 보다는, SPA 구조이기에 당연히 생길수 밖에 없는 상황으로 이해되어야 한다.

모든 소프트웨어 아키텍처 전략에는 트레이드 오프(Trade-off)가 존재한다. 흔히 '은탄환'은 없다는 말처럼, SPA가 모든 웹 애플리케이션에 가장 적합한 구조는 아니다.

SPA 직역하면 '단일 페이지 어플리케이션'으로, 기존의 전통적인 새로고침 방식의 웹과는 달리 필요한 정적파일을 한번에(나눠서도 가능하다) 모두 다운로드 받고, 이후 사용자와의 상호작용 가운데 필요한 데이터만 서버로부터 (비동기로) 동적으로 받게하여 트래픽의 총량을 줄이고, 네이티브 앱과 유사한 사용자 경험을 제공할 수 있는 어플리케이션 형태이다.

결국 SPA가 추구하는 핵심적인 가치는 웹의 (사용자 경험을 향상시키는, UX) 사용성이라 할 수 있으며 부가적으로 속도의 향상도 얻게 된다.(물론 속도에 큰 틀에서 보면 사용성에 포함시킬 수 있다.)

특히 모바일 환경에서는 트래픽 최소화와 속도 및 반응성, 사용성 등이 보다 중요한 이슈이므로 SPA 구조는 모바일 퍼스트(Mobile First) 전략에서는 거의 모범사례에 가까운 모델이라 할 수 있다.

최근 App-likeNaively Web을 지향하는 구글의 PWA(Progressive Web App) 역시 모바일 환경에서의 웹의 사용성을 향상시키는 일환으로 대두되고 있다. PWA가 SPA를 직접적으로 포함하는 개념은 아니지만 비동기 통신과 SPA 구조는 PWA와 궁합이 잘 맞는 선택이다. 개인적으로 SPA도 PWA와 같은 선상에서 이해되기를 바라는 마음이다.

위에서 언급한 SPA 단점(?)이란 것들도, 가만히 들여다 보면 SPA가 추구하는 사상에서 파생되는 당연한 특징들에 가까우며, 저런 단점들 때문에 SPA 적용에 대해 큰 의문이 든다면, 그것은 SPA 자체의 문제가 아니라 현재 어플리케이션이 SPA모델과는 어울리지 않을 가능성이 더 높다.

다시 말하지만, SPA가 모든 상황에 있어 최고의 아키텍처 전략이 아니라, SPA가 어울리는 곳에 사용했을 때만이 최적의 효과와 효율을 발휘할 수 있을 것이다.

위에서 언급한 SPA의 문제(문제라고 해 두자)들을 하나씩 살펴보자.

1. 초기 구동속도 문제
평소 우리는 많은 모바일 '네이티브 앱'을 사용한다. 당신의 스마트폰에 깔려 있는 많은 앱들을 생각해보라. 이 앱들을 사용하는 우리의 자세(?)를 돌이켜보자.

먼저 앱스토어에서 수메가(mb)에 달하는
 앱을, 몇 초에 걸쳐 다운로드 받는다. 그리고 또다시 몇 초의 시간을 설치하는데 할애한다. 아직 끝이 아니다. 설치한 앱을 처음 띄울때 초기 구동을 위한 각종 설정과 초기화 작업을 기다리며 앱 초기배너 혹은 광고배너를 무심히 바라본다.

드디어 앱이 실행되면 그때부터는 큰 기다림 없이 앱을 사용한다. 물론 이제부터는 빠른 응답과 높은 반응성을 만끽(?)하면서 말이다.

(또한 설치된 앱을 종료하고 다시 실행할 때도 약간의 시간을 감수해야 한다.)

이제 SPA로 돌아와보자. SPA 역시 모바일 네이티브 앱과 같은 효과를 웹에서 제공해 주고자 한다.
즉 앱의 사용성을 추구하고자 SPA에서도 초기에 필요한 대부분의 리소스를 다운로드 받는다. 물론 이것은 초기 구동속도를 조금 손해보고 더 많은 가치(전체적인 앱의 속도/반응성/사용성 등)를 추구하기 위함이다.

우리는 모바일 앱(극단적인 예로 모바일 게임을 떠올려 보자)을 사용하면서 초기 구동속도가 치명적인 단점이라고 생각하지 않는다. 오히려 다운로드와 설치과정 및 앱 초기 구동을 위한 약간의 시간들을 오히려 자연스럽게 받아들인다.

SPA 사상도 이와 유사하기에 초기 구동속도가 치명적인 단점이라고 언급되는 것은 곤란하다고 강조하고 싶다.

SPA의 사상은, 더 이상 웹이 단순한 웹 문서(웹 페이지)가 아닌 하나의 응용프로그램으로 바라보기에 개념의 전환이 필요한 것이다.

물론 그럼에도 불구하고 SPA도 (여전히) 웹 이기에 기존 웹의 경험을 무시할 수 없는 측면도 있을 것이다.

이런 조건 말이다. "SPA가 좋은건 알겠는데... 메인 페이지는 무조건 빨리 떠야 합니다."

SPA에서도 이런 상황에 대한 기술적 대안을 마련할 수 있다.
초기 페이지에서 모든 리소스를 다운받지 않고, 리소스를 청크(Chunk) 단위로 묶어서 해당 리소스에 대한 요청이 있을 때만 다운로드 받도록 하는 방법을 적용하면 초기 구동속도 문제를 많이 완화시킬 수 있다.

많은 자바스크립트 모듈 번들러에서 Lazy Loading 혹은 비동기 모듈로딩이라는 이름으로 이런 시나리오를 지원하며, 우리의 훌륭한 Angular에서도 모듈단위로 구성요소를 묶고 청크단위로 다운로드 받게 할 수 있다.

심지어 SPA에 PWA를 조합하면 보다 뛰어난 사용성을 부가적으로 제공할 수도 있다. 초기 구동에 필요한 필수적 파일을 웹쉘로 구성하고 백그라운드에서 서비스워커를 통해 비동기 리소스 탐방(?)을 실현할 수 있다.

다시 강조하지만, SPA에서 초기 구동속도는 더 이상 결정적인 단점으로 논해져서는 안된다.
오히려 현재 적용하려는 웹 사이트가 SPA 사상과 맞지 않는 것이 아닌가 살펴보기 바란다.


2. 검색엔진 최적화(SEO) 문제
SEO는 서버랜더링 방식이 아닌 자바스크립트 기반 비동기 연동 모델(클라이언트 랜더링 방식)에서는 항상 이슈가 되어 왔던 주제이다. SPA로 가면서 극단적(?) 자바스크립트 기반 웹이 되다 보니 그 문제가 더욱 부각되는 것이다. 구글 크롬 브라우저는 최신 웹 트랜드를 반영해서 자사의 검색엔진에서 클라이언트 래더링 방식도 검색 봇(Bot)이 컨텐츠를 가져갈 수 있도록 개선하고 있지만 기타 다른 브라우저에서는 그 방향성이 불투명하다.

그러나 이 문제 역시 비슷한 맥락에서 말할 수 있다.

다시 말하지만, SPA로 구현된 웹은 더 이상 단순한 정보제공을 위한 웹 문서(페이지)가 아니다.
SPA 결과물을 하나의 응용프로그램으로 본다면 기존 웹 페이지의 정보제공이란 측면의 SEO가 맞지 않을 수도 있다는 것이다.

생각해 보자. 모바일 네이티브 앱을 만들면서 SEO를 걱정하는 이는 드물 것이다.
(다시 극단적인 예로, 모바일 게임을 만들면서 SEO가 안된다고 걱정하는 것을 상상해보라)

SPA를 적용하면서도 개념은 여전히 웹 페이지에 머물러 있다면 둘 중 하나일 것이다.

SPA 사상을 이해하지 못했거나 해당 프로젝트가 SPA와 어울리지 않거나!

(다시한번) 그럼에도 불구하고 SPA도 웹이기에 SEO가 되어야 한다면 역시 대안 기술이 존재한다.

prerender.io 같은 상용 솔루션도 존재하고, Angular나 React같은 SPA 프레임워크에서도 서버 랜더링이라는 이름으로 SEO에 대응하고 있다.  

Angular의 경우 Angular Universal 이라는 SEO 대응 기술이 이미 존재한다.
아직까지 Node.js나 ASP.NET 서버 환경에서만 서버 랜더링을 지원하지만 향후 다른 서버플랫폼도 지원을 계속 추가해 나갈 것이며, 중요한 것은 SPA로 구현된 모든 페이지가 SEO가 되어야 하는 것은 아닐 것이다.

따라서 SEO에 노출되어야 하는 전략적인 페이지들을 선별해서 서버랜더링 기술을 적용하면 될 것이다.


3. 보안문제
SPA의 보안문제로 자주 언급되는 것이 사용자 정보가, 기존 서버기반 세션이 아닌 클라이언트 기반 쿠키라는 것이다. 

그런데 세션은 쿠키 아닌가? 세선도 클라이언트에는 쿠키형태로 저장되고 모든 요청의 헤더에 그 값이 전달된다. 단지 쿠키와 다른점은 서버측에도 클라이언트 쿠키와 매핑되는 세션정보가 저장되어 있다는 것만 다를 뿐 이다.

보안관점에서 보면 쿠키의 가장 큰 문제는 '도용'이다. 쿠키 정보의 기밀성이나 변조는 쉽게 방어할 수 있지만 누군가의 쿠키를 그대로 복사해서 재사용하는 것은 조금더 높은 방어기술을 요구한다.

그런데 이런 쿠키도용은 서버세션이라는 시나리오에서도 동일하게 적용된다. 물론 서버측에 세션만료시간내에 도용해야 한다는 제약이 존재하지만, 요즘 쿠키는 대부분 브라우저 쿠키로 영구적으로 저장하지 않으니 별반 차이가 없다고 보는 것이 맞다.

따라서 사용자 정보저장이라는 측면에서는 SPA의 쿠키나, 기존 서버랜더링 방식의 세션이나 별반 다를 것이 없다. 그리고 요즘 왠만한데는 쿠키 잘 쓰지도 않는다.

SPA의 보안이라는 측면에서 더욱 심각한 것은 핵심로직이 자바스크립트로 구현된다는 점이다.

기존에는 대부분의 비즈니스 로직이 서버에서 수행되어 최종 결과만을 (HTML 덩어리로) 전달받았을 뿐인데, SPA에서는 필요한 데이터만 전달받고 많은 비즈니스 기능을 클라이언트에서 수행하기 때문에 핵심 로직이 노출될 수 있다는 점이 문제라면 문제일 것이다.

따라서 SPA 보안문제를 언급하려면 이 관점에서 풀어나가야 한다.

이것은 별다른 방법이 없다.
핵심로직은 서버에서 수행하도록 하고 중요한 유효성 검사는 양측 모두에서 수행하도록 해야 한다.

유효성 검사의 양측 모두 구현은, 굳이 SPA가 아니더라도 기존방식에서도 그렇게 해 왔었고 해야 되는 것이었다. SPA라서 더욱 신경써야 할 포인트는 바로 클라이언트에서 수행되는 핵심로직을 최소화시키는 것이다.

자바스크립트 난독화만으로 해결될 문제가 아니기에 설계시 고민해야 하는 부분이다.
실제로 게임 어플리케이션을 만들 때 이런 고민을 한다.


4. IE 8 이하 지원
SPA가 비단 Angular, ReactJS로만 만들 수 있는 것은 아니지만, 진정한 SPA 다운 결과물을 만들기 위해서는 이런 프레임워크의 도움이 절실하다. 
그리고 이런 최신의 자바스크립트 프레임워크들은 IE8을 지원대상에서 제외하고 있는 추세이다.
따라서 SPA를 진정 원한다면 IE8 이하를 과감히 버리는 것을 고려해야 한다.

그렇게 하지 못한다면, IE8용 대안을 별도로 마련해야 하는데 대국민 서비스에 규모가 큰 조직이라면 해 볼만 하다. 그러나 서비스 특징과 비용 효율 측면을 따져보고 결정할 일이다.

-----------------------------------------------------------------

하나를 얻으면 하나를 잃게 되는 것이 세상사가 아닌가 한다.

SPA로 얻는 가치가 있다면 SPA로 잃는 가치도 있을 것이다. 그런데 SPA에서 언급되는 저러한 단점들이 과연 단점이라 할 수 있는지 의문이 든다. 

심지어 SPA의 특징으로 인해 부각되는 몇 가지 문제상황들은 대안 기술로 해결될 수 있는 부분이 많다.

결론을 내리자.
SPA 자체를 보지 말고, 현재 수행하는 프로젝트의 특성을 먼저 살펴보기 바란다.
지금 개발하려는 애플리케이션이 SPA의 사상과 일치하는지 말이다.

당신의 프로젝트에 SPA를 적용하지 않는다고 해서 SPA가 서운해 하거나 하지는 않을 것이다.



[Webpack] 비동기 번들 로딩

Posted in 모바일/Javascript // Posted at 2017. 1. 14. 12:09
728x90

Webpack을 사용한 비동기 번들 로딩.

관심사의 분리/분할과 정복 등의 기본 원리를 바탕으로 잘 나눠진 모듈로 분리된 자바스크립트 파일을 만들고..

배포할때는 웹팩으로 단 하나의 번들 파일로 배포해서 사용하는 좋은 시나리오에서...

어떤 경우에는 이런 방식이 부담되기도 하는데, 예를 들어 (필요는 하지만) 굳이 미리 다운로드 받지 않아도 되는 즉 필요할 때만 다운로드 받게 하고 싶은 경우가 있다.

이때 사용할 수 있는 것이 비동기 번들 로딩

내부적으로 번들파일이 나눠지고 해당 스크립트를 사용하게 되면 그때 관련된 번들파일이 다운로드 되게해서 초기 로딩 속도를 향상시키고 사용자의 행동에 따라 추가 스크립트를 제공해 주는 Lazy Loading 기법이라 할 수 있다.

* 비동기 번들 로딩 구현

1) 먼저 필요한 것들 설치
- webpack 환경(npm, webpack, babel-load, babel-core 등등 필요한 것)


2) webpack 설정파일(옵션)

module.exports = {

  entry: './src/index.js',

  output: {

    path: './public',

    filename: 'bundle.js'

  },

  module: {

    loaders: [

      {

        test: /\.jsx?$/,

        exclude: /(node_modules|bower_components)/,

        loader: 'babel-loader',

        query: {

          presets: ['es2015']

        }

      }

    ]

  }

};

3) 스크리트 작성 및 html 작성

//index.js
document.querySelector('button')  

  .addEventListener('click', () => {

    require.ensure([], function (ev) {

      var asyncModule = require('./asyncModule'); //비동기 로딩 스크립트가 다운로드 되는 시점

      asyncModule.showModal();

    });

}, false);


//asyncModule.js (비동기 로딩 대상 스크립트)

module.exports = {  

  showModal: () => {

    alert('load asyncModule.js');

  }

}

//index.html

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

</head>

<body>

    <h1> AsyncModle Loding Demo </h1>

    <button>async load</button>

    <script src="/public/bundle.js"></script>

</body>

</html>

4) 빌드
웹펙으로 빌드하면 다음과 같이 두 개의 번들 파일이 생성됨


브라우저에서 실행을 해 보면, 처음에는 index.html과 bundle.js 파일만 다운로드 받고, 버튼을 클릭하면 그때 1.bundle.js 파일을 다운받게 된다

처음 로딩 시


버튼 클릭 후

이제 중요한 것은 모듈을 어떻게 나누고, 비동기 로딩의 대상과 시점을 어떻게 하느냐.. 하는 전략.

샘플 수행의 거의 모든 소스는 다음 사이트를 참조함
(내 환경에서 버그가 나서 아주 미미한 수정을 가했으며 문구도 내 입맛에 맞게 맘대로 바꿈 ^^;)
=> http://jilles.me/webpack-async-bundle-loading/


'모바일 > Javascript' 카테고리의 다른 글

[Angular] Lazy Loading  (0) 2017.01.18
SPA 단점에 대한 단상  (1) 2017.01.17
[Webpack] 자바스크립 모듈 번들러, 웹팩(Webpack)  (0) 2017.01.12
[Angular2] Angular2 선택을 위한 명분  (0) 2016.12.19
[Node.js] Node With IIS  (1) 2016.07.19
728x90

Webpack 제공 기능 정리.

자바스크립트 개발과 관리...

대규모 복잡한 자바스크립트 개발 환경에서는 무지 편해지고 추가 이득이 수루룩... 

But, 소규모 단순한 자바스크립트 개발에서는 괜한 복잡성 증가...

삽으로 파야할 땅인지 포크레인으로 파야할 땅인지 부터 보고 결정.

요즘 대부분 포크레인이 요구되기는 함.




'모바일 > Javascript' 카테고리의 다른 글

SPA 단점에 대한 단상  (1) 2017.01.17
[Webpack] 비동기 번들 로딩  (0) 2017.01.14
[Angular2] Angular2 선택을 위한 명분  (0) 2016.12.19
[Node.js] Node With IIS  (1) 2016.07.19
[AngularJS] 폼(form)과 유효성 검사  (0) 2016.07.18

[Angular2] Angular2 선택을 위한 명분

Posted in 모바일/Javascript // Posted at 2016. 12. 19. 11:17
728x90

조만간 Front-end 개발 프레임워크를 선정해야 될 지도 모르겠다.

몇 년 전부터 AngularJS가 핫 트랜드로 떠오르면서, 진보적 프로젝트에서 많이 도입되어 왔다.

지금의 선택지는 AngularJS 버전 2의 릴리즈로 하나 더 늘어난 셈이다.

우리의 프로젝트에 AngularJS 버전 2를 도입을 고려한다면 다음과 같은 품질속성 관점의 접근이 일종의 명분이 될 수 있을 것이다.




성능

Angular2에서 성능 관점의 어프로치는 크게 두 가지로 속도와 용량부분이다.

AoT 컴파일(ahead of time compilation)
요즘 AoT 컴파일이 성능향상의 주요 컨셉으로 도입되는 추세다. 얼마전 안드로이드에서도 이와 유사한 컨셉이 도입되어 앱의 실행 성능을 향상시켜 왔는데, Angular2에서도 도입되었다.
AoT는 사전에 미리 컴파일 해 둔다는 개념으로, 기존 AngualrJS의 ng-** 따위의 해석을 위한 런타임의 컴파일 과정을 미리 해 둬서 실행 속도를 향상시키는 기법이다.

또한 이러한 AoT컴파일 덕분에 런타임시 필요했던 JIT(just-in-time compiler)를 더 이상 적재하지 않아도 되기 때문에 용량부분에서도 이점을 가져다 준다.(JIT 컴파일러는 기존 AngularJS 라이브러리 용량의 50%가까이 차지하던 녀석이다)

Lazy Loading
지연(Lazy) 바인딩, 지연 로딩 등의 개념도 다른 많은 언어들에서 차용하고 있는 최적화 기법이다.
지연(Lazy)이라는 개념은 최대한 필요한 시점까지 지연시킨다는 개념으로 불필요한 리소스 낭비를 막자는 컨셉이다. Angular2의 지연 로딩은 어플리케이션 실행 시점에 모든 모듈을 로딩하지 않고 현재 페이지에 실제로 필요한 모듈만 로딩함으로써 최적화를 시키는 기법이다. 역시 모든 내용을 처음부터 로딩하지 않게 함으로써 속도와 불필요한 리소스 낭비를 막을 수 있게 된다.

Digest Loop로 인한 성능저하 제거
이전 버전의 AngularJS에서 대표적으로 성능 문제로 지적되었던 Digest Loop로 인한 성능저하가 AngularJS에서는 더 이상 발생하지 않는다. 

코드 최적화
Angular2 자체의 코드 최적화도 수행되어 50KB 정도의 용량 축소가 되었다고 한다.


생산성

컴포넌트 중심 개발

기존 AngularJS는 컨트롤러 중심으로 개발이 진해되었던 것에 반해 Angular2에서는 컴포넌트 중심으로 개발이 진행된다. 오래된 SW 공학 예기지만, CBD(Component Based Developement)의 생산성 향상을 꾀할 수 있을 것이다. 물론 컴포넌트 설계가 잘 되었다는 가정하에..

TypeScript
AngularJS2에서는 MS(Microsoft)에서 개발한 자바스크립트를 위한 타입 명시적 스크립트 언어인 TypeScript를 주력 언어로 채택했다. TypeScript는 자바스크립트의 슈퍼 셋(Super Set)으로 자바스크립트의 문법을 그대로 이용 가능하며 명시적 타입 지정이 가능하도록 하여 타입 안정성을 향상시키고 ECMA Script 버전 7까지의 표준 스크립트 문법을 지원한다.

개인적으로 MS가 모바일 시대에 미미하게나마(?) 선전한 걸작으로 평가하고 싶다.
TypeScript로 개발하면 타입 안정적인 상황에서 객체 지향적 프로그래밍이 가능하고 원하는 버전의 자바스크립트 버전으로 변환이 가능해 생산성 향상을 꾀할 수 있다.

Learning Curve
생산성 향상은 마치 양날의 검과 같다. 컴포넌트 중심 개발, TypeScript라는 새로은 언어 등이 무조건 생산성 향상을 가져다 주지는 않는다. 컴포넌트는 해당 프로젝트와 도메인에 맞도록 컴포넌트 단위가 잘 분리되고 조립되어야 하며 TypeScript 역시 자바스크립트 기반이라고는 하지만 적절한 러닝커브가 존재한다. 즉 초기에 약간의 투자를 한다면 점차 그 혜택은 커질 것이다.


Mobile First
Angular2는 모바일 환경을 목표로 만들어진 고성능 프레임워크를 표방한다.
앞서 살펴본, 속도와 용량 최적화는 모바일 환경에서 보다 중요한 특성으로 어필될 것이며 생산성은 대규모 프로젝트에 많은 이점을 가져다 줄 것이다.


[Node.js] Node With IIS

Posted in 모바일/Javascript // Posted at 2016. 7. 19. 20:50
728x90

Node.js가 콘솔기반의 자체 HTTP 서버를 제공해 준다고는 하지만...

80포트 공유, 멀티 도메인과 서브 도메인 설정, HTTP 요청 로그, 동시 접속자 및 요청 큐 대기 상황, 웹 팜 및 웹 가든 구성, 디렉티브 권한 설정 등등 기존 IIS나 Apache 기반 환경에서 자주 사용했던 웹 서버 관련 사항은 어떻게 해야 하는가?

그리고 기존 IIS와 같은 웹 서버 환경에 추가하여 동시에 운용하고자 하는 경우는?

실무에서는 HTTP 통신서버의 비즈니스 기능 외에 다양한 옵션 및 환경설정이 요구되는데, Node.js가 이 부분을 모두 수용할 수 있는가?

솔직히 아직 Node.js를 복잡한 실무환경에서 제대로 적용해 보지 않은터라, 자세히 알지 못한다.

다만, IIS와 같은 나름 검증된 웹 서버에 호스팅되어 서비스 되면 좋겠다는 생각을 해 보던 차, 다음의 글을 발견했다.

http://www.sysnet.pe.kr/2/0/1556


한번 설정해 봐야 겠다.

이글을 보는 누군가, 기존 IIS 서버등에서 사용했던 많은 웹 서버 기능들을 Node.js 자체 환경에서도 여전히 안정적으로 사용가능한지 말해 줄 사람이 있다면, 댓글을 부탁드립니다.

 

[AngularJS] 폼(form)과 유효성 검사

Posted in 모바일/Javascript // Posted at 2016. 7. 18. 00:30
728x90

AngularJS는 HTML 폼을 확장하여 입력요소에 자바스크립트 객체를 바인딩하고, 전체 폼 또는 개별 입력요소에 대한 유효성 검사를 추가할 수 있는 방법을 제공한다.

* 폼의 데이터 바인딩
- 폼의 입력요소와 AngularJS의 상호작용은 (뷰와 컨트롤러의 상호작용이 늘 그렇듯이) $scope 객체를 매개로 이뤄진다. 폼 요소와 $scope 객체의 관계를 확인하기 위해 다음과 같이 간단한 코드를 작성해 본다.

<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var app = angular.module("myApp",[]);
app.controller("myCtrl", function($scope){ 
 $scope.checkScope = function(){  
  console.log($scope); //$scope객체를 console 로그에서 확인해 본다.
 };
});
</script>
<body ng-app="myApp" ng-controller="myCtrl">
<div>
 <p>Name: <input type="text" ng-model="name"></p>
 <p ng-bind="name"></p> 
 <button ng-click="checkScope();">check</button>
</div>
</body>
</html>

input 박스에 ng-model 디렉티브를 선언하고, 바로 밑 <p> 요소에 ng-bind 디렉티브를 동일한 이름으로 선언하였다. (ng-bind 대신 '{{ name }} ' 형태의 표현식으로 해도 동일하게 동작한다)

코드를 실행해 보면, input 요소에 입력한 값들이 자동으로 <p> 요소에 반영되어 데이터가 같이 변경되는 것을 확인할 수 있다.

이와 같은 폼 요소에 대한 자동 바인딩 내부에는 $scope 객체가 존재한다.
코드에서는 명시적으로 $scope 객체에 name이라는 속성을 추가한 적은 없지만, ng-model 디렉티브로 인해 입력요소에 값이 입력되는 순간 $scope에 name라는 속성이 자동으로 추가된다.

콘솔로그를 통해 확인해 보자.
코드가 처음 브라우저에 로딩된 순간에는 $scope객체에 name이 속성을 찾을 수 없다. 그러나 input요소에 값을 입력하는 순간 자동으로 $scope객체에 name속성이 (입력 값과 함께) 자동으로 추가된다. 다음 그림은 콘솔로그를 통해 확인해 본 것이다.

결국 뷰와 컨트롤러의 데이터바인딩은 $scope 객체를 통해 이뤄지며, 이는 폼 요소에 대한 ng-model 디렉티브 설정을 통해 $scope객체의 속성과 연결되어 양방향 바인딩이 자동으로 이뤄지는 메커니즘이다.

* 폼의 입력요소
폼의 입력요소에는 다음과 같은 것들이 있다.
- input 엘리먼트: 텍스트박스, 체크박스, 라디오 버튼 등 입력요소
- select 엘리먼트: 여러개의 아이템 중 하나를 선택할 수 있는 입력요소
- textarea 엘리먼트: 여러 행의 글을 작성할 수 있는 입력요소
- button 엘리먼트: 클릭으로 명령을 전송할 수 있는 입력요소

* CheckBox
- 다음 코드는 체크박스에 대한 AngularJS의 임의구현이다.  첫 번째 체크박스의 ng-model 값을 두 번째 체크박스의 ng-checked 디렉티브로 연결하고 있다. 이렇게 하면 두 번째 체크박스는 첫 번째 체크박스의 선택여부에 동기화되어 선택/해제 된다.
그리고 ng-show 디렉티브는 첫 번째 체크박스의 선택여부에 따라 화면에 표시할 지 여부를 결정한다.

<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app>
  <form>
    checkbox1: <input type="checkbox" ng-model="chk1">
    <br />
    checkbox2: <input type="checkbox" ng-checked=chk1">
  </form>
  <!-- checkbox가 선택된 경우 화면에 표시 -->
  <h1 ng-show="chk1">checkbox1 is checked</h1>
</div>
</body>
</html>

* RadioButton
- 다음 코드는 라이오버튼에 대한 AngularJS의 임의구현이다. 라이브버턴 마다 ng-model을 설정하고 {{xxx}} 표현식으로 선택된 값을 출력한다. 그리고 ng-switch계열 디렉티브를 통해 조건식을 구현할 수 있다.

<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body ng-app>
<form>
  Pick a topic:
  <input type="radio" ng-model="rdobtn" value="r1">Radio1
  <input type="radio" ng-model="rdobtn" value="r2">Radio2 
</form>
<div ng-switch="rdobtn">
  <!-- 선택된 값 출력 -->
  <h1>checked: {{rdobtn}}</h1>

  <!-- 선택된 값에 대한 조건식 로직 -->
  <div ng-switch-when="r1">
     <h1>radio1 is checked</h1>   
  </div>
  <div ng-switch-when="r2">
     <h1>radio2 is checked</h1>
  </div> 
</div>
</body>
</html>


* SelectBox
- 라이브버튼과 거의 유사한 임의코드이다.

<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body ng-app>
<form>
  Select a topic:
  <select ng-model="selbox">
    <option value="">
    <option value="v1">value1
    <option value="v2">value2
  </select>
</form>
<div ng-switch="selbox">
  <!-- 선택된 값 출력 -->
  <h1>selected: {{selbox}}</h1>

  <div ng-switch-when="v1">
     <h1>value1 is selected</h1>   
  </div>
  <div ng-switch-when="v2">
     <h1>value2 is selected</h1>   
  </div>
</div>
</body>
</html>

 

* AngularJS의 폼 확장
- AngularJS는 표준 HTML 폼 요소를 확장한 form 디렉티브와 폼의 상태를 추적하기 위한 FormController 컨트롤러의 인스턴스를 생성해 폼을 관리한다.

HTML페이지의 폼의 name 특성을 통해 폼이름을 지정하면 $scope객체에 지정된 이름으로 폼 객체가 추가되며 하위 폼 요소들도 주어진 이름으로 추가된 폼 객체의 하위 프로퍼티로 자동 추가된다. 이후 이들 이름을 기반으로 뷰 및 컨트롤러에서 폼 요소들을 참조하여 특정 작업을 수행할 수 있다.

폼 요소들이 지정된 name값을 기반으로 $scope 객체에 자동추가 된다는 것을 눈으로 확인해보자.
다음과 같은 입력요소를 하나 가지는 간단한 폼을 만든다. 주의할 점은 폼요소가 모두 로딩된 후 $scope 객체를 확인해야 하므로 <script> 요소를 </body> 직전에 위치 시켰다. (일반적으로 스크립트 태그는 이 위치에 두는 것을 권장하는데, 이는 DOM 구성이 스크립트 로딩과 실행으로 지연되지 않도록 하기 위함으로 모든 DOM이 구성되고 난 후 안정적으로 스크립트를 실행시키기 위함도 있다.)

<!DOCTYPE html>
<html lang="en">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="formCtrl">
  <form name="myForm">
    UserName:
    <input type="text" name="myInput" ng-model="myInput"><br>   
  </form>  
</div>
<script>
var app = angular.module('myApp', []);
app.controller('formCtrl', function($scope) { 
 console.log($scope); 
});
</script>
</body>
</html>

지금 상황에서 input 요소에 ng-model 디렉터리가 꼭 필요한 것은 아니지만, AngularJS는 ng-model 디렉티브가 추가된 입력요소에 한해서 $scope 객체에 등록시키기 때문에 추가한 것이다.
결과로 본 콘솔로그는 다음과 같다.

이렇게 폼 요소의 name 값을 기준으로 $scope객체에 자동추가 되기 때문에, 이후 컨트롤러나 뷰에서 해당 폼 요소의 이름으로 접근 가능한 것이다.

* 폼과 입력필드의 상태(폼 유효성 검사의 기반)
- AngularJS는 폼의 유효성 검사를 위해 폼과 입력요소의 상태값을 지속적으로 감시하고 관리한다.
다음은 각각 폼과 입력필드의 상태를 알 수 있는 참조변수를 보여준다.

 

 

 

 

사용자 반응에 따른 폼과 입력요소의 상태값 변화를 눈으로 확인해 보기 위해, 직전의 예제를 다음과 같이 조금 수정해 보자.

<!DOCTYPE html>
<html lang="en">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app>
  <form name="myForm">
    UserName:
    <input type="text" name="myInput" ng-model="myInput" required><br>   
  </form>  
  <div>
  <h1>Form States</h1>
   Pristine: {{myForm.$pristine}} <br /> 
   Dirty: {{myForm.$dirty}} <br />
   Valid: {{myForm.$valid}} <br />
   Invalid: {{myForm.$invalid}} <br />  
   Submitted: {{myForm.$submitted}} <br />
   Error: {{myForm.$error}} <br />
  
   <h1>Input States</h1>
   Touched: {{myForm.myInput.$touched}} <br />
   Untouched: {{myForm.myInput.$untouched}} <br />
   Pristine: {{myForm.myInput.$pristine}} <br /> 
   Dirty: {{myForm.myInput.$dirty}} <br />
   Valid: {{myForm.myInput.$valid}} <br />
   Invalid: {{myForm.myInput.$invalid}} <br />     
   Error: {{myForm.myInput.$error}} <br />

  </div>
</div>
</body>
</html>

 예제를 브라우저로 실행시키고, 입력요소에 값을 입력하거나 포커스를 이동시켜보자. 그러면 각 행위에 따라 폼의 상태값의 변경을 확인할 수 있을 것이다. 이와같은 폼 상태 변화는 AngularJS가 폼 유효성을 검사하는 기반으로 활용된다.

* 폼 초기화

- 폼을 최초 로딩된 상태로 초기화 시키는 것은, 최초값을 보관해 두었다가 다시 그 값으로 회귀 시켜 주면 된다. 다만 데이터 초기화와 AngularJS의 폼 유효성 검사를 위한 상태값까지 모두 초기화 시켜 주는 것이 좋다. 앞서 살펴본바와 같이 AngularJS는 폼 및 입력요소에 대한 상태값을 지속적으로 관리하는데, 이부분까지 모두 초기화 시켜주면 완벽하다.

다음 코드는 폼 초기화를 위한 코드 샘플인데, 데이터 초기화의 경우 원본을 유지한채 복사본을 사용하는 것으로 처리하고 추가로 $setPristine(), $setUntouched() 함수를 호출하여, 폼 변경에 따른 내부 상태값의 변화 역시 같이 초기화 시켜준다.

<!DOCTYPE html>
<html lang="en">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('formCtrl', function($scope) {
    //폼 초기화를 위한 초기값 저장 
    $scope.origin = {firstName:"OriginFirst", lastName:"OriginLast"};
    //초기값에 기반하여 복사 수행(이 값은 폼과 양방향 바인딩 됨)
    $scope.user = angular.copy($scope.origin);
   
    $scope.resetForm = function() { 
         //폼 요소들의 입력값을 초기 값으로 변경
         $scope.user = angular.copy($scope.origin);   
         //폼 요소를 최초의 깨끗한 상태로 돌리기
         $scope.myForm.$setPristine();
         //폼 요소를 한번도 손대지 않은 상태로 돌리기
         $scope.myForm.$setUntouched();
    };       
});
</script>
<body>
<div ng-app="myApp" ng-controller="formCtrl">
  <form name="myForm" novalidate>
    First Name:<br>
    <input type="text" ng-model="user.firstName"><br>
    Last Name:<br>
    <input type="text" ng-model="user.lastName">
    <br><br>
    <button ng-click="resetForm();">RESET</button>
  </form>
  <p>Pristine: {{myForm.$pristine}}</p>   
</div>
</body>
</html>


* 폼 유효성 검사
- 지금까지 살펴본 것처럼, AngularJS는 폼의 입력요소들의 변경에 따른 상태값을 지속적으로 관리하며 이를 기반으로 폼의 유효성 검사를 지원한다. 폼의 유효성 검사 기준은 HTML5에 새로이 추가된 유효성 관련 특성(Attribute)에 근거하거나 개발자가 직접 자신만의 유효성 검사 기준을 만들 수 있도록 지원한다.

HTML5의 새로운 입력타입과 특성에 기반한 유효성 검사
- 가장 흔한 유효성 검사는 입력필드에 대한 필수입력 여부와 입력필드 타입에 따른 유효값 검사이다.
다음 코드는 HTML5의 required 특성을 부여한 필수 입력필드에 대한 입력여부와 email 타입의 입력필드에 대한 입력값 유효성 검사 샘플이다. 입력필드에 대한 $valid는 입력값 여부에 따라 true/false 값 중 하나를 반환한다.

<form name="myForm">
  <input name="txtName" ng-model="txtName" required
  {{myForm.txtName.$valid}}
  <br />
  <input name="txtEmail" ng-model="txtEmail" type="email" required
  {{myForm.txtEmail.$valid}}
</form>

AngularJS 디렉티브에 기반한 유효성 검사
- AngularJS가 미리 정의해 둔 디렉티브 기반으로 유효성 검사도 가능하다. 다음 코드는 필수입력 지정과 입력문자의 최소/최대 길이를 지정하여 유효성 검사를 수행한다.

<form name="myForm">
   <input name="txtName" ng-model="txtName" ng-required="true" ng-minlength="3"
     ng-maxlength="5"
>
   {{myForm.txtName.$valid}}
</form>

입력폼 검사의 일반적 패턴
- 이번에는 좀 더 실용적인 코드 샘플을 보자. 하나의 필수 입력필드와 이메일 입력필드가 존재한다. 각 입력필드 바로 옆에는 유효성 실패에 대한 안내 메시지를 보여준다. ng-show 디렉티브는 주어진 조건값이 참/거짓에 따라 표시여부를 결정한다. 코드에서 주어진 조건은 입력필드에서 포서커를 잃고($touched) 유효성 검사에 실패한 경우($invalid)에 대한 And 조건으로 부여한다.
그리고 submit 버튼은 ng-disabled 디렉티비를 지정하는데 두 입력필드 중 하나라도 유효하지 않으면 비활성화 되도록 조건을 부여한다.

<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body ng-app>
<form name="myForm">
  <input name="txtName" ng-model="txtName" required autofocus>
  <span style="color:red" ng-show="myForm.txtName.$touched && myForm.txtName.$invalid">
   <span ng-show="myForm.txtName.$error.required">Username is required.</span>
  </span>
  <br />
  <input type="email" name="txtEmail" ng-model="txtEmail" required>
  <span style="color:red" ng-show="myForm.txtEmail.$touched && myForm.txtEmail.$invalid">
   <span ng-show="myForm.txtEmail.$error.required">Email is required.</span>
   <span ng-show="myForm.txtEmail.$error.email">Invalid email address.</span>
  </span>
  <br />
  <input type="submit" ng-disabled="myForm.txtName.$invalid || myForm.txtEmail.$invalid">
</form>
</body>
</html>


* ngMessages 모듈 이용하기
- ngMessages 서브모듈을 이용하여 유효성 검사에 따른 메시지 표시를 보다 체계적으로 관리할 수 있을 것이다. 이 모듈을 사용하려면 angular-messages.js 파일을 따로 참조해 줘야 한다.
ng-messages 디렉티브에 $error 객체를 바인딩해서 해당 입력요소가 유효한지 여부를 판단할 수 있게 한다. 그리고 ng-message 디렉티브에 검사하고자 하는 유효성 조건의 이름을 지정한다. 예에서는 총 3개(required, minlength, maxlength) 조건을 지정했다.
만일 해당 입력요소에 유효성 오류가 있고, 각 오류가 어떤 검사 기준인지에 따라 서로 다른 메시지를 보여주게 될 것이다.

<!DOCTYPE html>
<html>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular-messages.js"></script>

<script>
(function(angular) {
 angular.module('myApp', ['ngMessages']);
})(window.angular);
</script>

<body ng-app="myApp">
<form name="myForm">
  Name: <input type="text" name="txtName" ng-model="txtName" required ng-minlength="5" ng-maxlength="10">
  <div ng-messages="myForm.txtName.$error">
   <div ng-message="required">You did not enter a field</div>
    <div ng-message="minlength">Your field is too short</div>
    <div ng-message="maxlength">Your field is too long</div>
  </div>

</body>
</html>

 

* 사용자 정의 유효성 검사
- 지금까지는 입력요소의 필수입력여부, 타입 및 길이 등 HTML5의 새로운 특성이나 AngularJS가 내부적으로 지원하는 유효성 검사 기준을 사용하여 체크를 수행하였다.

그러나 실무환경에서는 보다 다양한 유효성 검사 조건이 존재할 수 있다. 예를 들어 입력필드에 들어가는 값은 반드시 숫자여야 하던지, 아님 어떤 정규식에 근거하여 유효성 검사를 수행하기도 한다.

이런 경우, 사용자 정의 유효성 검사기를 새로 만들면 된다. 사용자 정의 유효성 검사를 위해서는 커스텀 디렉티브를 생성하며 기존 디렉티브와의 차이점은 ngModel 모듈을 디렉티브에 추가해야 한다는 점이다.

다음 코드는 정규표현식에 기반한 사용자 정의 유효성 검사 디렉티브 구현을 보여준다. AngularJS 공식문서의 샘플코드이며, 커스텀 디렉티브의 link 함수를 통해 구현한다.

유효성 검사기의 이름은 integer으로 기존 필수입력 조건인 required와 마찬가지로 HTML의 특정 입력요소에 integer라는 이름으로 유효성 기준을 추가할 수 있다.

<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>

(function(angular) { 
 var app = angular.module('myApp', []);
 
 var INTEGER_REGEXP = /^\-?\d+$/;
 app.directive("integer", function(){
  return {
   require: 'ngModel',
   link: function(scope, elm, attrs, ctrl){
    ctrl.$validators.integer = function(modelValue, viewValue){
     if(ctrl.$isEmpty(modelValue)){
      //비어 있을 경우 유효한 것으로 간주한다.
      return true;
     };
     if(INTEGER_REGEXP.test(viewValue)){
      //정규식과 일치하므로 유효함
      return true;
     };
     //위 두조건을 모두 만족하지 않으므로 유효하지 않음
     return false;
    };
   }
  };
 });
})(window.angular);
</script>
<body ng-app="myApp">
<form name="myForm">
 <input type="text" ng-model="amount" name="amount" integer /><br />
 <span ng-show="myForm.amount.$error.integer">정수가 아닙니다.</span> 
</form>
</body>
</html>

 

728x90

웹은 기본적으로 '동일출저정책(Same Origin Policy, SOP)' 정책을  따른다.

이는 보안을 위한 기본정책으로, SOP는 하나의 출처(Origin)에서 로드된 문서나 스크립트가 다른 출처에 존재하는 리소스와 상호작용하지 못하도록 제약을 두는 것이다.

그런데, 간혹 이런 제약이 웹 응용프로그램을 만드는데 걸림돌이 되기도 한다.
Ajax 통신이 활발해 지고, 다른 사이트에 존재하는 Open API와 상호통신이 필요한 경우와 매쉬업(Mash-up)으로 새로운 2차 응용물을 개발하게 되면서.. 등등.. 이는 간혹 걸림돌이 된다.

근래 Node.JS로 서버를 만들고, Aptana Studio 자체 내장 웹서버로 몇 가지 테스트를 하고 있는데, 두 환경은 서로 다른 별도의 포트(Port)에서 웹을 동작시키기 때문에 여지없이 Cross Domain 문제가 발생한다.


호출하는 모든 웹 리소스를, Node.JS로부터 다운로드 받아서 실행하는 구조로 HTTP 서버를 구현하면 SOP 정책을 따를 수 있겠으나, 그렇지 못한 개발환경도 있을 터이고, 실제 서비스 환경에서는 더욱이 서로 다른 도메인(포트 포함)간 상호작용이 필요할 것이다.

SOP 정책을 우회하기 위해서는, 서버 측의 설정이 필요한데, 위 그림에서 친철히 안내하고 있는 것처럼 Access-Control-Allow-Origin 헤더, 즉 크로스 도메인으로 허용할 도메인 목록이 헤더로 제공되어야 한다.
이것을 CORS(Cross-Origin Resource Sharing)를 이용한 SOP 정책 우회라 한다.

Node.JS 환경에서 이 설정을 해보자.
다음과 같이, Node 기반 HTTP 서버의 응답헤더에 추가 해준다.

가장 중요한 설정은, Access-Control-Allow-Origin으로 허용할 도메인(포트)을 추가한다.
여러 도메인이 있을 경우, 중복해서 지정을 하거나 '*' 를 이용해 전체 허용도 가능하다.

var app = require("express")();

app.use(function(req, res, next) {
 res.header("Access-Control-Allow-Origin", "
http://127.0.0.1:8020");
    res.header("Access-Control-Allow-Headers", "X-Requested-With");
    res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
    next();
});

app.get('/users', function(req, res) { 
 var tempUsers = [{id:'park', city:'pusan'}, {id:'kim', city:'seoul'}]; 
    res.send(tempUsers);
});

app.listen(3002);
console.log("Listening on port 3002");

참고로 127.0.0.1:8020 도메인은, 필자의 Aptana Studio가 생성한 임의 주소이다.

이렇게 설정한 후, 클라이언트에서 요청하면 요청이 성공적으로 이뤄진다.

- 이 코드가 포함된 페이지는 127.0.0.1:8020 에 호스팅되어 동작한다.
<script>
window.onload = function(){
 var xhr = new XMLHttpRequest();
 xhr.onload = function(){
  console.log(xhr.response);  
 }; 
 xhr.open("GET", "http://127.0.0.1:3002/users");
 xhr.send();
}; 
</script>

그리고 언제나 그렇듯이 확장 라이브러리도 있다.
cors라는 이름의 Node.JS 확장인데, npm을 통해 설치하고 간단히 다음처럼 사용할 수 있다. 훨씬 간편하다.

var app = require("express")();

var cors = require('cors')();
app.use(cors);

그래도 언제나 보안은 중요한 문제이니, SOP 정책에 존중하면서, 필요시 아주 제한적으로 적용하는 것을 권장함.

 

'모바일 > Javascript' 카테고리의 다른 글

[Node.js] Node With IIS  (1) 2016.07.19
[AngularJS] 폼(form)과 유효성 검사  (0) 2016.07.18
[AngularJS] REST API 통신(with Node.js)  (0) 2016.07.15
[Node.js] socket.io를 활용한 웹채팅 구현  (5) 2016.07.15
[AngularJS] Route  (0) 2016.07.14

[AngularJS] REST API 통신(with Node.js)

Posted in 모바일/Javascript // Posted at 2016. 7. 15. 20:35
728x90

AngularJS는 원격 서버와의 HTTP 기반 통신을 위해 $http서비스를 제공한다.
$http는 브라우저의 XMLHttpRequest 객체나 JOSNP를 이용하여 원격 서버와 통신하기 위한 AngularJS의 서비스다.

이번 글에서는, $http서비스를 이용한 비동기 통신의 기본적 샘플을 구현해 보겠는데, 서버는 간단히 REST API 구현이 가능한 Node.JS를 사용한다.

* 서버 측 코드
- 먼저 Node.JS 기반으로 동작하는 HTTP 서버를 생성한다. express와 bodyParser 등 확장 라이브러리를 npm을 통해 먼저 설치해 둔다.

서버는 간단한 REST 기반 API 3개를 정의하고 있다.
사용자 목록(/users)과 단일사용자(/users/사용자id) 에 대한 GET API와 사용자등록(/users)을 위한 POST API로 구성되어 있다. 파일을 server.js로 저장하고, 쉘을 통해 server.js를 실행한다.
나머지 사항은 주석을 보면 이해될 정도로 심플하다.

var app = require("express")();
var url = require("url");

//post 데이터의 body 속성 접근 및 json파싱 위한 bodyParser 미들웨어 사용
var bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({extended:true}));
app.use(bodyParser.json());

//루트에 대한 get 요청에 응답
app.get("/", function(req, res){
  console.log("get:chatClient.html");
   //최초 루트 get 요청에 해대, 서버에 존재하는 client.html 파일 전송
   res.sendFile("client.html", {root: __dirname});
});
 
//GET API(http://127.0.0.1:3000/users), 전체사용자 목록
app.get('/users', function(req, res) {
   //임의의 사용자 리스트 생성(실제 환경에서는 db 등 연동 결과로 생성)
   var tempUsers = [{id:'park', city:'pusan'}, {id:'kim', city:'seoul'}];
   //클라이언트에 반환
   res.send(tempUsers);
});

//GET API(http://127.0.0.1:3000/users/1), 특정 사용자 id를 지정하여 한명의 사용자 정보 호출
app.get('/users/:id', function(req, res) {
   //매개변수로 전달된 사용자 id 값 추출
   var id = req.params.id;
   //전달받은 사용자 id 그대로 다시 반환
   res.send([{id:id, city:'pusan'}]);
});

//POST API, 새로운 사용자 생성
app.post('/users', function(req, res){
   //POST 데이터로 넘어온 JSON 추출
   var postData = req.body;
   console.log(postData);
   //클라이언트에 그대로 다시 반환(실제 환경에서는 db 삽입 등 작업)
   res.send([postData]); 
});

//3000포트로 http 서버 리스닝 시키기
app.listen(3000);

console.log("Listening on port 3000");

 

* 클라이언트 측 코드
- 자바스크립트와 HTML을 분리하면 좋지만, 글 작성 편의를 위해 하나의 파일로 만든다. 그리고 서버코드에서 지정한 대로, client.html로 저장한다.

<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
//원격 서버 URL 지정
var url = "http://127.0.0.1:3000/users/";

//AngularJS 메인모듈 생성
var app = angular.module('myApp', []);

app.controller('userController', function($http) {
 //현재 컨텍스트의 this 참조저장
 var userCtrl = this;

 //GET 요청
 //'http://127.0.0.1:3000/users' 또는 'http://127.0.0.1:3000/users/특정ID' 형태의 rest api 호출
 userCtrl.getUser = function(userID){
  //사용자 id가 매개변수로 넘어온 경우 url에 id 매개변수 붙이기
  var id = userID || "";

  $http.get(url + id)
  .then(function(response){
    //응답 성공 시 실행
    userCtrl.users = response.data; 
    userCtrl.response = response;        
  })
  .catch(function(error){
    //실패시 실행
    console.log('오류 발생');    
    userCtrl.response = error; 
  })
  .finally(function() {
    //성공, 실패 어떤 경우에도 실행
    console.log('finish getUser()');
   });
 };
 
 //POST 요청
 userCtrl.postUser = function(data){
  //post로 보낼 json 데이터 임의생성
  var postData = JSON.stringify({id:'newID', city:'newCity'});

  //http 요청객체 생성 
  var req = {
   method: 'POST',   
   url: url,
   headers: { 'Content-Type': 'application/json; charset=UTF-8'},
   data: postData
  };

  $http(req)
  .then(function(response){
     userCtrl.users = response.data;
     userCtrl.response = response;
  })
  .catch(function(error){
     console.log('오류 발생');    
     userCtrl.response = error; 
  })
  .finally(function() {
     console.log('finish postUser()');
  }); 
 };  
});
</script>

<body>
 <div ng-app="myApp" ng-controller="userController as userCtrl">
 <button ng-click='userCtrl.getUser();'>1.GET</button>
 <button ng-click='userCtrl.getUser("777");'>1.GET(using ID)</button>
 <button ng-click="userCtrl.postUser();">1.POST</button>
 <!-- 사용자 리스트 -->
 <ul>
    <li ng-repeat="user in userCtrl.users">
      {{ user.id + ', ' + user.city }}
    </li>
 </ul>
 <!-- 응답 객체 정보 출력 -->
 <p>
  response status: {{userCtrl.response}}
 </p> 
</div>
</body>
</html>

설명은 주석으로 충분한 듯 보이고, 실행화면은 다음과 같다.

- GET, http://127.0.0.1:3000/users/ 요청

 

- GET, http://127.0.0.1:3000/users/777 요청

 

- POST, http://127.0.0.1:3000/users/ 요청

 

728x90

재밌네... ㅎㅎ

별도의 비표준 플러그인 없이,  웹에서 채팅을 이렇게 쉽게 구현할 수 있는 시대라니...
6년전, 웹 소켓에 대해 나름 정리한 적이 있다.

 
이미 그 시대는 오래전(?)에 도래했건만, 개인적으로 방치해 두다가, 어제 밤 문득...

웹 소켓과 node.js를 사용해서 채팅 샘플을 구현해 보고 싶다는 생각이 스쳐 지나갔다.

socket.io라는 자바스크립트 라이브러리가 존재한다더니, 이건 뭐.. 다른건 볼 필요도 없네.

socket.io는 Node.js 기반으로 소켓 통신이 가능하도록 해 주는 확장 라이브러리인데, HTML5의 정식표준인 WebSocket을 지원하지만, 미지원 환경에 대한 호환성을 위해 Comet 같은 기술도 하나의 API로 추상화 시킨 양방향 통신 라이브러리이다.

socket.io는 WebSocket이 지원되지 않는 환경에서 실시간 양방향 통신을 구현하기 위해 다음과 같은 fallback 기술을 사용하고 있다.

- FlashSocket, AJAX Long Polling, AJAX Multi part Streaming, IFrame, JSONP Polling

기본적으로 Node.js 설치해 주고, express와 socket.io를 npm을 통해 다음과 같이 설치한다.
- npm install express
- npm install socket.io

이제 환경 설정은 끝이다. 자바스크립트 기반 개발환경은 이렇듯 심플하다. 내가 좋아하는 이유 중 주요한 팩터이다.

* socket.io를 이용한 웹 채팅 구현하기
먼저 다음과 같은 3개의 파일을 준비한다.

1. chatServer.js
- 채팅 서버 역할을 하는 자바스크립트 파일로, Node.JS에 의해 호스팅되며 socket.io 라이브러리를 통해 소켓 서버를 구현한다. 또한 부가적으로 http 리소스 요청에도 응답할 수 있도록 한다.

2. chatClient.js
- 채팅 서버와 통신을 하는 클아이언트 자바스크립트 파일로, 역시 socket.io 라이브러리를 사용하며 채팅을 위한 간단한 DOM 조작을 수행한다.

3. chatClient.html
- chatClient.js가 정의된 html 파일이며, 채팅창과 텍스트 박스로 간단히 구성된 뷰를 제공한다.

먼저 chatServer.js 파일의 코드부터 보자.

var app = require("express")();
var url = require("url");

//루트에 대한 get 요청에 응답
app.get("/", function(req, res){
 console.log("get:chatClient.html");
 //최초 루트 get 요청에 대해, 서버에 존재하는 chatClient.html 파일 전송
 res.sendFile("chatClient.html", {root: __dirname});
});

//기타 웹 리소스 요청에 응답
app.use(function(req, res){
 var fileName = url.parse(req.url).pathname.replace("/","");
 res.sendFile(fileName, {root: __dirname});
 console.log("use:", fileName); 
});

//http 서버 생성
var server = require('http').createServer(app);
server.listen(3000);
console.log("listening at http://127.0.0.1:3000...");

//클로저를 사용해, private한 유니크 id를 만든다
var uniqueID = (function(){
 var id = 0;
 return function(){ return id++; };
})();

//서버 소켓 생성
var socket = require('socket.io').listen(server);
//소켓 Connection 이벤트 함수
socket.sockets.on('connection', function(client){
  //클라이언트 고유값 생성 
  var clientID = uniqueID();
  console.log('Connection: '+ clientID);
 
  //서버 receive 이벤트 함수(클라이언트에서 호출 할 이벤트)    
  client.on('serverReceiver', function(value){
    //클라이언트 이베트 호출     
    socket.sockets.emit('clientReceiver', {clientID: clientID, message: value});  
  });

});

다음으로 chatClient.js 소스이다.

window.onload = function(){ 
 //클라이언트 소켓 생성
 var socket = io.connect('ws://127.0.0.1:3000');

 //DOM 참조
 var div = document.getElementById('message');
 var txt = document.getElementById('txtChat');
 //텍스트 박스에 포커스 주기 
 txt.focus();
 
 //텍스트 박스에 이벤트 바인딩
 txt.onkeydown = sendMessage.bind(this); 
 function sendMessage(event){     
  if(event.keyCode == 13){
   //메세지 입력 여부 체크   
   var message = event.target.value;
   if(message){
     //소켓서버 함수 호출  
     socket.emit('serverReceiver', message);
     //텍스트박스 초기화
     txt.value = '';
   }
  }
 };
 
 //클라이언트 receive 이벤트 함수(서버에서 호출할 이벤트)
 socket.on('clientReceiver', function(data){  
   //console.log('서버에서 전송:', data);   
   //채팅창에 메세지 출력하기
   var message = '['+ data.clientID + '님의 말' + '] ' + data.message;
   div.innerText += message + '\r\n';
   //채팅창 스크롤바 내리기  
   div.scrollTop = div.scrollHeight;   
 });
};

 

 마지막으로 chatClient.html 파일이다.

<script src="chatClient.js"></script>
<script src="/socket.io/socket.io.js"></script>

<div id="message" style="background-color:#F5F5F5; width:400px; height:200px; OVERFLOW-Y:auto; word-wrap: break-word"></div>

<div id="chatInput">  
 메시지를 입력하세요 <input type='text' id='txtChat' name="txtChat"> 
</div>

 

* 실행흐름
1) 서버 실행
- Node의 쉘기능을 이용해 다음과 같이 chatServer.js를 실행한다. 실행하면 다음 그림과 같이 console 로그가 프롬프트 창에 찍힌다.

 

2) 클라이언트 실행
- 클라이언트에서는 단순히 브라우저로 지정된 url로 접근하기만 하면 된다.

브라우저로 chatServer.js 서버로 접속하면, 루트 get요청(http://127.0.0.1:3000)에 따른 chatClient.html 파일이 클라이언트로 다운로드 되고, 이 HTML파일이 브라우저에서 실행되면서 chatClient.js 파일에 대한 요청이 다시 이뤄져 자바스크립트 코드가 다운로드 되고 실행된다. 이 클라이언트 자바스크립트가 실행되어 소켓서버와 클라이언트는 서로 연결이 이뤄진다.

다음 그림은 브라우저에서 해당 url로 접근했을 때 서버측 로그화면이다.

 


* 결과화면

- 총 4개의 사용자가 접근하는 시나리오로 가정하여, 4개의 브라우저를 동시에 사용한다. 각 브라우저에서 해당 url로 접근하고 각자 채팅을 해 본다. 이때 각 클라이언트 구분은 서버측 코드에서처럼 임의의 순처적 고유값을 부여한 숫자로 사용한다.

다음 화면은 브라우저 4개를 띄우고, 채팅을 해본 모습니다. 혼자놀기에 좋다 ㅡ,ㅡ;


지금까지 알아본 샘플 채팅은 채팅 구현을 위한 가장 기본적이면서도 필수적으로 구현되어야 하는 것들만 다뤘다. 실제 업무에서 채팅을 구현한다면 성능과 안정성 등을 위해 다양한 방어코드, 추가기능 코드, 최적화 기법 등의 추가 작업이 필요할 것이다.

기본적인 흐름과 코드를 알았으니, 살을 붙여 나가면 될 일이다. 

'모바일 > Javascript' 카테고리의 다른 글

[Node.js] CORS 설정(Cross Domain 요청 허용)  (0) 2016.07.15
[AngularJS] REST API 통신(with Node.js)  (0) 2016.07.15
[AngularJS] Route  (0) 2016.07.14
[AngularJS] Directive  (0) 2016.07.13
[AngularJS] $scope과 controller-as 문법  (0) 2016.07.12