Download imagens C++ (linux)

Obs.: Código atualizado em 20/09/2014 – 21:28 – Correções para download de imagens sem o header Content-Length.

Tutorial de como fazer downloads de imagens, páginas (html), arquivos, etc. através do uso de sockets em linguagem C++ (So linux) e tratamento do http header response (http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html). Sugestões e melhorias são bem vindas, ainda mais observando-se que eu não sou um desenvolvedor profissional de cpp.

Código desenvolvido no DevC++ Orwell e CygWin, usado para emular o ambiente de desenvolvimento gnu/linux no windows. Este código foi testado no SO linux também.

Resultado:

001

O link original da imagem é: http://www.softwarepreservation.org/projects/c_plus_plus/Initial%20meeting%20group%20portrait%201.jpg

Este código foi feito com base no fórum: http://ubuntuforums.org/showthread.php?t=989108. Eu estudei e adaptei o código do Slavik (de November 21st, 2008).

Arquivo HttpRequestBytes.h:

#ifndef _HTTP_REQUEST_BYTES_H
#define _HTTP_REQUEST_BYTES_H
#pragma once

//http://ubuntuforums.org/showthread.php?t=989108

/*
	autor: Emerson Shigueo Sugimoto - eme.vbnet[at]gmail.com, 31/07/2014
*/
#include <iostream>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>		//usleep
#include <arpa/inet.h>
#include <fstream>
#include <fcntl.h>
#include <sys/time.h>
#include <vector>
#include "ControlPartitionate.h"

#include "ThreadTTL.h"

using namespace std;

struct StcDataBuffer{
	char* ContentType;
	char* Buffer;
	long Size;
};

struct StcParametersTempSDB {
	int sockfd;
	StcDataBuffer rt;
};

#define CHUNK_SIZE 2048

class HttpRequestBytes {
public:

	const char* GetIp(const char*);
	StcDataBuffer GetHttpData(const char* host, const char* getrequest, int port = 80, double timeout = 1);
	const char* GetFormatFromContentType(StcDataBuffer sdb);
	const char* GetFormatFromContentType(char*);

private:
	static void recoverStcDataBuffer(void* args);

};
#endif
/*
//const char* host = (char*)"static.giantbomb.com";
//int port = 80;
//const char* getrequest = "/uploads/original/0/884/2022725-wallpaper_625864.jpg";
//---------------------------------------------
//const char* host = (char*)"www.bcamp.com.br";
//int port = 80;
//const char* getrequest = "/wp-content/uploads/2013/10/google-hummingbird.jpg";
//---------------------------------------------
const char* host = (char*)"127.0.0.1";
int port = 1050;
const char* getrequest = "/?action=snapshot";
//---------------------------------------------

HttpRequestBytes* hrb = new HttpRequestBytes();
StcDataBuffer sdb = hrb->GetHttpData(host, getrequest, port, 10);
const char* formato = hrb->GetFormatFromContentType(sdb.ContentType); int sformato = strlen(formato);
char* nomearquivo = (char*)"arquivo."; int snomearquivo = strlen(nomearquivo);

char* nomearquivofinal = (char*)calloc(sformato + snomearquivo, sizeof(char)); memset(nomearquivofinal, 0, sformato + snomearquivo);
memcpy(nomearquivofinal, nomearquivo, snomearquivo);
memcpy(nomearquivofinal + snomearquivo, formato, sformato);
//sprintf(nomearquivofinal, "%s%s", nomearquivo, formato);
//cout << "nomearquivofinal: '" << nomearquivofinal << "'" << endl;
BinaryUtil::SaveImage(nomearquivofinal, sdb.Buffer, sdb.Size);
*/

Arquivo HttpRequestBytes.cpp:

#include "HttpRequestBytes.h"

/*
	autor: Emerson Shigueo Sugimoto - eme.vbnet[at]gmail.com, 31/07/2014
*/

const char* HttpRequestBytes::GetIp(const char* host){
	struct hostent *hp = gethostbyname(host);
	if (hp == NULL) { perror("Can't resolve host"); return 0; }
	//printf("%s = ", hp->h_name);
   	return inet_ntoa( *( struct in_addr*)( hp -> h_addr_list[0]));
};

