사용자 도구

사이트 도구


kb:httpget

HTTP / GET

HTTP를 이용해서 파일을 다운로드받기

소켓을 이용하는 방법

내부적으로 GET 명령어를 이용해서 파일 내용을 받아온다.

파일 사이즈만을 알아내기 위해서는 HEAD 명령어를 사용하면 된다. 보통 다음과 같은 모양의 데이터가 날아오는데, 필요한 부분을 파싱해서 쓰면 된다.

HTTP/1.1 200 OK 
Accept-Ranges: bytes 
Date: Sat, 27 Jul 2002 10:58:14 GMT 
Content-Length: 776635 
Content-Type: image/jpeg 
Server: Apache/1.3.12 (Unix) mod_ssl/2.6.5 OpenSSL/0.9.5a 
Last-Modified: Mon, 24 Jul 2000 00:13:24 GMT 
ETag: "9b656-bd9bb-397b8a24" 
Via: 1.1 cache-6100-edi (NetCache NetApp/5.2.1R1D12) 
// GetHTTP.cpp
//
// Retrieves a file using the Hyper Text Transfer Protocol
// and prints its contents to stdout.
//
// Pass the server name and full path of the file on the 
// command line and redirect the output to a file. The program
// prints messages to stderr as it progresses.
//
// Example:
//      GetHTTP www.idgbooks.com /index.html > index.html
 
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <winsock.h>
 
void GetHTTP(LPCSTR lpServerName, LPCSTR lpFileName);
 
// Helper macro for displaying errors
#define PRINTERROR(s)   \
        fprintf(stderr,"\n%s: %d\n", s, WSAGetLastError())
 
void main(int argc, char **argv)
{
    WORD wVersionRequested = MAKEWORD(1,1);
    WSADATA wsaData;
    int nRet;
 
    // Check arguments
    if (argc != 3)
    {
        fprintf(stderr,
            "\nSyntax: GetHTTP ServerName FullPathName\n");
        return;
    }
 
    // Initialize WinSock.dll
    nRet = WSAStartup(wVersionRequested, &wsaData);
    if (nRet)
    {
        fprintf(stderr,"\nWSAStartup(): %d\n", nRet);
        WSACleanup();
        return;
    }
 
    // Check WinSock version
    if (wsaData.wVersion != wVersionRequested)
    {
        fprintf(stderr,"\nWinSock version not supported\n");
        WSACleanup();
        return;
    }
 
    // Set "stdout" to binary mode
    // so that redirection will work
    // for .gif and .jpg files
    _setmode(_fileno(stdout), _O_BINARY);
 
    // Call GetHTTP() to do all the work
    GetHTTP(argv[1], argv[2]);
 
    // Release WinSock
    WSACleanup();
}
 
void GetHTTP(LPCSTR lpServerName, LPCSTR lpFileName)
{
    // Use inet_addr() to determine if we're dealing with a name
    // or an address
    IN_ADDR     iaHost;
    LPHOSTENT   lpHostEntry;
 
    iaHost.s_addr = inet_addr(lpServerName);
    if (iaHost.s_addr == INADDR_NONE)
    {
        // Wasn't an IP address string, assume it is a name
        lpHostEntry = gethostbyname(lpServerName);
    }
    else
    {
        // It was a valid IP address string
        lpHostEntry = gethostbyaddr((const char *)&iaHost, 
                        sizeof(struct in_addr), AF_INET);
    }
 
    if (lpHostEntry == NULL)
    {
        PRINTERROR("gethostbyname()");
        return;
    }
 
    // Create a TCP/IP stream socket
    SOCKET Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (Socket == INVALID_SOCKET)
    {
        PRINTERROR("socket()"); 
        return;
    }
 
    // Find the port number for the HTTP service on TCP
    LPSERVENT lpServEnt;
    SOCKADDR_IN saServer;
    lpServEnt = getservbyname("http", "tcp");
    if (lpServEnt == NULL) saServer.sin_port = htons(80);
    else                   saServer.sin_port = lpServEnt->s_port;
 
    // Fill in the rest of the server address structure
    saServer.sin_family = AF_INET;
    saServer.sin_addr = *((LPIN_ADDR)*lpHostEntry->h_addr_list);
 
    // Connect the socket
    int nRet;
    nRet = connect(Socket, (LPSOCKADDR)&saServer, sizeof(SOCKADDR_IN));
    if (nRet == SOCKET_ERROR)
    {
        PRINTERROR("connect()");
        closesocket(Socket);
        return;
    }
 
    // Format the HTTP request
    char szBuffer[1024];
    sprintf(szBuffer, "GET %s\n", lpFileName);
    nRet = send(Socket, szBuffer, strlen(szBuffer), 0);
    if (nRet == SOCKET_ERROR)
    {
        PRINTERROR("send()");
        closesocket(Socket);    
        return;
    }
 
    // Receive the file contents and print to stdout
    while(1)
    {
        // Wait to receive, nRet = NumberOfBytesReceived
        nRet = recv(Socket, szBuffer, sizeof(szBuffer), 0);
        if (nRet == SOCKET_ERROR)
        {
            PRINTERROR("recv()");
            break;
        }
 
        fprintf(stderr,"\nrecv() returned %d bytes", nRet);
 
        // Did the server close the connection?
        if (nRet == 0) break;
 
        // Write to stdout
        fwrite(szBuffer, nRet, 1, stdout);
    }
 
    closesocket(Socket);    
}

