What exactly does URLConnection.setDoOutput() affect?


There's setDoOutput() in URLConnection. According to documentation I should

Set the DoOutput flag to true if you intend to use the URL connection for output, false if not.

Now I'm facing exactly this problem - the Java runtime converts the request to POST oncesetDoOutput(true) is called and the server only responds to GET requests. I want to understand what happens if I remove that setDoOutput(true) from the code.

What exactly will this affect? Suppose I set it to false - what can I do now and what can't I do now? Will I be able to perform GET requests? What is "output" in context of this method?

share|improve this question
add comment

You need to set it to true if you want to send (output) a request body, for example with POST or PUT requests. With GET, you do not usually send a body, so you do not need it.

Sending the request body itself is done via the connection's output stream:

conn.getOutputStream().write(someBytes);
share|improve this answer



출처 - http://stackoverflow.com/questions/8587913/what-exactly-does-urlconnection-setdooutput-affect






Android’s HTTP Clients


[이 포스트는 Dalvik 팀의 Jesse Wilson 에 의해 작성되었습니다. —Tim Bray]




  안드로이드의 많은 어플리케이션이 데이터를 주고 받을 때 HTTP 프로토콜을 사용합니다. 안드로이드 플랫폼에서는 두 종류의 HTTP 클라이언트를 제공하는데, 하나는 Java 의 기본적인 HttpURLConnection 이고, 다른 하나는 아파치 HTTP 클라이언트 입니다. 양쪽 모두 HTTPS 프로토콜, 스트리밍 방식의 업로드 및 다운로드, 타임아웃 설정, IPv6 지원, 커넥션 풀등의 기능을 제공합니다. 그렇다면 둘 중 어느 것을 사용하는 편이 좋을까요?


아파치 HTTP 클라이언트

  HTTPClient 인터페이스를 구현 한 DefaultHttpClient 와 AndroidHttpClient 클래스가 제공됩니다. 웹브라우저를 만들기에 충분할 만큼, 방대하고 유연한 API 가 지원되며, 안정적으로 동작하고 별다른 버그도 없습니다.

 

 하지만 너무 방대한 API 가 지원되기 때문에, 안드로이드 개발팀이 해당 클래스를 유지 보수 하고 성능을 개선하는데 어려움이 있습니다. 기존 어플리케이션과 호환성을 유지하기 위해서 너무 많은 사항을 고려해야하기 때문에 그렇습니다. 그래서, 사실 안드로이드 개발팀은 이 클래스를 개선하는 작업을 더이상 진행하고 있지 않습니다.


HttpURLConnection

 HttpURLConnection 은 일반적인 목적으로 사용가능한, 가벼운 HTTP 클라이언트 입니다. 대부분의 어플리케이션에 적합하지요. 이 클래스는 사실 좀 별볼일 없었는데, 하지만 핵심적인 API 만을 갖고 있는 단순한 형태이기 때문에, 성능 개선 작업을 진행하기가 그 만큼 더 용이하였습니다.


 프로요 이전 버전의 경우 사실 HttpURLConnection 클래스에는 몇 가지 이상한 버그들이 있었습니다. 한 예로, InputStream 을 열어둔 상태에서 close() 메서드를 호출하면, 커넥션 풀이 오작동하는 일들도 있었지요. 따라서, 많은 개발자 들이 아래와 같이 커넥션 폴링 기능을 아예 비활성화 시키기도 했습니다. (주> 버그의 내용을 간단히 소개합니다. HttpURLConnection 은 커넥션 풀을 지원함으로, 연결중인 HTTP 커넥션을 종료하더라도, 바로 소켓 연결이 끊어지는 것이 아니며, 커넥션 풀로 반환 되었다고, 이 후, 동일 주소에 대한 또 다른 요청이 생기면 새로운 커넥션을 생성하는 대신, 기존에 연결되어 있던 커넥션을 재활용 하게 됩니다. 이 떄, 이전 커넥션에서 데이타 InputStream 을 끝까지 읽기 전에 close() 를 하고, 해당 커넥션이 재활용될 때, 기존에 미처 읽지 못했던 데이터가 남아 있는 버그가 있다고 하는구요.) 


private void disableConnectionReuseIfNecessary() {
   
// HTTP connection reuse which was buggy pre-froyo
   
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
       
System.setProperty("http.keepAlive", "false");
   
}
}


 진저 브레드에서, HttpURLConnection 을 통해, 압축된 형식의 HTTP 응답을 수신할 수 있는 기능이 추가되었습니다. HttpURLConnection 은 Http 요청 시 자동으로 아래의 헤더를 추가하며, 만일 서버가 이에 따라 압축된 형식으로 응답을 전송해도 이를 적절히 처리하여 개발자에게 전달해 줍니다. (개발자는 데이타 인코딩/디코딩에 대하여 신경쓸 필요가 없습니다.)

