44import com .microsoft .azure .proton .transport .proxy .impl .ProxyHandlerImpl ;
55import com .microsoft .azure .proton .transport .proxy .impl .ProxyImpl ;
66
7+ import com .microsoft .azure .servicebus .primitives .ClientConstants ;
78import com .microsoft .azure .servicebus .primitives .StringUtil ;
89import com .microsoft .azure .servicebus .primitives .MessagingFactory ;
10+ import org .apache .qpid .proton .amqp .transport .ConnectionError ;
11+ import org .apache .qpid .proton .amqp .transport .ErrorCondition ;
12+ import org .apache .qpid .proton .engine .Connection ;
913import org .apache .qpid .proton .engine .Event ;
14+ import org .apache .qpid .proton .engine .Transport ;
1015import org .apache .qpid .proton .engine .impl .TransportInternal ;
1116import org .slf4j .Logger ;
1217import org .slf4j .LoggerFactory ;
1318
19+ import java .io .IOException ;
20+ import java .net .*;
1421import java .util .Base64 ;
1522import java .util .HashMap ;
23+ import java .util .List ;
1624import java .util .Map ;
1725
1826public class ProxyConnectionHandler extends WebSocketConnectionHandler {
1927 private static final Logger TRACE_LOGGER = LoggerFactory .getLogger (ProxyConnectionHandler .class );
28+ private final String proxySelectorModifiedError = "Proxy Selector has been modified." ;
2029
21- public static boolean shouldUseProxy (IAmqpConnection messagingFactory ) {
22- return !StringUtil .isNullOrEmpty (((MessagingFactory )messagingFactory ).getClientSettings ().getProxyHostName ());
30+ public static boolean shouldUseProxy (final String hostName ) {
31+ final URI uri = createURIFromHostNamePort (hostName , ClientConstants .HTTPS_PORT );
32+ final ProxySelector proxySelector = ProxySelector .getDefault ();
33+ if (proxySelector == null ) {
34+ return false ;
35+ }
36+
37+ final List <Proxy > proxies = proxySelector .select (uri );
38+ return isProxyAddressLegal (proxies );
2339 }
2440
2541 public ProxyConnectionHandler (IAmqpConnection messagingFactory ) { super (messagingFactory ); }
@@ -42,11 +58,67 @@ public void addTransportLayers(final Event event, final TransportInternal transp
4258 }
4359 }
4460
61+ @ Override
62+ protected void notifyTransportErrors (final Event event ) {
63+ final Transport transport = event .getTransport ();
64+ final Connection connection = event .getConnection ();
65+ if (connection == null || transport == null ) {
66+ return ;
67+ }
68+
69+ final ErrorCondition errorCondition = transport .getCondition ();
70+ final String hostName = event .getReactor ().getConnectionAddress (connection );
71+ final ProxySelector proxySelector = ProxySelector .getDefault ();
72+ if (errorCondition == null
73+ || !(errorCondition .getCondition ().equals (ConnectionError .FRAMING_ERROR )
74+ || errorCondition .getCondition ().equals (AmqpErrorCode .ProtonIOError ))
75+ || proxySelector == null
76+ || StringUtil .isNullOrEmpty (hostName )) {
77+ return ;
78+ }
79+
80+ final String [] hostNameParts = hostName .split (":" );
81+ if (hostNameParts .length != 2 ) {
82+ return ;
83+ }
84+
85+ int port ;
86+ try {
87+ port = Integer .parseInt (hostNameParts [1 ]);
88+ } catch (NumberFormatException ignore ) {
89+ return ;
90+ }
91+
92+ final IOException ioException = reconstructIOException (errorCondition );
93+ proxySelector .connectFailed (
94+ createURIFromHostNamePort (((MessagingFactory )this .getMessagingFactory ()).getHostName (), this .getProtocolPort ()),
95+ new InetSocketAddress (hostNameParts [0 ], port ),
96+ ioException
97+ );
98+
99+ }
100+
45101 private Map <String , String > getAuthorizationHeader () {
46- final String proxyUserName = ((MessagingFactory )messagingFactory ).getClientSettings ().getProxyUserName ();
47- final String proxyPassword = ((MessagingFactory )messagingFactory ).getClientSettings ().getProxyPassword ();
48- if (StringUtil .isNullOrEmpty (proxyUserName ) ||
49- StringUtil .isNullOrEmpty (proxyPassword )) {
102+ final PasswordAuthentication authentication = Authenticator .requestPasswordAuthentication (
103+ getOutboundSocketHostName (),
104+ null ,
105+ getOutboundSocketPort (),
106+ null ,
107+ null ,
108+ "http" ,
109+ null ,
110+ Authenticator .RequestorType .PROXY
111+ );
112+ if (authentication == null ) {
113+ return null ;
114+ }
115+
116+ final String proxyUserName = authentication .getUserName ();
117+ final String proxyPassword = authentication .getPassword () != null
118+ ? new String (authentication .getPassword ())
119+ : null ;
120+ if (StringUtil .isNullOrEmpty (proxyUserName )
121+ || StringUtil .isNullOrEmpty (proxyPassword )) {
50122 return null ;
51123 }
52124
@@ -60,11 +132,52 @@ private Map<String, String> getAuthorizationHeader() {
60132
61133 @ Override
62134 public String getOutboundSocketHostName () {
63- return ((MessagingFactory )messagingFactory ).getClientSettings ().getProxyHostName ();
135+ final InetSocketAddress socketAddress = getProxyAddress ();
136+ return socketAddress .getHostString ();
64137 }
65138
66139 @ Override
67140 public int getOutboundSocketPort () {
68- return ((MessagingFactory )messagingFactory ).getClientSettings ().getProxyHostPort ();
141+ final InetSocketAddress socketAddress = getProxyAddress ();
142+ return socketAddress .getPort ();
143+ }
144+
145+ private InetSocketAddress getProxyAddress () {
146+ final URI serviceUri = createURIFromHostNamePort (
147+ ((MessagingFactory )this .getMessagingFactory ()).getHostName (),
148+ this .getProtocolPort ());
149+ final ProxySelector proxySelector = ProxySelector .getDefault ();
150+ if (proxySelector == null ) {
151+ throw new IllegalStateException (proxySelectorModifiedError );
152+ }
153+
154+ final List <Proxy > proxies = proxySelector .select (serviceUri );
155+ if (!isProxyAddressLegal (proxies )) {
156+ throw new IllegalStateException (proxySelectorModifiedError );
157+ }
158+
159+ final Proxy proxy = proxies .get (0 );
160+ return (InetSocketAddress ) proxy .address ();
161+ }
162+
163+ private static URI createURIFromHostNamePort (final String hostName , final int port ) {
164+ return URI .create (String .format (ClientConstants .HTTPS_URI_FORMAT , hostName , port ));
165+ }
166+
167+ private static boolean isProxyAddressLegal (final List <Proxy > proxies ) {
168+ // only checks the first proxy in the list
169+ // returns true if it is an InetSocketAddress, which is required for qpid-proton-j library
170+ return proxies != null
171+ && !proxies .isEmpty ()
172+ && proxies .get (0 ).type () == Proxy .Type .HTTP
173+ && proxies .get (0 ).address () != null
174+ && proxies .get (0 ).address () instanceof InetSocketAddress ;
175+ }
176+
177+ private static IOException reconstructIOException (ErrorCondition errorCondition ) {
178+ // since proton library communicates all errors based on amqp-error-condition
179+ // it swallows the IOException and translates it to proton-io error code
180+ // we reconstruct the IOException here, but callstack is lost
181+ return new IOException (errorCondition .getDescription ());
69182 }
70183}
0 commit comments