10. Serviços da Web
10.1. Introdução
No capítulo anterior, apresentamos várias aplicações cliente-servidor TCP/IP. Como os clientes e o servidor trocam linhas de texto, elas podem ser escritas em qualquer linguagem de programação. O cliente precisa apenas conhecer o protocolo de comunicação esperado pelo servidor. Os serviços Web são aplicações de servidor TCP/IP que apresentam as seguintes características:
- São hospedados por servidores web e, portanto, o protocolo de comunicação cliente-servidor é o HTTP (HyperText Transport Protocol), um protocolo que opera sobre o TCP-IP.
- O serviço web possui um protocolo de comunicação padrão, independentemente do serviço prestado. Um serviço web oferece diversos serviços: S1, S2, ..., Sn. Cada um deles aguarda parâmetros fornecidos pelo cliente e retorna um resultado a ele. Para cada serviço, o cliente precisa saber:
- o nome exato do serviço, se
- a lista de parâmetros que devem ser fornecidos e seus tipos
- o tipo de resultado retornado pelo serviço
Uma vez conhecidos esses elementos, o diálogo cliente-servidor segue o mesmo formato, independentemente do serviço web consultado. A programação dos clientes é, assim, padronizada.
- Por motivos de segurança contra ataques provenientes da Internet, muitas organizações possuem redes privadas e abrem para a Internet apenas algumas portas de seus servidores: essencialmente a porta 80 do serviço web. Todas as outras portas estão bloqueadas. Assim, as aplicações cliente-servidor, conforme apresentadas no capítulo anterior, são desenvolvidas dentro da rede privada (intranet) e, em geral, não são acessíveis de fora. Hospedar um serviço em um servidor web torna-o acessível a toda a comunidade da internet.
- O serviço Web pode ser modelado como um objeto remoto. Os serviços oferecidos tornam-se, então, métodos desse objeto. Um cliente pode acessar esse objeto remoto como se ele fosse local. Isso oculta toda a parte de comunicação de rede e permite construir um cliente independente dessa camada. Se essa camada vier a mudar, o cliente não precisa ser modificado. Essa é uma enorme vantagem e, provavelmente, o principal trunfo dos serviços Web.
- Assim como nas aplicações cliente-servidor TCP/IP apresentadas no capítulo anterior, o cliente e o servidor podem ser escritos em qualquer linguagem de programação. Eles trocam linhas de texto. Essas linhas contêm duas partes:
- os cabeçalhos necessários ao protocolo HTTP
- o corpo da mensagem. No caso de uma resposta do servidor ao cliente, o corpo da mensagem está no formato XML (eXtensible Markup Language). No caso de uma solicitação do cliente ao servidor, o corpo da mensagem pode assumir várias formas, incluindo XML. A solicitação XML do cliente pode ter um formato específico chamado SOAP (Simple Object Access Protocol). Nesse caso, a resposta do servidor também segue o formato SOAP.
10.2. Os navegadores e o XML
Os serviços da Web enviam XML aos seus clientes. Os navegadores podem reagir de maneiras diferentes ao receber esse fluxo XML. O Internet Explorer possui uma folha de estilo predefinida que permite exibi-lo. O Netscape Communicator, por sua vez, não possui essa folha de estilo e não exibe o código XML recebido. É necessário visualizar o código-fonte da página recebida para ter acesso ao XML. Aqui está um exemplo para o seguinte código XML:
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="st.istia.univ-angers.fr">bonjour de nouveau !</string>
O Internet Explorer exibirá a seguinte página:

enquanto o Netscape Navigator exibirá:

Se visualizarmos o código-fonte da página recebida pelo Netscape, obtemos:

O Netscape recebeu exatamente o mesmo que o Internet Explorer, mas o exibiu de maneira diferente. Daqui em diante, usaremos o Internet Explorer para as capturas de tela.
10.3. Um primeiro serviço web
Vamos explorar os serviços web por meio de um exemplo muito simples, apresentado em três versões.
10.3.1. Versão 1
Para esta primeira versão, usaremos o VS.NET, que tem a vantagem de poder gerar uma estrutura básica de serviço web imediatamente operacional. Depois de compreender essa arquitetura, poderemos começar a trabalhar de forma independente. Esse será o objetivo das versões seguintes.
Com o VS.NET, vamos criar um novo projeto com a opção [Fichier/Nouveau/Projet]:

Observe os seguintes pontos:
- o tipo do projeto é Visual Basic (painel esquerdo)
- o modelo do projeto é Serviço Web ASP.NET (painel direito)
- o local é livre. Aqui, o serviço web será hospedado por um servidor web local IIS. Seu endereço será, portanto, http://localhost/[chemin], onde [chemin] deve ser definido. Aqui, escolhemos o caminho http://localhost/polyvbnet/demo. O VS.NET criará, então, uma pasta para este projeto. Onde? O servidor IIS possui uma raiz para a árvore de documentos da web que ele disponibiliza. Vamos chamar essa raiz de <IISroot>. Ela corresponde ao URL http://localhost. Deduz-se, portanto, que o URL http://localhost/polyvbnet/demo será associado à pasta <IISroot>/polyvbnet/demo. <IISroot> é normalmente a pasta \inetpub\wwwroot no disco em que o IIS foi instalado. No nosso exemplo, trata-se do disco E. A pasta criada pelo VS.NET é, portanto, a pasta e:\inetpub\wwwroot\polyvbnet\demo:

Como sempre, há uma profusão de pastas criadas. Nem sempre elas são úteis. Explicaremos apenas aquelas de que precisamos em um determinado momento. O VS.NET criou um projeto:

Encontramos alguns dos arquivos presentes na pasta física do projeto. O mais interessante para nós é o arquivo com a extensão asmx. Essa é a extensão dos serviços web. Um serviço web é gerenciado por VS.NET como um aplicativo do Windows, c.a.d — um aplicativo que possui uma interface gráfica e código para gerenciá-la. É por isso que temos uma janela de design:

Um serviço web normalmente não possui interface gráfica. Ele representa um objeto que pode ser chamado remotamente. Possui métodos, e os aplicativos os chamam. Portanto, vamos considerá-lo como um objeto clássico, com a particularidade de poder ser instanciado remotamente pela rede. Assim, não utilizaremos a janela de design apresentada por VS.NET. Vamos nos concentrar, em vez disso, no código do serviço, utilizando a opção Exibir/Código:

Há vários pontos a serem observados:
- o arquivo se chama Service1.asmx.vb e não Service1.asmx. Voltaremos ao conteúdo do arquivo Service1.asmx um pouco mais adiante.
- Encontramos uma janela de código semelhante à que tínhamos ao criar aplicativos do Windows com o VS.NET
O código gerado pelo VS.NET é o seguinte:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
#Região “Código gerado pelo Designer de Serviços da Web”
Public Sub New()
MyBase.New()
'Esta chamada é exigida pelo Web Service Designer.
InitializeComponent()
'Adicione seu código de inicialização após a chamada InitializeComponent()
End Sub
'Exigido pelo Web Service Designer
Private components As System.ComponentModel.IContainer
'REMARQUE: o procedimento a seguir é exigido pelo Web Service Designer
'Ela pode ser modificada usando o Web Services Designer.
'Não o altere usando o editor de código.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
components = New System.ComponentModel.Container()
End Sub
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
'CODEGEN: este procedimento é exigido pelo Web Services Designer
'Não a altere usando o editor de código.
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
#Fim da região
' EXEMPLE DE SERVICE WEB
' O exemplo de serviço HelloWorld() retorna a string “Hello World”.
' Para gerar, não comente as linhas a seguir; em seguida, salve e gere o projeto.
' Para testar este serviço web, certifique-se de que o arquivo .asmx seja a página inicial
' e clique em F5.
'
'<WebMethod()> Função Pública HelloWorld() As String
' HelloWorld = "Hello World"
' Fim da função
End Class
Em primeiro lugar, observemos que temos aqui uma classe, a classe Service1, que deriva da classe WebService:
Isso nos leva a importar o espaço de nomes System.Web.Services:
Imports System.Web.Services
A declaração da classe é precedida por um atributo de compilação:
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
O atributo System.Web.Services.WebService() indica que a classe a seguir é um serviço web. Esse atributo aceita diversos parâmetros, entre os quais um chamado NameSpace. Ele serve para colocar o serviço web em um espaço de nomes. De fato, é possível imaginar que existam vários serviços web chamados “meteo” no mundo. Precisamos de uma maneira de diferenciá-los. É o espaço de nomes que permite isso. Um pode se chamar [espacenom1].meteo e outro, [espacenom2].meteo. Aqui, encontramos um conceito análogo aos espaços de nomes das classes. O VS.NET gerou automaticamente um código que colocou em uma região do código-fonte:
Se analisarmos esse código, veremos que é o mesmo que o designer gerava ao criar aplicativos para Windows. Trata-se de um código que podemos simplesmente excluir caso não tenhamos uma interface gráfica, o que será o nosso caso para os serviços web.
A aula termina com um exemplo do que poderia ser um serviço web:
#Fim da Região
' EXEMPLE DE SERVICE WEB
' O exemplo de serviço HelloWorld() retorna a string “Hello World”.
' Para gerar, não comente as linhas a seguir; em seguida, salve e gere o projeto.
' Para testar este serviço web, certifique-se de que o arquivo .asmx seja a página inicial
' e clique em F5.
'
'<WebMethod()> Função Pública HelloWorld() As String
' HelloWorld = "Hello World"
' End Function
Com base no que acabamos de ver, simplificamos o código para que fique da seguinte forma:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Bonjour
Inherits System.Web.Services.WebService
<WebMethod()> Public Function Bonjour() As String
Return "bonjour !"
End Function
End Class
Agora fica um pouco mais claro.
- Um serviço web é uma classe derivada da classe WebService
- A classe é qualificada pelo atributo <System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>. Portanto, colocamos nosso serviço no namespace st.istia.univ-angers.fr.
- Os métodos da classe são qualificados por um atributo <WebMethod()>, indicando que se trata de um método que pode ser chamado remotamente pela rede
A classe que implementa nosso serviço web chama-se, portanto, Bonjour e possui um único método, também chamado Bonjour, que retorna uma sequência de caracteres. Estamos prontos para um primeiro teste.
- Vamos iniciar o servidor web IIS, caso ainda não tenha sido feito
- usemos a opção Depurar/Executar sem depuração. VS.NET
O VS.NET irá então compilar toda a aplicação, iniciar um navegador (geralmente o Internet Explorer, se estiver presente) e exibir a URL http://localhost/polyvbnet/demo/Service1.asmx:

Por que a URL http://localhost/polyvbnet/demo/Service1.asmx? Porque esse era o único arquivo .asmx do projeto:

Se houvesse vários arquivos .asmx, teríamos que especificar qual deles deveria ser executado primeiro. Para isso, basta clicar com o botão direito do mouse no arquivo .asmx em questão e selecionar a opção [Définir comme page de démarrage].

Talvez seja interessante saber o que contém o arquivo service1.asmx. De fato, com o VS.NET, trabalhamos no arquivo service1.asmx.vb e não no arquivo service1.asmx. Esse arquivo está localizado na pasta do projeto:

Vamos abri-lo com um editor de texto (Bloco de Notas ou outro). Obtemos o seguinte conteúdo:
O arquivo contém uma simples diretiva destinada ao servidor IIS, indicando:
- que se trata de um serviço web (palavra-chave WebService)
- que a linguagem da classe desse serviço é Visual Basic (Language="vb")
- que o código-fonte dessa classe será encontrado no arquivo Service1.asmx.vb (Codebehind="Service1.asmx.vb")
- que a classe que implementa o serviço se chama demo.Bonjour (Class="demo.Bonjour"). Observe-se que VS.NET colocou a classe Bonjour no espaço de nomes demo, que também é o nome do projeto.
Voltemos à página acessada pelo URL http://localhost/polyvbnet/demo/Service1.asmx:

Quem escreveu o código HTML da página acima? Não fomos nós, sabemos disso. Foi o IIS, que apresenta os serviços web de maneira padronizada. Essa página nos oferece dois links. Vamos seguir o primeiro, [Description du service]:

Opa... isso é um XML bem obscuro. Mas vamos dar uma olhada no URL
http://localhost/polyvbnet/demo/Service1.asmx?WSDL. Abra um navegador e digite diretamente essa URL. Você obterá o mesmo resultado de antes. Lembre-se, portanto, de que a URL http://serviceweb?WSDL dá acesso à descrição XML do serviço web. Voltemos à página inicial e cliquemos no link [Bonjour]. Lembre-se de que Bonjour é um método do serviço web. Se houvesse vários métodos, todos eles teriam sido apresentados aqui. Obtemos a seguinte nova página:

Truncamos deliberadamente a página obtida para não sobrecarregar nossa demonstração. Observemos novamente a URL obtida:
Se digitarmos essa URL diretamente em um navegador, obteremos o mesmo resultado acima. Somos incentivados a usar o botão [Appeler]. Vamos fazer isso. Obtemos uma nova página:

É novamente o XML. Encontramos ali duas informações que estavam presentes em nosso serviço web:
- o espaço de nomes st.istia.univ-angers.fr do nosso serviço
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>
- o valor retornado pelo método Bonjour:
Return "bonjour !"
O que aprendemos?
- como escrever um serviço web S
- como chamá-lo
Agora vamos nos concentrar em escrever um serviço web sem a ajuda do VS.NET.
10.3.2. Versão 2
No exemplo anterior, o VS.NET fez muitas coisas sozinho. É possível criar um serviço web sem essa ferramenta? A resposta é sim, e vamos mostrar isso agora. Com um editor de texto, vamos criar o seguinte serviço web:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Bonjour2
Inherits System.Web.Services.WebService
<WebMethod()> Public Function getBonjour() As String
Return "bonjour de nouveau !"
End Function
End Class
A classe se chama Bonjour2 e possui um método chamado getBonjour. Ela foi colocada no arquivo demo2.vb, que por sua vez está na árvore de diretórios do servidor IIS, dentro da pasta E:\Inetpub\wwwroot\polyvbnet\demo2. Trata-se de uma classe VB.NET clássica, que, portanto, pode ser compilada:
dos>vbc /out:demo2 /t:library /r:system.dll /r:system.web.services.dll demo2.vb
dos>dir
02/03/2004 18:04 286 demo2.vb
02/03/2004 18:10 77 demo2.asmx
02/03/2004 18:12 3 072 demo2.dll
Colocamos a compilação demo2.dll em uma pasta chamada “bin” (esse nome é obrigatório):
Agora criamos o arquivo demo2.asmx. É ele que será chamado pelos clientes da web. Seu conteúdo é o seguinte:
Já nos deparamos com essa diretiva. Ela indica que:
- a classe do serviço web se chama Bonjour2 e está localizada no assembly demo2.dll. O IIS procurará esse assembly em diferentes locais, principalmente na pasta bin do serviço web. É por isso que colocamos ali o assembly demo2.dll.
Agora podemos realizar diversos testes. Verificamos se o IIS está ativo e acessamos, por meio de um navegador, a URL http://localhost/polyvbnet/demo2/demo2.asmx:

Em seguida, a URL http://localhost/polyvbnet/demo2/demo2.asmx?WSDL

Em seguida, o URL http://localhost/polyvbnet/demo2/demo2.asmx?op=getBonjour, onde getBonjour é o nome do único método do nosso serviço web:

Utilizamos o botão [Appeler] acima:

Obtenemos, de fato, o resultado da chamada ao método getBonjour do serviço web. Agora sabemos como construir um serviço web sem o vs.net. Daqui em diante, ignoraremos a forma como o serviço web é construído para nos concentrarmos apenas nos arquivos fundamentais.
10.3.3. Versão 3
As duas versões anteriores do serviço web [Bonjour] utilizavam dois arquivos:
- um arquivo .asmx, ponto de entrada do serviço web
- um arquivo .vb, código-fonte do serviço web
Mostramos aqui que é possível utilizar apenas o arquivo .asmx. O código do serviço demo3.asmx é o seguinte:
Constatamos que o código-fonte do serviço agora está diretamente no arquivo-fonte do demo3.asmx. A diretiva
não faz mais referência a uma classe em um assembly externo, mas a uma classe localizada no mesmo arquivo-fonte. Vamos colocá-lo na pasta <IISroot>\polyvbnet\demo3:

Vamos executar o IIS e acessar a URL http://localhost/polyvbnet/demo3/demo3.asmx:

Observamos uma diferença significativa em relação à versão anterior: não precisamos compilar o código VB do serviço. O IIS realizou essa compilação por conta própria, por meio do compilador VB.NET instalado na mesma máquina. Em seguida, ele exibiu a página. Caso ocorra um erro de compilação, este será sinalizado pelo IIS:

10.3.4. Versão 4
Aqui, estamos interessados na configuração do servidor IIS. Até agora, sempre colocamos nossos serviços web na árvore de diretórios raiz <IISroot> do servidor IIS, neste caso, [e:\inetpub\wwwroot]. Mostramos aqui que podemos colocar o serviço web em qualquer lugar. Isso é feito por meio das pastas virtuais do IIS. Vamos colocar nosso serviço na seguinte pasta:

A pasta [D:\data\devel\vbnet\poly\chap9\demo3] não está presente na árvore de pastas do servidor IIS. É preciso indicar isso ao servidor criando uma pasta virtual IIS. Vamos executar o IIS e selecionar a opção [Avancé] abaixo:

É exibida uma lista de diretórios virtuais. Não vamos nos deter nela. Criamos um novo diretório virtual com o botão [Ajouter] acima:

