001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.impl.conn;
029
030import java.net.InetSocketAddress;
031import java.net.Proxy;
032import java.net.ProxySelector;
033import java.net.URI;
034import java.net.URISyntaxException;
035import java.util.List;
036
037import org.apache.http.HttpException;
038import org.apache.http.HttpHost;
039import org.apache.http.HttpRequest;
040import org.apache.http.annotation.Contract;
041import org.apache.http.annotation.ThreadingBehavior;
042import org.apache.http.conn.SchemePortResolver;
043import org.apache.http.protocol.HttpContext;
044
045/**
046 * {@link org.apache.http.conn.routing.HttpRoutePlanner} implementation
047 * based on {@link ProxySelector}. By default, this class will pick up
048 * the proxy settings of the JVM, either from system properties
049 * or from the browser running the application.
050 *
051 * @since 4.3
052 */
053@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
054public class SystemDefaultRoutePlanner extends DefaultRoutePlanner {
055
056    private final ProxySelector proxySelector;
057
058    /**
059     * @param proxySelector the proxy selector, or {@code null} for the system default
060     */
061    public SystemDefaultRoutePlanner(
062            final SchemePortResolver schemePortResolver,
063            final ProxySelector proxySelector) {
064        super(schemePortResolver);
065        this.proxySelector = proxySelector;
066    }
067
068    /**
069     * @param proxySelector the proxy selector, or {@code null} for the system default
070     */
071    public SystemDefaultRoutePlanner(final ProxySelector proxySelector) {
072        this(null, proxySelector);
073    }
074
075    @Override
076    protected HttpHost determineProxy(
077            final HttpHost    target,
078            final HttpRequest request,
079            final HttpContext context) throws HttpException {
080        final URI targetURI;
081        try {
082            targetURI = new URI(target.toURI());
083        } catch (final URISyntaxException ex) {
084            throw new HttpException("Cannot convert host to URI: " + target, ex);
085        }
086        ProxySelector proxySelectorInstance = this.proxySelector;
087        if (proxySelectorInstance == null) {
088            proxySelectorInstance = ProxySelector.getDefault();
089        }
090        if (proxySelectorInstance == null) {
091            //The proxy selector can be "unset", so we must be able to deal with a null selector
092            return null;
093        }
094        final List<Proxy> proxies = proxySelectorInstance.select(targetURI);
095        final Proxy p = chooseProxy(proxies);
096        HttpHost result = null;
097        if (p.type() == Proxy.Type.HTTP) {
098            // convert the socket address to an HttpHost
099            if (!(p.address() instanceof InetSocketAddress)) {
100                throw new HttpException("Unable to handle non-Inet proxy address: " + p.address());
101            }
102            final InetSocketAddress isa = (InetSocketAddress) p.address();
103            // assume default scheme (http)
104            result = new HttpHost(getHost(isa), isa.getPort());
105        }
106
107        return result;
108    }
109
110    private String getHost(final InetSocketAddress isa) {
111
112        //@@@ Will this work with literal IPv6 addresses, or do we
113        //@@@ need to wrap these in [] for the string representation?
114        //@@@ Having it in this method at least allows for easy workarounds.
115       return isa.isUnresolved() ?
116            isa.getHostName() : isa.getAddress().getHostAddress();
117
118    }
119
120    private Proxy chooseProxy(final List<Proxy> proxies) {
121        Proxy result = null;
122        // check the list for one we can use
123        for (int i=0; (result == null) && (i < proxies.size()); i++) {
124            final Proxy p = proxies.get(i);
125            switch (p.type()) {
126
127            case DIRECT:
128            case HTTP:
129                result = p;
130                break;
131
132            case SOCKS:
133                // SOCKS hosts are not handled on the route level.
134                // The socket may make use of the SOCKS host though.
135                break;
136            }
137        }
138        if (result == null) {
139            //@@@ log as warning or info that only a socks proxy is available?
140            // result can only be null if all proxies are socks proxies
141            // socks proxies are not handled on the route planning level
142            result = Proxy.NO_PROXY;
143        }
144        return result;
145    }
146
147}