#define _POSIX_C_SOURCE 200809L #include /* read, write, close */ #include /* socket, connect */ #include /* getaddrinfo */ #include /* openat, O_* */ #include /* strlen */ #include /* calloc, free */ #include #include #include int fetch( const char *host, const char *port, const char *request, int out_fd ) { struct addrinfo *res = NULL; int server_fd = -1; int saved_errno = 0; /* Ve většině případů je potřeba podprogramu ‹getaddrinfo› * sdělit, jaký typ socketu (proudový nebo datagramový) * plánujeme použít. V opačném případě bychom museli být * připraveni pracovat s obojím, což typicky není přímočaré * (navíc protokol HTTP, který zde používáme, v datagramové * podobě vůbec neexistuje). */ struct addrinfo hints = { .ai_socktype = SOCK_STREAM }; /* Nejprve nastavíme výsledek na hodnotu -1, aby se nám lépe * zapisoval kód, který ošetřuje chyby. Případný úspěch * poznamenáme až na závěr. */ int result = -1; /* Adresu vzdáleného stroje přeložíme podprogramem * ‹getaddrinfo›. Na přesném typu adresy nám nezáleží – výběr * ponecháme na operačním systému, určujeme pouze typ socketu * (viz výše). */ if ( getaddrinfo( host, port, &hints, &res ) != 0 ) return -1; /* Podle výsledků přichystáme socket a navážeme spojení. */ if ( ( server_fd = socket( res->ai_family, res->ai_socktype, res->ai_protocol ) ) == -1 ) goto err; if ( connect( server_fd, res->ai_addr, res->ai_addrlen ) == -1 ) goto err; /* Na vytvořeném spojení odešleme požadavek. */ if ( send( server_fd, request, strlen( request ), 0 ) == -1 ) goto err; /* Začneme přijímat odpověď – pro jednoduchost se nebudeme * zabývat obsahem hlaviček a budeme zapisovat pouze tělo * odpovědi (samozřejmě by bylo více realistické hlavičky * zpracovat). */ /* Musíme nicméně nalézt oddělovač – sekvenci "\r\n\r\n" – po * kterém až následuje tělo, které chceme zapsat. To provedeme * v relativně jednoduchém cyklu, který není závislý na * velikosti jednotlivých čtení ze socketu – data zpracovává po * jednom bajtu. Cyklus skončí jakmile oddělovač nalezneme * (‹matches == 4›) nebo narazíme na chybu nebo konec spojení * (což také považujeme za chybu, protože oddělovač je povinný, * i kdyby bylo tělo prázdné). */ int matches = 0; int index, bytes; char buffer[ 13 ]; while ( matches < 4 && ( bytes = read( server_fd, buffer, sizeof buffer ) ) > 0 ) { for ( index = 0; index < bytes && matches < 4; ++index ) if ( ( matches % 2 == 0 && buffer[ index ] == '\r' ) || ( matches % 2 == 1 && buffer[ index ] == '\n' ) ) ++ matches; else matches = 0; } if ( matches < 4 ) /* bytes == -1 implies matches < 4 */ goto err; /* malformed reply */ /* Do výstupního souboru zapíšeme zbytek posledního přenosového * okna (po nalezeném oddělovači) a pak už pouze přímočarým * způsobem kopírujeme data. */ if ( write( out_fd, buffer + index, bytes - index ) == -1 ) goto err; while ( ( bytes = read( server_fd, buffer, sizeof buffer ) ) > 0 ) if ( write( out_fd, buffer, bytes ) == -1 ) goto err; /* Tím je operace ukončena. Uvolníme lokální zdroje * (nezapomeneme zejména na výsledek podprogramu ‹getaddrinfo›), * určíme návratovou hodnotu a předáme řízení volajícímu. */ result = 0; err: saved_errno = errno; if ( res ) freeaddrinfo( res ); if ( server_fd != -1 ) close( server_fd ); errno = saved_errno; return result; } /* Použití demonstrujeme stažením webové stránky * ‹http://neverssl.com› do souboru (přeložíte-li tento program bez * ‹pb152.cpp›, můžete si výsledný soubor srovnat s tím, co vidíte * ve webovém prohlížeči). */ int main( void ) /* demo */ { const char *file = "zt.neverssl.txt", *host = "neverssl.com", *port = "80", *request = "GET / HTTP/1.0\r\n" "User-Agent: webget\r\n\r\n"; int oflags = O_APPEND | O_TRUNC | O_RDWR | O_CREAT; char buffer[ 8 ]; int dir_fd = open( ".", O_DIRECTORY ); if ( dir_fd == -1 ) err( 1, "opening working directory" ); int file_fd = openat( dir_fd, file, oflags, 0666 ); if ( file_fd == -1 ) err( 1, "opening %s", file ); if ( fetch( host, port, request, file_fd ) == -1 ) err( 1, "downloading http://neverssl.com" ); lseek( file_fd, 0, SEEK_SET ); if ( read( file_fd, buffer, sizeof( buffer ) ) == -1 ) err( 1, "reading %s", file ); assert( memcmp( buffer, "", 6 ) == 0 ); close( file_fd ); close( dir_fd ); return 0; }