ProxyVole + Butter

When creating desktop applications it is often taken for granted that you can open socket connections and call out to the internet. That is, until you have a corporate client with a locked down computer and an Internet proxy.

This article shows how, using Java's proxy-vole library, you can create middleware for Butter that automatically set proxies from your user's system settings.

Reliably detecting and setting proxy configuration settings in Java is notoriously difficult. Questions such as this and this on Stackoverflow prove that many people struggle with it. Sure, you can invent your own config files and make your user edit the settings in yet another place, but what of Proxy Auto-Config (PAC) files that are becoming increasingly prevalent?

Proxy-Vole

ProxyVole

Thankfully someone else (Bernd Rosstauscher) sought a solution to the problem and created the proxy-vole library. Proxy-vole does all the hard work for you and system proxy settings can be discovered in just a few lines of Java code:

select all
// proxy-vole code
ProxySearch proxySearch = ProxySearch.getDefaultProxySearch();
ProxySelector myProxySelector = proxySearch.getProxySelector();

// standard java proxy code
List<Proxy> proxies = proxySelector.select(new URI("http://foo/bar"));
if (proxies != null)
    for (Proxy proxy : proxies) {
        InetSocketAddress addr = (InetSocketAddress) proxy.address();

        if (addr != null) {
            System.out.println("proxy host : " + addr.getHostName());
            System.out.println("proxy port : " + addr.getPort());
        }
    }

To use proxy-vole in Fantom, you need to download the jar and place it in the %FAN_HOME%\lib\java\etc\ directory.

Butter Middleware

Butter

Butter, as of v1.1, lets you specify a proxy. The proxy Uri just needs to be set in the request stash. It is then picked up and used by the default HttpTerminator.

Converting the above Java code to Fantom and wrapping it up as Butter middleware is a trivial task:

select all
using afButter
using [java] java.lang::System
using [java] java.net::InetSocketAddress
using [java] java.net::Proxy
using [java] java.net::ProxySelector
using [java] java.net::URI
using [java] com.btr.proxy.search::ProxySearch

class ProxyVoleMiddleware : ButterMiddleware {
    ProxySelector?    proxySelector

    new make() {
        // proxySelector is null if the system doesn't use a proxy
        proxySelector = ProxySearch.getDefaultProxySearch.getProxySelector
    }

    override ButterResponse sendRequest(Butter butter, ButterRequest req) {
        if (proxySelector == null)
            return butter.sendRequest(req)

        iter := proxySelector.select(URI(req.url.encode)).iterator
        while (iter.hasNext) {
            proxy := (Proxy) iter.next
            addr  := (InetSocketAddress?) proxy.address

            if (addr != null) {
                proxyUrl := `${req.url.scheme}://${addr.getHostName}:${addr.getPort}`
                req.stash["afButter.proxy"] = proxyUrl
                break
            }
        }

        return butter.sendRequest(req)
    }
}

To use the ProxyVoleMiddleware just ensure it is included in the middleware stack when you create your Butter / ButterDish instance:

middleware := Butter.defaultStack.insert(0, ProxyVoleMiddleware())
butterdish := ButterDish(Butter.churnOut(middleware))

response   := butterdish.get(`http://www.example.org/`)

And that's it!

When you now make any Butter calls, the proxy settings are automatically detected for the URL being called and placed in the request stash for the HttpTerminator to use.

Logging

Sometimes you may wish to debug what proxy-vole is doing. In those cases you can turn on debugging; create a proxy-vole logger class:

select all
using [java] java.lang::Class
using [java] com.btr.proxy.util::Logger
using [java] com.btr.proxy.util::Logger$LogBackEnd as LogBackEnd
using [java] com.btr.proxy.util::Logger$LogLevel as LogLevel

class MyLogger : LogBackEnd {

    static Void enable() {
        Logger.setBackend(MyLogger())
    }

    override Bool isLogginEnabled(LogLevel? logLevel) {
        true
    }

    override Void log(Class? clas, LogLevel? loglevel, Str? msg, Obj?[]? params) {
        echo("$msg + $params")
    }
}

To enable and use, just call the static method and any subsequent calls to proxy-vole should be echoed to the console.

MyLogger.enable()

Discuss