Blog for QTI developers

2017/05/17

Curl로 구현한 구몬 스마트펜 매니저

1. 스마트펜이란?

 스마트펜이란 한마디로 정의하기는 어렵지만 펜 형태의 다기능 기계장치이다.  그중에서  스캔장치를 가지고 있고, 도트 형태의 배경 무늬를 가지는 인쇄물을 스캔하면 연결된 음원을 재생하는 기계가 국내에서는 교육용으로 많이 사용된다. 한솔, 교원과 같이 가정 학습 분야를 이끄는 회사에서도 이를 이용한다. 세이펜, 소리펜 등의 다양한 이름을 가지기도 한다.
교원(구몬)의 신형 스마트펜 - 2017.05 발매

2. 교원의 호출

 교원에서 새로운 스마트펜을 발매 예정인데, 이와 관련된 관리 프로그램을 제작하기 위하여 몇몇의 개발 업체를 호출하였다.  우리도 그 업체들 중 하나로 제안 및 견적 작업에 참여 하였다.
 교원의 요청은 간단 명료 하였다.
"학부모들이 스마트펜의 음원 파일을 직접 관리할 수 있게 해주세요 !! "

 이전까지 학부모들이 스마트펜에 필요한 음원 파일을 추가하는 것 자체가 어려워서 교사들이 직접 필요한 파일을 추가해서 학부모들에게 전달하는 형태로 판매가 이루어 지고 있었다. 새로 산 스마트펜이어도 교사가 개봉해서 학부모에게 전달하는 형태가 되었기 때문에 문제가 되고 있었다.  학부모 입장에서 새로산 스마트펜인데, 이미 포장이 회손된 상태인 것이었다.


3. 제안 단계

 교원측에서는 웹페이지에서 관리가 가능하도록 하고 싶다고 했다.  ActiveX가 아닌 이상 시스템 리소스에 직접적으로 접근이 불가능한 형태로 웹브라우져가 바뀌어 가고 있는 상황이라서 C/S 형태로 제안했다. 사용의 편리성, 존재감 등의 부가적인 요소를 추가적으로 강조하였다. 마지막으로 Windows 뿐만 아니라 Mac도 지원하는 것을 제안하였다. 결과적으로 우리 회사가 선정되었고, 초기 교원이 생각한 것보다 3배 정도의 프로젝트 규모가 되었다.


4. 개발 단계

 화면 설계를 전체적으로 세번을 했다.  첫번쨰와 두번째에 설계된 내용은 교원 직원들을 감동시키지 못했다. 사용하기에 너무 어려워 보인다는 것이었다.  FTP Client를 사용하는 우리들에게 있어서는 일반적인 인터페이스였다(경쟁사의 관리 프로그램도 마찬가지 이다). 하지만 학부모들에게는 어려운 것이었다. 두번째 화면 설계를 가지고 회의하던 마지막 부분에 누군가가 아이디어를 내 놓았다.
"모바일 앱처럼 만든다면 학부모들도 손쉽게 이용할 것이다"
모바일 앱처럼 사용이 편리하고, 일반 PC 프로그램 처럼 기능성이 향상된 내용으로 화면을 설계 하였다. 그 결과가 아래와 같다.


스마트폰을 사용하는 사람이라면 누구나 손쉽게 접근 가능하도록 설계 하였다. 직관적이며 필요한 작업을 모두 수행할 수 있는 인터페이스이다. 사용자 메뉴얼이 없는 UI를 만들기 위하여 노력하였다.

5. 끝으로

 의뢰자의 좋은 평가와 함께 구몬 스마트펜 매니저 개발 프로젝트는 종료되었다. 물론 스마트펜 매니저 관련하여 2개의 프로젝트가 더 있을 예정이다.


바로가기 링크 :  구몬 스마트펜 매니저 다운로드 페이지 열기

Share:

2017/01/16

