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:
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: