Skip to content

Commit 5cdbbd8

Browse files
author
oleksiys
committed
[1.9.x] implement AsyncHttpClient#941
AsyncHttpClient#941 "Grizzly provider always uses Basic auth when using a proxy"
1 parent 68c9e7a commit 5cdbbd8

File tree

4 files changed

+228
-74
lines changed

4 files changed

+228
-74
lines changed

src/main/java/com/ning/http/client/providers/grizzly/AhcEventFilter.java

Lines changed: 191 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.ning.http.client.AsyncHandler;
1818
import com.ning.http.client.FluentCaseInsensitiveStringsMap;
1919
import com.ning.http.client.MaxRedirectException;
20+
import com.ning.http.client.ProxyServer;
2021
import com.ning.http.client.Realm;
2122
import com.ning.http.client.Realm.AuthScheme;
2223
import com.ning.http.client.Request;
@@ -80,6 +81,7 @@ final class AhcEventFilter extends HttpClientFilter {
8081
super(maxHerdersSizeProperty);
8182
this.provider = provider;
8283
HANDLER_MAP.put(HttpStatus.UNAUTHORIZED_401.getStatusCode(), AuthorizationHandler.INSTANCE);
84+
HANDLER_MAP.put(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407.getStatusCode(), ProxyAuthorizationHandler.INSTANCE);
8385
HANDLER_MAP.put(HttpStatus.MOVED_PERMANENTLY_301.getStatusCode(), RedirectHandler.INSTANCE);
8486
HANDLER_MAP.put(HttpStatus.FOUND_302.getStatusCode(), RedirectHandler.INSTANCE);
8587
HANDLER_MAP.put(HttpStatus.SEE_OTHER_303.getStatusCode(), RedirectHandler.INSTANCE);
@@ -522,16 +524,13 @@ public boolean handleStatus(final HttpResponsePacket responsePacket,
522524

523525
final Realm newRealm;
524526
if (ntlmAuthenticate != null) {
527+
final Connection connection = ctx.getConnection();
525528
// NTLM
526529
// Connection-based auth
527-
isContinueAuth = ntlmChallenge(ctx.getConnection(),
528-
ntlmAuthenticate, req,
529-
req.getHeaders(), realm);
530-
531-
newRealm = new Realm.RealmBuilder().clone(realm)//
532-
.setUri(req.getUri())//
533-
.setMethodName(req.getMethod())//
534-
.build();
530+
newRealm = ntlmChallenge(connection,
531+
ntlmAuthenticate,
532+
req, realm, false);
533+
isContinueAuth = !Utils.isNtlmEstablished(connection);
535534
} else {
536535
// Request-based auth
537536
isContinueAuth = false;
@@ -585,66 +584,115 @@ public boolean handleStatus(final HttpResponsePacket responsePacket,
585584

586585
return false;
587586
}
587+
} // END AuthorizationHandler
588588

589-
private boolean ntlmChallenge(final Connection c,
590-
final String wwwAuth, final Request request,
591-
final FluentCaseInsensitiveStringsMap headers,
592-
final Realm realm)
593-
throws NTLMEngineException {
589+
private static final class ProxyAuthorizationHandler implements StatusHandler {
594590

595-
if (wwwAuth.equals("NTLM")) {
596-
// server replied bare NTLM => we didn't preemptively sent Type1Msg
597-
String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg();
591+
static final ProxyAuthorizationHandler INSTANCE = new ProxyAuthorizationHandler();
592+
// -------------------------------------- Methods from StatusHandler
598593

599-
addNTLMAuthorizationHeader(headers, challengeHeader, false);
600-
return true;
601-
} else {
602-
// probably receiving Type2Msg, so we issue Type3Msg
603-
addType3NTLMAuthorizationHeader(wwwAuth, headers, realm, false);
604-
// we mark NTLM as established for the Connection to
605-
// avoid preemptive NTLM
606-
Utils.setNtlmEstablished(c);
607-
608-
return false;
609-
}
594+
@Override
595+
public boolean handlesStatus(int statusCode) {
596+
return HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407.statusMatches(statusCode);
610597
}
611-
612-
private void addNTLMAuthorizationHeader(
613-
FluentCaseInsensitiveStringsMap headers,
614-
String challengeHeader, boolean proxyInd) {
615-
headers.add(authorizationHeaderName(proxyInd), "NTLM " + challengeHeader);
616-
}
617-
618-
private void addType3NTLMAuthorizationHeader(String auth,
619-
FluentCaseInsensitiveStringsMap headers, Realm realm,
620-
boolean proxyInd) throws NTLMEngineException {
621-
headers.remove(authorizationHeaderName(proxyInd));
622-
623-
if (isNonEmpty(auth) && auth.startsWith("NTLM ")) {
624-
String serverChallenge = auth.substring("NTLM ".length()).trim();
625-
String challengeHeader = NTLMEngine.INSTANCE.generateType3Msg(
626-
realm.getPrincipal(), realm.getPassword(),
627-
realm.getNtlmDomain(), realm.getNtlmHost(), serverChallenge);
628-
addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd);
598+
599+
@SuppressWarnings(value = {"unchecked"})
600+
@Override
601+
public boolean handleStatus(final HttpResponsePacket responsePacket,
602+
final HttpTransactionContext httpTransactionContext,
603+
final FilterChainContext ctx) {
604+
final List<String> proxyAuthHeaders =
605+
listOf(responsePacket.getHeaders()
606+
.values(Header.ProxyAuthenticate));
607+
608+
if (proxyAuthHeaders.isEmpty()) {
609+
throw new IllegalStateException("407 response received, but no "
610+
+ "Proxy-Authenticate header was present");
629611
}
630-
}
631-
632-
private String authorizationHeaderName(boolean proxyInd) {
633-
return proxyInd
634-
? Header.ProxyAuthorization.toString()
635-
: Header.Authorization.toString();
636-
}
637-
638-
private static List<String> listOf(final Iterable<String> values) {
639-
final List<String> list = new ArrayList<String>(2);
640-
for (String value : values) {
641-
list.add(value);
612+
613+
final GrizzlyAsyncHttpProvider provider =
614+
httpTransactionContext.provider;
615+
616+
final ProxyServer proxyServer = httpTransactionContext.getProxyServer();
617+
618+
if (proxyServer == null) {
619+
httpTransactionContext.invocationStatus = InvocationStatus.STOP;
620+
final AsyncHandler ah = httpTransactionContext.getAsyncHandler();
621+
622+
if (ah != null) {
623+
try {
624+
ah.onStatusReceived(
625+
httpTransactionContext.responseStatus);
626+
} catch (Exception e) {
627+
httpTransactionContext.abort(e);
628+
}
629+
}
630+
return true;
642631
}
643632

644-
return list;
645-
}
646-
} // END AuthorizationHandler
633+
final Request req = httpTransactionContext.getAhcRequest();
634+
635+
try {
636+
String ntlmAuthenticate = getNTLM(proxyAuthHeaders);
637+
638+
final Realm newRealm;
639+
if (ntlmAuthenticate != null) {
640+
// NTLM
641+
// Connection-based auth
642+
newRealm = ntlmProxyChallenge(ctx.getConnection(),
643+
ntlmAuthenticate,
644+
req, proxyServer);
645+
} else {
646+
final String firstAuthHeader = proxyAuthHeaders.get(0);
647+
648+
// BASIC or DIGEST
649+
newRealm = proxyServer.realmBuilder()
650+
.setUri(req.getUri())//
651+
.setOmitQuery(true)//
652+
.setMethodName(req.getMethod())//
653+
.setUsePreemptiveAuth(true)//
654+
.parseProxyAuthenticateHeader(firstAuthHeader)//
655+
.build();
656+
}
647657

658+
responsePacket.setSkipRemainder(true); // ignore the remainder of the response
659+
660+
final Connection c;
661+
662+
// @TODO we may want to ditch the keep-alive connection if the response payload is too large
663+
if (responsePacket.getProcessingState().isKeepAlive()) {
664+
// if it's HTTP keep-alive connection - reuse the
665+
// same Grizzly Connection
666+
c = ctx.getConnection();
667+
httpTransactionContext.reuseConnection();
668+
} else {
669+
// if it's not keep-alive - take new Connection from the pool
670+
final ConnectionManager m = provider.getConnectionManager();
671+
c = m.openSync(req);
672+
}
673+
674+
final Request nextRequest = new RequestBuilder(req)
675+
.setRealm(newRealm)
676+
.build();
677+
678+
final HttpTransactionContext newContext
679+
= httpTransactionContext.cloneAndStartTransactionFor(
680+
c, nextRequest);
681+
newContext.invocationStatus = InvocationStatus.STOP;
682+
683+
try {
684+
provider.execute(newContext);
685+
} catch (IOException ioe) {
686+
newContext.abort(ioe);
687+
}
688+
} catch (Exception e) {
689+
httpTransactionContext.abort(e);
690+
}
691+
692+
return false;
693+
}
694+
} // END ProxyAuthorizationHandler
695+
648696
private static final class RedirectHandler implements StatusHandler {
649697

650698
static final RedirectHandler INSTANCE = new RedirectHandler();
@@ -778,5 +826,88 @@ private static Realm getRealm(final HttpTransactionContext httpTransactionContex
778826
? realm
779827
: httpTransactionContext.provider.getClientConfig().getRealm();
780828
}
829+
830+
private static Realm ntlmChallenge(final Connection c,
831+
final String wwwAuth,
832+
final Request request,
833+
final Realm realm,
834+
final boolean proxyInd)
835+
throws NTLMEngineException {
836+
837+
final FluentCaseInsensitiveStringsMap headers = request.getHeaders();
838+
if (wwwAuth.equals("NTLM")) {
839+
// server replied bare NTLM => we didn't preemptively sent Type1Msg
840+
String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg();
841+
842+
addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd);
843+
} else {
844+
// probably receiving Type2Msg, so we issue Type3Msg
845+
addType3NTLMAuthorizationHeader(wwwAuth, headers, realm, proxyInd);
846+
// we mark NTLM as established for the Connection to
847+
// avoid preemptive NTLM
848+
Utils.setNtlmEstablished(c);
849+
}
850+
851+
return new Realm.RealmBuilder().clone(realm)//
852+
.setUri(request.getUri())//
853+
.setMethodName(request.getMethod())//
854+
.build();
855+
}
856+
857+
private static Realm ntlmProxyChallenge(final Connection c,
858+
final String wwwAuth, final Request request,
859+
final ProxyServer proxyServer)
860+
throws NTLMEngineException {
861+
862+
final FluentCaseInsensitiveStringsMap headers = request.getHeaders();
863+
headers.remove(Header.ProxyAuthorization.toString());
864+
865+
Realm realm = proxyServer.realmBuilder()//
866+
.setScheme(AuthScheme.NTLM)//
867+
.setUri(request.getUri())//
868+
.setMethodName(request.getMethod()).build();
869+
870+
addType3NTLMAuthorizationHeader(wwwAuth, headers, realm, true);
871+
// we mark NTLM as established for the Connection to
872+
// avoid preemptive NTLM
873+
Utils.setNtlmEstablished(c);
874+
875+
return realm;
876+
}
877+
878+
private static void addNTLMAuthorizationHeader(
879+
FluentCaseInsensitiveStringsMap headers,
880+
String challengeHeader, boolean proxyInd) {
881+
headers.add(authorizationHeaderName(proxyInd), "NTLM " + challengeHeader);
882+
}
883+
884+
private static void addType3NTLMAuthorizationHeader(String auth,
885+
FluentCaseInsensitiveStringsMap headers, Realm realm,
886+
boolean proxyInd) throws NTLMEngineException {
887+
headers.remove(authorizationHeaderName(proxyInd));
888+
889+
if (isNonEmpty(auth) && auth.startsWith("NTLM ")) {
890+
String serverChallenge = auth.substring("NTLM ".length()).trim();
891+
String challengeHeader = NTLMEngine.INSTANCE.generateType3Msg(
892+
realm.getPrincipal(), realm.getPassword(),
893+
realm.getNtlmDomain(), realm.getNtlmHost(), serverChallenge);
894+
addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd);
895+
}
896+
}
897+
898+
private static String authorizationHeaderName(final boolean proxyInd) {
899+
return proxyInd
900+
? Header.ProxyAuthorization.toString()
901+
: Header.Authorization.toString();
902+
}
903+
904+
private static List<String> listOf(final Iterable<String> values) {
905+
final List<String> list = new ArrayList<String>(2);
906+
for (String value : values) {
907+
list.add(value);
908+
}
909+
910+
return list;
911+
}
781912

782913
} // END AsyncHttpClientEventFilter

src/main/java/com/ning/http/client/providers/grizzly/AsyncHttpClientFilter.java

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import com.ning.http.util.AsyncHttpProviderUtils;
2929
import com.ning.http.util.AuthenticatorUtils;
3030
import com.ning.http.util.MiscUtils;
31-
import com.ning.http.util.ProxyUtils;
3231
import java.io.IOException;
3332
import java.net.URI;
3433
import java.net.URISyntaxException;
@@ -108,49 +107,56 @@ public NextAction handleEvent(final FilterChainContext ctx, final FilterChainEve
108107
}
109108
// ----------------------------------------------------- Private Methods
110109

111-
private boolean sendAsGrizzlyRequest(final HttpTransactionContext httpTxCtx, final FilterChainContext ctx) throws IOException {
110+
private boolean sendAsGrizzlyRequest(final HttpTransactionContext httpTxCtx,
111+
final FilterChainContext ctx) throws IOException {
112+
112113
final Connection connection = ctx.getConnection();
113114

114115
final boolean isUsedConnection = Boolean.TRUE.equals(USED_CONNECTION.get(connection));
115116
if (!isUsedConnection) {
116117
USED_CONNECTION.set(connection, Boolean.TRUE);
117118
}
118-
119119

120120
final Request ahcRequest = httpTxCtx.getAhcRequest();
121-
if (isUpgradeRequest(httpTxCtx.getAsyncHandler()) && isWSRequest(httpTxCtx.requestUri)) {
121+
if (isUpgradeRequest(httpTxCtx.getAsyncHandler()) &&
122+
isWSRequest(httpTxCtx.requestUri)) {
122123
httpTxCtx.isWSRequest = true;
123124
convertToUpgradeRequest(httpTxCtx);
124125
}
125126
final Request req = httpTxCtx.getAhcRequest();
126127
final Method method = Method.valueOf(ahcRequest.getMethod());
127128
final Uri uri = req.getUri();
128129
boolean secure = "https".equals(uri.getScheme());
129-
final ProxyServer proxy = ProxyUtils.getProxyServer(config, ahcRequest);
130+
final ProxyServer proxy = httpTxCtx.getProxyServer();
130131
final boolean useProxy = proxy != null;
131-
final boolean isEstablishingConnectTunnel = useProxy && (secure || httpTxCtx.isWSRequest) && !httpTxCtx.isTunnelEstablished(connection);
132+
final boolean isEstablishingConnectTunnel = useProxy &&
133+
(secure || httpTxCtx.isWSRequest) &&
134+
!httpTxCtx.isTunnelEstablished(connection);
135+
132136
if (isEstablishingConnectTunnel) {
133137
// once the tunnel is established, sendAsGrizzlyRequest will
134138
// be called again and we'll finally send the request over the tunnel
135139
return establishConnectTunnel(proxy, httpTxCtx, uri, ctx);
136140
}
137-
final HttpRequestPacket.Builder builder = HttpRequestPacket.builder().protocol(Protocol.HTTP_1_1).method(method);
141+
final HttpRequestPacket.Builder builder = HttpRequestPacket.builder()
142+
.protocol(Protocol.HTTP_1_1)
143+
.method(method);
138144

139145
if (useProxy && !((secure || httpTxCtx.isWSRequest) &&
140146
config.isUseRelativeURIsWithConnectProxies())) {
141147
builder.uri(uri.toUrl());
142148
} else {
143-
builder.uri(AsyncHttpProviderUtils.getNonEmptyPath(uri));
144-
builder.query(uri.getQuery());
149+
builder.uri(AsyncHttpProviderUtils.getNonEmptyPath(uri))
150+
.query(uri.getQuery());
145151
}
146152

147153
HttpRequestPacket requestPacket;
148154
final PayloadGenerator payloadGenerator = isPayloadAllowed(method) ? PayloadGenFactory.getPayloadGenerator(ahcRequest) : null;
149155
if (payloadGenerator != null) {
150156
final long contentLength = ahcRequest.getContentLength();
151157
if (contentLength >= 0) {
152-
builder.contentLength(contentLength);
153-
builder.chunked(false);
158+
builder.contentLength(contentLength)
159+
.chunked(false);
154160
} else {
155161
builder.chunked(true);
156162
}
@@ -320,7 +326,10 @@ private void addProxyHeaders(
320326
isUsedConnection, isConnect);
321327
}
322328

323-
private void setProxyAuthorizationHeader(final Request req, final HttpRequestPacket requestPacket, final ProxyServer proxy, final Realm realm, final boolean isUsedConnection, final boolean isConnect) throws IOException {
329+
private void setProxyAuthorizationHeader(final Request req,
330+
final HttpRequestPacket requestPacket, final ProxyServer proxy,
331+
final Realm realm, final boolean isUsedConnection,
332+
final boolean isConnect) throws IOException {
324333
final String reqAuth = AuthenticatorUtils.perRequestProxyAuthorizationHeader(
325334
req, realm, proxy, isConnect);
326335

0 commit comments

Comments
 (0)