ECMAScript, React.js 그리고 IE 8 (feat. Node.js & npm)

최근 C/S POS를 Web 기반으로 전환하는 프로젝트를 준비하고 있다.
 
이번 프로젝트의 최대 문제점이라고 할 수 있는 부분은, 최종 사용자가 `컴퓨터 운용에 익숙하지 않고, 다소 연령대가 높은 불특정 일반인`이라는 점이다. 아무래도 최종 사용자들에 대한 타겟이 이렇다보니, Web을 운용하는 브라우저가 사용자별로 천차만별일 것이며 그 호환성 해결 방법에 대한 부분을 불안요소로 지적했다.
 
그 결과 사용 환경은 최소한 `Microsoft Windows XP SP3` 운영체제와 `Internet Explorer 8.0` 브라우저로 결정이 되었다.
하지만, 집고 넘어갈 부분은 `MS Windows XP`와 `IE 8`은 MS社의 `수명 주기 지원 정책`에 따라 이미 지원이 중단된 제품들이라는 점이다. (맙소사...)
 
 
다음의 그래프는 2015년부터 2016년까지 데스크탑용 Web 브라우저에 대한 통계를 전세계 기준으로 정리한 자료와 한국 기준으로 정리된 자료이다.

 
2015-2016 데스크탑 브라우저별 사용 추세(세계)
 
2015-2016 데스크탑 브라우저별 사용 비율(세계)
 
2015-2016 데스크탑 브라우저 및 버전별 사용 추세(세계)
 
 
2015-2016 데스크탑 브라우저별 사용 추세(한국)
 
2015-2016 데스크탑 브라우저별 사용 비율(한국)
 
2015-2016 데스크탑 브라우저 및 버전별 사용 비율(한국)
 

이미 몇년 전부터 세계적으로 `Chrome`과 `Firefox`등 `IE`가 아닌 브라우저들이 차지하는 비율이 압도적이였지만, 최근 우리나라에서도 IE가 아닌 브라우저가 차지하는 비율이 상당히 높아졌다.
 
어쨌든 앞으로 개발될 어플리케이션은 시궁창 같은 `IE 8`에 호환성을 맞춰야되므로, 이를 위해서 상당한 시간과 노력이 필요하다는 것이다.
 
위 화면은 테스트를 위해 `ECMAScript` + `React.js` + `react-bootstrap 모듈`로 작성한 어플리케이션을 `Microsoft Windows XP SP3` 운영체제의 `Internet Explorer 8.0` 브라우저에서 실행 가능하도록 구현한 화면이다.
 
 

ECMAScript에 대하여

이 글은 ECMAScript가 무엇[1]이며, 어떠한 내용[2]을 담고 있는 지를 알리기 위한 글이 아니므로 해당 부분은 각주를 참고하기 바라며, 특별히 ECMAScript에 주목하게 된 계기는 다음과 같다.
 
이외에도 `Arrow 함수`라던지 `import`와 `export` 구문 등 매력적인 부분이 충분히 많지만, 위 목록에 명시해둔 항목들은 지난 10년동안 Curl로써 개발을 해온 개발자 입장에서는 무척이나 반가운 부분이다.
 
일반적인 자바스크립트 프로그램의 실행 환경은 브라우저이지만 이것이 유일한 실행 환경은 아니다. 그래서 실행 환경은 자신만의 호스트 객체를 제공할 경우가 있는데, 이 호스트 객체는 표준에 정의되지 않았으며 예상치 못한 방식으로 동작할 수도 있다. 하지만 코어 자바스크립트는 그런 실행 환경과는 무관하다.
`class 구문`은 자바스크립트 프로그램에 있어서 오히려 거추장스러울 수도 있지만, 객체지향 언어 프로그램에 익숙한 개발자들을 위한 설탕 혹은 계륵이라고 해야될 것 같다.
자바스크립는 예약어인 `var`를 사용해서 변수를 선언한다. `var`에 의해 선언된 변수는 전역 혹은 함수 유효 범위를 가지고 있어서 이는 호이스팅(hoisting)이라는 동작 방식과 맞물려 예기치 않은 오류를 발생시키는 경우가 있다. 하지만 `let` 예약어는 변수가 사용되는 블록, 구문 또는 표현식 유효 범위를 갖는 변수를 선언하므로 그런 위험은 없다. `let`은 Curl에서 변수를 선언할때 사용하는 예약어와 동일하다.
`Rest 매개 변수`와 `Default 매개 변수`는 Curl에서 당연스럽게 사용하던 기능이였는데, 드디어 다시 사용할 수 있게된 것이다! (만세~)
 
 