const char* HttpRequestBytes::GetFormatFromContentType(StcDataBuffer sdb){
	return HttpRequestBytes::GetFormatFromContentType(sdb.ContentType);
};
const char* HttpRequestBytes::GetFormatFromContentType(char* contenttype){ //image/jpeg
	//verifica se possui '/'
	int i = 0, sizeCT = strlen(contenttype);
	for(i = 0; i < sizeCT; i++) {
		if (contenttype[i] == '/') { i++; break; }
	}
	if (i >= sizeCT) { i = 0; }
	char* format = (char*)calloc(sizeCT - i, sizeof(char));
	memcpy(format, contenttype + i, sizeCT - i);
	return format;
};

void HttpRequestBytes::recoverStcDataBuffer(void* args){
	StcParametersTempSDB* sptsdb = reinterpret_cast<StcParametersTempSDB*>(args);
	
	unsigned int size_recv = 0;
	char chunk[CHUNK_SIZE]; memset(chunk, 0, CHUNK_SIZE);
	if((size_recv = recv(sptsdb->sockfd, chunk, CHUNK_SIZE, 0) ) < 0) {
		close(sptsdb->sockfd);
		return;
	}
	//cout << "- size_recv: " << size_recv << ", CHUNK_SIZE: " << CHUNK_SIZE << endl;
	unsigned int i = 0, j = 0, pos = 0;

	sptsdb->rt.ContentType = NULL;
	char* contentlength = NULL;

	for(i = 0; i < size_recv; i++){
		//cout << chunk[i] << "";
		if (i + 4 >= size_recv) { break; }
		if (chunk[i] == '\r' && chunk[i+1]=='\n' && chunk[i+2]=='\r' && chunk[i+3]=='\n'){ pos = i + 4; break; }
		
		//try to find Content-Type
		if (i + 12 >= size_recv) { break; }
		if (
			chunk[i]=='C' && chunk[i+1]=='o' && chunk[i+2]=='n' && chunk[i+3]=='t' && chunk[i+4]=='e' && chunk[i+5]=='n' &&
			chunk[i+6]=='t' && chunk[i+7]=='-' && (chunk[i+8]=='T' || chunk[i+8]=='t') && chunk[i+9]=='y' && chunk[i+10]=='p' && chunk[i+11]=='e'
		){
			//find '\r\n' position - fim da string
			j = 0;
			for(j = i + 12; j < size_recv; j++){
				if (j + 1 >= size_recv) { break; }
				if (chunk[j] == ';' || (chunk[j] == '\r' && chunk[j+1]=='\n')){ break; }
			}
			if (j > i){
				i += 14;
				sptsdb->rt.ContentType = (char*)malloc( (j - i) * sizeof(char) );
				unsigned int aux;
				for(aux = i; aux < j; aux++){
					sptsdb->rt.ContentType[aux-i] = chunk[aux];
				}
				//cout << "contenttype: '" << contenttype << "'" << endl;
			}
			//cout << i << " " << j << endl;
			continue;
		}
		//try to find Content-Length
		if (i + 14 >= size_recv) { break; }
		if (
			chunk[i]=='C' && chunk[i+1]=='o' && chunk[i+2]=='n' && chunk[i+3]=='t' && chunk[i+4]=='e' && chunk[i+5]=='n' &&
			chunk[i+6]=='t' && chunk[i+7]=='-' && chunk[i+8]=='L' && chunk[i+9]=='e' && chunk[i+10]=='n' && chunk[i+11]=='g'
			&& chunk[i+12]=='t' && chunk[i+13]=='h'
		){
			//find '\r\n' position - fim da string
			j = 0;
			for(j = i+14; j < size_recv; j++){
				if (j + 1 >= size_recv) { break; }
				if (chunk[j] == ';' || (chunk[j] == '\r' && chunk[j+1]=='\n')){ break; }
			}
			if (j > i){
				i += 16;
				contentlength = (char*)malloc( (j - i) * sizeof(char) );
				unsigned int aux;
				for(aux = i; aux < j; aux++){
					contentlength[aux-i] = chunk[aux];
				}
			}
			continue;
		}
		
	}
	//cout << endl << endl << "pos: " << pos << ", contenttype: " << (sptsdb->rt.ContentType==NULL?"null":sptsdb->rt.ContentType) << ", contentlength: " << (contentlength == NULL ? "null" : contentlength) << endl << endl;
	
	long long cl = contentlength == NULL ? 0 : atol(contentlength);
	
	sptsdb->rt.Size = 0;
	vector<StcVetorSize> vect;
	if (pos > 0 && size_recv - pos > 0){
		StcVetorSize pt;
		pt.Size = size_recv - pos;
		pt.Vetor = (char*)calloc(pt.Size, sizeof(char));
		j = 0;
		for(i = pos; i < size_recv; i++){
			pt.Vetor[j++] = chunk[i];
		}
		sptsdb->rt.Size += pt.Size;
		vect.push_back(pt);
	}
	
	do {
		memset(chunk, 0, CHUNK_SIZE);
		if((size_recv = recv(sptsdb->sockfd, chunk, CHUNK_SIZE, 0) ) <= 0) {
			close(sptsdb->sockfd);
			break;
		}

		StcVetorSize pt;
		pt.Size = size_recv;
		pt.Vetor = (char*)calloc(pt.Size, sizeof(char));
		memcpy(pt.Vetor, chunk, size_recv);
		sptsdb->rt.Size += pt.Size;
		vect.push_back(pt);
		
		if (cl > 0 && sptsdb->rt.Size >= cl) { break; }
		
		//for(i = 0; i < pt.Size; i++){ cout << (int)pt.Vetor[i] << " "; }
		//cout << "pt.Size:" << pt.Size << ", _sdbTemp.Size: " << sptsdb->rt.Size << ", cl: " << cl << endl;
	} while (size_recv > 0);
	
	pos = 0;
	sptsdb->rt.Buffer = (char*)calloc(sptsdb->rt.Size, sizeof(char));
	for(i = 0; i < vect.size(); i++){
		StcVetorSize tmp = vect.at(i);
		for(j = 0; j < tmp.Size; j++){
			sptsdb->rt.Buffer[pos++] = tmp.Vetor[j];
		}
	}
	close(sptsdb->sockfd);
};

