2012년 5월 5일 토요일

Browser에서의 Dart, Server에서의 Dart. 간단한 Http예제

이 문서는 DartWatch 의 번역 문서 입니다.^_^

Dart를 Client(Browser상에서)와 Server에서 사용할 수 있는지에 대해 물어보는 글 들을 메일링 리스트와 Stack Overflow에서 심심치 않게 볼 수 있습니다. 그래서 이 번 포스트에서는 예제를 통해 어떻게 하는 것인지 보여드리고자 합니다.

Server Side

첫번째로 Server Side에 대해서 다루고자 합니다. Dart는 Server에서 Python, Ruby그리고 Node.js와 같은 Server Side Script들 처럼 사용할 수 있습니다. Windows에서는 dart.exe <실행.dart> 방식으로 사용 됩니다. 다음 예제를 보시겠습니다.

//MyScript.dart
main() => print("Hello Dart");

그리고, 이것을 Console 에서 "dart.exe MyScript.dart"을 실행 시키면 다음과 같은 결과물을 볼 수 있습니다.

Hello Dart

그럼에도 불구하고 우리는 좀 더 유용한 것을 원합니다. 그래서 저는 다음 Diagram을 통해 어떻게 어플리케이션을 만들 수 있는지 계획을 세웠습니다.


Server Side Component는 2가지 행동을 실행 할 것입니다.
1. Http Protocol을 통해서 브라우저에게 다음 static 파일들을 전송할 것입니다.

- MyApp.html
- MyApp.dart
- MyApp.dart.js

2. 나머지 GET Http Request에 대해서는 요청된 이름을 가진 JSON 데이터를 전송합니다. 예를 들면 http://localhost:8080/helloWorld(/helloWorld가 GET됩니다.)를 호출 하게 되면, 다음과 같은 JSON 데이터가 전송 됩니다.

{"get":"helloWorld"}

이를 위해서 우리는 dart:io Library를 통해 HTTPServer를 이용해야 합니다. 모든 Dart 앱들은 main() 함수를 통해 시작이 되므로, MyServer.dart라는 화일을 다음과 같이 구성해 보겠습니다.

#import("dart:io"); // dart:io Library를 가져옵니다.


main() {
    HttpServer server = new HttpServer(); // Server를 생성합니다.

    serve.listen("127.0.0.1", 8080); // Listening을 시작합니다.

}

Server는 "dart.exe MyServer.dart"를 통해 실행 시킬 수 있습니다. 아직 이것은 아무 동작도 되지 않습니다. 그래서 우리는 Request에 반응 할 수 있는 Callback함수를 추가해야 합니다. 처음엔 기본적인 Response를 추가하겠습니다. 이 함수는 HttpRequest를 통해 URL을 분석하여 JSON 데이터를 생성하여 HttpResponse를 통해 데이터를 내려주는 것입니다. Callback은 defaultRequestHandler 속성을 통해서 할당 합니다.

#import("dart:io"); // dart:io Library를 가져옵니다.

#import("dart:json"); //json Library를 가져옵니다.


main() {
    HttpServer server = new HttpServer(); // Server를 생성합니다.

    server.defaultRequestHandler = defaultHandler; // Handle 함수를 할당 합니다.(하단에 있습니다)

    serve.listen("127.0.0.1", 8080); // Listening을 시작합니다.

    print("http://127.0.0.1:8080 서버가 활성화 되었습니다");

}

defaultHandler(HttpRequest req, HttpResponse res) {

    Map data = new Map(); // Response를 위한 Map을 생성합니다.

    print("${req.path}가 요청 되었습니다");

    data["get"] == req.path; // 요청된 Path를 data에 담습니다.

    String responseData = JSON.stringify(data); // map을 JSON으로 변경 합니다.

    res.outputStream.writeString(responseData); // Client 에게 응답을 합니다.

    res.outputStream.close(); // 응답을 닫습니다.

}


이제 Script를 실행 하고, 브라우저에 http://localhost:8080/hello를 입력하게 된다면 JSON데이터를 받을 수 있습니다.

두 번째로 실행 할 것은 Static파일 제공하기 입니다. 1회 요청에 3개 파일을 Load하고, 그 데이터를 응답하게 하는 방법을 알아보도록 하겠습니다.  MyApp.html을 요청 받고, Browser에 정상적으로 데이터를 보내는 예제를 만들어 보도록 하겠습니다.