Accept-Encoding: gzip

 단, 한 가지 주의점이 있는데, HTTP 의Content-Length 헤더는 실재 전송된 데이터의 크기를 나타냄으로, 이 경우, 압축된 데이타의 크기를 전달합니다. 따라서, 압축이 풀린 데이터를 저장하기 위한 버퍼를 생성할 떄, getContentLength() 값을 이용하여 버퍼 크기를 결정해서는 않됩니다. 대신 InputStream 의 read() 메서드가 -1 을 리턴할 때 까지 차곡 차곡 데이터를 읽어야 합니다.


 따라서, 여러분이 안드로이드 어플리케이션과 상호작용하는 웹 서버를 만들 때도, 이런 기능을 활용할 수 있도록 서버 측에 요청에 대한 응답을 압축해서 전달하닌 기능을 추가해 두면 좋을 것 있습니다. 만일 이 기능을 사용하고 싶지 않다면, HTTP 요청 시 "Accept-Encoding" 헤더를 다음과 같이 선언하시면 됩니다.

  urlConnection.setRequestProperty("Accept-Encoding", "identity");


 또한 진저브레드 버전에서는 HTTPS 에 관한 여러가지 기능 개선을 이루어 졌습니다. HttpsURLConnection 은 이제, Server Name Indication (SNI) 기능을 지원합니다. 따라서, 하나의 아이피 주소를 갖지만, 여러 HTTPS 호스트를 유지하는 웹서버와 별다른 문제없이 안전한 연결을 맺을 수 있습니다. 또한, 압축된 데이타 전송과 최근에 연결된 TLS 세션을 재활용 할 수 있는 Session Ticket 기능도 지원합니다. 만일, 이런 새로운 기능을 활용하여 연결이 실패할 경우, 안드로이드 시스템은 이런 기능을 제거 하고 자동으로 재연결을 시도합니다. 따라서, 개발자 분들은 HttpsURLConnection 을 이용하여 효율적으로 최신 서버와 데이터를 주고 받을 수 있으며, 동시에 오랜된 서버들과도 호환성 문제없이 네트워크를 연결을 구성할 수 있습니다.


 더 나악, 아이스크림 샌드위치 버전에서는 에서는 Response Cache(응답 캐쉬) 기능이 추가됩니다. 캐쉬가 활용됨으로 HTTP 요청에 대한 응답은 다음의 세 가지 방식으로 얻어질 수 있습니다.

  • 만일 이미 온전하게 캐슁 되어 있는 HTTP 응답이 있는 경우, 해당 값을 활용합니다. 별도의 네트워크 커넥션이 일어나지 않으며, 요청 결과가 즉시 반환됩니다.
  • 조건부 캐쉬가 존재하는 경우, 우선 캐슁된 결과가 유효한지 확인할 필요가 있습니다. 클라이언트는 우선, "만일 어제 이후로 foo.png 파일이 변경되었다면 나한테 해당 컨텐츠를 다오" 같은 식으로 서버에 요청을 전달하고, 서버는 업데이트된 컨텐츠를 전송하거나 304 Not Modified 상태 코드를 반환 할 것 입니다. 컨텐츠가 변경되지 않았다면 캐슁된 컨텐츠를 사용할 수 있습니다.
  • 캐슁된 응답이 없는 경우에는, 일반적인 방법으로 서버로 요청을 전달하고 응답을 수신합니다. 수신된 응답은 이 후 재 사용될 수 있도록, 로컬 캐쉬 공간에 저장됩니다.

 

 자바 리플렉션 기법을 이용하여, 호환성 문제를 걱정하지 않고, 캐쉬 기능을 지원하는 디바이스에서 한하여 해당 기능을 사용할 수 있습니다. 아래의 샘플 코드는 한 예인데, 아이스 크림 샌드위치 버전 에서는 캐쉬 기능을 사용하고, 이전 버전에서는 관련 기능을 사용하지 않도록 만들어 줍니다.

private void enableHttpResponseCache() {
   
try {
       
long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
       
File httpCacheDir = new File(getCacheDir(), "http");
       
Class.forName("android.net.http.HttpResponseCache")
           
.getMethod("install", File.class, long.class)
           
.invoke(null, httpCacheDir, httpCacheSize);
   
} catch (Exception httpResponseCacheNotAvailable) {
   
}
}


어떤 클라이언트를 사용해야 할 까요?

아파치 HTTP 클라이언트는 이클레어 및 프로요 버전에서 HttpUrlConnection 에 비하여 안정적으로 동작합니다. 이 플랫폼들을 위해서는 아파치 Http 클라이언트가 최선의 선택입니다.


 진저 브레드 이 후 부터는, HttpURLConnection 이 최선의 선택입니다. 간단한 API 와 작은 크기는 안드로이드에 꼭 알맞습니다. 투명한 압축 지원과 응답 캐슁 기능은 네트워크 사용을 최소화 하며, 속도를 향상 시키고 배터리를 절약하게 해 줍니다. 따라서, 여러분이 새롭게 제작하는 어플리케이션은 HttpURLConnection 을 사용하시는 편이 좋습니다. 이 클래스가 바로 안드로이드 개발팀이 열심히 작업을 수행하고 있는 클래스이기 때문입니다.



이 저작물은 아래 조건 만족 시
별도 허가 없이 사용 가능합니다

  • 저작자 명시 필수
  • 영리적 사용 불가
  • 내용 변경 불가

CCL


출처 - http://blog.naver.com/PostView.nhn?blogId=huewu&logNo=110120238610








How to check if InputStream is Gzipped?


Is there any way to check if InputStream has been gzipped? Here's the code:

public static InputStream decompressStream(InputStream input) {
    try {
        GZIPInputStream gs = new GZIPInputStream(input);
        return gs;
    } catch (IOException e) {
        logger.info("Input stream not in the GZIP format, using standard format");
        return input;
    }
}

I tried this way but it doesn't work as expected - values read from the stream are invalid. EDIT: Added the method I use to compress data:

public static byte[] compress(byte[] content) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
        GZIPOutputStream gs = new GZIPOutputStream(baos);
        gs.write(content);
        gs.close();
    } catch (IOException e) {
        logger.error("Fatal error occured while compressing data");
        throw new RuntimeException(e);
    }
    double ratio = (1.0f * content.length / baos.size());
    if (ratio > 1) {
        logger.info("Compression ratio equals " + ratio);
        return baos.toByteArray();
    }
    logger.info("Compression not needed");
    return content;

}
share|improve this question
 
Where does the InputStream come from? From URLConnection#getInputStream()? In a bit decent protocol like HTTP, the enduser should already be instructed somehow that the content is gzipped. – BalusC Jan 27 '11 at 15:47 
 
Given that GZIP has a 32 bit CRC, I find that surprising. A corrupt stream should throw an exception at the end at least. –  Peter Lawrey Jan 27 '11 at 15:47
 
I'm wondering if the OP means that values read from the stream AFTER the IOException occurs are not valid... which would make sense because the GZIPInputStream constructor would have consumed some of the bytes from the stream. –  Eric Giguere Jan 27 '11 at 15:50
 
Values are corrupted after the IOException occured. The InputStream comes from HttpURLConnection#getInputStream() –  voo Jan 27 '11 at 15:53
 
Right, that's because the GZipInputStream reads bytes from the original input stream. So you need to buffer the input stream as shown in the answer below. –  Eric Giguere Jan 27 '11 at 15:58
show 1 more comment

The InputStream comes from HttpURLConnection#getInputStream()

In that case you need to check if HTTP Content-Encoding response header equals to gzip.

URLConnection connection = url.openConnection();
InputStream input = connection.getInputStream();

if ("gzip".equals(connection.getContentEncoding())) {
    input = new GZIPInputStream(input);
}

// ...

This all is clearly specified in HTTP spec.


Update: as per the way how you compressed the source of the stream: this ratio check is pretty... insane. Get rid of it. The same length does not necessarily mean that the bytes are the same. Let italways return the gzipped stream so that you can always expect a gzipped stream and just applyGZIPInputStream without nasty checks.

share|improve this answer
 
Unfortunately, this is not exactly what I need since I use http to exchange binary data in the client-server architecture and as a result Content-Encoding is not set. Additionally, I won't be able to call getContentEndoing when the request comes from the client who's served by the servlet. But still thank you for the answer. –  voo Jan 27 '11 at 16:15
1 
Then other side is in essence abusing the HTTP protocol or it is not a HTTP service at all. Contact with the service admin how to figure in their way if the response is gzipped or not. Edit: wait, do you mean that there's a servlet which is proxying the request and that your input is coming from its response? Then that servlet needs to be fixed that it copies all mandatory HTTP headers as well. –  BalusC Jan 27 '11 at 16:16 
 
I updated the answer as per your question update. –  BalusC Jan 27 '11 at 16:27
1 
Last time I checked you were allowed to transport any kind of content over HTTP, gzip included, so it's not really an abuse. –  biziclop Jan 27 '11 at 16:42
1 
@BalusC "When present, its value indicates what additional content codings have been applied to the entity-body, and thus what decoding mechanisms must be applied in order to obtain the media-type referenced by the Content-Type header field" Which clearly means that if I want to transmit gzipped content, I shouldn't (indeed I mustn't) set the content-encoding field. Just to make it clear: not some content transport-coded in gzip but a file which happens to be gzip format. –  biziclop Jan 27 '11 at 16:53 




출처 - http://stackoverflow.com/questions/4818468/how-to-check-if-inputstream-is-gzipped









'Development > Java' 카테고리의 다른 글

java - URLConnection, cookie, session  (0) 2014.04.17
java - local host ip address  (0) 2014.02.13
java - 화폐 원 단위 콤마 표시  (2) 2013.10.02
java - apache POI 소개  (0) 2013.09.26
java - 한글 초성 검색  (0) 2013.08.30
Posted by linuxism
,


AngularJS 와 RequireJS 를 활용한 대규모 웹 어플리케이션 개발

AngularJS 기반 웹 어플리케이션, 어디서부터 시작해야 할까?

AngularJS를 기반으로 웹 어플리케이션을 개발할 때, AngularJS 사이트에서 제공해주는 문서들은 특정 API 활용 방법을 이해하거나 단순한 수준으로 시작하기에는 적합하지만 어떻게 수백에서 수천줄에 이르는 코드로 어플리케이션을 구성하고 발전시켜야하는지에 대해서는 명확한 가이드라인을 제공해주고 있지 않습니다. AngularJS를 기반으로 수차례의 크고 작은 개발을 진행하며 얻은 경험 지식과, 이를 기반으로 대규모 웹 어플리케이션 개발이 가능한 UI 프레임워크를 만드는 과정에서 고민하고 연구한 결과물을 정리해 보았습니다.

이 글은 대규모 웹 어플리케이션 개발에 있어 정답을 제시하는 글이 아니며, 대규모 웹 어플리케이션을 고려한 UI 프레임워크를 개발하는 과정에서 나온 산출물들 중 일부를 설명과 곁들여 공개하는 것입니다. 따라서 대규모 웹 어플리케이션 개발을 위한 인사이트를 얻는 방향으로 접근하시는 것이 좋습니다.

먼저는 파일과 디렉토리 구조를 어떻게 구성해야 하는지에서부터, 주요 자바스크립트 파일이 어떻게 구동되는지 하나하나 살펴보게 될 것입니다.

 

