[C++] URL로부터 바이너리 데이터 다운로드

예전에 만들어 놓은 것이 있는데.. 도통 찾을 수가 없어서 다시 만들어 본 함수입니다. URL 경로에 존재하는 데이터를 다운로드하여 로컬 파일로 저장해 주는 함수입니다. 실제 개발에 사용할 요량으로 인자가 제법 복잡합니다.

DWORD Download(HINTERNET hInternet, char *pszURL, 
    char *pszFileName, BYTE *pBuffer) {
    HINTERNET hURL = InternetOpenUrl(hInternet, pszURL, NULL, 0, 0, 0);
    if(hURL == NULL) {
        InternetCloseHandle(hInternet);
        return -2;
    }

    HANDLE hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, 
        NULL, CREATE_ALWAYS,     FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile == INVALID_HANDLE_VALUE) return -3;

    DWORD dwSize = 2048;
    DWORD dwRead, dwWritten, dwTotalSize;

    bool bOK = HttpQueryInfo(hURL, HTTP_QUERY_CONTENT_LENGTH, 
        pBuffer, &dwRead, NULL);
    if(!bOK) return -4;
    dwTotalSize = atoi((const char *)pBuffer);

    do {
        InternetQueryDataAvailable(hURL, &dwSize, 0, 0);
        InternetReadFile(hURL, pBuffer, dwSize, &dwRead);
        WriteFile(hFile, pBuffer, dwRead, &dwWritten, NULL);
    } while(dwRead != 0);

    InternetCloseHandle(hURL);
    CloseHandle(hFile);

    return dwTotalSize;
}

아래는 위의 함수를 직접 사용하는 코드입니다.

HINTERNET hInternet = InternetOpen("MyAGENT", INTERNET_OPEN_TYPE_PRECONFIG, 
    NULL, NULL, 0);
if(hInternet == NULL) return 0;

BYTE *pBuffer = new BYTE[1024*1024];
DWORD dwTotalSize;
 
dwTotalSize = Download(hInternet, "http://www.s.com/a.zip", "c:/a.zip", pBuffer);
printf("TotalSize: %d\n", dwTotalSize);

dwTotalSize = Download(hInternet, "http://www.s.com/b.zip", "c:/b.zip", pBuffer);
printf("TotalSize: %d\n", dwTotalSize);

delete [] pBuffer;
InternetCloseHandle(hInternet);

1번 코드에서처럼 가장먼저 hInternet 객체를 만듭니다. 이 객체를 재활용하여 다수의 URL을 통해 파일을 다운로드할 수 있습니다. 5번 코드는 파일을 다운로드하는데 사용하는 버퍼입니다. Download 함수를 여러번 사용할 것을 대비하여 버퍼를 재활용할 수 있도록 하였습니다. 또한 Downoad 함수의 결과값은 다운로드된 바이너리 데이터의 전체 바이트 수입니다. 음수인 경우 ERROR로 간주할 수 있습니다. 14번과 15번 코드처럼 사용한 리소스는 반환합니다.

추가로 아래의 Download2 함수는 다운로드된 데이터를 파일로 기록하지 않고 메모리 버퍼에 저장하는 함수입니다. 다운로드된 데이터를 파일에 저장하지 않고 바로 메모리 상에서 사용하고자 할때 사용할 수 있습니다.

DWORD Download2(HINTERNET hInternet, char *pszURL, BYTE *pBuffer) {
    HINTERNET hURL = InternetOpenUrl(hInternet, pszURL, NULL, 0, 0, 0);
    if(hURL == NULL) {
        InternetCloseHandle(hInternet);
        return -2;
    }

    DWORD dwSize;
    DWORD dwRead, dwWritten, dwTotalSize;
    DWORD dwCursor = 0;

    bool bOK = HttpQueryInfo(hURL, HTTP_QUERY_CONTENT_LENGTH, pBuffer, 
        &dwRead, NULL);
    if(!bOK) return -4;
    dwTotalSize = atoi((const char *)pBuffer);

    do {
        InternetQueryDataAvailable(hURL, &dwSize, 0, 0);
        InternetReadFile(hURL, (LPVOID)(pBuffer + dwCursor), dwSize, &dwRead);
        dwCursor += dwRead;
    } while(dwRead != 0);

    InternetCloseHandle(hURL);

    return dwTotalSize;
}

끝으로 이 함수 사용을 위해 헤더 파일로 wininet.h와 라이브러리 파일로 wininet.lib가 필요합니다.

[JavaScript] 클래스 정의 API ㅡ 3/3

이제 끝으로 Rectangle를 상속받는 PositionedRectangle 클래스를 정의해 보겠습니다. Rectangle 클래스는 단순히 가로와 세로 크기값만을 가지고 있습니다. 여기에 위치값까지 갖도록 한 클래스가 바로 PositionedRectangle 클래스입니다.