#import("dart:io"); // dart:io Library를 가져옵니다.
#import("dart:json"); //json Library를 가져옵니다.

main() {

    HttpServer server = new HttpServer(); // Server를 생성합니다.
    server.addRequestHandler(matchMyApp, myAppHandler); // 만약에 "myApp"로 시작하는 요청이 있으면 Handle합니다.
    server.defaultRequestHandler = defaultHandler; // Handle 함수를 할당 합니다.(하단에 있습니다)
    serve.listen("127.0.0.1", 8080); // Listening을 시작합니다.
    print("http://127.0.0.1:8080 서버가 활성화 되었습니다");

}



defaultHandler(HttpRequest req, HttpResponse res) {

    Map data = new Map(); // Response를 위한 Map을 생성합니다.
    print("${req.path}가 요청 되었습니다");
    data["get"] == req.path; // 요청된 Path를 data에 담습니다.
    String responseData = JSON.stringify(data); // map을 JSON으로 변경 합니다.
    res.outputStream.writeString(responseData); // Client 에게 응답을 합니다.
    res.outputStream.close(); // 응답을 닫습니다.

}

bool matchMyApp(HttpRequest req){

    bool result = req.path.startsWith("/MyApp"); // MyApp으로 시작 되면
    print("MyApp과 일치여부 : $result");
    return result;

}

// 파일을 열고 Client에게 전송한다.
void myAppHandler(HttpRequest req, HttpResponse res) {
    File file = new File(".${req.path}"); // 요청한 파일을 가져옵니다. 예): ./MyApp.html
    file.openInputStream().pipe(res.outputStream); // 요청에 응답합니다.
}

이제 Server를 돌리게 되면, Client가 MyApp.html을 호출 시 정상적으로 응답해줄 수 있습니다.(지금은 Editor 마법사에 의해 Dart 앱이 만들어질 것입니다.)
GitHub에서 이 파일을 다운로드 받고 실행 하신 후, http://localhost:8080/MyApp.html 혹은 http://localhost:8080/helloWorld를 실행해보시기 바랍니다.
(이것은 에디터 빌드넘버 7232 에서 안정적으로 동작합니다.)

Client Side Dart...


Client Side의 Dart를 시작해봅시다. 우선 Editor에서 New > Web Application으로 예제 앱을 만들어 봅니다.( Server Side Dart에서 호출이 될 수 있도록 MyApp이라고 이름을 설정합니다.)

Editor는 MyApp.dart와 MyApp.html 파일을 생성할 것입니다.MyApp.dart는 최초에 다음과 같을 것입니다.
#import('dart:html');

class MyApp{
    MyApp(){
    }
    void run() {
        write("Hello World!");
    }
    void write(String message) {
        // HTML library는 "document" 전역 변수를 정의 합니다.
        document.query('#status').innerHTML = message;
    }
}

void main(){
    new MuApp().run(); // Class의 instance를 생성하고, run함수를 호출 합니다.

    // TODO : ui element를 이곳에 더 추가합니다.
}

Editor에서 "Tools > Generate Javascript"를 선택 하면 MyApp.dart.js 파일을 생성할 수 있습니다. HTML 파일을 살펴보면, Dartium에서는 자동으로 MyApp.dart 파일을 Load할 수 있도록 링크가 걸려 있을 것입니다. 그리고, 이 HTML파일은 Dartium이 아닌 브라우저를 위해서 MyApp.dart.js를 Load할 수 있도록 되어있습니다.
이 파일들은 MyServer.dart와 같은 폴더에 위치 되어야 합니다. 다시 한번 dart.exe를 이용해 MyServer.dart를 실행 시키고, http://127.0.0.1:8080/MyApp.html 을 요청 해보면 정상적으로 동작 됨을 볼 수 있습니다.

