Brandon Rhodes John Goerzen
Novatec
Original English language edition published by Apress, Copyright © 2014 by Apress, Inc.. Portugueselanguage edition for Brazil copyright © 2015 by Novatec Editora. All rights reserved. Edição original em inglês publicada pela Apress, Copyright © 2014 by Apress, Inc. Edição em português para o Brasil copyright © 2015 pela Novatec Editora. Todos os direitos reservados. Copyright © 2015 da Novatec Editora Ltda. Todos os direitos reservados e protegidos pela Lei 9.610 de 19/02/1998. É proibida a reprodução desta obra, mesmo parcial, por qualquer processo, sem prévia autorização, por escrito, do autor e da Editora. Editor: Rubens Prates Tradução: Aldir José Coelho Corrêa da Silva Assistente editorial: Priscila A. Yoshimatsu Revisão gramatical: Viviane Oshima Editoração eletrônica: Carolina Kuwabata ISBN: 978-85-7522-437-3 MP20150528 Histórico de impressões: Junho/2015
Primeira edição
Novatec Editora Ltda. Rua Luís Antônio dos Santos 110 02460-000 – São Paulo, SP – Brasil Tel.: +55 11 2959-6529 E-mail: novatec@novatec.com.br Site: novatec.com.br Twitter: twitter.com/novateceditora Facebook: facebook.com/novatec LinkedIn: linkedin.com/in/novatec MP20150528
capítulo 1
Introdução à rede cliente-servidor
Este livro aborda a programação de redes na linguagem Python. Ele abrange os conceitos básicos, os módulos e as bibliotecas de terceiros que provavelmente você usará quando se comunicar com máquinas remotas usando os protocolos de comunicação mais populares da Internet. Não houve espaço no livro para ensinar como programar em Python se você nunca viu a linguagem ou se nunca escreveu um programa de computador; ele presume que o leitor já tenha aprendido algo sobre programação Python nos ótimos tutoriais e livros existentes sobre o assunto. Espero que os exemplos de Python do livro lhe deem noções de como estruturar e escrever seu próprio código. Será usado todo tipo de recurso Python avançado sem explicação ou justificativa – no entanto, ocasionalmente, poderei destacar a maneira como estou usando uma técnica ou estrutura específica quando achar particularmente interessante ou conveniente. Por outro lado, este livro não pressupõe que você saiba algo sobre redes! Se já tiver usado um navegador web ou enviado um email, você deve saber o suficiente para ler o livro desde o início e conhecer as redes de computadores ao avançar. Abordarei o assunto do ponto de vista de um programador de aplicativos que está implementando um serviço conectado à rede – como um site, um servidor de email ou um jogo de computador – ou escrevendo um programa cliente projetado para usar esse serviço. É bom ressaltar, no entanto, que você não aprenderá como instalar ou configurar redes no livro. As disciplinas de design de redes, gerenciamento de sala de servidores e provisionamento automatizado já são temas próprios, que não costumam se coadunar com a disciplina de programação de computadores como abordada neste livro específico. Embora Python esteja se tornando uma parte importante do cenário da provisionamento graças a projetos como o OpenStack, SaltStack e Ansible, é melhor procurar livros e documentação que sejam somente sobre provisionamento e suas várias tecnologias se você quiser aprender mais sobre elas.
21
22
Programação de Redes com Python
Os alicerces: pilhas e biblliotecas Quando você começar a explorar a programação de redes com Python, dois conceitos aparecerão repetidamente. • A ideia de uma pilha de protocolos, em que serviços de rede mais simples são usados como base para a construção de serviços mais sofisticados. • O fato de que com frequência você usará bibliotecas Python de código já escrito – sejam módulos da biblioteca padrão interna que vem com Python ou pacotes de distribuições de terceiros para serem baixados e instalados – que sabem como falar a língua do protocolo de rede que se quer usar. Em muitos casos, a programação de redes envolve apenas selecionar e usar uma biblioteca que já dê suporte às operações de rede a serem executadas. As principais metas deste livro são mostrar várias bibliotecas de rede importantes disponíveis para Python ao mesmo tempo em que apresentamos os serviços de rede de nível inferior nos quais elas se baseiam. Conhecer o material de nível inferior será útil se você quiser entender como as bibliotecas funcionam e para que saiba o que está ocorrendo quando algo der errado em um nível inferior. Comecemos com um exemplo simples. Aqui está em endereço postal: 207 N. Defiance St Archbold, OH
Quero saber a latitude e a longitude desse endereço físico. Por acaso, o Google fornece uma API de geocodificação que pode fazer essa conversão. O que você teria que fazer para se beneficiar desse serviço de rede usando Python? Quando há um novo serviço de rede que queremos usar, sempre é útil tentar saber se alguém já implementou o protocolo – nesse caso, o protocolo de geocodificação do Google – com o qual o programa precisará se comunicar. Comece percorrendo a documentação da biblioteca padrão Python, procurando algo relacionado à geocodificação. http://docs.python.org/3/library/
Encontrou algo sobre geocodificação? Nem eu. Mas é importante um programador Python examinar o sumário da biblioteca padrão com frequência, mesmo se geralmente você não encontrar o que procura, porque cada passada de olhos irá familiarizá-lo mais com os serviços que estão incluídos em Python. O blog “Python
Capítulo 1 ■ Introdução à rede cliente-servidor
23
Module of the Week” de Doug Hellmann é outra ótima referência na qual você pode conhecer os recursos que vêm com Python graças à sua biblioteca padrão. Já que nesse caso a biblioteca padrão não tem um pacote para ajudar, você pode recorrer ao Índice de Pacotes Python (Python Package Index), um excelente recurso para a busca de todos os tipos de pacotes Python de uso geral fornecidos como colaboração de outros programadores do mundo todo. É claro que você também pode verificar o site do fornecedor cujo serviço deseja usar para ver se ele disponibiliza uma biblioteca Python para acessá-lo. Ou pode procurar no Google Python mais o nome do serviço web e ver se algum dos primeiros resultados conduz a um pacote que queira testar. Em nosso exemplo, procurei Python Package Index, que reside neste URL: https://pypi.python.org/
Após acessar, inseri geocoding e imediatamente encontrei um pacote chamado pygeocoder, que fornece uma interface organizada de acesso aos recursos de geocodificação do Google (no entanto, você notará pela descrição que ele não foi disponibilizado pelo fornecedor e sim escrito por alguém que não é do Google). http://pypi.python.org/pypi/pygeocoder/
Esta situação é tão comum – encontrar um pacote Python que já pareça fazer exatamente o que procuramos e, portanto, queremos testar no sistema – que devo fazer uma pausa e apresentar a melhor tecnologia Python para o teste rápido de uma nova biblioteca: virtualenv! No passado, instalar um pacote Python era um ato reprovável e irreversível que requeria privilégios administrativos na máquina e deixava permanentemente alterada a instalação da linguagem feita para o sistema. Após alguns meses de desenvolvimento pesado com Python, a instalação específica do sistema podia se tornar uma terra de ninguém com vários pacotes, todos instalados manualmente, e podíamos até descobrir que os novos pacotes deixaram de funcionar por incompatibilidade com os pacotes antigos que ficaram na unidade de disco rígido provenientes de um projeto concluído há meses. Programadores Python cuidadosos não passam mais por essa situação. Muitos instalam um único pacote com abrangência em todo o sistema – recomendável – que costuma ser o virtualenv! Uma vez que o virtualenv estiver instalado, você poderá criar qualquer número de pequenos e autônomos “ambientes virtuais Python” onde os pacotes poderão ser instalados e desinstalados e com os quais será possível fazer testes, tudo isso sem contaminar a instalação Python do sistema.
24
Programação de Redes com Python
Ao término de um projeto ou experimento específico, só será preciso remover seu diretório de ambiente virtual e o sistema estará limpo. No caso em questão, você quer criar um ambiente virtual para testar o pacote pygeocoder. Se ainda não tiver instalado o virtualenv em seu sistema, visite este URL para fazer o download e instalá-lo: http://pypi.python.org/pypi/virtualenv
Quando você estiver com o virtualenv instalado, poderá criar um novo ambiente usando os comandos a seguir. (No Windows, o diretório do binário Python no ambiente virtual será chamado de Scripts em vez de bin.) $ virtualenv –p python3 geo_env $ cd geo_env $ ls bin/ include/ lib/ $ . bin/activate $ python -c 'import pygeocoder' Traceback (most recent call last): File "<string>", line 1, in <module> ImportError: No module named 'pygeocoder'
Como você pode ver, o pacote pygeocoder ainda não está disponível. Para instalá-lo, use o comando pip que se encontra dentro de seu ambiente virtual, que agora está em seu caminho já que você executou o comando activate. $ pip install pygeocoder Downloading/unpacking pygeocoder Downloading pygeocoder-1.2.1.1.tar.gz Running setup.py egg_info for package pygeocoder Downloading/unpacking requests>=1.0 (from pygeocoder) Downloading requests-2.0.1.tar.gz (412kB): 412kB downloaded Running setup.py egg_info for package requests Installing collected packages: pygeocoder, requests Running setup.py install for pygeocoder Running setup.py install for requests Successfully installed pygeocoder requests Cleaning up...
Capítulo 1 ■ Introdução à rede cliente-servidor
25
O binário python que está dentro do virtualenv agora terá o pacote pygeocoder disponível. $ python -c 'import pygeocoder'
Já que o pacote pygeocoder foi instalado, você deve poder executar o programa simples chamado search1.py, como mostrado na listagem 1.1.
Listagem 1.1 – Procurando a longitude e a latitude #!/usr/bin/env python3 # Programação de Redes com Python # https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter01/search1.py from pygeocoder import Geocoder if __name__ == '__main__': address = '207 N. Defiance St, Archbold, OH' print(Geocoder.geocode(address)[0].coordinates)
Ao executá-lo na linha de comando, você deve ver um resultado como este: $ python3 search1.py (41.521954, -84.306691)
E na tela de seu computador estará a resposta à nossa pergunta sobre a latitude e a longitude do endereço! A resposta foi extraída diretamente do serviço web do Google. O primeiro exemplo de programa foi um enorme sucesso. Ficou incomodado por ter aberto um livro sobre programação de redes com Python só para ser imediatamente induzido a baixar e instalar um pacote de terceiros que transformou o que poderia ser um interessante problema de rede em um tedioso script Python de três linhas? Calma! Você verá que em 90 por cento das vezes é exatamente assim que os desafios de programação são resolvidos – com a procura de outros programadores da comunidade Python que já tenham se deparado com o problema e beneficiando-se com inteligência e concisão de suas soluções. No entanto, ainda não terminamos de examinar esse exemplo. Você viu que com frequência um serviço de rede complexo pode ser acessado de maneira bem trivial. Mas o que existe por trás da interessante interface do pygeocoder? Como o serviço funciona realmente? Agora você verá, com detalhes, que esse sofisticado serviço é na verdade apenas a camada superior de uma pilha de rede que envolve pelo menos meia dúzia de níveis diferentes.
26
Programação de Redes com Python
Camadas de aplicativos A primeira listagem de programa usou uma biblioteca Python de terceiros, baixada do Python Package Index, para resolver um problema. Ela conhecia tudo sobre a API Geocoding do Google e as regras para usá-la. Mas e se essa biblioteca não existisse? E se você tivesse que construir um cliente para a API Maps do Google por conta própria? Para saber a resposta, examine o programa search2.py, mostrado na listagem 1.2. Em vez de usar uma biblioteca de terceiros habilitada para a geocodificação, ele desce um nível e usa a popular biblioteca requests que está por trás do pygeocoder e que, como você pode ver no comando pip install anterior, também foi instalada em seu ambiente virtual.
Listagem 1.2 – Buscando um documento JSON na API Geocoding do Google #!/usr/bin/env python3 # Programação de Redes com Python # https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter01/search2.py import requests def geocode(address): parameters = {'address': address, 'sensor': 'false'} base = 'http://maps.googleapis.com/maps/api/geocode/json' response = requests.get(base, params=parameters) answer = response.json() print(answer['results'][0]['geometry']['location']) if __name__ == '__main__': geocode('207 N. Defiance St, Archbold, OH')
A execução desse programa Python retorna uma resposta muito parecida com a do primeiro script. $ python3 search2.py {'lat': 41.521954, 'lng': -84.306691}
A saída não é exatamente igual – é possível ver, por exemplo, que os dados JSON codificaram o resultado como um “objeto” que requests passou para você como um dicionário Python. Está claro, porém, que esse script fez o mesmo que o primeiro.
Capítulo 1 ■ Introdução à rede cliente-servidor
27
A primeira coisa que você notará sobre esse código é que a semântica oferecida pelo módulo pygeocoder de nível superior está ausente. A menos que examinemos cuidadosamente o código, podemos não notar que ele está indagando sobre um endereço postal! Enquanto search1.py pedia diretamente que um endereço fosse convertido em uma latitude e uma longitude, a segunda listagem constrói meticulosamente tanto um URL base quanto um conjunto de parâmetros de consulta cuja finalidade pode não ficar clara a menos que você já tenha lido a documentação do Google. A propósito, se quiser ler a documentação, você pode encontrar a API aqui: http://code.google.com/apis/maps/documentation/geocoding/
Se examinar cuidadosamente o dicionário de parâmetros de consulta de search2.py, você verá que o parâmetro address fornece o endereço postal específico sobre o qual estamos perguntando. O outro parâmetro informa ao Google que você não está emitindo essa consulta de localização com base em dados extraídos dinamicamente do sensor de localização de um dispositivo móvel. Ao receber um documento como resultado do acesso ao URL, chamamos manualmente o método response.json() para interpretá-lo como JSON e então nos aprofundamos na estrutura de dados de várias camadas resultante para encontrar o elemento correto dela que contém a latitude e a longitude. O script search2.py faz então a mesma coisa que search1.py – mas em vez de fazê-lo na linguagem de endereços e latitudes, fala sobre os pequenos detalhes de construir um URL, buscar uma resposta e analisá-la como JSON. Esta é uma diferença comum quando descemos de uma camada de uma pilha de rede para a camada abaixo dela: enquanto o código de nível superior falava sobre o que uma solicitação significava, o código de nível inferior só pode ver os detalhes de como a solicitação é construída.
Conversando com um protocolo Bem, o segundo exemplo de script cria um URL e busca o documento que corresponde a ele. Essa operação parece bem simples e, claro, seu navegador web faz de tudo para que ela pareça elementar. No entanto, a verdadeira razão para um URL poder ser usado na busca de um documento é que ele é uma espécie de roteiro que descreve onde encontrar – e como buscar – um determinado documento na web. O URL é composto pelo nome de um protocolo, seguido pelo nome da
28
Programação de Redes com Python
máquina na qual o documento reside e terminando com o caminho que nomeia um documento específico nessa máquina. Portanto, o que faz o programa Python search2.py poder resolver o URL e buscar o documento é o URL fornecer instruções que dizem a um protocolo de nível inferior como encontrá-lo. Na verdade, o protocolo de nível inferior que o URL usa é o famoso Hypertext Transfer Protocol (HTTP), que é a base de quase todas as comunicações modernas na web. Você aprenderá mais sobre ele nos capítulos 9, 10 e 11 deste livro. É o HTTP que fornece o mecanismo pelo qual a biblioteca Requests pode buscar o resultado no Google. O que aconteceria se removêssemos essa camada mágica – se quiséssemos usar o HTTP para fazer a busca diretamente? O resultado seria o programa search3.py, como mostrado na listagem 1.3.
Listagem 1.3 – Estabelecendo uma conexão HTTP direta com o Google Maps #!/usr/bin/env python3 # Programação de Redes com Python # https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter01/search3.py import http.client import json from urllib.parse import quote_plus base = '/maps/api/geocode/json' def geocode(address): path = '{}?address={}&sensor=false'.format(base, quote_plus(address)) connection = http.client.HTTPConnection('maps.google.com') connection.request('GET', path) rawreply = connection.getresponse().read() reply = json.loads(rawreply.decode('utf-8')) print(reply['results'][0]['geometry']['location']) if __name__ == '__main__': geocode('207 N. Defiance St, Archbold, OH')
Nesta listagem, você está manipulando diretamente o protocolo HTTP: pedindo que ele se conecte com uma máquina específica, para emitir uma solicitação GET com um caminho construído manualmente e ler a resposta diretamente a partir da conexão HTTP. Em vez de poder fornecer convenientemente seus parâmetros
Capítulo 1 ■ Introdução à rede cliente-servidor
29
de consulta como chaves e valores separados em um dicionário, você está tendo que embuti-los diretamente, à mão, no caminho que está solicitando, primeiro escrevendo um ponto de interrogação (?), que é então seguido pelos parâmetros no formato nome=valor separados pelo caractere &. O resultado da execução do programa, no entanto, é muito parecido com o dos programas mostrados anteriormente. $ python3 search3.py {'lat': 41.521954, 'lng': -84.306691}
Como você verá no decorrer deste livro, o HTTP é apenas um dos muitos protocolos para os quais a biblioteca padrão Python fornece uma implementação interna. Em search3.py, em vez de ter de se preocupar com todos os detalhes de como o HTTP funciona, seu código pode simplesmente pedir que uma solicitação seja enviada e então examinar a resposta resultante. É claro que os detalhes do protocolo com os quais o script tem de lidar são mais primitivos do que os de search2.py, porque descemos outro nível na pilha de protocolos, mas pelo menos ainda podemos usar a biblioteca padrão para manipular os dados de rede reais e certificar-se se eles estão certos.
Comunicação de rede bruta É óbvio que o HTTP não pode enviar dados entre duas máquinas apenas por via aérea. Em vez disso, o protocolo deve operar usando alguma abstração ainda mais simples. Na verdade, ele usa a capacidade dos sistemas operacionais modernos de dar suporte a uma comunicação de rede em texto sem formatação entre dois programas diferentes através de uma rede IP empregando o protocolo TCP. Em outras palavras, o protocolo HTTP opera ditando exatamente qual será a aparência do texto das mensagens que serão trocadas entre dois hosts que possam ser comunicar em TCP. Quando descemos abaixo do HTTP para examinar o que ocorre ali, estamos descendo para o nível mais baixo da pilha de rede que ainda podemos acessar facilmente com Python. Examine cuidadosamente o programa search4.py, mostrado na listagem 1.4. Ele faz exatamente a mesma solicitação de rede que os três programas anteriores fizeram ao Google Maps, mas a faz enviando uma mensagem de texto bruta através da Internet e recebendo um agrupamento de texto em resposta.
30
Programação de Redes com Python
Listagem 1.4 – Conversando com o Google Maps através de um soquete simples #!/usr/bin/env python3 # Programação de Redes com Python # https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter01/search4.py import socket from urllib.parse import quote_plus request_text = """\ GET /maps/api/geocode/json?address={}&sensor=false HTTP/1.1\r\n\ Host: maps.google.com:80\r\n\ User-Agent: search4.py (Foundations of Python Network Programming)\r\n\ Connection: close\r\n\ \r\n\ """ def geocode(address): sock = socket.socket() sock.connect(('maps.google.com', 80)) request = request_text.format(quote_plus(address)) sock.sendall(request.encode('ascii')) raw_reply = b'' while True: more = sock.recv(4096) if not more: break raw_reply += more print(raw_reply.decode('utf-8')) if __name__ == '__main__': geocode('207 N. Defiance St, Archbold, OH')
Na passagem de search3.py para search4.py, cruzamos um limiar importante. Em todas as listagens de programa anteriores, você usou uma biblioteca Python – escrita na própria linguagem Python – que sabia como se comunicar com um protocolo de rede complicado em seu nome. Mas aqui você alcançou o básico: está chamando a função bruta socket() que é fornecida pelo sistema operacional
Capítulo 1 ■ Introdução à rede cliente-servidor
31
host para dar suporte a comunicações de rede básicas em uma rede IP. Em outras palavras, está usando os mesmos mecanismos que um programador de sistemas de nível inferior usaria na linguagem C ao escrever essa mesma operação de rede. Você aprenderá mais sobre soquetes nos próximos capítulos. Por enquanto, é possível notar em search4.py que a comunicação de rede bruta é uma simples questão de enviar e receber sequências de bytes. A solicitação enviada é uma sequência de bytes e a resposta – que, nesse caso, é apenas exibida na tela para que você possa apreciá-la em toda a sua glória de baixo nível – é outra grande sequência de bytes. (Consulte a seção “Codificação e Decodificação”, mais à frente neste capítulo, para ver os detalhes de por que a sequência é decodificada antes de ser exibida.) A solicitação HTTP, cujo texto pode ser visto dentro da função sendall(), é composta pela palavra GET – o nome da operação que queremos executar – seguida pelo caminho do documento que você deseja buscar e a versão do HTTP suportada. GET /maps/api/geocode/json?address=207+N.+Defiance+St%2C+Archbold%2C+OH&sensor=false HTTP/1.1
Em seguida, há uma série de cabeçalhos compostos por um nome, dois pontos e um valor, e então temos um par retorno de carro/nova linha que termina a solicitação. A resposta, que será exibida como saída do script se você executar search4.py, é mostrada na listagem 1.5. Preferi apenas exibir a resposta na tela nesse exemplo, em vez de escrever o complexo código de manipulação de texto que poderia interpretá-la. Fiz isso porque achei que apenas ler a resposta HTTP em sua tela lhe daria uma noção melhor de sua aparência do que se você tivesse que decifrar um código projetado para interpretá-la.
Listagem 1.5 – Saída da execução de search4.py HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Date: Sat, 23 Nov 2013 18:34:30 GMT Expires: Sun, 24 Nov 2013 18:34:30 GMT Cache-Control: public, max-age=86400 Vary: Accept-Language Access-Control-Allow-Origin: * Server: mafe X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN Alternate-Protocol: 80:quic Connection: close
32
Programação de Redes com Python
{ "results" : [ { ... "formatted_address" : "207 North Defiance Street, Archbold, OH 43502, USA", "geometry" : { "location" : { "lat" : 41.521954, "lng" : -84.306691 }, ... }, "types" : [ "street_address" ] } ], "status" : "OK" }
Podemos ver que a resposta HTTP é muito parecida em estrutura com a solicitação HTTP. Ela começa com uma linha de status, que é seguida por vários cabeçalhos. Após uma linha em branco, o conteúdo da resposta propriamente dito é exibido: uma estrutura de dados JavaScript, em um formato simples conhecido como JSON, que responde a consulta descrevendo a localização geográfica que a pesquisa na API Geocoding do Google retornou. É claro que todas essas linhas de status e cabeçalhos são o tipo de detalhe de baixo nível com o qual o módulo Python httplib lidava nas listagens mais antigas. Aqui, estamos vendo a aparência da comunicação se essa camada de software for removida.
Um suportando o outro até o fim Espero que você tenha apreciado esses exemplos iniciais das formas que a programação de redes com Python pode assumir. Se fizermos uma pausa, podemos usar essa série de exemplos para ressaltar vários pontos sobre a programação de redes em Python. Em primeiro lugar, talvez agora você possa entender com mais clareza o que significa o termo pilha de protocolos: significa construir uma comunicação de alto nível semanticamente sofisticada (“Quero a localização geográfica desse endereço
Capítulo 1 ■ Introdução à rede cliente-servidor
33
postal”) acima de comunicações mais simples e rudimentares que no fim da cadeia são apenas strings de texto indo e vindo entre dois computadores usando seu hardware de rede. A pilha de protocolos específica que você acabou de examinar tem quatro protocolos de altura. • No topo está a API Geocoding do Google, que nos ajuda a expressar nossas consultas geográficas na forma de URLs, que então buscam dados JSON contendo coordenadas. • URLs nomeiam documentos que podem ser recuperados com o uso do HTTP. • O HTTP dá suporte a comandos orientados a documentos, como GET, usando soquetes TCP/IP brutos. • Os soquetes TCP/IP só sabem como enviar e receber sequências de bytes. Veja que cada camada da pilha usa as ferramentas fornecidas pela camada abaixo dela e por sua vez oferece recursos para a próxima camada superior. Um segundo ponto que ficou claro com esses exemplos é como o suporte de Python é completo para cada um dos níveis de rede em que acabamos de operar. Só quando é usado um protocolo específico do fornecedor, e é preciso formatar solicitações para que o Google as entenda, é que é necessário usar uma biblioteca de terceiros; não escolhi a biblioteca requests para a segunda listagem porque a biblioteca padrão não tem o módulo urllib.request e sim porque sua API é muito confusa. Todos os outros níveis de protocolos com os quais nos deparamos já tinham um forte suporte dentro da biblioteca padrão Python. Seja para buscar o documento em um URL específico ou enviar e receber strings em um soquete de rede bruto, a linguagem Python estaria pronta com funções e classes que você poderia usar para executar a tarefa. Em terceiro lugar, observe que meus programas diminuíram consideravelmente em qualidade à medida que eu me forçava a usar protocolos de nível cada vez mais baixo. As listagens search2.py e search3.py, por exemplo, começaram a embutir elementos em código como a estrutura da forma e os nomes de host de uma maneira inflexível e que pode ser difícil de editar depois. O código de search4.py é ainda pior: ele inclui uma solicitação HTTP escrita à mão e sem parâmetros cuja estrutura é totalmente opaca para Python. E, claro, não contém a lógica que seria necessária para a análise e interpretação da resposta HTTP e a compreensão de condições de erro de rede que possam ocorrer.
34
Programação de Redes com Python
Isso ilustra uma lição da qual você deve lembrar a cada capítulo deste livro: a de que é difícil implementar protocolos de rede corretamente e, portanto, devemos usar a biblioteca padrão ou bibliotecas de terceiros sempre que possível. Principalmente quando você estiver criando um cliente de rede, ficará tentado a simplificar excessivamente o código; tenderá a ignorar muitas condições de erro que podem surgir, a se preparar apenas para as respostas mais prováveis, a evitar escapar parâmetros apropriadamente porque acredita que suas strings de consulta só incluirão caracteres alfabéticos simples e, em geral, a escrever um código frágil com o menor conhecimento possível dos detalhes técnicos do serviço com o qual está conversando. Se em vez disso usar uma biblioteca de terceiros que tenha desenvolvido uma implementação completa de um protocolo, e que tenha precisado suportar muitos desenvolvedores Python que a usaram para diversas tarefas, você se beneficiará de todos os casos críticos e apuros complicados que o implementador da biblioteca detectou e aprendeu como manipular apropriadamente. Em quarto lugar, é preciso enfatizar que protocolos de rede de nível superior – como a API Geocoding para a busca das coordenadas de um endereço – geralmente funcionam ocultando as camadas de rede abaixo deles. Se você só usou a biblioteca pygeocoder, pode não saber que URLs e o HTTP são os mecanismos de nível inferior que estão sendo usados para construir e responder suas consultas! Uma pergunta interessante, cuja resposta varia dependendo do cuidado com que uma biblioteca Python foi escrita, é se a biblioteca oculta erros corretamente nos níveis mais baixos. Um erro na rede que tornasse o Google temporariamente inalcançável a partir do local em que você se encontra poderia lançar uma exceção de rede bruta, de baixo nível, no meio de um código que estivesse apenas tentando encontrar as coordenadas de um endereço? Ou todos os erros serão convertidos em uma exceção de alto nível específica da geocodificação? Preste atenção no tópico de captura de erros de rede ao avançar pelos capítulos deste livro, principalmente os da primeira parte com sua ênfase na rede de baixo nível. Para concluir, chegamos ao tópico que o ocupará pelo resto da primeira parte do livro: na verdade, a interface socket() usada em search4.py não é o nível de protocolo mais baixo em ação nessa solicitação ao Google! Assim como o exemplo tem protocolos de rede operando acima do nível de soquetes brutos, também há protocolos abaixo da abstração dos soquetes que Python não pode ver porque o sistema operacional é que os gerencia. As camadas que operam abaixo da API socket() são as seguintes:
Capítulo 1 ■ Introdução à rede cliente-servidor
35
• O Transmission Control Protocol (TCP) dá suporte a comunicações bidirecionais compostas por fluxos de bytes enviando (ou talvez reenviando), recebendo e reordenando mensagens de rede pequenas chamadas pacotes. • O Internet Protocol (IP) sabe como enviar pacotes entre computadores diferentes. • A “camada de link” na parte mais baixa, composta por dispositivos de hardware de rede como portas Ethernet e placas wireless, que pode enviar mensagens físicas entre computadores diretamente conectados. No resto deste capítulo, e nos dois capítulos seguintes, você examinará esses níveis mais baixos de protocolos. Começaremos neste capítulo examinando o nível IP e então daremos prosseguimento nos próximos capítulos para ver como dois protocolos bem diferentes – UDP e TCP – dão suporte a dois tipos básicos de comunicação que podem ocorrer entre aplicativos em um par de hosts conectados à Internet. Mas, primeiro, algumas palavras sobre bytes e caracteres.
Codificação e decodificação A linguagem Python 3 faz uma distinção clara entre strings de caracteres e sequências de bytes de baixo nível. Bytes são os números binários reais que os computadores enviam e recebem durante a comunicação de rede, e cada byte é composto por oito dígitos binários e indo do valor binário 00000000 a 11111111, portanto, do inteiro decimal 0 a 255. As strings de caracteres em Python podem conter símbolos Unicode como a (“A minúsculo latino”, como o Unicode o chama), } (“chave direita”) ou ∅ (conjunto vazio). Embora cada caractere Unicode tenha um identificador numérico associado a ele, chamado de seu ponto de código, você pode tratar isso como um detalhe de implementação interno – Python 3 tem o cuidado de fazer os caracteres sempre se comportarem como caracteres e só quando você solicitar é que a linguagem converterá os caracteres de e para bytes visíveis externamente. Estas duas operações têm nomes formais. Decodificação é o que ocorre quando os bytes estão a caminho de seu aplicativo e
você precisa descobrir o que eles significam. Pense em seu aplicativo, quando ele recebe bytes de um arquivo ou através da rede, como um espião clássico da Guerra Fria cuja tarefa é decifrar a transmissão de bytes brutos que estão chegando por um canal de comunicação.
36
Programação de Redes com Python
Codificação é o processo de pegar strings de caracteres que você está pronto para
apresentar para o mundo externo e transformá-las em bytes usando uma das muitas codificações que os computadores digitais usam quando precisam transmitir ou armazenar símbolos empregando os bytes que são sua única moeda de troca. Poderíamos comparar o espião tendo de transformar a mensagem em números para transmissão à conversão dos símbolos em um código que possa ser enviado através da rede. Essas duas operações são expostas de maneira simples e óbvia em Python 3 como um método decode() que você pode aplicar a sequências de bytes após recebê-las e um método encode() para ser chamado em strings de caracteres quando você estiver pronto para enviá-las. As técnicas são mostradas na listagem 1.6.
Listagem 1.6 – Decodificando bytes de entrada e codificando caracteres para saída #!/usr/bin/env python3 # Programação de Redes com Python # https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter01/stringcodes.py if __name__ == '__main__': # Conversão de bytes do ambiente externo em caracteres Unicode input_bytes = b'\xff\xfe4\x001\x003\x00 \x00i\x00s\x00 \x00i\x00n\x00.\x00' input_characters = input_bytes.decode('utf-16') print(repr(input_characters)) # Conversão de caracteres em bytes antes de seu envio output_characters = 'We copy you down, Eagle.\n' output_bytes = output_characters.encode('utf-8') with open('eagle.txt', 'wb') as f: f.write(output_bytes)
Os exemplos deste livro tentam diferenciar com cuidado bytes de caracteres. Observe que os dois têm aparências diferentes quando exibimos sua representação: sequências de bytes começam com a letra b e se apresentam como b'Hello', enquanto as strings de caracteres não recebem um caractere inicial e se apresentam simplesmente como 'world'. Para tentar evitar confusões entre sequências de bytes e strings de caracteres, Python 3 oferece a maioria dos métodos de strings somente para o tipo string de caracteres.
Capítulo 1 ■ Introdução à rede cliente-servidor
37
Internet Protocol Tanto o networking, que ocorre quando conectamos vários computadores com um link físico para que possam se comunicar, quanto o internetworking, que conecta redes físicas adjacentes para formar um sistema muito maior como a Internet, são basicamente esquemas elaborados para permitir o compartilhamento de recursos. É claro que tudo que existe em um computador precisa ser compartilhado: unidades de disco, memória e CPU são monitoradas cuidadosamente pelo sistema operacional para que os programas individuais executados no computador possam acessar esses recursos sem atrapalhar uns aos outros. A rede é outro recurso que o sistema operacional precisa proteger para que os programas possam ser comunicar uns com os outros sem interferir em outras comunicações que possam estar ocorrendo na mesma rede. Os dispositivos de rede físicos que seu computador usa para se comunicar – como placas Ethernet, transmissores wireless e portas USB – são eles próprios projetados com uma capacidade elaborada para compartilhar o mesmo meio físico entre muitos dispositivos diferentes que queiram conversar. Várias placas Ethernet podem ser conectadas ao mesmo hub; 30 placas wireless podem estar compartilhando o mesmo canal de rádio; e um modem DSL usa a multiplexação no domínio da frequência, um conceito básico em engenharia elétrica, para evitar que seus sinais digitais interfiram nos sinais analógicos enviados quando falamos no telefone. A unidade básica de compartilhamento entre dispositivos de rede – em outras palavras, sua moeda de troca – é o pacote. Um pacote é uma sequência de bytes cujo tamanho pode variar de alguns bytes a milhares de bytes, que são transmitidos como uma única unidade entre os dispositivos de rede. Embora existam redes especializadas, principalmente em áreas como telecomunicações, na qual cada byte que passa por uma linha de transmissão pode ser roteado separadamente para um destino diferente, as tecnologias de uso mais genérico usadas na construção de redes digitais para os computadores modernos são todas baseadas na unidade maior do pacote. Com frequência um pacote só tem duas propriedades no nível físico: os dados da sequência de bytes que ele carrega e um endereço ao qual deve ser distribuído. O endereço de um pacote físico geralmente é um identificador exclusivo que nomeia uma das outras placas de rede conectadas ao mesmo segmento Ethernet ou canal wireless do computador que está transmitido o pacote. A tarefa de uma placa de rede é enviar e receber esses pacotes sem que o sistema operacional do computador precise conhecer os detalhes de como a rede usa fios, voltagens e sinais para operar.
38
Programação de Redes com Python
Mas o que, então, é o Internet Protocol? O Internet Protocol é um esquema que impõe um sistema de endereços uniforme para todos os computadores conectados à Internet no mundo inteiro e que possibilita que os pacotes viajem de uma extremidade à outra da Internet. Idealmente, seu navegador web deve poder se conectar com um host de qualquer local sem precisar saber que dispositivos de rede cada pacote está percorrendo em sua jornada. É raro um programa Python operar em um nível tão baixo a ponto de ver o Internet Protocol em ação, mas é no mínimo útil saber como ele funciona.
Endereços IP A versão original do Internet Protocol atribui um endereço de 4 bytes a cada computador conectado à rede mundial. Geralmente esses endereços são escritos como quatro números decimais, separados por pontos, com cada um representando um único byte. Cada número pode variar então de 0 a 255. Logo, um endereço IP tradicional de 4 bytes tem esta aparência: 130.207.244.244
Já que endereços puramente numéricos podem ser difíceis dos humanos lembrarem, as pessoas que usam a Internet costumam ver nomes de host em vez de endereços IP. O usuário pode simplesmente digitar google.com e esquecer que em segundo plano esse nome é mapeado para um endereço como 74.125.67.103, para o qual o computador pode realmente endereçar pacotes para transmissão pela Internet. No script getname.py, mostrado na listagem 1.7, temos um programa Python simples que solicita ao sistema operacional – Linux, Mac OS, Windows ou qualquer sistema em que o programa esteja sendo executado – que resolva o nome de host www.python.org. O serviço de rede específico, chamado Domain Name System (DNS, Sistema de Nomes de Domínio), que é acionado para responder consultas de nome de host é bem complexo e será discutido com mais detalhes no capítulo 4.
Listagem 1.7 – Convertendo um nome de host em um endereço IP #!/usr/bin/env python3 # Programação de Redes com Python # https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter01/getname.py
Capítulo 1 ■ Introdução à rede cliente-servidor
39
import socket if __name__ == '__main__': hostname = 'www.python.org' addr = socket.gethostbyname(hostname) print('The IP address of {} is {}'.format(hostname, addr))
Por enquanto, você só precisa lembrar de duas coisas. • Em primeiro lugar, independentemente de quanto um aplicativo de Internet pareça elegante, o Internet Protocol sempre usará endereços IP numéricos para direcionar pacotes para seu destino. • Em segundo lugar, geralmente os detalhes complicados de como os nomes de host são resolvidos para endereços IP são manipulados pelo sistema operacional. Como ocorre com a maioria dos detalhes da operação do Internet Protocol, o sistema operacional prefere manipulá-los ele próprio, ocultando-os tanto de você quanto de seu código Python. Na verdade, a situação do endereçamento pode ser um pouco mais complexa atualmente do que o simples esquema de 4 bytes que acabamos de descrever. Já que o mundo está começando a ficar sem endereços IP de 4 bytes, está sendo implantado um esquema de endereços estendido, chamado IPv6, que permite o uso de endereços absolutamente gigantescos de 16 bytes que devem atender as necessidades da humanidade por um longo tempo. Eles são escritos diferentemente dos endereços IP de 4 bytes e têm esta aparência: fe80::fcfd:4aff:fecf:ea4e
Contanto que seu código aceite endereços IP ou nomes de host do usuário e os passe diretamente para uma biblioteca de rede para processamento, provavelmente você nunca precisará se preocupar com a diferença entre IPv4 e IPv6. O sistema operacional em que o código Python estiver sendo executado saberá que versão do IP ele está usando e deve interpretar os endereços de acordo. Geralmente, os endereços IP tradicionais podem ser lidos da esquerda para a direita: o primeiro ou os dois primeiros bytes especificam uma organização, e com frequência o byte seguinte especifica a sub-rede em que a máquina de destino reside. O último byte resume o endereço a essa máquina ou serviço específico. Também há alguns intervalos especiais de endereços IP que têm significado próprio.
40
Programação de Redes com Python
• 127.*.*.*: Endereços IP que começam com o byte 127 estão em um intervalo reservado especial que é local da máquina em que um aplicativo está sendo executado. Quando seu navegador web, cliente FTP ou programa Python se conectar a um endereço desse intervalo, estará solicitando para se comunicar com algum outro serviço ou programa sendo executado na mesma máquina. A maioria das máquinas faz uso de apenas um endereço de todo esse intervalo: o endereço IP 127.0.0.1 é usado universalmente para indicar “a própria máquina em que esse programa está sendo executado” e costuma ser acessado através do nome de host localhost. • 10.*.*.*, 172.16–31.*.*, 192.168.*.*: Estes intervalos IP são reservados para as chamadas sub-redes privadas. As autoridades que controlam a Internet fizeram uma promessa: nunca fornecerão endereços IP desses três intervalos para empresas que estiverem instalando servidores ou serviços. Logo, eles não têm significado em nenhum local da Internet e não nomeiam nenhum host com o qual você poderia querer se conectar. Estão então livres para serem usados em qualquer rede interna da empresa onde você quiser ficar à vontade para atribuir endereços IP se preferir não tornar esses hosts acessíveis a partir de outros locais da Internet. Você pode até ver alguns desses endereços privados em sua própria casa: com frequência seu roteador wireless ou modem DSL atribuirá endereços IP de um desses intervalos privados aos computadores e laptops da casa e ocultará todo o tráfego de Internet existente por trás do endereço IP “real” que o provedor de serviços de Internet alocou para seu uso.
Roteamento Uma vez que um aplicativo solicitar ao sistema operacional para enviar dados para um endereço IP específico, o sistema terá que decidir como irá transmitir esses dados usando uma das redes físicas com as quais a máquina está conectada. Essa decisão (isto é, a definição de para onde enviar cada pacote do Internet Protocol de acordo com o endereço IP que ele nomeia como seu destino) se chama roteamento. Quase todos os códigos Python, ou até mesmo todos, que você escrever durante sua carreira serão executados em hosts em comunicação com a Internet, com uma única interface de rede conectando-os ao resto do mundo. Para essas máquinas, o roteamento passa a ser uma decisão muito simples.
Capítulo 1 ■ Introdução à rede cliente-servidor
41
• Se o endereço IP tiver o formato 127.*.*.*, o sistema operacional saberá que o pacote foi destinado a outro aplicativo na mesma máquina. Ele não será nem mesmo enviado para um dispositivo de rede físico para transmissão e sim passado diretamente pelo sistema operacional para outro aplicativo via uma cópia de dados interna. • Se o endereço IP pertencer à mesma sub-rede em que a máquina se encontra, o host de destino poderá ser encontrado com uma simples verificação do segmento Ethernet local, canal wireless ou qualquer que seja a rede local, e o pacote será enviado para uma máquina conectada localmente. • Caso contrário, sua máquina encaminhará o pacote para uma máquina gateway que conectará sua sub-rede local ao resto da Internet. Ficará então a cargo da máquina gateway decidir se deve enviar o pacote depois disso. É claro que o roteamento só é simples assim na fronteira com a Internet, onde as únicas decisões são saber se é preciso manter o pacote na rede local ou enviá-lo para a rede mundial. Não é difícil perceber que as decisões de roteamento são muito mais complexas para os dispositivos de rede dedicados que formam o backbone da Internet! Nesse caso, nos switches que conectam continentes inteiros, elaboradas tabelas de roteamento têm de ser construídas, consultadas e constantemente atualizadas para que fique claro que os pacotes destinados ao Google devem seguir em uma direção, pacotes direcionados a um endereço IP da Amazon devem seguir outro caminho e pacotes que vão para a sua máquina adotarão uma rota diferente. Mas é raro aplicativos Python serem executados em roteadores de backbone da Internet, logo, a situação mais simples de roteamento que acaba de ser descrita é quase sempre a que você verá em ação. Fui um pouco vago nos parágrafos anteriores sobre como seu computador decidirá se um endereço IP pertence à uma sub-rede local ou se deve ser encaminhado através de um gateway para o resto da Internet. Para ilustrar a ideia de uma sub-rede, onde todos os hosts compartilham o mesmo prefixo de endereço IP, escrevi o prefixo seguido por asteriscos para as partes do endereço que podem variar. É óbvio que a lógica binária que controla a pilha de rede de seu sistema operacional não insere realmente pequenos asteriscos ASCII em sua tabela de roteamento! Em vez disso, as sub-redes são especificadas pela combinação de um endereço IP com uma máscara que indica quantos de seus bits mais significativos têm de coincidir para um host pertencer à sub-rede. Se você lembrar que cada byte de um endereço IP representa oito bits de dados binários, poderá ler facilmente números de sub-rede. Eles têm esta aparência:
42
Programação de Redes com Python
• 127.0.0.0/8 – Este padrão, que descreve o intervalo de endereços IP discutido anteriormente e que está reservado para o host local, especifica que os 8 primeiros bits (1 byte) devem coincidir com o número 127 e que os 24 bits restantes (3 bytes) podem ter qualquer valor. • 192.168.0.0/16 – Este padrão será encontrado em qualquer endereço IP que pertença ao intervalo privado 192.168 porque os 16 primeiros bits devem coincidir de maneira exata. Os 16 últimos bits do endereço de 32 bits podem ter qualquer valor. • 192.168.5.0/24 – Aqui temos a definição de uma sub-rede individual específica. Esta é provavelmente a máscara de sub-rede mais comum em toda a Internet. Os três primeiros bytes do endereço são previamente especificados e têm de corresponder ao padrão para um endereço IP fazer parte desse intervalo. Só o último byte (os últimos oito bits) pode variar entre as máquinas do intervalo. Isso fornece 256 endereços exclusivos. Normalmente, o endereço .0 é usado como nome da sub-rede e o endereço .255 é usado como destino de um “pacote de broadcast” endereçado a todos os hosts da sub-rede (como você verá no próximo capítulo), o que deixa 254 endereços livres para serem atribuídos a computadores. Com frequência, o endereço .1 é usado para o gateway que conecta a sub-rede ao resto da Internet, mas algumas empresas e escolas preferem usar outro número para seus gateways. Em quase todos os casos, o código Python espera que o sistema operacional host tome as decisões de roteamento de pacotes corretamente – da mesma forma que espera que ele resolva nomes de host em endereços IP.
Fragmentação de pacotes Um último conceito do Internet Protocol que merece menção é a fragmentação de pacotes. Embora devesse ser um detalhe obscuro ocultado com sucesso do programa pela pilha de rede do sistema operacional, ele causou problemas suficientes no decorrer da história da Internet para merecer pela menos uma breve menção aqui. A fragmentação é necessária porque o Internet Protocol dá suporte a pacotes muito grandes – eles podem ter até 64KB – mas geralmente os dispositivos de rede a partir dos quais as redes IP são construídas dão suporte a tamanhos de pacote muito menores. As redes Ethernet, por exemplo, só dão suporte a pacotes de 1.500 bytes. Logo, os pacotes da Internet incluem um flag de “não fragmentação”
Capítulo 1 ■ Introdução à rede cliente-servidor
43
(DF, don’t fragment) com o qual o emitente pode decidir o que quer que ocorra se o pacote for muito grande para passar por uma das redes físicas existentes entre o computador de origem e o destino: • Se o flag DF não estiver ativado, a fragmentação será permitida, e quando o pacote chegar à fronteira da rede que não o acomoda, o gateway poderá dividi-lo em pacotes menores e marcá-los para serem reagrupados na outra extremidade. • Se o flag DF estiver ativado, a fragmentação será proibida, e se não houver espaço para o pacote ele será descartado e uma mensagem de erro será retornada – em um pacote de sinalização especial chamado pacote Internet Control Message Protocol (ICMP) – para a máquina que o enviou para que ela possa tentar dividir a mensagem em partes menores e reenviá-la. Geralmente os programas Python não têm controle sobre o flag DF; em vez disso, ele é ativado pelo sistema operacional. A lógica que o sistema costuma usar é mais ou menos esta: se você estiver em uma comunicação UDP (Capítulo 2) composta por datagramas individuais percorrendo a Internet, o sistema operacional deixará o flag desativado para que cada datagrama alcance o destino em quantas partes forem necessárias; mas se você estiver em uma comunicação TCP (Capítulo 3) cujo longo fluxo de dados pode ter centenas ou milhares de pacotes, o sistema operacional ativará o flag DF para poder selecionar o tamanho certo para o pacote e permitir que a conversa flua sem problemas, sem seus pacotes sendo constantemente fragmentados em trânsito, o que tornaria a comunicação muito menos eficiente. O maior pacote que uma sub-rede da Internet pode aceitar é chamado de sua unidade máxima de transmissão (MTU, maximum transmission unit), e costumava haver um grande problema no processamento de MTUs que prejudicava muitos usuários da Internet. Nos anos 90, os provedores de serviços de Internet (quase sempre empresas telefônicas oferecendo conexões DSL) começaram a usar o PPPoE, um protocolo que insere pacotes IP dentro de um invólucro que deixa um espaço para eles de apenas 1.492 bytes em vez dos 1.500 bytes totais geralmente permitidos na Ethernet. Muitos sites da Internet estavam despreparados para isso porque usavam pacotes de 1.500 bytes por padrão e tinham bloqueado todos os pacotes ICMP como uma medida de segurança mal direcionada. Como consequência, seus servidores podiam não receber os erros ICMP que informavam que seus grandes pacotes de 1.500 bytes “sem fragmentação” estavam alcançando as conexões DSL dos usuários, mas não cabiam nelas.
44
Programação de Redes com Python
A consequência intrigante dessa situação era que pequenos arquivos ou páginas web podiam ser vistos sem problema, e protocolos interativos como o Telnet e o SSH funcionavam, já que essas duas atividades tendem a enviar pacotes pequenos com menos de 1.492 bytes. Contudo, quando o usuário tentava baixar um arquivo grande ou quando um comando Telnet ou SSH abria várias telas cheias de saída ao mesmo tempo, a conexão travava e parava de responder. Atualmente esse problema é encontrado raras vezes, mas ele ilustra como um recurso de baixo nível do IP pode gerar consequências vistas pelo usuário e, portanto, por que devemos nos lembrar de todos os recursos do IP ao escrever e depurar programas de rede.
Aprendendo mais sobre o IP Nos próximos capítulos, você examinará as camadas de protocolo que ficam acima do IP e verá como seus programas Python podem ter diferentes tipos de comunicações de rede usando os diversos serviços construídos acima do Internet Protocol. Mas e se ficar intrigado com a descrição anterior de como o IP funciona e quiser saber mais? Os recursos oficiais que descrevem o Internet Protocol são as solicitações de comentários (RFCs, requests for comment) publicadas pela IETF que definem exatamente como o protocolo funciona. Elas foram escritas cuidadosamente e, quando combinadas com uma xícara de café forte e algumas horas de tempo livre para leitura, nos introduzem a cada detalhe de como o Internet Protocol opera. Por exemplo, aqui está a RFC que define o protocolo propriamente dito: http://tools.ietf.org/html/rfc791
Você também pode encontrar RFCs sendo referenciadas em fontes gerais como a Wikipédia, e com frequência as RFCs citam outras RFCs que descrevem mais detalhes de um protocolo ou esquema de endereçamento. Se quiser aprender tudo sobre o Internet Protocol e os outros protocolos que são executados acima dele, pode ser interessante adquirir o respeitado livro TCP/IP Illustrated, Volume 1: the protocols (2nd edition), de Kevin R. Fall e W. Richard Stevens (Addison-Wesley Professional, 2011). Ele aborda, em detalhes minuciosos, todas as operações de protocolos sobre as quais este livro só terá espaço para mencionar. Também há outros bons livros sobre redes em geral, e que em particular podem ajudar na configuração de redes se instalação de redes IP e roteamento forem algo que você faça no trabalho ou até mesmo em casa para colocar seus computadores na Internet.
Capítulo 1 ■ Introdução à rede cliente-servidor
45
Resumo Todos os serviços de rede exceto os mais rudimentares são implementados acima de alguma outra função de rede mais básica. Você examinou uma “pilha” assim nas seções de abertura deste capítulo. O protocolo TCP/IP (que será abordado no capítulo 3) dá suporte à simples transmissão de sequências de bytes entre um cliente e um servidor. O protocolo HTTP (Capítulo 9) descreve como essa conexão pode ser usada para um cliente solicitar um documento específico e o servidor responder fornecendo-o. A World Wide Web (Capítulo 11) codifica as instruções de recuperação de um documento hospedado pelo HTTP em um endereço especial chamado URL, e o formato de dados padrão JSON é popular quando o documento retornado pelo servidor precisa apresentar dados estruturados para o cliente. Acima de toda essa estrutura, o Google oferece um serviço de geocodificação que permite que os programadores construam um URL ao qual o Google responde com um documento JSON descrevendo uma localização geográfica. Sempre que informações textuais têm de ser transmitidas na rede – ou salvas em armazenamento persistente orientado a bytes, como em um disco – os caracteres precisam ser codificados como bytes. Há vários esquemas amplamente usados para a representação de caracteres como bytes. Os mais comuns na Internet moderna são a simples e limitada codificação ASCII e o poderoso e geral sistema Unicode, principalmente sua codificação específica conhecida como UTF-8. Sequências de bytes Python podem ser convertidas em caracteres reais com o uso do método decode(), e strings de caracteres comuns podem ser obtidas novamente através do método encode(). Python 3 tenta nunca converter bytes em strings automaticamente – uma operação que demandaria apenas que a linguagem soubesse a codificação desejada – e, portanto, códigos Python 3 apresentarão mais chamadas a decode() e encode() do que você pode ter vivenciado em Python 2. Para a rede IP transmitir pacotes em nome de um aplicativo, é necessário que os administradores de redes, fornecedores de dispositivos e programadores de sistemas operacionais tenham conversado para atribuir endereços IP a máquinas individuais, estabelecer tabelas de roteamento no nível tanto de máquina quanto de roteador e configurar o Sistema de Nomes de Domínio (Capítulo 4) para associar endereços IP a nomes vistos pelos usuários. Os programadores Python devem saber que cada pacote IP percorre seu caminho pela rede em direção do destino e que um pacote pode ser fragmentado se for grande demais para caber em um dos “saltos” entre roteadores ao longo do caminho.
46
Programação de Redes com Python
Há duas maneiras básicas de usar o IP a partir da maioria dos aplicativos. Elas são usar cada pacote como uma mensagem autônoma ou solicitar um fluxo de dados que seja dividido em pacotes automaticamente. Esses protocolos se chamam UDP e TCP e são os assuntos que este livro abordará nos capítulos 2 e 3.