파일 및 디렉토리 구성

Brain Ford는 “Building Huuuuuge Apps with AngularJS” 라는 포스팅에서 대규모 어플리케이션을 개발할 때 중요한 것은 작고 집중되고 모듈화된 부분으로 개발해서 점진적으로 확장시켜나가는 것이 중요하다고 조언했습니다. 하지만 이렇게 대규모 어플리케이션을 어떠한 분류로 나누고 또 어떤 구조로 구성해야하는지에 대한 고민이 또다시 생기게 됩니다.

나누어진 부분들은 필연적으로 파일과 디렉토리로 분류되게 되는데, angular seed project 등에서도 권장하는 디렉토리 구조가 있고, 또 웹 상에서 검색해보면 다양한 구조로 파일과 디렉토리를 나누는 방법을 찾을 수 있습니다.

여기에서는 현재까지 여러 프로젝트에서 누적된 경험을 바탕으로, 모듈화된 AngularJS 프로젝트에 가장 적합한 파일과 디렉토리 구조를 소개하고자 합니다.파일 및 디렉토리 구조

  • 먼저 css와 img에는 각각 어플리케이션에서 공통적으로 사용되는 스타일시트와 이미지 데이터가 들어가게 됩니다.
  • lib 폴더에는 angular, jquery 등 어플리케이션에서 사용되는 라이브러리들이 위치합니다.
  • partials 폴더에는 ng-view에 뿌려줄 html template 파일들이 위치합니다.
  • js 폴더에는 모듈화된 스크립트 파일들이 위치하게 됩니다.

js 폴더와 partials 폴더 안의 각 파일들은 반드시 한 가지의 내용만 담고 있는 것을 권장합니다. 예를 들어, 자바스크립트 파일 하나에는 오직 하나의 컨트롤러 혹은 오직 하나의 다이렉티브만 담겨있어야 한다는 것입니다. 프로젝트의 규모가 커져서 파일이 많아질수록, 더 많은 서브 디렉토리를 두어서 구조를 더욱 체계화하여 관리할 필요가 있습니다.

이러한 파일과 디렉토리 구조는 앞서 이야기한 작고 집중되고 모듈화된 부분으로 개발하려는 방향성을 고민하여 설계된 것입니다. 이렇게 모듈화된 구조를 통해 필요한 부분만 선별적으로 로딩할 수 있게되고 초기 로딩 속도에 크게 영향을 받는 UX 개선에 도움을 얻을 수 있습니다. 모듈화와 동적 로딩은 requireJS를 기반으로 구현되었으며, 이에 관련해서는 이 글에 자세히 설명되어 있습니다.

 

웹 어플리케이션 구조

웹 어플리케이션 구조

전체적인 어플리케이션 구조는 위와 같습니다. document 전체에 대해 myApp 이라는 모듈이 부트스트래핑되고, html 태그에 CommonController 라는 컨트롤러가 자리합니다. CommonController에는 전체 메뉴와 같이 어플리케이션 전반에서 공통적으로 반복해서 사용되는 부분에 대한 컨트롤을 담당하게 됩니다.

ng-view directive가 있는 부분이 동적으로 로딩되는 부분으로, partials 폴더 아래에 있는 partial view 템플릿들을 가져와서 뿌려주게 됩니다. 이렇게 로딩되는 view 마다 각 view에 해당하는 컨트롤러 역시 동적으로 로딩되어 할당됩니다.

이러한 구조에 대한 이해를 바탕으로, 실제 어떤 흐름으로 웹 어플리케이션이 구동되며 모듈들이 관리되는지를 이어서 살펴보겠습니다.

 

웹 어플리케이션 흐름

웹 어플리케이션 흐름

웹 어플리케이션의 전체적인 큰 그림은 위와 같습니다.

index.html 파일을 열면 여기에 require.js 파일을 script 태그로 가져오게 됩니다. RequireJS 는 로드된 후 data-main 속성에 따라 가장 먼저 main.js 파일을 동적으로 불러와서 실행하게 됩니다.

main.js 파일은 RequireJS 모듈 형태로 선언되어 있는데, 디펜던시 선언부 및 디펜던시 로드 뒤 실행부로 나뉘어져 있습니다. 실행부에는 myApp Angular 모듈을 부트스트래핑하도록 되어 있지만, 그 전에 디펜던시들이 먼저 로드 되어야 하므로, angularJS, jQuery 등의 라이브러리와 app.jsroute-config.js,routes.js 파일이 로드된 뒤에야 부트스트래핑이 진행되게 됩니다.

app.js 파일 역시 RequireJS 모듈 형태로 선언되어 있는데, 실행부에는 myApp 모듈 및 CommonController를 선언하는 내용으로 되어 있습니다. 이 내용 역시 바로 실행되지 않고 그 전에 디펜던시가 로드된 뒤 실행되게 됩니다. 따라서 디펜던시인 route-config.js 파일의 로드를 먼저 처리합니다.

route-config.js 파일 역시 RequireJS 모듈 형태로 동적으로 서비스, 다이렉티브, 필터 등을 로드할 때 필요한 스크립트들이 디펜던시로 등록되어 있습니다. 이 디펜던시들이 로드되면, AngularJS에서 경로(route) 설정 시 사용할 config 함수를 선언합니다.

route-config.js 파일의 처리가 끝나면 다시 역으로 app.js 파일의 실행부가 처리되고, 이어서 route-config.js 파일과 app.js 파일을 디펜던시로 갖고 있던 route.js 파일의 실행부가 처리됩니다. route.js 파일의 실행부에는 AngularJS의 경로 설정 로직이 있습니다.