var PositionedRectangle = Class({
    name: "PositionedRectangle",
    extend: Rectangle,
    construct: function(x,y,w,h) {
        this.superclass(w, h);
        this._x = x;
        this._y = y;
    },
    methods: {
        draw: function() {
            alert("draw PositionedRectangle");
        },
        getPosition: function() {
            return new Coordinate(this._x, this._y);
        }
    },
    requires: [aShape],
    statics: {
        isSame: function(a, b) {
            return a._x == b._x && a._y == b._y && 
                a._width == b._width && a._height == b._height;
        }
    }
});

중요한 부분은 바로 3번 코드로 상속받고자 하는 클래스를 지정하는 extend입니다. 바로 이 extend로 지정된 클래스의 모든 속성값을 상속받게 되는데 이 PositionedRectangle 클래스는 Rectangle 클래스의 모든 속성(변수와 매서드)를 상속받게 됩니다.

추가로 18번 코드는 정적 속성(변수와 매서드)를 정의하는 방법입니다. 이는 C++과 자바와 같은 언어에서 클래스 차원에서 제공하는 바로 그 기능입니다.

이제 이렇게 정의한 클래스들을 직접 사용하는 예를 보이면 다음과 같습니다.

var c = new Circle(100, 100, 30);
var r = new PositionedRectangle(10, 10, 100, 100)

alert(r instanceof Rectangle);
alert(r instanceof PositionedRectangle);
alert(r instanceof Object);
alert(r instanceof Circle);

c.draw();
r.draw();

alert(c.getCenter().toString());
alert(PositionedRectangle.isSame(r, r));

1번과 2번 코드는 클래스를 통해 객체를 생성하고 있습니다. 그리고 4~7번 코드에서 각 객체가 어떤 클래스의 인스턴스인지를 검사하고 있습니다. 결과값은 순서대로 true, true, true, false입니다. 그리고 9~10번 코드는 draw 함수를 호출하고 있습니다. 마친가지로 12번 ~ 13번 코드는 정의한 매서드를 사용하고 있습니다.

이상으로 클래스 정의 API에 대한 내용을 마무리합니다. 중요한 것은 자바스크립트는 결코 완벽한 클래스 정의를 제공해주지 못합니다. 이는 언어적 한계입니다. 이러한 한계를 해결한 버전이 자바스크립트 2.0입니다. 자바스크립트 2.0이 나오기 전까지는 이러한 클래스 정의 API를 개발자가 직접 만들고 이해하여 사용해야 하며 스스로에 대한 약속을 정해 반드시 준수해야 합니다. 이러한 약속은 private 변수의 경우 변수명 앞에 밑줄(_)을 넣는 등에 대한 것입니다. 가장 중요한 것은 문서화 입니다. 자바스크립트는 매우 유연한 언어이므로 문서화를 통해 약속을 분명히 기술하여 지킬 수 있도록 해야합니다.

[JavaScript] 클래스 정의 API ㅡ 2/3

총 5개의 클래스가 존재하는데 먼저 가장 간단한 Coordinate 클래스를 정의하면 다음과 같습니다.

var Coordinate = Class({
    name: "Coordinate",
    construct: function(x, y) {
        this._x = x;
        this._y = y;
    },
    methods: {
        getX: function() {
            return this._x;
        },
        getY: function() {
            return this._y;
        },
        toString: function() {
            return "(" + this._x + ", " + this._y + ")";
        }
    }
});

앞서 언급했듯이 자바스크립트에서 클래스의 정의는 개발자 스스로에 대한 약속이 필요하다고 하였습니다. 그 약속에 해당하는 부분이 바로 4,5번 코드에 있는 _x, _y 변수입니다. 변수명 앞에 밑줄(_)을 하였음으로 해서 이 변수는 private 변수라는 약속을 정한 것입니다. 개발자 스스로의 약속이므로 이 약속을 깼다고 하여도 자바스크립트는 어떤한 오류를 제공하지 않습니다. 물론 이러한 약속에 대해서 경고를 제공하도록 할 수 있으나 그렇게 되면 클래스 정의 API가 더욱 복잡해지게 될 것입니다. 그리고 7번의 methods 객체에 필요한 매서드를 추가하였습니다.

이제 다음은 추상 클래스인 Shape 클래스에 대한 코드입니다.

var aShape = Class({
    name: "aShape",
    methods: { 
        draw: function() { throw "abstract method"; }
    }
});

코드를 살펴보면, 4번에 draw라는 추상 매서드를 제공하였습니다. draw 매서드 역시 추상 매서드다라는 약속이지 자바스크립트 언어 차원에서 추상 매서드로 강제하지 않습니다. 대신 이 매서드를 호출하게 되면 예외를 발생하도록 하였습니다.

다음으로 위의 추상클래스인 Shape를 구현하는 Rectangle와 Circle 클래스에 대한 코드를 살펴보겠습니다.

var Rectangle = Class({
    name: "Rectangle",
    construct: function(w, h) {
        this._width = w;
        this._height = h;
    },
    methods: {  
        draw: function() { 
            alert("draw Rectangle");
        },
        getWidth: function() {
            return this._width;
        },  
        getHeight: function() {
            return this._height;
        }
    },
    requires: [aShape]
});

