CustomBinding en ServiceEndpoint con el contrato ‘IServicioWeb’ carece de TransportBindingElement

El otro día generando un cliente .NET para un servicio web JAXWS basado en SOAP 1.2 me topé con el siguiente problema:

System.InvalidOperationException: 
CustomBinding en ServiceEndpoint con el contrato 'IServicioWeb' carece de TransportBindingElement. 
Todos los enlaces deben tener al menos un elemento de enlace que se derive de TransportBindingElement.

O lo que es lo mismo pero en la lengua de Shakespeare:

System.InvalidOperationException: 
The CustomBinding on the ServiceEndpoint with contract 'Contract Name' lacks a TransportBindingElement. 
Every binding must have at least one binding element that derives from TransportBindingElement.

El servicio web se había generado desde Visual Studio utilizando la herramienta Add Service Reference, que por debajo hace uso de SvcUtil.exe, siguiendo los pasos que en su momento dejé recogidos en la entrada «Generando clientes de servicios web JAX-WS desde .NET».

Leyendo detenidamente el mensaje de error, se llega a la conclusión de que la sección customBinding no tiene referencia a ningún TransportBindingElement.

TransportBindingElement es una clase abstracta que representa un tipo de binding (enlace) de transporte. Los tipos de binding de transporte más comunes son:

  • Peer to peer – PeerTransportBindingElement
  • Http – HttpTransportBindingElement
  • Https – HttpsTransportBindingElement
  • TCP – TcpTransportBindingElement
  • Named Pipe – NamedPipeTransportBindingElement
  • MS MQ – MsmqTransportBindingElement
  • MS MQ Integration – MsmqIntegrationBindingElement
  • Connection Oriented – ConnectionOrientedTransportBindingElement

En este caso, el acceso al servicio web se realiza utilizando protocolo HTTP, por lo que hay que añadir una referencia a HttpTransportBindingElement en la configuración del cliente.

La manera simple de incluir esta información es en el fichero app.config de la aplicación.

Tras la generación con la herramienta SvcUtil.exe de Visual Studio el app.config tiene el siguiente aspecto:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <customBinding>
                <binding name="ServicioWebPortBinding">
                    <textMessageEncoding messageVersion="Soap12" />
                </binding>
            </customBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:8080/ServicioWebWSImpl/ServicioWeb"
                binding="customBinding" bindingConfiguration="ServicioWebPortBinding"
                contract="ServicioWebSR.ServicioWeb" name="ServicioWebPort" />
        </client>
    </system.serviceModel>
</configuration>

La solución al problema es añadir la configuración <httpTransport> en la definición del <CustomBinding> del fichero app.config.

La sintaxis de esta configuración es:

<httpTransport
    allowCookies=Boolean"
    authenticationScheme="Digest/Negotiate/Ntlm/Basic/Anonymous"
    bypassProxyOnLocal=Boolean"
    hostnameComparisonMode="StrongWildcard/Exact/WeakWildcard"
    keepAliveEnabled="Boolean"
    maxBufferSize="Integer"
    proxyAddress="Uri"
    proxyAuthenticationScheme="None/Digest/Negotiate/Ntlm/Basic/Anonymous"
IntegratedWindowsAuthentication: Specifies Windows authentication"
    realm="String"
    transferMode="Buffered/Streamed/StreamedRequest/StreamedResponse"
        unsafeConnectionNtlmAuthentication="Boolean"
        useDefaultWebProxy="Boolean" />  
  • allowCookies – Un valor booleano que especifica si el cliente acepta las cookies y las propaga en solicitudes futuras. El valor predeterminado es false.
  • authenticationScheme – Especifica el protocolo utilizado para autenticar solicitudes de cliente que son procesadas por un agente de escucha HTTP. El valor predeterminado es anónimo. Protocolos de autenticación válidos pueden ser:
    • Digest: especifica la autenticación implícita.
    • Negotiate: negocia con el cliente para determinar el esquema de autenticación. Si cliente y el servidor son compatibles con Kerberos, se utiliza; de lo contrario, se utiliza NTLM
    • Ntlm: especifica la autenticación NTLM.
    • Basic: especifica la autenticación básica.
    • Anonymous: especifica la autenticación anónima.
  • bypassProxyOnLocal – Un valor booleano que indica si se omitirá el servidor proxy para las direcciones locales. El valor predeterminado es false.
  • hostnameComparisonMode – Especifica el modo de comparación de HostName HTTP usado para analizar los URI. El valor predeterminado es StrongWildcard. Para analizar las URIs se puede hacer de tres maneras:
    • StrongWildcard: («+») coincide con todos los posibles nombres del host en el contexto del esquema especificado, puerto y URI relativo.
    • Exact: ningún carácter comodín
    • WeakWildcard: («*») coincide con todo posible nombre del host en el contexto del esquema especificado, puerto y URI relativo con los que no se han coincidido explícitamente o a través del mecanismo del carácter comodín fuerte.
  • keepAliveEnabled – Un valor booleano que especifica si se debe establecer una conexión continua con el recurso de Internet.
  • maxBufferSize – Un entero positivo que especifica el tamaño máximo del búfer. El valor predeterminado es 524288.
  • proxyAddress – Un URI que especifica la dirección del proxy HTTP. Si useSystemWebProxy es true, este valor debe ser null. El valor predeterminado es null.
  • proxyAuthenticationScheme – Especifica el protocolo utilizado para autenticar solicitudes de cliente que son procesadas por un proxy HTTP. La opción por defecto es Anonymous. Las diferentes posibilidades de autenticación son:
    • None: no se lleva a cabo ninguna autenticación.
    • Digest: especifica la autenticación implícita
    • Negotiate: negocia con el cliente para determinar el esquema de autenticación. Si cliente y el servidor son compatibles con Kerberos, se utiliza; de lo contrario, se utiliza NTLM
    • Ntlm: especifica la autenticación NTLM
    • Basic: especifica la autenticación básica
    • Anonymous: especifica la autenticación anónima
    • IntegratedWindowsAuthentication: especifica la autenticación de Windows
  • realm – Una cadena que especifica el dominio kerberos que se utilizará en el proxy/servidor. El valor predeterminado es una cadena vacía.
  • transferMode – Especifica si los mensajes se almacenan en búfer, se transmiten o si son una solicitud o una respuesta. El valor por defecto es Buffered. Las diferentes opciones de transferencia son:
    • Buffered: los mensajes de respuesta y solicitud están almacenados en búfer.
    • Streamed: se transmiten los mensajes de solicitud y respuesta
    • StreamedRequest: se transmite el mensaje de solicitud y el mensaje de respuesta está almacenado en búfer
    • StreamedResponse: se transmite el mensaje de respuesta y el mensaje de solicitud está almacenado en búfer
  • unsafeConnectionNtlmAuthentication – Un valor booleano que especifica si la conexión compartida no segura está habilitada en el servidor. El valor predeterminado es false. Si está habilitado, la autenticación NTLM se realiza una vez en cada conexión TCP.
  • useDefaultWebProxy – Un valor que especifica si se utiliza la configuración del proxy del equipo en lugar de la configuración específica del usuario. El valor predeterminado es true.

Conclusión

Todavía no tengo claro porque la generación automática del cliente no incluye la definición del tipo de transporte a utilizar en la comunicación. Lo que si tengo claro es que la solución es tan simple como añadir esta definición en el fichero de configuración de la aplicación.

        <bindings>
            <customBinding>
                <binding name="ServicioWebPortBinding">
                    <textMessageEncoding messageVersion="Soap12" />
			<httpTransport />
                </binding>
            </customBinding>
        </bindings>

Para este caso, y en vista de que la comunicación se hace por un canal HTTP sin proxy ni ninguna cosa rara, basta con añadir la etiqueta <httpTransport /> para solucionar el problema. Si existiera algún proxy entre el cliente y el servicio web, o algún sistema de autenticación, o se quisiera limitar las llamadas, se pueden ir añadiendo las posibilidades descritas a esta configuración.

Si la comunicación no fuera HTTP habría que generar otro tipo de binding de transporte, con sus propias configuraciones explicadas en la biblioteca MSDN de Microsoft.

Links

Usando yum detrás de un proxy

Uno de los grandes handicaps que más menudo sufro en el trabajo es estar detrás de un proxy. Siempre tengo que andar configurándolo por aquí y por allá, y por si fuera poco las configuraciones varían dependiendo de los programas que lo usan.

Una de estos programas «especiales» es la actualización del software mediante yum en los servidores con CentOS. Yum tiene como particularidad que no utiliza la configuración general del proxy que se realiza desde las utilidades gráficas de GNOME, y por tanto hay que configurarlo de manera personalizada.

Configurando la utilización de un proxy en yum a nivel de servidor.

Este método activa en yum la utilización del proxy de manera global para todos los usuarios del servidor. Pero, hay que tener en cuenta algunas cosillas.

Si el proxy tiene autenticación, este método requiere introducir el usuario y la password plana (sin cifrado) en un fichero de configuración que es accesible por cualquiera con un poco de conocimiento y algo de maña. Si la seguridad es importante, ya habéis deducido que este método puede no ser del todo recomendable.

También hay que tener en cuenta que cualquier usuario con permisos para ejecutar yum, hará actualizaciones usando la cuenta del proxy configurada.

Si las observaciones anteriores no disgustan, la activación del proxy para las operaciones con yum se hace en el fichero /etc/yum.conf añadiendo tres variables:

# /etc/yum.conf
# SERVER_PROXY CONFIGURATION
proxy=http://dns_servidor_proxy:puerto_servidor_proxy
proxy_username=usuario_proxy
proxy_password=password_proxy

Configurando la utilización de un proxy en yum para un usuario específico.

Si la anterior configuración no parecía adecuada porque la configuración del usuario y la password se realiza a nivel de servidor y es utilizada por cualquier usuario que ejecute yum, ésta configuración a nivel de usuario cuadrará mejor con las expectativas.

Con este método, cada usuario introducirá la configuración del proxy en el fichero ~/.bash_profile que está en su directorio home.

# ~/.bash_profile
# PERSONAL_PROXY_CONFIGURATION
http_proxy="http://usuario_proxy:password_proxy@dns_servidor_proxy:puerto_servidor_proxy"
export http_proxy

De esta manera, la ejecución de yum esta personalizada con la información de proxy que cada usuario ha introducido en su fichero personal.

Conclusión

Ambas configuraciones funcionan correctamente y permiten actualizar el servidor utilizando yum detrás de un proxy.

La elección de una u otra será una cuestión de qué configuración cuadra más con la política de seguridad que se quiere adoptar.

Y esto es todo, espero que sea útil.