여기까지 모든 디펜던시들의 처리가 끝나고 나면 다시 main.js 파일의 실행부가 처리되게 됩니다. 이 시점에서 myApp 모듈의 부트스트래핑이 작동되어 Angular Application 이 실행되게 됩니다.

이때, 입력된 route 에 따라서 해당 partial view template 파일과 controller 가 동적으로 로드됩니다.

 

다시 정리하면, RequireJS 를 통해 설정된 디펜던시들이 로드된 후 비로소 Angular Application이 시작되는 형태로, 또 각 route 마다 RequireJS가 template 과 controller 등을 동적으로 가져와서 추가해주는 방식으로 구현되어 있습니다.

그럼 이제, 각 스크립트 파일 안의 로직이 실제로 어떻게 동작하는지 구체적으로 살펴보겠습니다.

 

세부 흐름 분석

흐름의 시작은 index.html 파일입니다. require.js 에 대한 포스팅에서 이미 설명한 것과 크게 다르지 않은 구조로 설계되어 있습니다.

 

index.html

기본적으로 반드시 필요한 스타일시트는 link 태그로 먼저 입력하지만, 페이지마다 동적으로 필요한 스타일시트는 하단의 ng-repeat과 ng-href 를 활용해서 동적으로 로드될 수 있도록 설계되었습니다. IE8에서도 정상 동작하는 방식입니다.

하단의 require.js 파일을 로드해주는 부분에 data-main 속성을 설정해서 RequireJS가 로드된 후 바로 로드해서 실행해줄 JavaScript 파일을 지정해줄 수 있는데, 위의 예에서는 require.js 파일이 로드된 후에 바로 js 폴더 아래에 main.js 파일을 불러와서 실행하도록 되어 있습니다.

index.html 파일에서는 구체적으로 어떤 라이브러리들이 사용되는지 감추어짐으로써 전체적인 코드의 가독성도 높아지는 것을 볼 수 있습니다.

 

main.js

require.js 파일이 로드된 뒤 가장 처음 불러오는 파일인 main.js 스크립트는 크게 두 부분으로 나누어져 있습니다.

먼저는 RequireJS 의 환경을 설정하는 부분이고, 설정이 끝난 뒤에는 require 함수를 사용해서 디펜던시를 불러온 뒤 angular module을 부트스트래핑 하게 됩니다. 환경 설정에 대한 부분은 지난 require.js 에 대한 글에 충분하게 설명되어 있습니다.

앞서 흐름에 대해 살펴볼때 이야기했던 것처럼 angular module이 부트스트래핑 되는 것은 나열된 디펜던시들이 모두 로드된 시점이므로 모든 흐름의 끝에 실행됩니다.

나열된 디펜던시 중 ‘text’ 라는 것은 require.js 의 text plug-in 으로 스크립트 파일이 아닌 텍스트 형태의 파일을 동적으로 가져올 수 있도록 해주는 기능을 합니다.

 

app.js

AngularJS나 jQuery 와 같은 라이브러리 외에 main.js의 디펜던시로 걸린 파일 중 하나가 app.js 파일입니다. 이 디펜던시 설정으로 인해 app.js 파일이 로드되어 실행 된 후에야 main.js 파일의 실행된다는 것은 앞서 언급한 부분입니다.

app.js 파일에도 역시 디펜던시 설정이 걸려 있는데, AngularJS의 route 설정을 위한 route-config.js 파일입니다.  route-config.js 파일이 로드된 뒤에 app.js 에서는 myApp 모듈을 선언합니다. AngularJS는 모듈  선언시에만 접근할 수 있는 provider 들이 있는데 Lazy Loading을  위해서  이  provider 들을 별도로 저장해둡니다. 이 provider 들을  저장하는 부분이 route-config.js 에 구현되어 있기 때문에 route-config.js 파일이 디펜던시로 잡혀있는 것입니다.

또한 app.js 파일에는 앱 전체적으로 공통적으로 사용되는  CommonController가  선언되어  있습니다. 사실 각 partial view마다 또 컨트롤러가 존재하는데, 이 공통 컨트롤러에서는 partial view 외에 부분에서 사용되는 내용들이 위치하게 됩니다. 예를들어, 위의 소스에서는 동적으로 스타일 시트를  업데이트하는 로직이 들어 있는 것을  볼  수 있습니다. 이 외에도 앱 전체 메뉴를  설정하는  부분 등도 이 컨트롤러에 포함될 수 있습니다.

여기에서는 컨트롤러 하나만 myApp 모듈에 추가하고  있지만, 공통적으로 사용되는 directive가  존재한다면 이  app.js 파일에서 선언해서 추가해 주는 것도 가능합니다. 그  외에도 공통적으로 사용되고 동적으로 추가될 필요가 없는 value 값이나 service, filter 등도 여기에서 선언해두  추가해주는 것이 좋습니다.

 

 route-config.js

앞서 app.js 파일에 이 route-config.js 파일이 디펜던시로 잡혀있었기 때문에 route-config.js 파일이 먼저 실행되게 됩니다.

또 route-config.js 파일에는 각각 directive, service, filter 를 동적으로 등록시켜줄 때 사용되는 lazy-directives, lazy-services, lazy-filters 가 디펜던시로 잡혀있기 때문에 이 파일들이 로드된 뒤에 route-config.js 파일이 실행됩니다.