MyApp.dart를 좀 더 개선해보기로 하겠습니다. UI Element들을 더 추가하고, XMLHttpRequest(XHR)을 이용하여 Server Side에서 응답하는 JSON데이터를 받아보는 함수를 만들어보도록 하겠습니다.
이 함수는 Textbox, Button 그리고 서버에서 응답하는 데이터를 표시 할 수 있도록 Span Tag를 추가 할 것입니다. 그리고, Button을 클릭하면 Textbox에 입력된 URL에 XMLHttpRequest 객체를 이용하여 "GET"요청을 실행 할 것입니다. 예를 들어, "HelloWorld"를 TextBox에 입력 하고 Button을 클릭하게 되면 "http://127.0.0.1:8080/HelloWorld"에 요청하고 해당 JSON데이터로 응답할 것입니다. 아마도 {"get":"HelloWorld"}가 될 것입니다.

코드로 표현 해보도록 하겠습니다.
#import('dart:html');

class MyApp{
    MyApp(){
    }
    void run() {
        write("Hello World!");
    }
    void write(String message) {
        // HTML library는 "document" 전역 변수를 정의 합니다.
        document.query('#status').innerHTML = message;
    }
}

void main(){
    new MuApp().run(); // Class의 instance를 생성하고, run함수를 호출 합니다.

    addUiElements() 
}

addUiElements() {
  // UI를 추가합니다.
  var elements = new Element.html("""
Enter some text:
Request=
Response=
"""); document.body.nodes.add(elements); ButtonElement button = document.body.query("#myButton"); // Button을 선택합니다. // Button에 Click이벤트를 추가합니다. button.on.click.add(EventListener (data) { // TextBox에서 URL을 가져와 XHR호출을 하게 됩니다. // URL을 가져옵니다. InputElement textbox = document.body.query("#myTextbox"); // TextBox를 선택합니다. String url = "http://127.0.0.1:8080/${textbox.value}"; // 중요: url은 호출 된 서버와 같아야 합니다. // 만약에 http://localhost 로 호출을 하였다면, // url도 http://localhost로 변경해야 합니다. // 그렇지 않다면 Access-Control-Origin 에러가 발생 됩니다. // UI에 url을 적습니다. SpanElement requestSpan = document.body.query("#requestSpan"); // 요청을 기록할 Span requestSpan.innerHTML += "GET: $url"; // UI에 요청을 적습니다. // 서버로 부터 응답이 오면 실행 되는 부분 XMLHttpRequest request = new XMLHttpRequest.get(url, (XMLHttpRequest request) { SpanElement responseSpan = document.body.query("#responseSpan"); // 응답 Span responseSpan.innerHTML += "${request.responseText}"; // 응답을 출력합니다. }); }); }

MyApp.dart를 수정 한 후, 다시 한번 Javascript 생성을 해주시기 바랍니다.
여기까지의 코드는 GitHub에서 받아 볼 수 있습니다. 총 4개의 파일로 구성 되어있습니다.(MyServer.dart, MyApp.html, MyApp.dart, MyApp.dart.js)
간단히 이 파일들을 한 폴더 안에 위치 시키고, 다음 명령어를 내려보세요.
path/to/dart.exe MyServer.dart
그 이후, Daritum 혹은 브라우저로 다음 URL을 실행 해보세요.
http://127.0.0.1:8080/MyApp.html
Cross Origin에러가 발생 되었다면, XMLHttpRequest가 다른 서버에서 호출하고 있다는 거을 뜻합니다. 서버가 http://127.0.0.1:8080 인지 재확인 해주시기 바랍니다.

만약에 문제가 없다면 다음과 같은 화면을 보실 수 있게 됩니다.




여러분이 Dart를 Client Side와 Server Side에서 어떻게 사용하는지 이해되었기를 희망합니다.
이 Server Side Implementation은 믿을 수 없을 정도로 기초적입니다. 어떠한 Http Header를 제공하지 않습니다. 예를 들어, MyApp이라는 것만 골라내는 코드를 만들기는 상당히 어렵습니다. 여러분들이 한 번 도전 해보시기 바랍니다.

비록 이 블로그에서는 Dart를 Client Side와 Server Side에서 동시에 사용하고 있습니다만, 여러분들은 다른 언어로 만들어 볼 수 있습니다. 예로, Server Side Dart + Client Side Flash 혹은 Server Side Ruby + Client Side Dart 등등이 되겠습니다.

JSON을 다루는 것을 더 알기 원한다면, 이 문서를 참조 해주세요.

댓글 없음:

댓글 쓰기