Usando o botão [Parcourir], indicamos a pasta física que contém o serviço web, neste caso a pasta [D:\data\devel\vbnet\poly\chap9\demo3]. Atribuímos um nome lógico (virtual) a essa pasta: [virdemo3]. Isso significa que os documentos contidos na pasta física [D:\data\devel\vbnet\poly\chap9\demo3] estarão acessíveis na rede por meio da URL [http://<machine>/virdemo3]. A caixa de diálogo acima contém outras configurações que deixamos como estão. Confirmamos a caixa de diálogo. A nova pasta virtual aparece na lista de pastas virtuais de IIS:

Agora, abrimos um navegador e acessamos a URL [http://localhost/virdemo3/demo3.asmx]. Obtemos o mesmo resultado de antes:

10.3.5. Conclusão
Mostramos várias maneiras de criar um serviço web. Posteriormente, utilizaremos o método da versão 3 para a criação do serviço e o método 4 para sua localização. Assim, não precisaremos do VS.NET. No entanto, vale ressaltar a utilidade de usar o VS.NET pela ajuda que ele oferece na depuração. Existem ferramentas gratuitas para desenvolver aplicativos web, notadamente o produto WebMatrix, patrocinado pela Microsoft, que pode ser encontrado no URL [http://www.asp.net/webmatrix]. É uma excelente ferramenta para iniciar a programação web sem investimento prévio.
10.4. Um serviço web de operações
Consideremos um serviço web que oferece cinco funções:
- adicionar(a,b), que retorna a+b
- subtrair(a,b), que retornará a-b
- multiplicar(a,b), que retornará a*b
- dividir(a,b), que retornará a/b
- fazer_tudo(a,b), que retornará a matriz [a+b, a-b, a*b, a/b]
O código VB.NET deste serviço é o seguinte:
<%@ WebService language="VB" class=operations %>
imports system.web.services
<WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class operations
Inherits WebService
<WebMethod> _
Function ajouter(a As Double, b As Double) As Double
Return a + b
End Function
<WebMethod> _
Function soustraire(a As Double, b As Double) As Double
Return a - b
End Function
<WebMethod> _
Function multiplier(a As Double, b As Double) As Double
Return a * b
End Function
<WebMethod> _
Function diviser(a As Double, b As Double) As Double
Return a / b
End Function
<WebMethod> _
Function toutfaire(a As Double, b As Double) As Double()
Return New Double() {a + b, a - b, a * b, a / b}
End Function
End Class
Repetimos aqui algumas explicações já fornecidas, mas que merecem ser relembradas ou complementadas. A classe operations se assemelha a uma classe VB.NET, com alguns pontos a serem observados:
- os métodos são precedidos por um atributo <WebMethod()>, que indica ao compilador quais métodos devem ser “publicados” (c.a.d) e disponibilizados ao cliente. Um método que não seja precedido por esse atributo ficaria invisível para clientes remotos. Poderia ser um método interno utilizado por outros métodos, mas não destinado a ser publicado.
- A classe deriva da classe WebService definida no namespace System.Web.Services. Essa herança nem sempre é obrigatória. Neste exemplo, especificamente, seria possível dispensá-la.
- A própria classe é precedida por um atributo <WebService(Namespace="st.istia.univ-angers.fr")> destinado a atribuir um namespace ao serviço web. Um fornecedor de classes atribui um namespace às suas classes para lhes dar um nome único e, assim, evitar conflitos com classes de outros fornecedores que possam ter o mesmo nome. Para os serviços web, o princípio é o mesmo. Cada serviço web deve poder ser identificado por um nome único, neste caso, st.istia.univ-angers.fr.
- Não definimos um construtor. Portanto, será usado implicitamente o construtor da classe pai.
O código-fonte anterior não se destina diretamente ao compilador VB.NET, mas ao servidor web IIS. Ele deve ter o sufixo .asmx e ser salvo na estrutura de diretórios do servidor web. Aqui, estamos salvando-o com o nome operations.asmx na pasta <IISroot>\polyvbnet\operations:

Associamos a essa pasta física a pasta virtual IIS [operations]:
![]() | ![]() |
Acessemos o serviço por meio de um navegador. O URl a ser solicitado é o [http://localhost/operations/operations.asmx]:

Recebemos um documento da Web com um link para cada um dos métodos definidos no serviço web operations. Vamos acessar o link ajouter:

A página exibida nos propõe testar o método ajouter, fornecendo-lhe os dois argumentos a e b de que ele necessita. Recordemos a definição do método ajouter:
Observe-se que a página reproduziu os nomes dos argumentos a e b utilizados na definição do método. Ao clicar no botão Appeler, obtém-se a seguinte resposta em uma janela separada do navegador:

Se, no exemplo acima, utilizarmos [Affichage/Source], obtemos o seguinte código:

Vamos repetir a operação para o método [toutfaire]:

Obtemos a seguinte página:

Vamos usar o botão [Appeler] acima:

Em todos os casos, a resposta do servidor tem o seguinte formato:
- a resposta está no formato XML
- a linha 1 é padrão e está sempre presente na resposta
- as linhas seguintes dependem do tipo de resultado (duplo, ArrayOfDouble), do número de resultados e do espaço de nomes do serviço web (st.istia.univ-angers.fr neste caso).
Existem vários métodos para consultar um serviço web e obter sua resposta. Voltemos ao URL do serviço:

e sigamos o link [ajouter]. Na página exibida, são apresentados dois métodos para consultar a função [ajouter] do serviço web:



Esses dois métodos de acesso às funções de um serviço web são chamados, respectivamente, de: HTTP-POST e SOAP. Vamos analisá-los agora, um por um.
Observação: nas primeiras versões do VS.NET, existia um terceiro método chamado HTTP-GET. Na data de redação deste documento (março de 2004), esse método parece não estar mais disponível. Isso significa que o serviço web gerado pelo VS.NET não aceita solicitações GET. Isso não significa que não seja possível criar serviços web que aceitem solicitações GET, especialmente com outras ferramentas além do VS.NET ou simplesmente manualmente.
10.5. Um cliente HTTP-POST
Seguimos o método proposto pelo serviço web:

Vamos comentar o que está escrito. Em primeiro lugar, o cliente web deve enviar os seguintes cabeçalhos HTTP:
O cliente web faz uma solicitação POST para o URL /operations/operations.asmx/adicionar, de acordo com o protocolo HTTP versão 1.1 | |
Especifica-se a máquina de destino da solicitação. Neste caso, localhost. Este cabeçalho tornou-se obrigatório a partir da versão 1.1 do protocolo HTTP | |
Especifica-se aqui que, após os cabeçalhos HTTP, serão enviados parâmetros adicionais no formato urlencoded. Esse formato substitui certos caracteres por seus códigos hexadecimais. | |
Este é o tamanho, em caracteres, da sequência de parâmetros que será enviada após os cabeçalhos HTTP. |
Os cabeçalhos HTTP são seguidos por uma linha em branco e, em seguida, pela sequência de parâmetros do POST, composta por [Content-Length] caracteres, no formato a=XX&b=YY, em que XX e YY são as cadeias “urlencodadas” dos valores dos parâmetros a e b. Sabemos o suficiente para reproduzir o que foi mostrado acima com nosso cliente TCP genérico, já utilizado no capítulo sobre programação TCP/IP:
- iniciamos IIS
- o serviço está disponível na URL [http://localhost/operations/operations.asmx]
- usamos o cliente TCP genérico em uma janela DOS
dos>clttcpgenerique localhost 80
Commandes :
POST /operations/operations.asmx/ajouter HTTP/1.1
HOST: localhost
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-length: 7
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
a=2&b=3
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:26 GMT
<-- X-Powered-By: ASP.NET
<-- Connection: close
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 90
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">5</double>
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Observemos, em primeiro lugar, que adicionamos o cabeçalho [Connection: close] para solicitar ao servidor que feche a conexão após enviar a resposta. Isso é necessário neste caso. Se não for especificado, por padrão, o servidor manterá a conexão aberta. No entanto, a resposta do servidor é uma sequência de linhas de texto, sendo que a última não termina com um caractere de fim de linha. Acontece que nosso cliente genérico TCP lê linhas de texto que terminam com o caractere de fim de linha usando o método ReadLine. Se o servidor não fechar a conexão após o envio da última linha, o cliente fica bloqueado porque aguarda um caractere de fim de linha que não chega. Se o servidor fechar a conexão, o método ReadLine do cliente é encerrado e o cliente não fica bloqueado.
Imediatamente após receber a linha vazia que sinaliza o fim dos cabeçalhos HTTP, o servidor IIS envia uma primeira resposta:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
Essa resposta, composta exclusivamente pelos cabeçalhos HTTP, indica ao cliente que ele pode enviar os 7 caracteres que disse querer enviar. O que fazemos:
É importante observar aqui que nosso cliente TCP envia mais de 7 caracteres, pois os envia com um marcador de fim de linha (WriteLine). Isso não incomoda o servidor, que, dos caracteres recebidos, considerará apenas os 7 primeiros, e porque, em seguida, a conexão é encerrada (Connection: close). Esses caracteres excedentes teriam sido um problema se a conexão tivesse permanecido aberta, pois, nesse caso, teriam sido interpretados como pertencentes ao comando seguinte do cliente. Após receber os parâmetros, o servidor envia sua resposta:
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:26 GMT
<-- X-Powered-By: ASP.NET
<-- Connection: close
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 90
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">5</double>
Agora temos os elementos necessários para escrever um cliente programado para nosso serviço web. Será um cliente de console chamado httpPost2, que será utilizado da seguinte forma:
dos>httpPost2 http://localhost/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 6 7
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=6&b=7
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
[résultat=13]
soustraire 8 9
--> POST /operations/operations.asmx/soustraire HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=8&b=9
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">-1</double>
[résultat=-1]
fin
dos>
O cliente é chamado passando-lhe o URL do serviço web:
Em seguida, o cliente lê os comandos digitados no teclado e os executa. Esses comandos estão no formato:
onde função é a função do serviço web chamada (somar, subtrair, multiplicar, dividir) e a e b são os valores sobre os quais essa função irá operar. Por exemplo:
A partir daí, o cliente enviará a solicitação HTTP necessária ao servidor web e receberá uma resposta. As trocas entre cliente e servidor são exibidas na tela para facilitar a compreensão do processo:
ajouter 6 7
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=6&b=7
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
[résultat=13]
Vemos acima a troca de dados já observada com o cliente TCP genérico, com uma única diferença: o cabeçalho HTTP Connection: Keep-Alive solicita ao servidor que não feche a conexão. Assim, a conexão permanece aberta para a próxima operação do cliente, que, portanto, não precisa se reconectar ao servidor. Isso o obriga, no entanto, a utilizar um método diferente de ReadLine() para ler a resposta do servidor, já que sabemos que ela é uma sequência de linhas cuja última não termina com um caractere de fim de linha. Uma vez obtida toda a resposta do servidor, o cliente a analisa para encontrar o resultado da operação solicitada e exibi-lo:
Vamos examinar o código do nosso cliente:
' espaços de nomes
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Web
' cliente de um serviço web de operações
Public Module clientPOST
Public Sub Main(ByVal args() As String)
' sintaxe
Const syntaxe As String = "pg URI"
Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' número de argumentos
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' observa-se a solicitação de URI
Dim URIstring As String = args(0)
' conectamos-nos ao servidor
Dim uri As Uri = Nothing ' l'URI du service web
Dim client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Try
' conexão com o servidor
uri = New Uri(URIstring)
client = New TcpClient(uri.Host, uri.Port)
' são criados os fluxos de entrada e saída do cliente TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
Catch ex As Exception
' URI incorreto ou outro problema
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
' criação de um dicionário de funções do serviço web
Dim dicoFonctions As New Hashtable
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
' as solicitações do usuário são digitadas no teclado
' na forma função a b
' elas terminam com o comando “fin”
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
Dim fonction As String = Nothing ' nom d'une fonction du service web
Dim a, b As String ' les arguments des fonctions du service web
' solicita ao usuário
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b")
' gerenciamento de erros
Dim erreurCommande As Boolean
Try
' ciclo de entrada dos comandos digitados no teclado
While True
' sem erros no início
erreurCommande = False
' leitura do comando
commande = Console.In.ReadLine().Trim().ToLower()
' concluído?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' decomposição do comando em campos
champs = Regex.Split(commande, "\s+")
Try
' são necessários três campos
If champs.Length <> 3 Then
Throw New Exception
End If
' o campo 0 deve ser uma função reconhecida
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' o campo 1 deve ser um número válido
a = champs(1)
Double.Parse(a)
' o campo 2 deve ser um número válido
b = champs(2)
Double.Parse(b)
Catch
' comando inválido
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' a solicitação é enviada ao serviço web
If Not erreurCommande Then executeFonction([IN], OUT, uri, fonction, a, b)
End While
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
' fim da conexão cliente-servidor
Try
[IN].Close()
OUT.Close()
client.Close()
Catch
End Try
End Sub
...........
' exibição de erros
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' exibição de erro
System.Console.Error.WriteLine(msg)
' encerramento com erro
Environment.Exit(exitCode)
End Sub
End Module
Temos aqui elementos que já vimos várias vezes e que não requerem comentários específicos. Vamos agora examinar o código do método executeFonction, onde se encontram as novidades:
' executeFonction
Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal fonction As String, ByVal a As String, ByVal b As String)
' executa a função (a, b) no serviço web com URI URI
' as trocas cliente-servidor ocorrem por meio dos fluxos IN e OUT
' o resultado da função está na linha
' <double xmlns="st.istia.univ-angers.fr">double</double>
' enviada pelo servidor
' construção da string de consulta
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' construção da tabela de cabeçalhos HTTP a ser enviada
Dim entetes(5) As String
entetes(0) = "POST " + uri.AbsolutePath + "/" + fonction + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: application/x-www-form-urlencoded"
entetes(3) = "Content-Length: " & nbChars
entetes(4) = "Connection: Keep-Alive"
entetes(5) = ""
' envio dos cabeçalhos HTTP ao servidor
Dim i As Integer
For i = 0 To entetes.Length - 1
' envio ao servidor
OUT.WriteLine(entetes(i))
' saída na tela
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
' leitura da primeira resposta do servidor web HTTP/1.1 100
Dim ligne As String = Nothing
' uma linha do fluxo de leitura
ligne = [IN].ReadLine()
While ligne <> ""
'eco
Console.Out.WriteLine(("<-- " + ligne))
' próxima linha
ligne = [IN].ReadLine()
End While
'eco da última linha
Console.Out.WriteLine(("<-- " + ligne))
' envio dos parâmetros da solicitação
OUT.Write(requête)
' eco
Console.Out.WriteLine(("--> " + requête))
' construção da expressão regular para determinar o tamanho da resposta XML
' no fluxo da resposta do servidor web
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' leitura da segunda resposta do servidor web após o envio da solicitação
' armazenamos o valor da linha Content-Length
ligne = [IN].ReadLine()
While ligne <> ""
' saída na tela
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' linha seguinte
ligne = [IN].ReadLine()
End While
' exibição da última linha
Console.Out.WriteLine("<--")
' construção da expressão regular que permite recuperar o resultado
' no fluxo da resposta do servidor web
Dim modèle As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' lê-se o restante da resposta do servidor web
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' decompõe-se a resposta em linhas de texto
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' percorre-se as linhas de texto em busca do resultado
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' acompanhamento
Console.Out.WriteLine(("<-- " + lignes(i)))
' comparação da linha atual com o modelo
MatchRésultat = ModèleRésultat.Match(lignes(i))
' encontrou-se o resultado?
If MatchRésultat.Success Then
' registra-se o resultado
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' exibimos o resultado
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
Em primeiro lugar, o cliente HTTP-POST envia sua solicitação no formato POST:
' construção da string de consulta
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' construção da tabela de cabeçalhos HTTP a ser enviada
Dim entetes(5) As String
entetes(0) = "POST " + uri.AbsolutePath + "/" + fonction + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: application/x-www-form-urlencoded"
entetes(3) = "Content-Length: " & nbChars
entetes(4) = "Connection: Keep-Alive"
entetes(5) = ""
' envio dos cabeçalhos HTTP ao servidor
Dim i As Integer
For i = 0 To entetes.Length - 1
' envio ao servidor
OUT.WriteLine(entetes(i))
' saída na tela
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
No cabeçalho
deve-se indicar o tamanho dos parâmetros que serão enviados pelo cliente após os cabeçalhos HTTP:
Para isso, utiliza-se o seguinte código:
' construção da string de consulta
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
O método HttpUtility.UrlEncode(string cadeia) transforma alguns dos caracteres de chaîne em %n1n2, onde n1n2 é o código ASCII do caractere transformado. Os caracteres afetados por essa transformação são todos aqueles que têm um significado específico em uma consulta POST (o espaço, o sinal =, o sinal &, etc.). Nesse caso, o método HttpUtility.UrlEncode normalmente é desnecessário, uma vez que a e b são números que não contêm nenhum desses caracteres específicos. Ele é utilizado aqui apenas a título de exemplo. Ela requer o namespace System.Web. Assim que o cliente enviar seus cabeçalhos HTTP:
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
o servidor responde com o cabeçalho HTTP 100 Continue:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<--
O código se limita a ler e exibir na tela essa primeira resposta:
' lê-se a primeira resposta do servidor web HTTP/1.1 100
Dim ligne As String = Nothing
' uma linha do fluxo de leitura
ligne = [IN].ReadLine()
While ligne <> ""
'eco
Console.Out.WriteLine(("<-- " + ligne))
' linha seguinte
ligne = [IN].ReadLine()
End While
'eco da última linha
Console.Out.WriteLine(("<-- " + ligne))
Após ler essa primeira resposta, o cliente deve enviar seus parâmetros:
Ele faz isso com o seguinte código:
' envio dos parâmetros da consulta
OUT.Write(requête)
' eco
Console.Out.WriteLine(("--> " + requête))
O servidor então enviará sua resposta. Ela é composta por duas partes:
- os cabeçalhos HTTP, terminados por uma linha em branco
- a resposta no formato XML
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
Primeiramente, o cliente lê os cabeçalhos HTTP para localizar a linha Content-Length e obter o tamanho da resposta XML (neste caso, 90). Esse valor é obtido por meio de uma expressão regular. Seria possível fazer isso de outra maneira e, sem dúvida, de forma mais eficiente.
' construção da expressão regular para determinar o tamanho da resposta XML
' no fluxo da resposta do servidor web
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' leitura da segunda resposta do servidor web após o envio da solicitação
' armazenamos o valor da linha Content-Length
ligne = [IN].ReadLine()
While ligne <> ""
' saída na tela
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' linha seguinte
ligne = [IN].ReadLine()
End While
' exibição da última linha
Console.Out.WriteLine("<--")
Assim que obtivermos o comprimento N da resposta XML, basta ler N caracteres no fluxo IN da resposta do servidor. Essa sequência de N caracteres é redividida em linhas de texto para fins de acompanhamento na tela. Entre essas linhas, procuramos a linha do resultado:
novamente por meio de uma expressão regular. Assim que o resultado é encontrado, ele é exibido.
O final do código do cliente é o seguinte:
' construção da expressão regular que permite recuperar o resultado
' no fluxo da resposta do servidor web
Dim modèle As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' lê-se o restante da resposta do servidor web
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' a resposta é dividida em linhas de texto
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' percorre-se as linhas de texto em busca do resultado
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' acompanhamento
Console.Out.WriteLine(("<-- " + lignes(i)))
' comparação da linha atual com o modelo
MatchRésultat = ModèleRésultat.Match(lignes(i))
' encontrou-se o resultado?
If MatchRésultat.Success Then
' registra-se o resultado
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' exibindo o resultado
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.6. Um cliente SOAP
Analisamos aqui um segundo cliente que utilizará um diálogo cliente-servidor do tipo SOAP (Simple Object Access Protocol). É apresentado um exemplo de diálogo para a função ajouter:


A solicitação do cliente é uma solicitação POST. Portanto, vamos encontrar alguns dos mecanismos do cliente anterior. A principal diferença é que, enquanto o cliente HTTP-POST enviava os parâmetros a e b na forma
, o cliente SOAP os envia em um formato XML mais complexo:
POST /operations/operations.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "st.istia.univ-angers.fr/ajouter"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>double</a>
<b>double</b>
</ajouter>
</soap:Body>
</soap:Envelope>
Em resposta, ele recebe uma resposta XML, igualmente mais complexa do que as respostas vistas anteriormente:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouterResponse xmlns="st.istia.univ-angers.fr">
<ajouterResult>double</ajouterResult>
</ajouterResponse>
</soap:Body>
</soap:Envelope>
Embora a solicitação e a resposta sejam mais complexas, trata-se, de fato, do mesmo mecanismo HTTP utilizado pelo cliente HTTP-POST. A instrução do cliente SOAP pode, portanto, ser modelada a partir daquela do cliente HTTP-POST. Aqui está um exemplo de execução:
dos>clientsoap1 http://localhost/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 3 4
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>3</a>
<b>4</b>
</ajouter>
</soap:Body>
</soap:Envelope>
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:33 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 345
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>7</ajouterResult></ajouterResponse
></soap:Body></soap:Envelope>
[résultat=7]
Apenas o método executeFonction muda. O cliente SOAP envia os cabeçalhos HTTP de sua solicitação. Eles são apenas um pouco mais complexos do que os de HTTP-POST:
ajouter 3 4
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
O código que os gera:
' executeFonction
Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal fonction As String, ByVal a As String, ByVal b As String)
' executa a função(a,b) no serviço web com o URI URI
' as trocas cliente-servidor ocorrem por meio dos fluxos IN e OUT
' o resultado da função está na linha
' <double xmlns="st.istia.univ-angers.fr">double</double>
' enviada pelo servidor
' construção da string de consulta SOAP
Dim requêteSOAP As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf
requêteSOAP += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" + ControlChars.Lf
requêteSOAP += "<soap:Body>" + ControlChars.Lf
requêteSOAP += "<" + fonction + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf
requêteSOAP += "<a>" + a + "</a>" + ControlChars.Lf
requêteSOAP += "<b>" + b + "</b>" + ControlChars.Lf
requêteSOAP += "</" + fonction + ">" + ControlChars.Lf
requêteSOAP += "</soap:Body>" + ControlChars.Lf
requêteSOAP += "</soap:Envelope>"
Dim nbCharsSOAP As Integer = requêteSOAP.Length
' construção da tabela de cabeçalhos HTTP a ser enviada
Dim entetes(6) As String
entetes(0) = "POST " + uri.AbsolutePath + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: text/xml; charset=utf-8"
entetes(3) = "Content-Length: " & nbCharsSOAP
entetes(4) = "Connection: Keep-Alive"
entetes(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + fonction + """"
entetes(6) = ""
' envio dos cabeçalhos HTTP ao servidor
Dim i As Integer
For i = 0 To entetes.Length - 1
' envio ao servidor
OUT.WriteLine(entetes(i))
' saída na tela
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
Ao receber essa solicitação, o servidor envia sua primeira resposta, que o cliente exibe:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
O código de leitura desta primeira resposta é o seguinte:
' leitura da primeira resposta do servidor web HTTP/1.1 100
Dim ligne As String = Nothing
' uma linha do fluxo de leitura
ligne = [IN].ReadLine()
While ligne <> ""
'eco
Console.Out.WriteLine(("<-- " + ligne))
' linha seguinte
ligne = [IN].ReadLine()
End While 'while
'eco da última linha
Console.Out.WriteLine(("<-- " + ligne))
O cliente enviará agora seus parâmetros no formato XML dentro do que chamamos de envelope SOAP:
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>3</a>
<b>4</b>
</ajouter>
</soap:Body>
</soap:Envelope>
O código:
O servidor enviará então sua resposta definitiva:
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:33 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 345
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>7</ajouterResult></ajouterResponse
></soap:Body></soap:Envelope>
O cliente exibe na tela os cabeçalhos HTTP recebidos, enquanto procura a linha Content-Length:
' construção da expressão regular que permite determinar o tamanho da resposta XML
' no fluxo da resposta do servidor web
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' leitura da segunda resposta do servidor web após o envio da solicitação
' armazenamos o valor da linha Content-Length
ligne = [IN].ReadLine()
While ligne <> ""
' saída na tela
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' linha seguinte
ligne = [IN].ReadLine()
End While 'while
' exibição da última linha
Console.Out.WriteLine("<--")
Assim que o tamanho N da resposta XML for conhecido, o cliente lê N caracteres do fluxo da resposta do servidor, divide a sequência recuperada em linhas de texto para exibi-las na tela e procura a tag XML do resultado: <ajouterResult>7</ajouterResult> e exibe este último:
' construção da expressão regular que permite recuperar o resultado
' no fluxo da resposta do servidor web
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' lê-se o restante da resposta do servidor web
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' a resposta é dividida em linhas de texto
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' percorre-se as linhas de texto em busca do resultado
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' acompanhamento
Console.Out.WriteLine(("<-- " + lignes(i)))
' comparação da linha atual com o modelo
MatchRésultat = ModèleRésultat.Match(lignes(i))
' encontrou-se o resultado?
If MatchRésultat.Success Then
' anota-se o resultado
strRésultat = MatchRésultat.Groups(1).Value
End If
'linha seguinte
Next i
' exibe-se o resultado
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.7. Encapsulamento das trocas cliente-servidor
Imaginemos que nosso serviço web operations seja utilizado por diversas aplicações. Seria interessante disponibilizar a elas uma classe que fizesse a interface entre a aplicação cliente e o serviço web e que ocultasse a maior parte das trocas de dados em rede, que, para a maioria dos desenvolvedores, não são triviais. Teríamos, assim, o seguinte esquema:
![]() |
O aplicativo cliente se comunicaria com a interface cliente-servidor para enviar suas solicitações ao serviço web. Essa interface realizaria todas as comunicações de rede necessárias com o servidor e retornaria o resultado obtido ao aplicativo cliente. Assim, o aplicativo cliente não precisaria mais se preocupar com as comunicações com o servidor, o que facilitaria muito sua programação.
10.7.1. A classe de encapsulamento
Com base no que foi abordado nos parágrafos anteriores, agora conhecemos bem as trocas de dados entre o cliente e o servidor. Vimos até mesmo três métodos. Optamos por encapsular o método SOAP. A classe é a seguinte:
' espaços de nomes
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports System.Web
Imports Microsoft.VisualBasic
' clientSOAP do serviço Web operations
Public Class clientSOAP
' variáveis de instância
Private uri As uri = Nothing ' l'URI du service web
Private client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Private [IN] As StreamReader = Nothing ' le flux de lecture du client
Private OUT As StreamWriter = Nothing ' le flux d'écriture du client
' dicionário de funções
Private dicoFonctions As New Hashtable
' lista de funções
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' detalhado
Private verbose As Boolean = False ' à vrai, affiche à l'écran les échanges client-serveur
' construtor
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' saída detalhada
Me.verbose = verbose
' conexão ao servidor
uri = New Uri(uriString)
client = New TcpClient(uri.Host, uri.Port)
' criação dos fluxos de entrada e saída do cliente TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' criação do dicionário de funções do serviço web
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
End Sub
' encerramento da conexão com o servidor
Public Sub Close()
' fim da conexão cliente-servidor
[IN].Close()
OUT.Close()
client.Close()
End Sub
' executeFonction
Public Function executeFonction(ByVal fonction As String, ByVal a As String, ByVal b As String) As String
' executa a função (a, b) no serviço web com URI URI
' as trocas cliente-servidor ocorrem por meio dos fluxos IN e OUT
' o resultado da função está na linha
' <double xmlns="st.istia.univ-angers.fr">double</double>
' enviada pelo servidor
' função válida?
fonction = fonction.Trim().ToLower()
If Not dicoFonctions.ContainsKey(fonction) Then
Return "[fonction [" + fonction + "] indisponible : (ajouter, soustraire,multiplier,diviser)]"
End If
' argumentos a e b válidos?
Dim doubleA As Double = 0
Try
doubleA = Double.Parse(a)
Catch
Return "[argument [" + a + "] incorrect (double)]"
End Try
Dim doubleB As Double = 0
Try
doubleB = Double.Parse(b)
Catch
Return "[argument [" + b + "] incorrect (double)]"
End Try
' divisão por zero?
If fonction = "diviser" And doubleB = 0 Then
Return "[division par zéro]"
End If
' construção da string de consulta SOAP
Dim requêteSOAP As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf
requêteSOAP += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" + ControlChars.Lf
requêteSOAP += "<soap:Body>" + ControlChars.Lf
requêteSOAP += "<" + fonction + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf
requêteSOAP += "<a>" + a + "</a>" + ControlChars.Lf
requêteSOAP += "<b>" + b + "</b>" + ControlChars.Lf
requêteSOAP += "</" + fonction + ">" + ControlChars.Lf
requêteSOAP += "</soap:Body>" + ControlChars.Lf
requêteSOAP += "</soap:Envelope>"
Dim nbCharsSOAP As Integer = requêteSOAP.Length
' construção da tabela de cabeçalhos HTTP a ser enviada
Dim entetes(6) As String
entetes(0) = "POST " + uri.AbsolutePath + " HTTP/1.1"
entetes(1) = "Host: " + uri.Host + ":" + uri.Port.ToString
entetes(2) = "Content-Type: text/xml; charset=utf-8"
entetes(3) = "Content-Length: " + nbCharsSOAP.ToString
entetes(4) = "Connection: Keep-Alive"
entetes(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + fonction + """"
entetes(6) = ""
' envio dos cabeçalhos HTTP ao servidor
Dim i As Integer
For i = 0 To entetes.Length - 1
' envio ao servidor
OUT.WriteLine(entetes(i))
' saída na tela
If verbose Then
Console.Out.WriteLine(("--> " + entetes(i)))
End If
Next i
' lê-se a primeira resposta do servidor web HTTP/1.1 100
Dim ligne As String = Nothing
' uma linha do fluxo de leitura
ligne = [IN].ReadLine()
While ligne <> ""
'eco
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' linha seguinte
ligne = [IN].ReadLine()
End While
'eco da última linha
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' envio dos parâmetros da consulta
OUT.Write(requêteSOAP)
' eco
If verbose Then
Console.Out.WriteLine(("--> " + requêteSOAP))
End If
' construção da expressão regular para determinar o tamanho da resposta XML
' no fluxo da resposta do servidor web
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' leitura da segunda resposta do servidor web após o envio da solicitação
' armazenamos o valor da linha Content-Length
ligne = [IN].ReadLine()
While ligne <> ""
' saída na tela
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' linha seguinte
ligne = [IN].ReadLine()
End While
' exibição da última linha
If verbose Then
Console.Out.WriteLine("<--")
End If
' construção da expressão regular que permite recuperar o resultado
' no fluxo da resposta do servidor web
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' lê-se o restante da resposta do servidor web
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' decompõe-se a resposta em linhas de texto
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' percorre-se as linhas de texto em busca do resultado
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' acompanhamento
If verbose Then
Console.Out.WriteLine(("<-- " + lignes(i)))
End If ' comparaison ligne courante au modèle
MatchRésultat = ModèleRésultat.Match(lignes(i))
' encontramos?
If MatchRésultat.Success Then
' registra-se o resultado
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' retorna-se o resultado
Return strRésultat
End Function
End Class
Não há nada de novo em relação ao que já vimos. Simplesmente retomamos o código do cliente SOAP que estudamos e o reorganizamos um pouco para transformá-lo em uma classe. Ela possui um construtor e dois métodos:
' fabricante
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' executeFonction
Public Function executeFonction(ByVal fonction As String, ByVal a As String, ByVal b As String) As String
' encerramento da conexão com o servidor
Public Sub Close()
e possui os seguintes atributos:
' variáveis de instância
Private uri As Uri = Nothing ' l'URI du service web
Private client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Private [IN] As StreamReader = Nothing ' le flux de lecture du client
Private OUT As StreamWriter = Nothing ' le flux d'écriture du client
' dicionário de funções
Private dicoFonctions As New Hashtable
' lista de funções
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' detalhamento
Private verbose As Boolean = False ' à vrai, affiche à l'écran les échanges client-serveur
Passamos dois parâmetros ao construtor:
- o URI do serviço web ao qual ele deve se conectar
- um valor booleano verbose que, na verdade, determina se as trocas de dados de rede devem ser exibidas na tela; caso contrário, elas não serão exibidas.
Durante a construção, são criados os fluxos IN de leitura de rede, OUT de gravação de rede, bem como o dicionário das funções gerenciadas pelo serviço. Assim que o objeto for criado, a conexão cliente-servidor é aberta e seus fluxos IN e OUT ficam disponíveis.
O método Close permite fechar a conexão com o servidor.
O método ExecuteFonction é aquele que foi escrito para o cliente SOAP analisado, com algumas pequenas diferenças:
- Os parâmetros uri, IN e OUT, que antes eram passados como parâmetros para o método, não precisam mais ser passados, uma vez que agora são atributos de instância acessíveis a todos os métodos da instância
- O método ExecuteFonction, que antes retornava um tipo void e exibia o resultado da função na tela, agora retorna esse resultado e, portanto, um tipo string.
Normalmente, um cliente utilizará a classe clientSOAP da seguinte maneira:
- criação de um objeto clientSOAP, que estabelecerá a conexão com o serviço web
- uso repetido do método executeFonction
- encerramento da conexão com o serviço web por meio do método Close.
Vamos analisar um primeiro cliente.
10.7.2. Um cliente de console
Retomamos aqui o cliente SOAP analisado quando a classe clientSOAP ainda não existia e o reestruturamos para que agora utilize essa classe:
' espaços de nomes
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
Public Module testClientSoap
' solicita o URI do serviço web operations
' executa de forma interativa os comandos digitados no teclado
Public Sub Main(ByVal args() As String)
' sintaxe
Const syntaxe As String = "pg URI [verbose]"
' número de argumentos
If args.Length <> 1 And args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' detalhado?
Dim verbose As Boolean = False
If args.Length = 2 Then
verbose = args(1).ToLower() = "verbose"
End If
' conecta-se ao serviço web
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' erro de conexão
erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
End Try
' as solicitações do usuário são digitadas no teclado
' no formato função a b — elas terminam com o comando fin
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
' solicitação ao usuário
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b" + ControlChars.Lf)
' gerenciamento de erros
Dim erreurCommande As Boolean
Try
' ciclo de entrada dos comandos digitados no teclado
While True
' inicialmente, sem erros
erreurCommande = False
' leitura do comando
commande = Console.In.ReadLine().Trim().ToLower()
' concluído?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' decomposição do comando em campos
champs = Regex.Split(commande, "\s+")
' são necessários três campos
If champs.Length <> 3 Then
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
' registrando o erro
erreurCommande = True
End If
' envia-se a solicitação ao serviço web
If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
' próxima solicitação
End While
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
' fim da conexão cliente-servidor
Try
client.Close()
Catch
End Try
End Sub
' exibição dos erros
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' exibição do erro
System.Console.Error.WriteLine(msg)
' encerramento com erro
Environment.Exit(exitCode)
End Sub
End Module
O cliente agora está consideravelmente mais simples e não há nenhuma comunicação de rede nele. O cliente aceita dois parâmetros:
- o URI do serviço web operations
- a palavra-chave opcional verbose. Se estiver presente, as trocas de dados de rede serão exibidas na tela.
Esses dois parâmetros são utilizados para construir um objeto clientSOAP que garantirá as trocas de dados com o serviço web.
' conectando-se ao serviço web
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' erro de conexão
erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
End Try
Assim que a conexão com o serviço web for estabelecida, o cliente pode enviar suas solicitações. Estas são digitadas no teclado, analisadas e, em seguida, enviadas ao servidor por meio da chamada ao método executeFonction do objeto clientSOAP.
' enviando solicitação ao serviço web
If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
A classe clientSOAP é compilada em um “assembly”:
dos>vbc /r:clientSOAP.dll testClientSOAP.vb
dos>dir
04/03/2004 08:46 6 913 clientSOAP.vb
04/03/2004 09:07 7 168 clientSOAP.dll
A aplicação cliente testClientSoap é então compilada por:
dos>vbc /r:clientSOAP.dll /r:system.dll testClientSOAP.vb
dos>dir
04/03/2004 09:08 2 711 testClientSOAP.vb
04/03/2004 09:08 4 608 testClientSOAP.exe
Aqui está um exemplo de execução sem detalhes:
dos>testclientsoap http://localhost/st/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 1 3
résultat=4
soustraire 6 7
résultat=-1
multiplier 4 5
résultat=20
diviser 1 2
résultat=0.5
x
syntaxe : [ajouter|soustraire|multiplier|diviser] a b
x 1 2
résultat=[fonction [x] indisponible : (ajouter, soustraire,multiplier,diviser)]
ajouter a b
résultat=[argument [a] incorrect (double)]
ajouter 1 b
résultat=[argument [b] incorrect (double)]
diviser 1 0
résultat=[division par zéro]
fin
É possível acompanhar as trocas de dados na rede solicitando uma execução “verbosa”:
dos>testClientSOAP http://localhost/operations/operations.asmx detalhado
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 4 8
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 08:15:25 GMT
<-- X-Powered-By: ASP.NET
<--
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>4</a>
<b>8</b>
</ajouter>
</soap:Body>
</soap:Envelope>
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 08:15:25 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 346
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>12</ajouterResult></ajouterResponse></soap:Body></soap:Envelope>
résultat=12
fin
Vamos agora criar um cliente gráfico.
10.7.3. Um cliente gráfico para Windows
Agora, vamos consultar nosso serviço web com um cliente gráfico que também utilizará a classe clientSOAP. A interface gráfica será a seguinte:
![]() |
Os controles são os seguintes:
n.º | tipo | nome | função |
TextBox | txtURI | o URI do serviço web operations | |
Botão | btnOuvrir | abre a conexão com o serviço web | |
Botão | btnFermer | encerra a conexão com o serviço web | |
ComboBox | cmbFonctions | a lista de operações (somar, subtrair, multiplicar, dividir) | |
TextBox | txtA | os argumentos das funções | |
TextBox | txtB | o argumento b das funções | |
TextBox | txtRésultat | o resultado da função (a, b) | |
Botão | btnCalculer | inicia o cálculo da função(a,b) | |
TextBox | txtErreur | exibe uma mensagem de status da conexão |
Existem algumas restrições de funcionamento:
- o botão btnOuvrir só fica ativo se o campo txtURI não estiver vazio e se ainda não houver uma conexão aberta
- o botão btnFermer só fica ativo quando uma conexão com o serviço web tiver sido aberta
- o botão btnCalculer só fica ativo quando uma conexão estiver aberta e os campos txtA e txtB não estiverem vazios
- os campos txtRésultat e txtErreur têm o atributo ReadOnly definido como verdadeiro
O cliente começa abrindo a conexão com o serviço web por meio do botão [Ouvrir]:

Em seguida, o usuário pode escolher uma função e valores para a e b:





Segue abaixo o código do aplicativo. Omitimos o código do formulário, que não é relevante neste contexto.
'espaços de nomes
Imports System
Imports System.Windows.Forms
' a classe do formulário
Public Class FormClientSOAP
Inherits System.Windows.Forms.Form
' atributos de instância
Dim client As clientSOAP ' client SOAP du service web operations
#Região “Código gerado pelo Windows Form Designer”
Public Sub New()
MyBase.New()
'Esta chamada é exigida pelo Windows Form Designer.
InitializeComponent()
' outras inicializações
myInit()
End Sub
'O método substituído Dispose do formulário limpa a lista de componentes.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
....
End Sub
...
Private Sub InitializeComponent()
....
End Sub
#Fim da Região
Private Sub myInit()
' inicialização do formulário
cmbFonctions.SelectedIndex = 0
btnOuvrir.Enabled = False
btnFermer.Enabled = True
btnCalculer.Enabled = False
End Sub
Private Sub txtURI_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtURI.TextChanged
' o conteúdo do campo de entrada foi alterado — definimos o estado do botão “Abrir”
btnOuvrir.Enabled = txtURI.Text.Trim <> ""
End Sub
Private Sub btnOuvrir_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnOuvrir.Click
' solicitação para estabelecer uma conexão com o serviço web
Try
' criação de um objeto do tipo [clientSOAP]
client = New clientSOAP(txtURI.Text.Trim, False)
' estados dos botões
btnOuvrir.Enabled = False
btnFermer.Enabled = True
' o URI não pode mais ser modificado
txtURI.ReadOnly = True
' status do cliente
txtErreur.Text = "Liaison au service web ouverte"
Catch ex As Exception
' ocorreu um erro — ele é exibido
txtErreur.Text = ex.Message
End Try
End Sub
Private Sub btnFermer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnFermer.Click
' encerrando a conexão com o serviço web
client.Close()
' estados dos botões
btnOuvrir.Enabled = True
btnFermer.Enabled = False
' URI
txtURI.ReadOnly = False
' status do cliente
txtErreur.Text = "Liaison au service web fermée"
End Sub
Private Sub btnCalculer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' cálculo de uma função f(a,b)
' apaga-se o resultado anterior
txtRésultat.Text = ""
Try
txtRésultat.Text = client.executeFonction(cmbFonctions.Text, txtA.Text.Trim, txtB.Text.Trim)
Catch ex As Exception
' ocorreu um erro de rede
txtErreur.Text = ex.Message
' fechando a conexão
btnFermer_Click(Nothing, Nothing)
End Try
End Sub
Private Sub txtA_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtA.TextChanged
' alteração do valor de A
btnCalculer.Enabled = txtA.Text.Trim <> "" And txtB.Text.Trim <> ""
End Sub
Private Sub txtB_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtB.TextChanged
' alteração do valor de B
txtA_TextChanged(Nothing, Nothing)
End Sub
' método principal
Public Shared Sub main()
Application.Run(New FormClientSOAP)
End Sub
End Class
Mais uma vez, a classe clientSOAP oculta toda a parte de rede do aplicativo. O aplicativo foi construído da seguinte maneira:
- o assembly clientSOAP.dll, que contém a classe clientSOAP, foi colocado na pasta do projeto
- a interface gráfica clientsoapgui.vb foi criada com VS.NET e, em seguida, compilada em uma janela do DOS:
dos>vbc /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll /r:clientSOAP.dll clientsoapgui.vb
dos>dir
04/03/2004 09:13 7 168 clientSOAP.dll
04/03/2004 16:44 9 866 clientsoapgui.vb
04/03/2004 16:44 11 264 clientsoapgui.exe
A interface gráfica foi então iniciada por:
10.8. Um cliente proxy
Vamos recapitular o que acabamos de fazer. Criamos uma classe intermediária que encapsula as trocas de dados de rede entre um cliente e um serviço web, conforme o esquema abaixo:
![]() |
A plataforma .NET leva essa lógica ainda mais longe. Uma vez identificado o serviço web a ser acessado, podemos gerar automaticamente a classe que servirá de intermediária para acessar as funções do serviço web e que ocultará toda a parte de rede. Essa classe é chamada de proxy para o serviço web para o qual foi gerada.
Como gerar a classe proxy de um serviço web? Um serviço web sempre vem acompanhado de um arquivo de descrição no formato XML. Se o URI do nosso serviço web “operations” for http://localhost/operations/operations.asmx, seu arquivo de descrição está disponível em http://localhost/operations/operations.asmx?wsdl, como mostra a captura de tela a seguir:

Trata-se de um arquivo XML que descreve com precisão todas as funções do serviço web, indicando, para cada uma delas, o tipo e o número de parâmetros, bem como o tipo do resultado. Esse arquivo é chamado de arquivo WSDL do serviço porque utiliza a linguagem WSDL (Web Services Description Language). A partir desse arquivo, é possível gerar uma classe proxy com a ajuda da ferramenta wsdl:
dos>wsdl http://localhost/operations/operations.asmx?wsdl /language=vb
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Écriture du fichier 'D:\data\devel\vbnet\poly\chap9\clientproxy\operations.vb'.
dos>dir
04/03/2004 17:17 6 663 operations.vb
A ferramenta wsdl gera um arquivo-fonte VB.NET (opção /language=vb) com o nome da classe que implementa o serviço web, neste caso, operations. Vamos examinar uma parte do código gerado:
'------------------------------------------------------------------------------
' <gerado automaticamente>
' Este código foi gerado por uma ferramenta.
' Versão de tempo de execução: 1.1.4322.573
'
' Alterações neste arquivo podem causar comportamento incorreto e serão perdidas se
' o código for regenerado.
' </autogenerated>
'------------------------------------------------------------------------------
Option Strict Off
Option Explicit On
Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Xml.Serialization
'
'Este código-fonte foi té gerado automaticamente généré pelo wsdl, Versão=1.1.4322.573.
'
'<remarks/>
<System.Diagnostics.DebuggerStepThroughAttribute(), _
System.ComponentModel.DesignerCategoryAttribute("code"), _
System.Web.Services.WebServiceBindingAttribute(Name:="operationsSoap", [Namespace]:="st.istia.univ-angers.fr")> _
Public Class operations
Inherits System.Web.Services.Protocols.SoapHttpClientProtocol
'<remarks/>
Public Sub New()
MyBase.New
Me.Url = "http://localhost/operations/operations.asmx"
End Sub
'<observações/>
<System.Web.Services.Protocols.SoapDocumentMethodAttribute("st.istia.univ-angers.fr/ajouter", RequestNamespace:="st.istia.univ-angers.fr", ResponseNamespace:="st.istia.univ-angers.fr", Use:=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle:=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)> _
Public Function ajouter(ByVal a As Double, ByVal b As Double) As Double
Dim results() As Object = Me.Invoke("ajouter", New Object() {a, b})
Return CType(results(0),Double)
End Function
'<observações/>
Public Function Beginajouter(ByVal a As Double, ByVal b As Double, ByVal callback As System.AsyncCallback, ByVal asyncState As Object) As System.IAsyncResult
Return Me.BeginInvoke("ajouter", New Object() {a, b}, callback, asyncState)
End Function
'<observações/>
Public Function Endajouter(ByVal asyncResult As System.IAsyncResult) As Double
Dim results() As Object = Me.EndInvoke(asyncResult)
Return CType(results(0),Double)
End Function
....
Esse código parece um pouco complexo à primeira vista. Não precisamos entender os detalhes para poder usá-lo. Vamos examinar, primeiro, a declaração da classe:
A classe leva o nome operations do serviço web para o qual foi criada. Ela deriva da classe SoapHttpClientProtocol:

Nossa classe proxy possui um único construtor:
O construtor atribui ao atributo url o valor URL do serviço web associado ao proxy. A classe operations acima não define, por si só, o atributo url. Este é herdado da classe da qual o proxy deriva: System.Web.Services.Protocols.SoapHttpClientProtocol. Vamos agora examinar o que se refere ao método ajouter:
Public Function ajouter(ByVal a As Double, ByVal b As Double) As Double
Dim results() As Object = Me.Invoke("ajouter", New Object() {a, b})
Return CType(results(0),Double)
End Function
Pode-se observar que ela possui a mesma assinatura do serviço Web operations, onde estava definida da seguinte forma:
<WebMethod> _
Function ajouter(a As Double, b As Double) As Double
Return a + b
End Function 'ajouter
A forma como essa classe se comunica com o serviço web não é mostrada aqui. Essa comunicação é totalmente gerenciada pela classe pai System.Web.Services.Protocols.SoapHttpClientProtocol. No proxy, encontra-se apenas o que o diferencia dos outros proxies:
- o URL do serviço web associado
- a definição dos métodos do serviço associado.
Para utilizar os métodos do serviço web operations, um cliente precisa apenas da classe proxy operations gerada anteriormente. Vamos compilar essa classe em um arquivo assembly:
Agora, vamos escrever um cliente de console. Ele é chamado sem parâmetros e executa as solicitações digitadas no teclado:
dos>testclientproxy
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b
ajouter 4 5
résultat=9
soustraire 9 8
résultat=1
multiplier 10 4
résultat=40
diviser 6 7
résultat=0,857142857142857
toutfaire 10 20
résultats=[30,-10,200,0,5]
diviser 5 0
résultat=+Infini
fin
O código do cliente é o seguinte:
' espaços de nomes
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Public Module testClientProxy
' executa de forma interativa os comandos digitados no teclado
' e os envia ao serviço web operations
Public Sub Main()
' não há mais argumentos — o URL do serviço web está codificado de forma estática no proxy
' criação de um dicionário das funções do serviço web
Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser", "toutfaire"}
Dim dicoFonctions As New Hashtable
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
' cria-se um objeto proxy de operações
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' erro de conexão
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
' as solicitações do usuário são digitadas no teclado
' no formato função a b — elas terminam com o comando fin
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
' solicita ao usuário
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b" + ControlChars.Lf)
' alguns dados locais
Dim erreurCommande As Boolean
Dim fonction As String
Dim a, b As Double
' ciclo de entrada dos comandos digitados no teclado
While True
' inicialmente, sem erros
erreurCommande = False
' leitura do comando
commande = Console.In.ReadLine().Trim().ToLower()
' concluído?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' decomposição do comando em campos
champs = Regex.Split(commande, "\s+")
Try
' são necessários três campos
If champs.Length <> 3 Then
Throw New Exception
End If
' o campo 0 deve ser uma função reconhecida
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' o campo 1 deve ser um número válido
a = Double.Parse(champs(1))
' o campo 2 deve ser um número válido
b = Double.Parse(champs(2))
Catch
' comando inválido
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' a solicitação é enviada ao serviço web
If Not erreurCommande Then
Try
Dim résultat As Double
Dim résultats() As Double
If fonction = "ajouter" Then
résultat = myOperations.ajouter(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "soustraire" Then
résultat = myOperations.soustraire(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "multiplier" Then
résultat = myOperations.multiplier(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "diviser" Then
résultat = myOperations.diviser(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "toutfaire" Then
résultats = myOperations.toutfaire(a, b)
Console.Out.WriteLine(("résultats=[" + résultats(0).ToString + "," + résultats(1).ToString + "," + _
résultats(2).ToString + "," + résultats(3).ToString + "]"))
End If
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
End If
End While
End Sub
' exibição de erros
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' exibição de erro
System.Console.Error.WriteLine(msg)
' interrupção com erro
Environment.Exit(exitCode)
End Sub
End Module
Analisamos apenas o código específico para o uso da classe proxy. Primeiramente, é criado um objeto proxy operations:
' criação de um objeto proxy de operações
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' erro de conexão
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
As linhas de função a e b são digitadas no teclado. A partir dessas informações, chamamos os métodos apropriados do proxy:
' envio da solicitação ao serviço web
If Not erreurCommande Then
Try
Dim résultat As Double
Dim résultats() As Double
If fonction = "ajouter" Then
résultat = myOperations.ajouter(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "soustraire" Then
résultat = myOperations.soustraire(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "multiplier" Then
résultat = myOperations.multiplier(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "diviser" Then
résultat = myOperations.diviser(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "toutfaire" Then
résultats = myOperations.toutfaire(a, b)
Console.Out.WriteLine(("résultats=[" + résultats(0).ToString + "," + résultats(1).ToString + "," + _
résultats(2).ToString + "," + résultats(3).ToString + "]"))
End If
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
Aqui, processamos pela primeira vez a operação toutfaire, que realiza as quatro operações. Ela havia sido ignorada até agora porque envia uma matriz de números encapsulada em um envelope XML, mais complicado de gerenciar do que as respostas simples XML das outras funções, que fornecem apenas um único resultado. Vemos que, aqui, com a classe proxy, usar o método toutfaire não é mais complicado do que usar os outros métodos. O aplicativo foi compilado em uma janela do DOS da seguinte maneira:
dos>vbc /r:operations.dll /r:system.dll /r:system.web.services.dll testClientProxy.vb
dos>dir
04/03/2004 17:17 6 663 operations.vb
04/03/2004 17:24 7 680 operations.dll
04/03/2004 17:41 4 099 testClientProxy.vb
04/03/2004 17:41 5 632 testClientProxy.exe
10.9. Configurar um serviço web
Um serviço web pode precisar de informações de configuração para ser inicializado corretamente. Com o servidor IIS, essas informações podem ser colocadas em um arquivo chamado web.config, localizado na mesma pasta que o serviço web. Suponhamos que queiramos criar um serviço web que precise de duas informações para ser inicializado: um nome e uma idade. Essas duas informações podem ser colocadas no arquivo web.config da seguinte forma:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
Os parâmetros de inicialização estão contidos no envelope XML:
Um parâmetro de inicialização com o nome P e o valor V será declarado com a linha:
<add key="P" value="V"/>
Como o serviço web recupera essas informações? Quando IIS carrega um serviço web, ele verifica se há, na mesma pasta, um arquivo web.config. Se houver, ele o lê. O valor V de um parâmetro P é obtido pela instrução:
onde ConfigurationSettings é uma classe no espaço de nomes System.Configuration.
Vamos testar essa técnica no seguinte serviço web:
<%@ WebService language="VB" class=personne %>
Imports System.Web.Services
imports System.Configuration
<WebService([Namespace] := "st.istia.univ-angers.fr")> _
Public Class personne
Inherits WebService
' atributos
Private nom As String
Private age As Integer
' construtor
Public Sub New()
' inicialização de atributos
nom = ConfigurationSettings.AppSettings("nom")
age = Integer.Parse(ConfigurationSettings.AppSettings("age"))
End Sub
<WebMethod> _
Function id() As String
Return "[" + nom + "," + age.ToString + "]"
End Function
End Class
O serviço web personne possui dois atributos, nom e age, que são inicializados em seu construtor sem parâmetros a partir dos valores lidos no arquivo de configuração web.config do serviço personne. Esse arquivo é o seguinte:
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
Além disso, o serviço web possui um <WebMethod> sem parâmetros, que se limita a retornar os atributos nom e age. O serviço está registrado no arquivo-fonte personne.asmx, que está localizado, juntamente com seu arquivo de configuração, na pasta c:\inetpub\wwwroot\st\personne:
Vamos associar uma pasta virtual IIS/config à pasta física anterior. Vamos executar o IIS e, em seguida, acessar com um navegador a URL http://localhost/config/personne.asmx do serviço personne:

Vamos seguir o link do único ID de método:

O método id não possui parâmetros. Vamos usar o botão Appeler:

Conseguimos recuperar as informações contidas no arquivo web.config do serviço.
10.10. O serviço web IMPOTS
Retomamos o aplicativo IMPOTS, agora bem conhecido. Da última vez que trabalhamos com ele, transformamos-no em um servidor remoto que podia ser acessado pela internet. Agora, vamos transformá-lo em um serviço web.
10.10.1. O serviço web
Partimos da classe impôt, criada no capítulo sobre bancos de dados e que é construída a partir das informações contidas no banco de dados ODBC:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Public Class impôt
' dados necessários para o cálculo do imposto
' provêm de uma fonte externa
Private limites(), coeffR(), coeffN() As Decimal
' construtor
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' verifica-se se as três tabelas têm o mesmo tamanho
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' tudo certo
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' construtor 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' inicializa as três matrizes de limites, coeffR, coeffN a partir
' do conteúdo da tabela Timpots do banco de dados ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN são as três colunas dessa tabela
' pode gerar uma exceção
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
' a consulta SELECT
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' tabelas para recuperar os dados
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' tenta-se acessar o banco de dados
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' está sendo criado um objeto de comando
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' executando a consulta
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Análise da tabela recuperada
While myReader.Read()
' os dados da linha atual são inseridos nas tabelas
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' liberação dos recursos
myReader.Close()
impotsConn.Close()
' as tabelas dinâmicas são transferidas para tabelas estáticas
Me.limites = New Decimal(tLimites.Count) {}
Me.coeffR = New Decimal(tLimites.Count) {}
Me.coeffN = New Decimal(tLimites.Count) {}
Dim i As Integer
For i = 0 To tLimites.Count - 1
limites(i) = Decimal.Parse(tLimites(i).ToString())
coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
Next i
End Sub
' cálculo do imposto
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' cálculo do número de cotas
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' cálculo da renda tributável e do quociente familiar
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' cálculo do imposto
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' retorno do resultado
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
No serviço web, só é possível usar um construtor sem parâmetros. Portanto, o construtor da classe passará a ser o seguinte:
' construtor
Public Sub New()
' inicializa as três tabelas de limites, coeffR, coeffN a partir
' do conteúdo da tabela Timpots do banco de dados ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN são as três colunas dessa tabela
' pode gerar uma exceção
' recupera-se os parâmetros de configuração do serviço
Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN")
Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE")
Dim colLimites As String = ConfigurationSettings.AppSettings("COL_LIMITES")
Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR")
Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN")
' consulta-se o banco de dados
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Os cinco parâmetros do construtor da classe anterior agora são lidos no arquivo web.config do serviço. O código do arquivo-fonte impots.asmx é o seguinte. Ele retoma a maior parte do código anterior. Limitamo-nos a destacar as partes do código específicas do serviço web:
<%@ WebService language="VB" class=impots %>
' criação de um serviço web de impostos
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Imports System.Configuration
Imports System.Web.Services
<WebService([Namespace]:="st.istia.univ-angers.fr")> _
Public Class impôt
Inherits WebService
' os dados necessários para o cálculo do imposto
' provêm de uma fonte externa
Private limites(), coeffR(), coeffN() As Decimal
Private OK As Boolean = False
Private errMessage As String = ""
' construtor
Public Sub New()
' inicializa as três tabelas de limites, coeffR, coeffN, a partir
' do conteúdo da tabela Timpots do banco de dados ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN são as três colunas dessa tabela
' pode gerar uma exceção
' recupera-se os parâmetros de configuração do serviço
Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN")
Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE")
Dim colLimites As String = ConfigurationSettings.AppSettings("COL_LIMITES")
Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR")
Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN")
' a consulta é feita no banco de dados
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
Dim myReader As OdbcDataReader ' lecteur de données Odbc
' a consulta SELECT
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' tabelas para recuperar os dados
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' tenta-se acessar o banco de dados
Try
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' cria-se um objeto de comando
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' executando a consulta
myReader = sqlCommand.ExecuteReader()
' Análise da tabela recuperada
While myReader.Read()
' os dados da linha atual são inseridos nas tabelas
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' liberação dos recursos
myReader.Close()
impotsConn.Close()
' as tabelas dinâmicas são transferidas para tabelas estáticas
Me.limites = New Decimal(tLimites.Count) {}
Me.coeffR = New Decimal(tLimites.Count) {}
Me.coeffN = New Decimal(tLimites.Count) {}
Dim i As Integer
For i = 0 To tLimites.Count - 1
limites(i) = Decimal.Parse(tLimites(i).ToString())
coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
Next i
' Tudo certo
OK = True
errMessage = ""
Catch ex As Exception
' erro
OK = False
errMessage += "[" + ex.Message + "]"
End Try
End Sub
' cálculo do imposto
<WebMethod()> _
Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' cálculo do número de cotas
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' cálculo do rendimento tributável e do quociente familiar
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' cálculo do imposto
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' retorno do resultado
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
' ID
<WebMethod()> _
Function id() As String
' para verificar se tudo está correto OK
Return "[" + OK + "," + errMessage + "]"
End Function
End Class
Vamos explicar as poucas modificações feitas na classe impots, além daquelas necessárias para transformá-la em um serviço web:
- a leitura do banco de dados no construtor pode falhar. Por isso, adicionamos dois atributos à nossa classe e um método:
- A variável booleana OK assume o valor vrai se o banco de dados puder ser lido; caso contrário, assume o valor faux
- a string errMessage contém uma mensagem de erro se o banco de dados não puder ser lido.
- O método id, sem parâmetros, permite obter o valor desses dois atributos.
- Para lidar com um eventual erro de acesso ao banco de dados, a parte do código do construtor relacionada a esse acesso foi colocada entre try-catch.
O arquivo de configuração do serviço web.config é o seguinte:
<configuration>
<appSettings>
<add key="DSN" value="mysql-impots" />
<add key="TABLE" value="timpots" />
<add key="COL_LIMITES" value="limites" />
<add key="COL_COEFFR" value="coeffr" />
<add key="COL_COEFFN" value="coeffn" />
</appSettings>
</configuration>
Durante uma primeira tentativa de carregamento do serviço impots, o compilador informou que não encontrou o espaço de nomes Microsoft.Data.Odbc utilizado na diretiva:
Após consultar a documentação
- foi adicionada uma diretiva de compilação no web.config para indicar que era necessário utilizar o assembly Microsoft.Data.odbc
- uma cópia do arquivo microsoft.data.odbc.dll foi colocada na pasta bin do projeto. Essa pasta é sistematicamente verificada pelo compilador de um serviço web quando ele procura por um “assembly”.
Outras soluções parecem possíveis, mas não foram exploradas aqui. O arquivo de configuração ficou, portanto, assim:
<configuration>
<appSettings>
<add key="DSN" value="mysql-impots" />
<add key="TABLE" value="timpots" />
<add key="COL_LIMITES" value="limites" />
<add key="COL_COEFFR" value="coeffr" />
<add key="COL_COEFFN" value="coeffn" />
</appSettings>
<system.web>
<compilation>
<assemblies>
<add assembly="Microsoft.Data.Odbc" />
</assemblies>
</compilation>
</system.web>
</configuration>
O conteúdo da pasta impots\bin:
O serviço e seu arquivo de configuração foram colocados em impots:
A pasta física do serviço web foi associada à pasta virtual /impots de IIS. A página do serviço é, portanto, a seguinte:

Se seguirmos o link id:

Se utilizarmos o botão Appeler:

O resultado anterior exibe os valores dos atributos OK (true) e errMessage (""). Neste exemplo, o banco de dados foi carregado corretamente. Isso nem sempre foi o caso, e é por isso que adicionamos o método id para ter acesso à mensagem de erro. O erro era que o nome DSN do banco de dados havia sido definido como DSN (usuário), quando deveria ter sido definido como DSN (sistema). Essa distinção é feita no gerenciador de fontes ODBC de 32 bits:
![]() |
Voltemos à página do serviço:

Vamos seguir o link calculer:

Definimos os parâmetros da chamada e a executamos:

O resultado está correto.
10.10.2. Gerar o proxy do serviço impots
Agora que temos um serviço web impots em funcionamento, podemos gerar sua classe proxy. Vale lembrar que ela será utilizada por aplicativos clientes para acessar o serviço web impots de forma transparente. Primeiro, utilizamos o utilitário wsdl para gerar o arquivo-fonte da classe proxy; em seguida, este é compilado em uma DLL.
dos>wsdl /language=vb http://localhost/impostos/impots.asmx
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Écriture du fichier 'D:\data\serge\devel\vbnet\poly\chap9\impots\impots.vb'.
D:\data\serge\devel\vbnet\poly\chap9\impots>dir
09/03/2004 10:20 <REP> bin
09/03/2004 10:58 4 651 impots.asmx
09/03/2004 11:05 3 364 impots.vb
09/03/2004 10:19 431 web.config
dos>vbc /t:library /r:system.dll /r:system.web.services.dll /r:system.xml.dll impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
pour Microsoft (R) .NET Framework version 1.1.4322.573
Copyright (C) Microsoft Corporation 1987-2002. Tous droits réservés.
dos>dir
09/03/2004 10:20 <REP> bin
09/03/2004 10:58 4 651 impots.asmx
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:05 3 364 impots.vb
09/03/2004 10:19 431 web.config
10.10.3. Usar o proxy com um cliente
No capítulo sobre bancos de dados, criamos um aplicativo de console que permite o cálculo do imposto:
dos>dir
27/02/2004 16:56 5 120 impots.dll
27/02/2004 17:12 3 586 impots.vb
27/02/2004 17:08 6 144 testimpots.exe
27/02/2004 17:18 3 328 testimpots.vb
dos>testimpots
pg DSNimpots tabImpots colLimites colCoeffR colCoeffN
dos>testimpots odbc-mysql-dbimpots impots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
O programa testimpots utilizava, na época, a classe impôt clássica, aquela contida no arquivo impots.dll. O código do programa testimpots.vb era o seguinte:
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
Imports Microsoft.VisualBasic
' página de teste
Module testimpots
Sub Main(ByVal arguments() As String)
' programa interativo de cálculo de impostos
' o usuário digita três dados no teclado: casado nbEnfants salário
' o programa exibe, então, o imposto a pagar
Const syntaxe1 As String = "pg DSNimpots tabImpots colLimites colCoeffR colCoeffN"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' verificação dos parâmetros do programa
If arguments.Length <> 5 Then
' mensagem de erro
Console.Error.WriteLine(syntaxe1)
' fim
Environment.Exit(1)
End If 'if
' recuperação dos argumentos
Dim DSNimpots As String = arguments(0)
Dim tabImpots As String = arguments(1)
Dim colLimites As String = arguments(2)
Dim colCoeffR As String = arguments(3)
Dim colCoeffN As String = arguments(4)
' criação de um objeto de imposto
Dim objImpôt As impôt = Nothing
Try
objImpôt = New impôt(DSNimpots, tabImpots, colLimites, colCoeffR, colCoeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' loop infinito
While True
' inicialmente, sem erros
Dim erreur As Boolean = False
' solicitando os parâmetros para o cálculo do imposto
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' o que fazer?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' verificação do número de argumentos na linha inserida
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' verificação da validade dos parâmetros
' casado
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' salário
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' os parâmetros estão corretos — calculamos o imposto
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
End If
End While
End Sub
End Module
Retomamos o mesmo programa para que ele utilize agora o serviço web impots por meio da classe proxy impots criada anteriormente. Somos obrigados a modificar um pouco o código:
- enquanto a classe original impôt possuía um construtor com cinco argumentos, a classe proxy impots possui um construtor sem parâmetros. Os cinco parâmetros, como vimos, agora estão definidos no arquivo de configuração do serviço web.
- portanto, não há mais necessidade de passar esses cinco parâmetros como argumentos para o programa de teste
O novo código é o seguinte:
Imports System
Imports Microsoft.VisualBasic
' página de teste
Module testimpots
Public Sub Main(ByVal arguments() As String)
' programa interativo de cálculo de imposto
' o usuário digita três dados no teclado: casado nbEnfants salário
' o programa exibe então o imposto a pagar
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' criação de um objeto de imposto
Dim objImpôt As impôt = Nothing
Try
objImpôt = New impôt
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' loop infinito
Dim erreur As Boolean
While True
' inicialmente, sem erros
erreur = False
' são solicitados os parâmetros para o cálculo do imposto
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' o que fazer?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' verificação do número de argumentos na linha inserida
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
If Not erreur Then
' verificação da validade dos parâmetros
' casado
Dim marié As String = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
Dim nbEnfants As Integer = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument nbEnfants incorrect : tapez un entier positif ou nul"))
erreur = True
End Try
' salário
Dim salaire As Integer = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument salaire incorrect : tapez un entier positif ou nul"))
erreur = True
End Try
' se os parâmetros estiverem corretos — calcula-se o imposto
If Not erreur Then Console.Out.WriteLine(("impôt=" + objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
End If
End While
End Sub
End Module
Temos o proxy impots.dll e o código-fonte testimpots na mesma pasta.
dos>dir
09/03/2004 11:28 <REP> bin
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:34 3 396 testimpots.vb
09/03/2004 10:19 431 web.config
Estamos compilando o código-fonte testimpots.vb:
dos>vbc /r:impots.dll /r:microsoft.visualbasic.dll /r:system.web.services.dll /r:system.dll testimpots.vb
dos>dir
09/03/2004 11:28 <REP> bin
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:05 3 364 impots.vb
09/03/2004 11:35 5 632 testimpots.exe
09/03/2004 11:34 3 396 testimpots.vb
09/03/2004 10:19 431 web.config
e, em seguida, executamos:
dos>testimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Obtemos, de fato, o resultado esperado.