디펜던시가 로드된 뒤에는 route 설정과 관련된 함수들이 선언됩니다. 각각의 provider를 저장하는 함수들과 path에 따라 lazy-loading  을 구현하는 부분이 config 함수에 선언됩니다.

소스에 주석으로 설명이 되어 있지만 간단하게 전체 로직을 살펴보자면, 템플릿과 스크립트 파일 등을 파라메터로 받아서 차후에 호출이 들어올 경우 requireJS로 이들을 동적으로 가져와 등록하도록 예약하는 로직입니다.

 

route.js

route-config.js 파일과 app.js 파일을 디펜던시로 갖고 있던 route.js 파일의 실행부가 처리됩니다. route.js 파일의 실행부에는 AngularJS의 경로 설정 로직이 있습니다.

모듈의 config 메서드를 사용해서 각각의 경로를 설정해주게 되는데, 여기에서는 4개의 경로만 설정해주었습니다. 실제 프로젝트에서는 $http 서비스 등을 사용해서 메뉴  관련 데이터를 JSON으로 동적으로 받아와서  처리해주는  방법도 고민해볼  수 있습니다.

 

이렇게 route.js 파일도 로드  및 실행이 완료되고 나면 다시 main.js 파일의 콜백 함수 부분으로 돌아가게 되고  비로소 myApp 모듈이 부트스트래핑되며  Angular Application 이 실행됩니다.

 

동적으로 로딩되는  컨트롤러 예 – grid.js

path 가 grid 일 경우 grid.js 파일이 컨트롤러로서 동적으로 로드됩니다.

RequireJS module 형태로 선언되어 있는데, 이 grid 메뉴의 화면에는 pqGrid를 사용하므로 디펜던시로 pqGrid 라이브러리를 넣고 있는 것을 볼 수 있습니다.

위와 같이 define으로 모듈을 선언한 뒤에 첫 파라메터로 배열 형태로 필요한 디펜던시를 선언합니다.

 

디펜던시 로드가 완료되면 아래 콜백 함수가 실행되는데, 콜백함수에서는 Angular Controller 형태로 함수를 선언해서 이 함수를 리턴해주는 것을 볼 수 있습니다.

또 동적으로 CSS를 설정해주기 위해 컨트롤러 내부에 $emit 으로 추가하고자하는 css 의 경로를 보내게 됩니다. 이러한 방식으로 CSS를 동적으로 추가/제거 해줌으로써 너무 많은 CSS 추가로 인해 스타일이 충돌하는 것을 예방할 수 있습니다.

 

 

실제 구현 미리보기

여기까지 설명한 내용을 바탕으로 동적으로 템플릿, 컨트롤러, 다이렉티브, 서비스, 필터 등을 로드하는 프로젝트 샘플을 제작해보았습니다. 이 프로젝트 샘플은 아래 프로젝트 샘플 다운로드 부분에서 다운 받을 수 있는 링크를 얻을 수 있습니다. 이 프로젝트 샘플을 좀더 개선하고 발전시켜 적용한 사례가 JCF UI 사이트입니다.

 

preview01

기본 경로인 view1 path로 들어간 모습입니다. CSS, 컨트롤러, 필터, 템플릿 모두 동적으로 로드되어 반영된 것을 볼 수 있습니다.

 

preview02

view2 메뉴를 누르면 역시 마찬가지로  CSS, 컨트롤러, 다이렉티브, 템플릿 모두 동적으로 로드되어 반영된 것을 볼 수 있습니다. 기존의 CSS는 제거되고 새로운 CSS만 반영된 것 역시 확인할 수 있습니다.

 

preview03

grid 메뉴를 누르면 디펜던시 설정된 pqGrid 라이브러리를 동적으로 로드하여 실행되는 것을 확인할 수 있습니다.

 

 

프로젝트 샘플 다운로드 – GitHub

지금까지 설명한 내용을 바탕으로 제작된 프로젝트 샘플을 GitHub에서 받으실 수 있습니다. 직접 받아서 수정하고 실행해본다면 전체적인 구조를 이해하고 활용하는데 도움이 될 것입니다.

 

githuboctacat

 

 

 

결론

지금까지 AngularJS를 기반으로 대규모 웹 어플리케이션을 개발할 때 고민하게 되는 모듈화, 리소스의 동적로딩 등에 대해 살펴보았습니다. 또 이를 바탕으로 간단한 샘플 프로젝트를 작성했습니다.

앞서 말씀드린 것처럼 이 글은 대규모 웹 어플리케이션 개발에 있어 정답을 제시하는 글이 아닙니다. route 를 동적으로 추가하는 부분이나, breadcrumb을 관리하는 부분 등 실제 대규모 웹 어플리케이션을 개발할 때 필요한 부분들이 많이 빠져있기 때문입니다. 하지만 부족하나마 대규모 웹 어플리케이션을 개발하는데 있어 기준점은 될 수 있지 않을까 생각해봅니다.

부족한 설명과 부실한 예제였지만, 대규모 웹 어플리케이션 개발을 위한 안목을 얻을 수 있는 시간이었기를 기대합니다. 다음 포스팅에서는 대규모 웹 개발을 위해 좀더 개선된 형태의 seed project를 살펴보도록 하겠습니다.


재하 안 | 2013년 5월 9일 |



출처 - http://jcf.daewoobrenic.co.kr/blog/?p=237

http://programmingsummaries.tistory.com/229