var Circle = Class({
    name: "Circle",
    construct: function(x, y, r) {
        this._x = x;
        this._y = y;
        this._r = r;
    },
    methods: {
        draw: function() {
            alert("draw Circle");
        },
        getCenter: function() {
            return new Coordinate(this._x, this._y);
        },
        getRadius: function() {
            return this._r;
        }
    },
    requires: [aShape] 
});

동시에 Rectangle와 Circle 클래스에 대한 코드를 보이는 이유는 이 두개가 서로 동일한 구조를 갖고 있음을 강조하기 위함입니다. 이 두 클래스는 Shape의 추상 매서드인 draw를 분명히 구현하고 있으며 각기 자신에게 맞는 매서드를 추가적으로 정의하고 있습니다. 여기서 추상매서드를 구현하고 있는지에 대한 검사는 18번과 39번의 코드에서처럼 requires 속성을 통해 검사를 해주게 됩니다. 만약 이 requires 속성을 통해 추상 매서드를 구현하지 않았을 경우 예외를 친절하게 예외를 발생해 주도록 클래스 정의 API를 구현하였습니다.

[JavaScript] 클래스 정의 API ㅡ 1/3

자바스크립트는 C++이나 C#/Java 등에서와 같이 클래스(Class)를 직접적으로 정의할 수 있는 API를 제공해 주지 않습니다. 그러나 자바스크립트에서는 프로토타입(Prototype)을 이용하여 클래스의 특성을 제공할 수 있습니다. 그러나 이러한 프로토타입 기반의 클래스 정의에 대한 API는 개발자가 직접 만들어 사용해야 합니다. 게다가 비록 프로토타입을 이용해 클래스 개념을 자바스크립에서 사용할 수 있다고는 하여도 C++이나 C#/Java와 같은 완전한 클래스를 정의하지는 못합니다. 대신 개발자는 스스로 엄격한 약속을 통해 이러한 부족함으로 매꿔야만 합니다.
아래의 코드는 자바스크립트에서 클래스를 정의하기 위한 API의 한가지 예입니다. 자바스크립트를 이용해 클래스를 정의하는 API는 매우 다양하고 많습니다. 그중 제가 필요에 의해 아래의 제시한 클래스 정의 API는 데이비드 플래너건의 자바스크립트 완벽가이드라는 책을 많은 부분 참고했으며 불필요한 부분을 제거한 예입니다.

function Class(x) {
    var classname = x.name;
    var superclass = x.extend || Object;
    var constructor = x.construct || function() {};
    var methods = x.methods || {};
    var statics = x.statics || {};
    var requires = x.requires || [];
    var proto = new superclass();

    for(var p in proto) if(proto.hasOwnProperty(p)) delete proto[p];
 
    proto.constructor = constructor;
    proto.superclass = superclass;
    proto.classname = classname; 
    constructor.prototype = proto;
 
    for(var p in methods) proto[p] = methods[p];
    for(var p in statics) constructor[p] = statics[p];

    for(var i=0; i        var c = requires[i];
        for(var p in c.prototype) {
            if(typeof c.prototype[p] != "function" || 
                p == "constructor" || p == "superclass") continue;
   
            if(p in proto && typeof proto[p] == "function" && 
                proto[p].length == c.prototype[p].length) continue;
    
            throw new Error(classname + " class requires " + p + " method.");
        }
    } 

    return constructor;
}

위의 클래스 정의 API는 다음과 같이 클래스의 4가지 본연의 기능에 충실할 것을 전제로 하였습니다.

  • 클래스를 정의하는 방법 제공할 것
  • 클래스 상속을 지원할 것
  • 추상 클래스을 구현할때 해당 추상 클래스의 모든 매서드를 구현하고 있는지 검사할 것
  • 객체가 어떤 클래스의 타입인지 검사할 수 있도록 할 것

위의 전제 조건들에 대해서 실제 위의 클래스 정의 API를 대한 사용예를 통해 살펴보겠습니다. 위의 클래스 정의 API를 통해 다음과 같은 클래스 계층을 구성하고자 합니다.


사용자 삽입 이미지Shape는 draw라는 매서드를 갖는 추상 클래스입니다. 그리고 이 Shape를 구현하는 클래스로 Rectangle과 Circle 클래스가 있습니다. 여기에 PositionedRectangle라는 클래스는 Rectangle를 상속받습니다. 또한 Coordinate라는 클래스가 존재하며 이 클래스는 PositionedRectangle와 Circle 클래스 사용합니다.

[G1X] 손 길..


사무실이 강변역 바로 옆이다. 그리고 집은 강변역에서 버스를 타고 간다. 늦은 시간까지 외부에서 다른 일을 보다 집에 갈때 버스를 타기 위해 강변역을 들려야 한다.. 하지만 지금처럼 다소 늦은 시간이면 집 대신 사무실로 발길을 돌린다..