Windows Internet API를 이용한 방법

WindowsInternetApi 페이지의 HTTP 항목을 참고

인터넷 익스플로러를 이용하는 방법

URLDownloadToFile 함수를 이용하면 인터넷 익스플로러를 통해 파일을 다운로드받을 수 있다. 인터넷 익스플로러가 이어받기/캐시 처리 등을 자동으로 해주기 때문에 편하다.

URLDownloadToFile 함수를 이용하기 위해서는 IBindStatusCallback 인터페이스를 상속받은 클래스를 하나 만들어서, OnProgress 함수를 구현해줘야한다. OnProgress 함수는 다운로드 상태를 사용자에게 보여주기 위한 콜백 함수다. 사용자에게 진행 상황을 보여주지 않아도 된다면, 빈 상태로 둬도 상관 없지만, 기본적인 에러 처리는 집어넣어 주는 것이 좋다.

StatusCallBack.h

#!cpp
#ifndef __STATUSCALLBACK_H__
#define __STATUSCALLBACK_H__
 
#include <windows.h>
#include <urlmon.h>
#include <string>
 
//////////////////////////////////////////////////////////////////////////////
/// \class CCallback
/// \brief 인터넷 익스플로러를 이용해 HTTP로 파일을 다운로드받기 위한 
/// COM 인터페이스 클래스.
//////////////////////////////////////////////////////////////////////////////
 
class CCallback : public IBindStatusCallback
{
private:
    std::string m_URL;     ///< 다운로드받을 파일의 URL
    DWORD       m_Timeout; ///< 타임아웃
 
public:
    /// \brief 생성자
    CCallback(const std::string& url, DWORD timeout=INFINITE);
 
    /// \brief 소멸자
    ~CCallback();
 
public:
    STDMETHOD(OnStartBinding)(
        /* [in] */ DWORD dwReserved,
        /* [in] */ IBinding __RPC_FAR *pib)
        { return E_NOTIMPL; }
 
    STDMETHOD(GetPriority)(
        /* [out] */ LONG __RPC_FAR *pnPriority)
        { return E_NOTIMPL; }
 
    STDMETHOD(OnLowResource)(
        /* [in] */ DWORD reserved)
        { return E_NOTIMPL; }
 
    STDMETHOD(OnProgress)(
        /* [in] */ ULONG ulProgress,
        /* [in] */ ULONG ulProgressMax,
        /* [in] */ ULONG ulStatusCode,
        /* [in] */ LPCWSTR wszStatusText);
 
    STDMETHOD(OnStopBinding)(
        /* [in] */ HRESULT hresult,
        /* [unique][in] */ LPCWSTR szError)
        { return E_NOTIMPL; }
 
    STDMETHOD(GetBindInfo)(
        /* [out] */ DWORD __RPC_FAR *grfBINDF,
        /* [unique][out][in] */ BINDINFO __RPC_FAR *pbindinfo)
        { return E_NOTIMPL; }
 