'Development > AngularJS' 카테고리의 다른 글

AngularJS  (0) 2013.10.13
angularjs - AngularJS tags  (0) 2013.10.13
Posted by linuxism
,


자바스크립트 성능 향상의 키 #1 - DocumentFragment

2011/08/09 11:30



웹 페이지 상에서 자바스크립트 성능 향상을 위한 방법을 몇가지를 연재물로 공유해 보도록 하겠습니다.

자바스크립트로 DOM 객체를 생성하고 핸들링 할 때, 간단히 성능을 높이는 방법으로 DocumentFragment 를 쓰는 방법이 있습니다.

DocumentFragment  가 무엇이고 왜 속도가 올라가는지, 어떻게 사용하는지에 대해 알려드리겠습니다.

 

먼저, DocumentFragment 란.. 웹 페이지에 객체를 생성할 때 생성 객체를 Body 에 넣기 전에 미리 만들어 두는 것을 말합니다.

그럼 어디서 성능 향상 포인트가 발생하는지 알아보겠습니다.

 

웹브라우져는 웹페이지 내부의 View 영역 안에 보이는 객체에 변화가 생길 때 reflow 를 하게 됩니다.

 

re·flow

명사[U], (전문 용어)

1.리플로 납땜, 리플로 연납접(땜납을 녹여 금속을 이어붙이는 것)
2.리플로(컴퓨터 화면에서 텍스트가 차지하는 공간을 조정하는 것)

 

reflow란, 뷰 영역을 재조정 하는 프로세스인데, 한 객체의 크기가 변경될 경우 다른 여러 객체들의 크기도 함께 영향 받을 수 있습니다. 이런 것들을 재조정 하는 과정이라고 보시면 됩니다. 예를들어 텍스트 글자가 길어져서 다음 라인으로 넘어가니, 그걸 담는 박스 높이도 길어지고.. 아래 있는 객체를 밀어내고 하는 등등.. 하나의 객체만 움직여도 수많은 다른 객체들이 영향 받을 수 있고.. 그 객체들이 영향 받을 때에도 또다시 reflow 가 발생해서 사이드이팩트가 꽤 크게 발생하기도 합니다.

 

이 reflow 를 최소화 함으로써 얻을 수 있는 성능 향상 방법으로 DocumentFragment 를 사용 할 수 있습니다.

 

만약 DIV 객체를 1 개 생성하고 그 안에 A 태그 객체를 1000 개 생성한다고 합시다.

 

1. Div 생성

2. Body 에 Div 넣기

3. A 생성

4. Div 에 A 넣기*1000

 

이걸 스크립트로 하면 다음과 같습니다. (시간측정도 포함 했습니다)

[Result] 눌러서 동작을 확인해보세요. 안보이시면 - http://jsfiddle.net/hbsto/EM5cs/



다음은 DocumentFragment 를 사용해 보겠습니다.

 

1. Div 생성

2. A 생성

3. Div에 A넣기*1000

4. Body 에 Div 넣기

 

이걸 스크립트로 하면 다음과 같습니다.

http://jsfiddle.net/hbsto/Uvzp5/

    

IE9 기준으로 첫번째는 575ms, 두번째는 514ms 가 나왔습니다.

엄청 간단한 방법으로, 이렇게 단순한 구조의 DOM 에서도 10% 가까운 향상을 확인 할 수 있습니다.

 

달라진 점은 Body 에 Div 가 들어가는 타이밍 뿐입니다.

첫번째에서 Div 는 시작과 함께 Body 에 들어가서 매번 A 를 추가 했습니다.

두번째는 Div 를 먼저 만들어 놓고 A를 추가한 후 맨 마지막에 Body 에 추가 했습니다.

 

첫번째는 reflow 가 1001 번 일어나게 됩니다.

두번째는 reflow 가 1번 일어나게 됩니다.

아직 Body 안에 들어가지 않은 객체는 단지 메모리에 생성된 아주 가벼운 형태로 존재하며, 이 상태에서 자식을 추가하는 행위는 View 영역에 아무런 영향을 주지 않게 되어 reflow 가 발생하지 않습니다.

 

이 성능 향상 방법은 웹 페이지 내의 객체가 복잡하게 구성되어 있고 CSS 가 다양할수록 효과가 커집니다.

객체를 실시간 생성할 때에는 Container 에 해당하는 부모 객체를 가장 마지막에 DOM Tree 에 넣어 주는 것이 유리합니다~_~


CCL

출처 - http://blog.naver.com/PostView.nhn?blogId=bitofsky&logNo=50117823005&categoryNo=0&parentCategoryNo=0&viewDate=&currentPage=8&postListTopCurrentPage=1&userTopListOpen=true&userTopListCount=5&userTopListManageOpen=false&userTopListCurrentPage=8






JavaScript 최적화: DOM 핸들링 속도 개선

JavaScript 최적화: DOM 핸들링 속도 개선

들어가기 전에

자바스크립트는 오늘날 웹 서비스 개발에서 뗄레야 뗄 수 없는 언어이다. 자바스크립트를 통하여 누구나 손쉽게 문서 객체 모델 (Document Object Model이하 DOM)을 자유롭게 조작하여 역동적인 웹 페이지를 만들 수 있다. 이렇게 대부분의 웹 사이트에서 사용하고 있는 스크립트를 사용한 DOM의 조작 기능에는 성능상의 문제가 있다. 이 글에서는 스크립트를 사용한 DOM 조작 시 발생하는 성능저하 문제점과 그 최적화에 대해 알아본다.

 