StcDataBuffer HttpRequestBytes::GetHttpData(const char* host, const char* getrequest, int port, double timeout) {
	cout << "[s] realtime " << host << ":" << port << getrequest << endl;
	StcParametersTempSDB sptsdb;
	
	int socket_desc;
    struct sockaddr_in server;
     
    //Create socket
    socket_desc = socket(AF_INET, SOCK_STREAM , 0);
    if (socket_desc == -1) { printf("Could not create socket"); return sptsdb.rt; }
    const char* ip = GetIp(host); if (!ip) { close(socket_desc); cout << "err ip" << endl; return sptsdb.rt; }
    
 	server.sin_addr.s_addr = inet_addr(ip);
    server.sin_family = AF_INET;
    server.sin_port = htons(port);

    //Connect to remote server
    if (connect(socket_desc, (struct sockaddr*)&server, sizeof(server)) < 0) {
        puts("connect error");
        close(socket_desc);
        return sptsdb.rt;
    }
    //cout << "Connected " << host << ":" << port << " request: " << getrequest << endl;
    
    const char* templategetrequest = "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n";
    char* message = (char*)malloc( (strlen(host) + strlen(getrequest) + strlen(templategetrequest)-4) * sizeof(char) );
    sprintf(message, templategetrequest, getrequest, host);
    if(send(socket_desc, message, strlen(message), 0) < 0) {
        puts("Send failed");
        close(socket_desc);
        return sptsdb.rt;
    }

	sptsdb.sockfd = socket_desc;
	
	ThreadTTL* tth = new ThreadTTL(timeout, recoverStcDataBuffer, reinterpret_cast<void*>(&sptsdb));
	pthread_t threadtemp = tth->InitThread();
	pthread_join(threadtemp, NULL);
	return sptsdb.rt; //ponteiro para o retorno
};

Observe o método recv_timeout(int socket_desc, double timeout = 1), ele possui dois métodos de controle de requisição de socktes, um por timeout e outro pelo número de bytes recebidos pelo socket, o paramêtro http response possui um atributo chamado ‘Content-Length’, que informa o número de bytes de resposta da request.
NÃO USE apenas o retorno da função recv (http://man7.org/linux/man-pages/man2/recvmsg.2.html) como indicativo de final de request !!!!, obtive muitos problemas ao fazer isto, por isto o uso de dois métodos de controle de fim de request – timeout e número de bytes recebidos pelo socket.

Arquivo main.cpp:

#include "HttpRequestBytes.h"
 
/*
    autor: Emerson Shigueo Sugimoto - eme.vbnet[at]gmail.com, 31/07/2014
*/
 
int main(int argc, char** argv) {
     
    //large image:
    //const char* host = (char*)"static.giantbomb.com"; int port = 80; const char* getrequest = "/uploads/original/0/884/2022725-wallpaper_625864.jpg"; 
 
    //const char* host = (char*)"10.2.2.74"; int port = 9090; const char* getrequest = "/images/mail-us.png";
    //const char* host = (char*)"www.defesabr.com"; int port = 80; const char* getrequest = "/MD/Planobrasil/Programatitan/Plataforma_Petroleo.jpg";
    //const char* host = (char*)"beagleboard.org"; int port = 80; const char* getrequest = "/static/uploads/BeagleBoardCompliantLowRes.png";
    //const char* host = (char*)"www.softwarepreservation.org"; int port = 80; const char* getrequest = "/projects/c_plus_plus/Initial%20meeting%20group%20portrait%201.jpg";
     
    //request html com erro:
    const char* host = (char*)"emersonbsi.wordpress.com"; int port = 80; const char* getrequest = "/";
    //request html ok:
    //const char* host = (char*)"beej.us"; int port = 80; const char* getrequest = "/guide/bgnet/";
     
    //const char* host = (char*)"www.softwarepreservation.org"; int port = 80; const char* getrequest = "/projects/c_plus_plus/Initial%20meeting%20group%20portrait%201.jpg";
     
    HttpRequestBytes* hrb = new HttpRequestBytes();
    StcDataBuffer sdb = hrb->GetHttpData(host, getrequest, port);
     
    if (sdb.Size == 0) {
        cout << "erro na request - " << host << ":" << port << endl; return 0;
    }
 
    cout << sdb.ContentType << ", size: " << sdb.Size << ", GetFormatFromContentType: " << hrb->GetFormatFromContentType(sdb) << endl;
     
    char* nomeimagem = (char*)"image001.";
    const char* formato = hrb->GetFormatFromContentType(sdb);
    char* nomearquivo = (char*)calloc(strlen(nomeimagem) + strlen(formato), sizeof(char));
    sprintf(nomearquivo, "%s%s", nomeimagem, formato);
  
    ofstream outfile(nomearquivo, ofstream::binary);
    outfile.write(sdb.Buffer, sdb.Size);
    outfile.close();
     
    free(hrb);
 
    cout << "Salvo no disco: " << nomearquivo << endl;
     
    return 0;
};

Parâmetros de uso:
ex.: para a imagem da url: http://www.softwarepreservation.org/projects/c_plus_plus/Initial%20meeting%20group%20portrait%201.jpg

const char* host = (char*)"www.softwarepreservation.org";
int port = 80;
const char* getrequest = "/projects/c_plus_plus/Initial%20meeting%20group%20portrait%201.jpg";

onde:

  • host é o host, www ou ip, sem o http ou barras ‘/’
  • port é a porta, e;
  • getrequest é o resto do host

A resposta do método (StcDataBuffer sdb = hrb->GetHttpData(host, getrequest, port);) é um struct StcDataBuffer:

struct StcDataBuffer{
	char* ContentType;
	char* Buffer;
	long Size;
};

Observe que o parâmetro char* Buffer contém o vetor da request, que é o vetor de bytes, long Size é o tamanho do vetor de bytes (char* Buffer) e char* ContentType é o Content-Type da request, algo como ‘image/jpg’. O método StcDataBuffer sdb = hrb->GetHttpData(host, getrequest, port); é genérico ! com ele você pode fazer o download de uma request http de qualquer tipo de objeto !, para este tutorial eu fiz o download de uma imagem e salvei no disco desta forma:

cout << sdb.ContentType << ", size: " << sdb.Size << ", GetFormatFromContentType: " << hrb->GetFormatFromContentType(sdb) << endl;

char* nomeimagem = (char*)"image001.";
const char* formato = hrb->GetFormatFromContentType(sdb);
char* nomearquivo = (char*)calloc(strlen(nomeimagem) + strlen(formato), sizeof(char));
sprintf(nomearquivo, "%s%s", nomeimagem, formato);

ofstream outfile(nomearquivo, ofstream::binary);
outfile.write(sdb.Buffer, sdb.Size);
outfile.close();

Sugestões e melhorias são bem vindas, ainda mais no controle de estados dos erros.

Mais referências sobre sockets podem ser encontradas em:

http://beej.us/guide/bgnet/

Deixe um comentário