EternalWindows
URL Monikers / POSTの使用

URLモニカはHTTPプロトコルを使用してHTMLの中身を取得しますが、 このような取得方法には主にGETとPOSTの2種類が存在します。 単純にHTMLの中身を取得したい場合はGETで構いませんし、 URLモニカの既定の動作もそのようになっています。 ただし、フォームなどを通じてデータを受け取ることを想定しているページは、 POSTを通じてデータを渡さなければならないことがあります。 たとえば、次のページではフォームを通じてデータをサーバーに渡しています。

http://eternalwindows.jp/network/winhttp/winhttp02.html

上記ページでテキストボックスに文字列を入力してボタンを選択すれば、 入力したメッセージが出力されることが分かります。 上記ページのサンプルではPOSTの使用をWinHTTPで実現していますが、 今回はそれをURL Monikersで使用するのが目的です。

今回のプログラムは、POSTを使用する例を示しています。

#include <windows.h>
#include <urlmon.h>

#pragma comment (lib, "urlmon.lib")

#define ID_DOWNLOAD 10

class CBindStatusCallback : public IBindStatusCallback, IHttpNegotiate
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP OnStartBinding(DWORD dwReserved, IBinding *pib);
	STDMETHODIMP GetPriority(LONG *pnPriority);
	STDMETHODIMP OnLowResource(DWORD reserved);
	STDMETHODIMP OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText);
	STDMETHODIMP OnStopBinding(HRESULT hresult, LPCWSTR szError);
	STDMETHODIMP GetBindInfo(DWORD *grfBINDF, BINDINFO *pbindinfo);
	STDMETHODIMP OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC *pformatetc, STGMEDIUM *pstgmed);
	STDMETHODIMP OnObjectAvailable(REFIID riid, IUnknown *punk);

	STDMETHODIMP BeginningTransaction(LPCWSTR szURL, LPCWSTR szHeaders, DWORD dwReserved, LPWSTR *pszAdditionalHeaders);
	STDMETHODIMP OnResponse(DWORD dwResponseCode, LPCWSTR szResponseHeaders, LPCWSTR szRequestHeaders, LPWSTR *pszAdditionalRequestHeaders);

	CBindStatusCallback();
	~CBindStatusCallback();

private:
	LONG m_cRef;
};

HWND g_hwndListBox = NULL;

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static CBindStatusCallback *pBindStatusCallback = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		HMENU hmenu;

		hmenu = CreateMenu();

		InitializeMenuItem(hmenu, TEXT("ダウンロード(&D)"), ID_DOWNLOAD);
		
		SetMenu(hwnd, hmenu);

		g_hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		pBindStatusCallback = new CBindStatusCallback;

		return 0;
	}
	
	case WM_COMMAND: {
		int nId = LOWORD(wParam);

		if (nId == ID_DOWNLOAD) {
			WCHAR szUrl[] = L"http://eternalwindows.jp/network/winhttp/sample.php";
			WCHAR szSaveFile[] = L"C:\\sample.html";
			
			URLDownloadToFile(NULL, szUrl, szSaveFile, 0, pBindStatusCallback);
		}
		
		return 0;
	}
	
	case WM_SIZE:
		MoveWindow(g_hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		if (pBindStatusCallback != NULL)
			pBindStatusCallback->Release();
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId)
{
	MENUITEMINFO mii;
	
	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_ID | MIIM_TYPE;
	mii.fType      = MFT_STRING;
	mii.wID        = nId;
	mii.dwTypeData = lpszItemName;
	
	InsertMenuItem(hmenu, nId, FALSE, &mii);
}


// CBindStatusCallback


CBindStatusCallback::CBindStatusCallback()
{
	m_cRef = 1;
}

CBindStatusCallback::~CBindStatusCallback()
{
}

STDMETHODIMP CBindStatusCallback::QueryInterface(REFIID riid, void **ppvObject)
{
	*ppvObject = NULL;

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IBindStatusCallback))
		*ppvObject = static_cast<IBindStatusCallback *>(this);
	else if (IsEqualIID(riid, IID_IHttpNegotiate))
		*ppvObject = static_cast<IHttpNegotiate *>(this);
	else
		return E_NOINTERFACE;

	AddRef();

	return S_OK;
}


STDMETHODIMP_(ULONG) CBindStatusCallback::AddRef()
{
	return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CBindStatusCallback::Release()
{
	if (InterlockedDecrement(&m_cRef) == 0) {
		delete this;
		return 0;
	}

	return m_cRef;
}

STDMETHODIMP CBindStatusCallback::OnStartBinding(DWORD dwReserved, IBinding *pib)
{
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("OnStartBinding"));

	return S_OK;
}

STDMETHODIMP CBindStatusCallback::GetPriority(LONG *pnPriority)
{
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("GetPriority"));

	return E_NOTIMPL;
}

STDMETHODIMP CBindStatusCallback::OnLowResource(DWORD reserved)
{
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("OnLowResource"));
	
	return E_NOTIMPL;
}

STDMETHODIMP CBindStatusCallback::OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText)
{
	WCHAR szBuf[256];

	switch (ulStatusCode) {
	case BINDSTATUS_FINDINGRESOURCE:
		wsprintfW(szBuf, L"%sを検索中", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	case BINDSTATUS_CONNECTING:
		wsprintfW(szBuf, L"%sを接続中", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	case BINDSTATUS_SENDINGREQUEST:
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)L"リクエストの送信");
		break;
	case BINDSTATUS_MIMETYPEAVAILABLE:
		wsprintfW(szBuf, L"利用可能なMIMEタイプ %s", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	case BINDSTATUS_BEGINDOWNLOADDATA:
		wsprintfW(szBuf, L"%sをダウンロード開始", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	case BINDSTATUS_DOWNLOADINGDATA:
		wsprintfW(szBuf, L"%sをダウンロード中", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	case BINDSTATUS_ENDDOWNLOADDATA:
		wsprintfW(szBuf, L"%sをダウンロード終了", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	case BINDSTATUS_COOKIE_SENT:
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)L"クッキーをリクエストと共に送信");
		break;
	case BINDSTATUS_CACHEFILENAMEAVAILABLE:
		wsprintfW(szBuf, L"利用可能な一時ファイルまたはキャッシュ %s", szStatusText);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	default:
		wsprintfW(szBuf, L"status %d", ulStatusCode);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		break;
	}
	
	return S_OK;
}

STDMETHODIMP CBindStatusCallback::OnStopBinding(HRESULT hresult, LPCWSTR szError)
{
	TCHAR szBuf[256];

	if (SUCCEEDED(hresult))
		lstrcpy(szBuf, TEXT("OnStopBinding"));
	else
		wsprintf(szBuf, TEXT("OnStopBinding (error %x)"), hresult);

	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);

	return S_OK;
}

STDMETHODIMP CBindStatusCallback::GetBindInfo(DWORD *grfBINDF, BINDINFO *pbindinfo)
{
	CHAR szData[] = "msg=abc";
	
	*grfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA | BINDF_GETNEWESTVERSION | BINDF_NOWRITECACHE;

	pbindinfo->dwBindVerb = BINDVERB_POST;
	pbindinfo->stgmedData.tymed = TYMED_HGLOBAL;
	pbindinfo->stgmedData.hGlobal = (LPSTR)GlobalAlloc(GPTR, lstrlenA(szData) + 1);
	lstrcpyA((LPSTR)pbindinfo->stgmedData.hGlobal, szData);
	pbindinfo->cbstgmedData = lstrlenA(szData);
	
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("GetBindInfo"));

	return S_OK;
}

STDMETHODIMP CBindStatusCallback::OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC *pformatetc, STGMEDIUM *pstgmed)
{
	return S_OK;
}

STDMETHODIMP CBindStatusCallback::OnObjectAvailable(REFIID riid, IUnknown *punk)
{
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("OnObjectAvailable"));

	return S_OK;
}

STDMETHODIMP CBindStatusCallback::BeginningTransaction(LPCWSTR szURL, LPCWSTR szHeaders, DWORD dwReserved, LPWSTR *pszAdditionalHeaders)
{
	WCHAR szAddHeader[] = L"Content-Type: application/x-www-form-urlencoded\r\n";

	if (!pszAdditionalHeaders)
		return E_POINTER;

	*pszAdditionalHeaders = (LPWSTR)CoTaskMemAlloc(sizeof(szAddHeader));
	lstrcpyW(*pszAdditionalHeaders, szAddHeader);

	return S_OK;
}

STDMETHODIMP CBindStatusCallback::OnResponse(DWORD dwResponseCode, LPCWSTR szResponseHeaders, LPCWSTR szRequestHeaders, LPWSTR *pszAdditionalRequestHeaders)
{
	if (!pszAdditionalRequestHeaders)
		return E_POINTER;
	
	*pszAdditionalRequestHeaders = NULL;

	return S_OK;
}

GETではなくPOSTを使用する場合は、IBindStatusCallback::GetBindInfoを適切に処理します。

STDMETHODIMP CBindStatusCallback::GetBindInfo(DWORD *grfBINDF, BINDINFO *pbindinfo)
{
	CHAR szData[] = "msg=abc";
	
	*grfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA | BINDF_GETNEWESTVERSION | BINDF_NOWRITECACHE;

	pbindinfo->dwBindVerb = BINDVERB_POST;
	pbindinfo->stgmedData.tymed = TYMED_HGLOBAL;
	pbindinfo->stgmedData.hGlobal = (LPSTR)GlobalAlloc(GPTR, lstrlenA(szData) + 1);
	lstrcpyA((LPSTR)pbindinfo->stgmedData.hGlobal, szData);
	pbindinfo->cbstgmedData = lstrlenA(szData);
	
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("GetBindInfo"));

	return S_OK;
}

BINDINFO.dwBindVerbは既定でBINDVERB_GETになっていますが、これをBINDVERB_POSTに変更します。 この場合はBINDINFO.stgmedDataに渡したいデータを指定することになっており、 tymedにTYMED_HGLOBALを指定した場合は、hGlobalにGlobalAllocで確保したメモリを指定できます。 このメモリに格納しているデータは"msg=abc"になっていますが、これはmsgにabcという値を指定する意味になります。 サーバーがmsgという文字列をサポートしていれば、msgに格納された値を考慮してHTMLを返すことになるでしょう。

POSTを使用する場合は、HTTPプロトコルのヘッダに特定の文字列を追加する必要があります。 このためにアプリケーションはIHttpNegotiateを実装し、IHttpNegotiate::BeginningTransactionを適切に処理します。

STDMETHODIMP CBindStatusCallback::BeginningTransaction(LPCWSTR szURL, LPCWSTR szHeaders, DWORD dwReserved, LPWSTR *pszAdditionalHeaders)
{
	WCHAR szAddHeader[] = L"Content-Type: application/x-www-form-urlencoded\r\n";

	if (!pszAdditionalHeaders)
		return E_POINTER;

	*pszAdditionalHeaders = (LPWSTR)CoTaskMemAlloc(sizeof(szAddHeader));
	lstrcpyW(*pszAdditionalHeaders, szAddHeader);

	return S_OK;
}

引数として渡されるpszAdditionalHeadersに、追加したい文字列を格納したメモリを指定します。 このメモリはCoTaskMemAllocで確保するようにし、その中身はszAddHeaderで指定したものにしておきます。


戻る