React.js에 대하여

`React`는 Facebook의 오픈소스 프로젝트로서, UI 개발에 최적화된 기술이며 브라우저별 또는 브라우저의 버전별 차이로 인해 발생할 수 있는 문제점들을 완화 시켜준다. 더군다나 React의 `JSX`는 놀랍게도 Curl과 흡사한 코딩 방법을 제공한다.
물론 JSX를 사용하지 않는다고 해서 React를 활용할 수 없는 것은 아니지만, 개발과 유지보수를 생각한다면 수고를 줄일 수 있는 방법을 굳이 마다할 이유는 없다.
현재 React의 최신 버전은 15.4.2(2017/01/06 발표)이며, IE 8을 지원하는 마지막 버전은 0.14.8(2016/03/29 발표)이다.
 
다음은 위 테스트용 어플리케이션에서 JSX로 작성된 React.js의 코드들이다.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//App.jsx
import React, { Component } from 'react';
import { render } from 'react-dom';
 
import PilotHeader from './components/PilotHeader.jsx';
import PilotNav from './components/PilotNav.jsx';
import PilotMenuItem from './components/PilotMenuItem.jsx';
 
export default class App extends Component {
    constructor(props) {
        super(props);
    }
 
    render() {
        return (
            <div>
                <PilotHeader />
                <PilotNav />
 
                <div className="jumbotron">
                    <div className="container">
                        <div className="row">
                            <PilotMenuItem />
                            <PilotMenuItem />
                            <PilotMenuItem />
                            <PilotMenuItem />
                        </div>
                        <div className="row">
                            <PilotMenuItem />
                            <PilotMenuItem />
                            <PilotMenuItem />
                            <PilotMenuItem />
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}
 
render(<App />document.getElementById('root'));
 
cs
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//PilotHeader.jsx
import React, { Component } from 'react';
import { PageHeader } from 'react-bootstrap';
 
class PilotHeader extends Component {
    render() {
        return (
            <PageHeader>
                ECMAScript, React.js 그리고 IE 8
            </PageHeader>
        );
    }
//End-of Class
 
export default PilotHeader;
 
cs
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//PilotNav.jsx
import React, { Component } from 'react';
import { Tabs, Tab, TabContainer, TabContent, TabPane } from 'react-bootstrap';
 
class PilotNav extends Component {
    render() {
        return (
            <Tabs defaultActiveKey={2} id="uncontrolled-tab-example">
                <Tab eventKey={1} title="Tab 1">Tab 1 content</Tab>
                <Tab eventKey={2} title="Tab 2">Tab 2 content</Tab>
                <Tab eventKey={3} title="Tab 3" disabled>Tab 3 content</Tab>
            </Tabs>
        );
    }
//End-of Class
 
export default PilotNav;
 
cs
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//PilotMenuItem.jsx
import React, { Component } from 'react';
import { ButtonToolbar, Button } from 'react-bootstrap';
 
class PilotMenuItem extends Component {
    render() {
        return (
            <div className="col-xs-6 col-md-3">
                <ButtonToolbar>
                    <Button>Default</Button>
                    <Button bsStyle="primary">Primary</Button>
                    <Button bsStyle="success">Success</Button>
                    <Button bsStyle="info">Info</Button>
                    <Button bsStyle="warning">Warning</Button>
                    <Button bsStyle="danger">Danger</Button>
                    <Button bsStyle="link">Link</Button>
                    <Button bsStyle="primary" bsSize="large" active>Primary button</Button>
                    <Button bsSize="large" active>Button</Button>
                </ButtonToolbar>
 
                <h3 className="thumbnail">item_1</h3>
            </div>
        );
    }
//End-of Class
 
export default PilotMenuItem;
 
cs
 
위의 JSX 코드에서 조금 특별한 점은, `react-bootstrap 모듈`을 이용함으로서 `Bootstrap`의 장점을 고스란히 누릴 수 있다는 것이다.
 
이렇게 작성된 코드들은 IE 8에서 날 것(?)으로 사용할 수 없다.
그러므로 우선 Babel을 이용해 범용 JavaScript 코드로 변환하고, IE 8에서 인식 가능하게 만들기 위해서 `es3ify-loader`로 다시 한번 더 변환한다.
최종적으로, 변환된 JavaScript 코드들은 `webpack`을 이용해서 번들 파일로 만든다.
 
 

IE 8에 대하여

위에서 생성된 번들 파일을 HTML에 절적히 포함시켜 브라우저에서 볼 수 있게 구성한다.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>ECMAScript, React.js 그리고 IE 8</title>
 
        <!-- Latest compiled and minified CSS -->
        <link rel="stylesheet" href="bootstrap-3.3.7/css/bootstrap.min.css" />
        <!-- Optional theme -->
        <link rel="stylesheet" href="bootstrap-3.3.7/css/bootstrap-theme.min.css" />
 
        <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
        <link rel="stylesheet" href="css/ie10-viewport-bug-workaround.css" />
 
        <!-- IE8 에서 HTML5 요소와 미디어 쿼리를 위한 HTML5 shim 와 Respond.js -->
        <!--[if lt IE 9]>
            <script src="js/ie/html5shiv.min.js"></script>
            <script src="js/ie/respond.src.js"></script>
            <script src="js/ie/es5-shim.min.js"></script>
            <script src="js/ie/es5-sham.min.js"></script>
        <![endif]-->
    </head>
 
    <body>
        <!--[if lt IE 8]>
        <div class="jumbotron">
            <div class="container">
                <h1>알림!</h1>
                <p>현재 사용하는 브라우저를 지원하지 않습니다.</p>
            </div>
        </div>
        <![endif]-->
 
        <!--[if (IE 8)|(IE 9)]>
        <div class="alert alert-danger alert-dismissible" role="alert">
            <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
            <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
            <span class="sr-only"><strong><알림!></strong></span>
             최신의 `Internet Explorer` 브라우저 또는 `<a class="alert-link" href="https://www.google.co.kr/chrome/browser/desktop/" target="_blank">Chrome 브라우저</a>`를 사용하면 더욱 원활한 환경에서 사용이 가능합니다.
        </div>
        <![endif]-->
        <!--[if gte IE 8]><!-->
        <!-- React JavaScript -->
        <div id='root'></div>
        <script src="bundle.js" defer></script>
 
        <!-- Bootstrap core JavaScript -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
        <script>window.jQuery || document.write('<script src="js/jquery.min.js"><\/script>')</script>
        <script src="bootstrap-3.3.7/js/bootstrap.min.js"></script>
        <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
        <script src="js/ie10-viewport-bug-workaround.js"></script>
        <!--<![endif]-->
    </body>
</html>
 
cs
 
위 HTML 코드에서 8, 27, 36, 44번째 줄에 최종 사용자들의 브라우저 환경에 따라 대응하기 위해서 `조건부 주석`이 사용되었다.
 
        <!--[if lt IE 8]>
        <div class="jumbotron">
            <div class="container">
                <h1>알림!</h1>
                <p>현재 사용하는 브라우저를 지원하지 않습니다.</p>
            </div>
        </div>
        <![endif]-->
 
cs
 
특히 27번째 줄부터 34번째 줄까지의 코드로 IE 8 버전 미만의 브라우저는 단호하게 거부하도록 처리하고,
 
        <!--[if (IE 8)|(IE 9)]>
        <div class="alert alert-danger alert-dismissible" role="alert">
            <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
            <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
            <span class="sr-only"><strong><알림!></strong></span>
             최신의 `Internet Explorer` 브라우저 또는 `<a class="alert-link" href="https://www.google.co.kr/chrome/browser/desktop/" target="_blank">Chrome 브라우저</a>`를 사용하면 더욱 원활한 환경에서 사용이 가능합니다.
        </div>
        <![endif]-->
 
cs
 
36번째 줄부터 43번째 줄까지의 코드로 IE 8 또는 IE 9 버전의 브라우저일때, 최신의 브라우저를 사용하도록 알림을 띄워 최종 사용자들이 자발적으로 업데이트를 하게 유도한다.
 
        <!-- React JavaScript -->
        <div id='root'></div>
        <script src="bundle.js" defer></script>
 
cs
 
46번째 줄과 47번째 줄이 번들된 파일을 HTML 영역에 실제로 구현하는 부분이다.
 
 
미래를 예측해서 잘 짜여진 프로그램의 수명이 얼마나 지속될지는 아무도 예측할 수 없다.
그렇지만 현재 적용 가능한 기술로만 단순히 프로그램을 구현한다면, 그 프로그램의 수명은 얼마가지 않을 것이다.
IE 8과 같은 오래된 브라우저에 지원하지도 않는 최신의 기술들을 억지로 들이붓는 것이 과연 잘하는 일인지 고민을 많이 했다.
하지만 2011년에 Curl로 개발한 POS 프로그램이 S그룹의 식자재 기업의 핵심 운영 프로그램으로 아직도 사용하고 있다.
나는 내가 만든 프로그램의 수명이 오랫동안 지속되기를 바란다.  
 

 
Share:

2016/12/16

Curl로 구현한 ProcessBook

1. Plant Monitoring

 Plant Monitoring 관점에서 OSIsoft사의 PI ProcessBook이라는 제품이 있다. 각종 센서에서 생성되어 서버에 저장된 데이터를 Visualization하여 사용자에게 제공할 수 있는 도구이다. 오랜 세월 다듬어진 이 제품은 강력한 기능을 제공하지만 그만큼 가격이 비싸다고 할 수 있다. OSIsoft의 제품 가격은 현장에서 운영하시는 분들이 잘 아시겠지만, 연간 사용료를 근간으로 하는 가격 정책을 가지는 것으로 알고 있다.  연간 사용료는 센서의 개수에 따라서 다르며, 같은 제품이더라도 센서의 개수가 많아질수록 비싸진다. 즉 연간 사용료가 수억원이 될 수도 있다.  PI라는 제품군에는 여러가지가 포함되어 있다. 종류가 많기에 거론 하기 보다는 아래에 링크를 달아 둔다.

바로가기 링크 :  PI System Product List

2. ProcessBook

 PI System 제품군에서 사용자에게 현실적으로 다가가는 것의 중심에는 ProcessBook 이라는 것이 있다. 모든 공장에서 PI System 제품군의 전체 기능을 사용하는 것은 극히 드물며 한국 실정에서는 UI 측면에서 일반적으로 ProcessBook만을 사용한다.  이번 프로젝트의 최대 목표 역시 기존 ProcessBook을 대체 할 수 있는 것을 개발하는 것이었다.

3. Plant Monitoring System (PMS)

금번 프로젝트는 PMS라 명명하고 아래의 범위를 개발하였다.

  • 서버 (AWS EC2 이용)

  - 서버를 상황에 따라 증설 가능한 Scalable한 구성으로 개발
  - 수집된 데이터의 저장
  - 각 클라이언트의 데이터 요청에 대한 응답


  • 클라이언트 (Curl로 개발)

  -  ProcessBook에 준하는 Designer
  -  SVG 이미지 표시 기능
  - 서버와의 통신 및 데이터 처리
  - 실시간 데이터 표시 및 과거 이력 데이터 조회
  - 예제 작성

4. PMS 특징

작성되어진 예제 중 하나는 아래와 같다.  (석유 화학 공정의 일부이다. )



위의 예제는 OSIsoft사의 예제와 동일하나 전체적으로 장시간 모니터링을 위한 화면이어서 화려함 보다는 부담 없는 색상을 사용하였고, 사용성 측면에서 기능을 개선하였다. 

특징을 살펴보면 아래와 같다.
  • Auto scaling  기능

 PI ProcessBook은 화면을 디자인 하고 나서 사용할 때에는 디자인한 크기로 화면을 제공한다. 하지만 PMS는 화면 크기에 따라서 자동으로 표시되는 내용을 확대 하거나 축소한다. 언제든지 디자인한 모든 내용이 화면에 표시되는 형태이다. 물론 화면이 작은 경우 스크롤바를 표시하여 디자인한 크기 그대로 표시할 수 있으나, 사용자 측면에서 그것을 원하지는 않는다고 생각한다. 사용자들은 언제나 모든 내용이 표시되기를 원할 것이다.

  • Threshold 기능

 각 센서의 값을 표시할 때 설정한 값 이상이면 배경을 넣고 표시하도록 개선하였다. 시각적으로 보다 더 쉽게 알아 볼 수 있도록 하였다.

  • History 기능

 과거 5분간(기본 설정값 & 변경가능)의 데이터에 대해서는 툴팁으로 손쉽게 조회 가능하도록 하였다. 핵심은 데이터를 로컬에 축적하고 필요한 경우 서버측에 요청하지 않고 표시한다. 

  • 다른 화면 연결 기능

 다른 화면과 연결하는 기능이 추가 되었다.  설명을 하자면 화면 내에  Group을 표시 하는 기능이 있고, 이 아이콘을 클릭하면 Group의 상세 내용이 표시되는 형태이다. 


6.  Grouping 응용

여러 개의 센서들을 모아서 한 아이콘으로 표시하는 Grouping 기능을 응용해서 사용자에게 초기 화면을 제공한다면 아래와 같은 형태로 만든다. 예제는 한국의 울산에 있는 공장의 대략적인 대지 형태와 설비들의 그룹을 배치한 형태이다. (물론 예제는 석유 화학 공정 만을 표시하고 있지 않으며, 각 공정의 연관성 또한 없다.)
사용자는 홈 화면에서 보고 싶은 화면으로 연결해서 들어간다. 





7. 사용자 화면 

사용자가 보게 될 화면들은 아래와 같다. 컨셉은 모바일UI 처럼 슬라이딩으로 화면이 전환 되며, 이전 화면 링크를 눌러서 되돌아갈 수 있다. 







중요 지표들을 따로 Grouping해서 아래의 그림과 같이 사용자에게 제공할 수 있다.



8. 편집 모드

편집 모드로 전환하면 화면에 팔레트가 표시된다. 이 팔레트로부터 Drag & Drop을 하여 아이템을 추가 한다. 물론 PI ProcessBook 에서 제공하는 팔레트 보다는 훨씬 더 빈약하지만 이것은 아이콘을 SVG로 제작하면 보강이 가능한 구조이다. 아이콘들은 SVG로 제작되기 때문에 아이콘의 크기를 변경하더라도 깨끗하게 표시된다.




9. 끝으로

이러한 UI의 응용 분야는 많다. Plant Monitoring 이외에도 Network Monitoring, Security Monitoring등에 적용 가능하다. 


Share:

2016/09/09

Curl로 구현한 Excel


Curl로 만드는 세상은 무궁무진하다.   MFC와 비교한다면 문제 해결 범위가 좁은 것은 사실이지만, 개발의 편의성 측면에서 볼 때  MFC 보다 우월하다고 생각한다.  내가 지금까지 접해본 수십 개의 개발 언어 중에서 내 구미에 가장 알 맞는 언어인 것 같다.

우리나라 IT 대기업에서도  Excel을 만들 생각은 하지 않는다. 있다면 한컴 정도?
그런데 대기업도 아닌 우리 회사에서 Excel과 비슷한 것을 만들 수 있다는 점 하나 만 보더라도 Curl은 상당히 매력적인 언어이다.

이번에는 MS의 Excel을 대체 해보고자 Curl로 만든 것이  FastSheet다. 대략  30M/M로 개발되었다. 결과물은 아래와 같다.

<그림 1 : Excel? >

마치 Excel을 보는 듯한 인터페이스(사실 인터페이스는 거의 동일하다)를 제공한다. 기존의 Excel 사용자라면 아무 문제 없이 사용할 수 있으리라 생각된다. 함수 입력도 Excel에서 제공하는 수식 편집 기능과 동일하다.

<그림 2 : FastSheet의 함수 입력기 >

Excel에서 지원 하는 모든 함수를 제공하지는 않지만 사용 빈도가 높은 90개 정도의 함수를 지원한다. 물론 FastSheet의 소스를 수정하지 않고 사용자 정의한 함수가 추가 가능하다. 뿐만 아니라, 차트 기능도 제공한다. 

<그림 3 : FastSheet의 차트 >

물론 인쇄 기능도 당연히 제공한다.  Excel 만큼의 굉장한 인쇄 옵션은 아직 제공하고 있지는 않지만, 가로 세로 방향에, 용지에 맞춰서 인쇄를 할 수도 있다. 

<그림 4 : 인쇄 미리 보기 화면 >


이미 구글이나 MS가 웹으로 이런 기능을 만들었는데, 뒤늦게 이런 것을 왜 만드냐고 질문한다면 나는 이렇게 말하고 싶다. 

 "Benz나 BMW가 자동차를 만들었는데, 현대/기아는 왜 자동차를 만들어요?"


Share:

2016/08/25

JavaScrip와 HTTP 접근 제어

동일 출처 정책에 대하여

  웹 개발을 하다보면 동일 출처 정책[1]을 접하게 된다.
더 정확히 말해서 JavaScript(이하 'JS'로 표기)로 웹 개발을 하려다 보니 동일 출처 정책 문제를 접하게 되었다.
Curl[2]을 사용한다면 알 필요가 없는 부분이긴 하지만 JS를 사용해서 개발하려다 보니 모르고 있을 수 없게 된 것이다.
이 문제는 'AJAX 그리고, JavaScript를 이용한 Google Chart 생성' 포스팅에서 XMLHttpRequest(이하 'XHR'로 표기)를 사용한 예제를 작성하던 중 처음 접하게 되었다.

로컬의 7777 포트에서 XHR을 사용해서 또 다른 로컬의 8000 포트로 request를 했더니, Chrome의 console에 다음과 같은 메세지가 출력되며 request가 실패했다.
XMLHttpRequest cannot load http://127.0.0.1:8000/.
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:7777' is therefore not allowed access.
이처럼 한 자원이 자신이 호스팅되는 출처(Origin Server)와 다른 출처의 자원을 요청할 때 Cross-origin HTTP request가 발생하며, 이것은 동일 출처 정책에 위반된다.
동일 출처란 프로토콜, 호스트명, 포트가 같다는 것을 의미하는데, XHR가 실행한 request의 대상이 되는 포트가 달랐기 때문에 제한된 것이다.

웹 브라우저[3]들은 일반적으로 동일 출처 정책을 따르며, 보안 문제 때문에 스크립트에서 발생시키는 Cross-origin HTTP request는 제한한다.
초기 웹사이트의 보안을 위해서는 동일 출처 정책이 유효했을지 모르지만, 최근과 같이 여러 도메인에 걸쳐 구성되는 웹 프로젝트들을 고려한다면 이 정책은 불편할 수밖에 없다.
다만 위에서 출력된 메세지와 같이 별도의 조치를 취해주면 제한을 피할 수 있다.


CORS에 대하여

  CORS[4] 표준은 서버에세 웝브라우저가 정보를 읽어 들일 수 있는 일련의 출처 정보를 기술하게 하는 새로운 HTTP 헤더를 추가함으로써 Cross-origin 자원 공유에서 발생할 수 있는 문제를 해결한다.
결론부터 말하면, 서버단의 HTTP Response와 클라이언트단의 HTTP Request 각각에 특정 헤더들을 추가했다.

다음은 서버단에 추가된 헤더들에 대한 코드이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// server.js
    ... 전략 ...
 
function handleRequest(req, res) {
    res.writeHead(200, {
        'Content-Type''application/json',
 
        // 요청을 허용하는 출처.
        'Access-Control-Allow-Origin''*',
        // 클라이언트에서 preflight의 요청 결과를 저장할 기간.
        'Access-Control-Max-Age'3600,
        // 요청을 허용하는 메소드들. 기본값은 GET, POST
        'Access-Control-Allow-Methods''POST, GET, OPTIONS, DELETE',
        // 요청을 허용하는 헤더.
        'Access-Control-Allow-Headers''x-requested-with'
    });
 
    ... 일부 생략 ...
}

다음은 클라이언트단에 추가된 헤더들에 대한 코드이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// server.js
    ... 전략 ...
 
class AjaxRequest {
    constructor() {
    ... 생략 ...
    }
 
    _initXMLHttpRequest() {
    ... 생략 ...
    }
 
    _initAjaxData(func, reqUrl) {
        let xhr = this._initXMLHttpRequest();
        if(xhr != 'undefined') {
            // Event 처리; request 이후 state 변경에 따른 처리를 담당한다.
            let stateHandler = function() {
 
    ... 일부 생략 ...
 
                if(xhr.readyState == 4) { //complete
                    if (xhr.status == 200) {
                        let jsonObj = JSON.parse(xhr.responseText);
 
                        let rs = jsonObj.RecordSet;
                        let rfs = new RecordFields(rs.RecordFields);
                        let rds = new RecordData(rs.RecordData);
 
                        func(rfs, rds);
                    } else if(xhr.status == 404) {
                        console.log('Not found');
                    }
                }
            };
 
            // Event 처리; Time out이 선언되면 처리를 담당한다.
            let timeoutHandler = function() {
                xhr.abort();
                alert('Time Out');
            };
 
            xhr.onreadystatechange = stateHandler; // 상태 변경
            xhr.ontimeout = timeoutHandler; // 응답시간 초과
            // xhr.withCredentials = true;
 
            xhr.open('GET', reqUrl, true);
            // 요청을 보내는 페이지의 출처(도메인).
            xhr.setRequestHeader('Origin''127.0.0.1:7777');
            // 실제 요청하는 메소드.
            xhr.setRequestHeader('Access-Control-Request-Method''GET')
            xhr.send(null);
        } else {
            console.log('AJAX (XMLHTTP) not supported.');
        }
    }
 
    ... 생략 ...
 
//End-of Class


 
-- 각주
  • [1] Same-origin Policy
  • [2] 노파심에서 이야기하면, Curl은 cURL이 아니다.
    Curl에 대해서는 'QTI 홈페이지(http://www.qtii.co.kr)' 또는 'Curl 홈페이지(http://www.curl.com)'을 참조하기 바람.
  • [3] IE에서는 가능하다.
  • [4] CORS; Cross-Origin Resource Sharing
Share: