Pobieranie kodu HTML stron w C++ za pomocą WinSock2

Dzisiejsza notka będzie o tym jak pobrać kod HTML strony WWW w aplikacji C++ korzystając z WinSock2.

Pierwszą rzeczą, którą trzeba wiedzieć jest to w jaki sposób robi to przeglądarka internetowa. Zacznę od tego jak wygląda typowy adres strony WWW na przykładzie adresu do działu artykułów na stronie http://www.gamedev.pl/:

http://www.gamedev.pl/articles.php

Powyższy adres składa się z kilku części:

  • “http://” – protokół reprezentujący sposób transmisji danych, dzięki niemu przeglądarka wie w jaki sposób komunikować się z serwerem oraz na jaki port wysyłać żądania
  • “www.gamedev.pl” – domena na którą będzie wysłane zapytanie – podany adres jest tłumaczony na adres IP przez serwer DNS
  • “/articles.php” – adres żądanego plik lub żądanie dla serwera WWW, które aplikacja wykorzystuje do stworzenia nagłówka

Przeglądarka mając adres strony tworzy odpowiedni nagłówek, który zostaje wysłany do serwera WWW. Jak już wspomniałem adresem tego serwera jest domena zawarta w adresie WWW natomiast port jest określany na podstawie protokołu, czyli w przypadku “http://” jest to port 80. Nagłówek HTTP powinien zawierać następujące elementy:

  • rodzaj zapytania – w przypadku pobrania strony jest to “GET”
  • adres żądanego pliku lub żądanie – to co chcemy od serwera otrzymać, najczęściej jest to adres pliku na serwerze, czyli “/articles.php” w tym przykładzie
  • wersja protokołu – typowo HTTP/1.1
  • domena hosta

Dodatkowo, jeśli jest to konieczne, można wysłać informacje o tym, jakiej przeglądarki używamy (UserAgent), informacje o akceptowanych plikach, kodowaniu, ciasteczkach oraz czasie trwania połączenia, więcej można dowiedzieć się z tej strony wiki oraz samych nagłówków, które wysyła przeglądarka. Jako ciekawostkę mogę dodać, że istnieje fajna wtyczka dla Firefoxa, która pokazuje nagłówki wysyłane przez przeglądarkę – Live HTTP headers.

Oto nagłówek uzyskany z pomocą tej wtyczki podczas łączenia się do przykładowej strony:

1
2
3
4
5
6
7
8
9
GET /articles.php HTTP/1.1
Host: www.gamedev.pl
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; pl; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: pl,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-2,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

Jak widać jest on trochę długi (usunąłem informacje o ciasteczkach, nie są tutaj potrzebne). Aby pobrać kod strony wystarczą tak naprawdę dwie pierwsze linijki. Jednak ważną rzeczą jest, aby po każdej linijce występowała para znaków \r\n a koniec nagłówka reprezentowany był przez dwie pary tych znaków. W przypadku, gdy nagłówek nie będzie zawierał tak skonstruowanego nagłówka, serwer po prostu udrzuci zapytanie.

Teraz pora na kod C++ z WinSock2, oto on:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Usuwa zbędne definicje z nagłówka
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
 
#include <winsock2.h>
#include <ws2tcpip.h>
#include <string>
 
#define BUFFER_SIZE 2048
#pragma comment(lib, "ws2_32.lib")	// Niezbędna biblioteka
using namespace std;
 
int main()
{
	//************************************************
	// Inicjalizacja WinSock2
	//************************************************
	WSADATA wsaData;
	int error;
	string answer;
	ZeroMemory(&wsaData, sizeof(wsaData));
 
	if(FAILED(WSAStartup(MAKEWORD(2,2), &wsaData)))	// MAKEWORD(2,2) - Wersja WinSock
	{
		return 1;
	}
 
	char recvBuffer[BUFFER_SIZE];
	addrinfo hint;					// Struktura przechowująca dane o połączeniu
	addrinfo* wsResult;				// Wskaźnik na rezultat
	SOCKET pSocket;					// Właściwy pSocket
 
	ZeroMemory(recvBuffer, sizeof(recvBuffer));
	ZeroMemory(&hint, sizeof(hint));
	hint.ai_family = AF_UNSPEC;		// Rodzaj transmisji - nieokreślony
	hint.ai_socktype = SOCK_STREAM;	// Typ gniazda - strumień
	wsResult = NULL;
	pSocket = INVALID_SOCKET;
 
	//************************************************
	// Tworzenie zapytania	
	//************************************************
 
	// Wyciąganie informacji z adresu
	string httpAddress = "http://www.gamedev.pl/articles.php";
	string temp = httpAddress.substr(httpAddress.find("http://") + sizeof("http://") - 1);	// Tylko http:// więc można wyciąć
	string domain = temp.substr(0, temp.find_first_of('/'));								// Domena
	string addressTail = temp.substr(temp.find_first_of('/'));								// Żądanie pliku
 
	// Tworzenie nagłówka
	temp = string("GET ") + addressTail + " HTTP/1.1\r\n"
			   + "Host: " + domain + "\r\n"
			   //+ "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; pl; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5\r\n"
			   //+ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
			   //+ "Accept-Language: pl,en-us;q=0.7,en;q=0.3\r\n"
			   //+ "Accept-Encoding: gzip,deflate\r\n"
			   //+ "Accept-Charset: ISO-8859-2,utf-8;q=0.7,*;q=0.7\r\n"
			   //+ "Keep-Alive: 300\r\n"
			   //+ "Connection: keep-alive\r\n"
			   + "\r\n\r\n";
 
	// Pobieranie IP serwera z serwera DNS
	if(FAILED(getaddrinfo(domain.c_str(), TEXT("80"), &hint, &wsResult)))
	{
		printf("Nie udalo sie pobrac IP\n");
		return 1;
	}
 
	// Wskaźnik na zwrócone adresy
	addrinfo* ptr = wsResult;
 
	// Tworzenie socketu
	if(FAILED(pSocket = socket(wsResult->ai_family, wsResult->ai_socktype, wsResult->ai_protocol))) 
	{
		freeaddrinfo(wsResult);
		printf("Nie udało się utworzyc socketu\n");
		return 1;
	}
 
	//************************************************
	// Wysyłanie zapytania	
	//************************************************
 
	// Łączenie się do serwera
	if(FAILED(connect(pSocket, ptr->ai_addr, (int)ptr->ai_addrlen))) 
	{
		printf("Nie udalo sie polaczyc do serwera\n");
		goto Error;
	}
 
	// Wysyłanie nagłówka
	if(FAILED(send(pSocket, temp.c_str(), temp.size(), 0)))
	{
		printf("Nie udalo sie wyslac naglowka\n");
		goto Error;
	}
 
	// Kończenie wysyłania
	if(FAILED(shutdown(pSocket, SD_SEND)))
	{
		printf("Nie udalo sie zamknac polaczenia\n");
		goto Error;
	}
 
	//************************************************
	// Odbieranie danych
	//************************************************
 
	// Odbieranie danych
	do
	{
		ZeroMemory(recvBuffer, sizeof(recvBuffer));
		error = recv(pSocket, recvBuffer, sizeof(recvBuffer), 0);
 
		if(error > 0)
		{
			answer += string(recvBuffer);
		}
	}
	while(error > 0);
 
	closesocket(pSocket);		// Zamknięcie socketu
	freeaddrinfo(wsResult);		// oraz zwolnienie struktury addrinfo
 
	printf("Kod HTML strony \"http://www.gamedev.pl/articles.php\":\n\n%s", answer.c_str());
	return 0;
 
Error:
	closesocket(pSocket);
	freeaddrinfo(wsResult);
	return 1;
}

Wynikiem tego kodu jest wyświetlony kod HTML przykładowej strony.