@Override
    public ClassicHttpResponse execute(
            final ClassicHttpRequest userRequest,
            final ExecChain.Scope scope,
            final ExecChain chain) throws IOException, HttpException {
        Args.notNull(userRequest, "HTTP request");
        Args.notNull(scope, "Scope");

        if (Method.CONNECT.isSame(userRequest.getMethod())) {
            throw new ProtocolException("Direct execution of CONNECT is not allowed");
        }

        final String exchangeId = scope.exchangeId;
        final HttpRoute route = scope.route;
        final HttpClientContext context = scope.clientContext;
        final ExecRuntime execRuntime = scope.execRuntime;

        final HttpHost routeTarget = route.getTargetHost();
        final HttpHost proxy = route.getProxyHost();

        try {
            final ClassicHttpRequest request;
            if (proxy != null && !route.isTunnelled()) {
                final ClassicRequestBuilder requestBuilder = ClassicRequestBuilder.copy(userRequest);
                if (requestBuilder.getAuthority() == null) {
                    requestBuilder.setAuthority(new URIAuthority(routeTarget));
                }
                requestBuilder.setAbsoluteRequestUri(true);
                request = requestBuilder.build();
            } else {
                request = userRequest;
            }

            // Ensure the request has a scheme and an authority
            if (request.getScheme() == null) {
                request.setScheme(routeTarget.getSchemeName());
            }
            if (request.getAuthority() == null) {
                request.setAuthority(new URIAuthority(routeTarget));
            }

            final URIAuthority authority = request.getAuthority();
            if (authority.getUserInfo() != null) {
                throw new ProtocolException("Request URI authority contains deprecated userinfo component");
            }

            final HttpHost target = new HttpHost(
                    request.getScheme(),
                    authority.getHostName(),
                    schemePortResolver.resolve(request.getScheme(), authority));
            final String pathPrefix = RequestSupport.extractPathPrefix(request);

            final AuthExchange targetAuthExchange = context.getAuthExchange(target);
            final AuthExchange proxyAuthExchange = proxy != null ? context.getAuthExchange(proxy) : new AuthExchange();

            if (!targetAuthExchange.isConnectionBased() &&
                    targetAuthExchange.getPathPrefix() != null &&
                    !pathPrefix.startsWith(targetAuthExchange.getPathPrefix())) {
                // force re-authentication if the current path prefix does not match
                // that of the previous authentication exchange.
                targetAuthExchange.reset();
            }
            if (targetAuthExchange.getPathPrefix() == null) {
                targetAuthExchange.setPathPrefix(pathPrefix);
            }

            if (authCacheKeeper != null) {
                authCacheKeeper.loadPreemptively(target, pathPrefix, targetAuthExchange, context);
                if (proxy != null) {
                    authCacheKeeper.loadPreemptively(proxy, null, proxyAuthExchange, context);
                }
            }

            RequestEntityProxy.enhance(request);

            for (;;) {

                if (!request.containsHeader(HttpHeaders.AUTHORIZATION)) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{} target auth state: {}", exchangeId, targetAuthExchange.getState());
                    }
                    authenticator.addAuthResponse(target, ChallengeType.TARGET, request, targetAuthExchange, context);
                }
                if (!request.containsHeader(HttpHeaders.PROXY_AUTHORIZATION) && !route.isTunnelled()) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{} proxy auth state: {}", exchangeId, proxyAuthExchange.getState());
                    }
                    authenticator.addAuthResponse(proxy, ChallengeType.PROXY, request, proxyAuthExchange, context);
                }

                // This is where the actual network communication happens (eventually)
                final ClassicHttpResponse response = chain.proceed(request, scope);

                if (Method.TRACE.isSame(request.getMethod())) {
                    // Do not perform authentication for TRACE request
                    ResponseEntityProxy.enhance(response, execRuntime);
                    return response;
                }
                final HttpEntity requestEntity = request.getEntity();
                if (requestEntity != null && !requestEntity.isRepeatable()) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{} Cannot retry non-repeatable request", exchangeId);
                    }
                    ResponseEntityProxy.enhance(response, execRuntime);
                    return response;
                }
                if (needAuthentication(
                        targetAuthExchange,
                        proxyAuthExchange,
                        proxy != null ? proxy : target,
                        target,
                        pathPrefix,
                        response,
                        context)) {
                    // Make sure the response body is fully consumed, if present
                    final HttpEntity responseEntity = response.getEntity();
                    if (execRuntime.isConnectionReusable()) {
                        EntityUtils.consume(responseEntity);
                    } else {
                        execRuntime.disconnectEndpoint();
                        if (proxyAuthExchange.getState() == AuthExchange.State.SUCCESS
                                && proxyAuthExchange.isConnectionBased()) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("{} resetting proxy auth state", exchangeId);
                            }
                            proxyAuthExchange.reset();
                        }
                        if (targetAuthExchange.getState() == AuthExchange.State.SUCCESS
                                && targetAuthExchange.isConnectionBased()) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("{} resetting target auth state", exchangeId);
                            }
                            targetAuthExchange.reset();
                        }
                    }
                    // Reset request headers
                    final ClassicHttpRequest original = scope.originalRequest;
                    request.setHeaders();
                    for (final Iterator<Header> it = original.headerIterator(); it.hasNext(); ) {
                        request.addHeader(it.next());
                    }
                } else {
                    ResponseEntityProxy.enhance(response, execRuntime);
                    return response;
                }
            }
        } catch (final HttpException ex) {
            execRuntime.discardEndpoint();
            throw ex;
        } catch (final RuntimeException | IOException ex) {
            execRuntime.discardEndpoint();
            for (final AuthExchange authExchange : context.getAuthExchanges().values()) {
                if (authExchange.isConnectionBased()) {
                    authExchange.reset();
                }
            }
            throw ex;
        }
    }
Request execution handler in the classic request execution chain
that is responsible for implementation of HTTP specification requirements.
<p>
Further responsibilities such as communication with the opposite
endpoint is delegated to the next executor in the request execution
chain.
</p>

@since 4.3
httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java