DOM의 빠른 갱신 시 발생하는 성능 문제

낮은 버전의 브라우저 특히 IE 에서는 스크립트를 사용하여 DOM을 빠르게 갱신하는 경우 심각한 성능저하를 발생시킨다. 아직까지 각 사이트에서 많이 사용되는 IE7을 기준으로 예제를 작성하였다.

< 예제1> 테이블 병합

이 스크립트는 테이블을 읽어 첫번째 열을 2개씩 묶어 병합하는 스크립트 코드이다. 위 예제를 통해 IE7에서의 성능저하를 체감하기 위해서는 CSS 적용이 필요하다. 눈에 띄는 결과를 보여줄 수 있는 테스트를 위해 2개의 열과 1000개의 행을 가진 테이블을 준비하였으며 아래와 같은 CSS를 설정하였다.

td, th{font-size: 13px;border: 1px solid black;}
th{background-color: #000000; height:40px;color: white;}
td{background-color: #EEEEEE; height:20px;color: black;}
table{border: 1px solid black;border-collapse:collapse;}

<예제1-1> 예제 CSS

브라우저병합 소요시간(초)
IE 78.852s
IE 80.224s
IE 90.217s
Fire Fox (25)0.010s
Chrome (30)0.014s

<표1> 테이블 병합 스크립트[예제1] 브라우저 별 소요시간

위 예제의 결과로 IE7에서의 큰 성능저하를 볼 수 있다. 먼저 원인을 짚고 넘어가보자. 성능 저하의 원인은 스크립트가 DOM을 수정함으로써 생기는 리플로우(reflow)에 있다. 리플로우란 브라우저가 DOM요소들을 다시 렌더링하는 과정을 말하는데 최신 브라우저는 DOM이 빠르게 갱신될 때 과도한 렌더링으로 인한 소요시간 증가를 막아주지만 IE7은 그렇지 않다. 예제에서는 500번의 열 크기 변환과 500번의 열 삭제 총 1000번의 리플로우가 있었다. 이러한 많은양의 리플로우가 발생하는 경우 브라우저는 큰 성능저하를 보여주며 웹 페이지의 DOM이 복잡하게 구성되어 있고 CSS가 많이 적용된 사이트일수록 더욱 심해진다.

이 문제의 해결책은 바로 DocumentFragment를 사용하는 것이다. DocumentFragment는 화면상에 표시되지 않는 document-like한 컨테이너로 DOM이 갱신될 때 마다 과도한 리플로우를 하지 않도록 한다

<예제2>DocumentFragment를 사용한 리플로우를 최소화 한 DOM 갱신

 

브라우저병합 소요시간(초)
IE 73.647s
IE 80.166s
IE 90.044s
Fire Fox (25)0.008s
Chrome (30)0.006s

<표2> – 브라우저 별 병합 스크립트 소요시간 2

1000회의 리플로우 횟수를 2회로 줄인 결과 예제 스크립트는 IE7에서 기존 코드 대비 약 3배의 성능향상이 있었다. 다른 브라우저도 컴퓨터 성능에 따라 약간씩 다르지만 평균적으로 2배의 성능향상을 보여준다. IE에서 가장 성능저하가 매우 심한 테이블로 예제를 작성하였으나 리플로우를 줄임으로써 얻는 최적화 방식은 IE만이 아닌 모든 브라우저에서 스크립트로 DOM을 조작하는 모든 영역에서 활용할 수 있는 방식임을 예제를 통해 알 수 있다.

DocumentFragment는 본래 용도인 생성에도 빠른 성능을 보여준다.

<예제3-1> appendChild를 사용한 DOM 그리기

 

<예제3-2> documentFragment를 사용한 DOM 그리기

브라우저예제3-1 소요시간(초)예제3-2 소요시간(초)
IE 70.201s0.080s
IE 80.104s0.030s
IE 90.044s0.020s
Fire Fox (25)0.005s0.002s
Chrome (30)0.003s0.000s

<표2> 브라우저별 DOM 그리기 소요시간

단순한 DOM 변경에도 평균적으로 2배의 성능향상이 있었다.

마치며

DocumentFragment을 사용한 DOM 조작 스크립트 최적화 방법은 웹 개발에서 흔하게 무시되곤 하는 영역이다. 최신 브라우저의 경우 리플로우가 발생하지 않도록 최적화되고 성능이 향상되어 그 차이가 체감되지 않기 때문이다. 그러나 ActiveX 기반의 국내 웹 환경상 낮은 버전의 IE를 요구하는 경우가 분명히 존재하며 그런 환경이 아니더라도 많은 DOM을 조작해야만 하는 역동적인 화면에서의 브라우저의 부담을 덜어주고 빠른 화면갱신을 위해서 DocumentFragment를 활용하면 충분한 최적화를 얻을 수 있을 것이다.

 

브라우저별 DocumentFragment 사용과 미사용 성능차이

chart



출처 - http://www.nextree.co.kr/p2081/







'Development > JavaScript' 카테고리의 다른 글

javascript - iife setTimeout  (0) 2014.04.01
javascript - window.JSON 객체  (0) 2014.03.27
javascript - requirejs와 AMD  (0) 2014.01.19
javascript - CommonJS와 AMD  (0) 2014.01.19
javascript - url link 자동 인식(autoLink) 처리  (0) 2014.01.08
Posted by linuxism
,