    STDMETHOD(OnDataAvailable)(
        /* [in] */ DWORD grfBSCF,
        /* [in] */ DWORD dwSize,
        /* [in] */ FORMATETC __RPC_FAR *pformatetc,
        /* [in] */ STGMEDIUM __RPC_FAR *pstgmed)
        { return E_NOTIMPL; }
 
    STDMETHOD(OnObjectAvailable)(
        /* [in] */ REFIID riid,
        /* [iid_is][in] */ IUnknown __RPC_FAR *punk)
        { return E_NOTIMPL; }
 
    STDMETHOD_(ULONG, AddRef)() { return 0; }
    STDMETHOD_(ULONG, Release)() { return 0; }
 
    STDMETHOD(QueryInterface)(
    /* [in] */ REFIID riid,
    /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject)
        { return E_NOTIMPL; }
};
 
#endif //__BINDSTATUSCALLBACK_H__

<code>StatusCallBack.h</code>

#!cpp
#include "StatusCallback.h"
#include <shlwapi.h>
#include <mmsystem.h>
#include <iostream>
#include <assert.h>

//////////////////////////////////////////////////////////////////////////////
/// \brief 생성자
/// \param url 다운로드받을 파일의 URL
/// \param timeout 타임아웃
//////////////////////////////////////////////////////////////////////////////
CCallback::CCallback(const std::string& url, DWORD timeout)
: m_URL(url), m_Timeout(timeout)
{
    assert(!m_URL.empty());
}

//////////////////////////////////////////////////////////////////////////////
/// \brief 소멸자
//////////////////////////////////////////////////////////////////////////////
CCallback::~CCallback()
{
}

//////////////////////////////////////////////////////////////////////////////
/// \brief 진행 상황을 알려주기 위해 인터넷 익스플로러가 호출하게 되는
/// 콜백 함수
///
/// \param ulProgress 다운로드받은 바이트 수
/// \param ulProgressMax 다운로드받아야할 전체 바이트 수
/// \param ulStatusCode 상태 코드
/// \param wszStatusText 상태 문자열
/// \return HRESULT 인터넷 익스플로러에게 알려줘야하는 결과값. 다운로드를
/// 계속해야하는 경우에는 S_OK를 반환해야하고, 중단하려면 E_ABORT를 반환해야 
/// 한다.
//////////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE CCallback::OnProgress(ULONG ulProgress, 
                                                ULONG ulProgressMax,
                                                ULONG ulStatusCode, 
                                                LPCWSTR wszStatusText)
{
    // 이 함수 내부에서 혹시 사용자가 중지 버튼을 눌렀다던가, 
    // 타임아웃이 되었다던가, ulStatusCode가 이상하던가 하면,
    // E_ABORT를 반환해서, 다운로드를 중단해야 한다.
    
    if (m_Timeout < timeGetTime())
    {
        std::cout << "대기 시간이 초과되었습니다" << std::endl;
        return E_ABORT;
    }
    
    // 다운로드를 중지해야하는 상황이 아니라면, 
    // ulProgress 값과, ulProgressMax 값을 이용해 
    // 프로그레스바 등을 적당히 업데이트하면 된다.
    if (0 != ulProgressMax)
    {
        std::cout << "다운로드 중입니다 - " << m_URL 
            << "(" << int( 100.0 * ulProgress / ulProgressMax) ) << "%)" << std::endl;
    }
    else
    {
        std::cout << "다운로드 중입니다 - " << m_URL << std::endl;
    }

    return S_OK;
}

샘플

string URL = "http://somewhere.com/something.zip";
string filename = "c:\something.zip";
 
CCallback callback(URL, timeGetTime() + 60 * 1000);
HRESULT hr = ::URLDownloadToFile(
    NULL, URL.c_str(), filename.c_str(), 0, &callback);

:!: CodeProject에 괜찮은 예제가 있으니 이를 참고하면 좋다.

:!: 캐시에 있는 파일을 무시하고, 새로 받기를 원한다면 DeleteUrlCacheEntry 함수를 이용하자.

kb/httpget.txt · 마지막으로 수정됨: 2014/11/08 14:57 (바깥 편집)