Skip to content

Commit

Permalink
Rework the proxy settings configuration
Browse files Browse the repository at this point in the history
Make several changes in the code to add a new policy for the way we
handle proxy settings in bloop. This new policy hopes to reduce proxy
configuration problems in different clients that run in different
environments in the same machine.
  • Loading branch information
jvican committed Nov 27, 2018
1 parent aafa51b commit 07591f7
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 158 deletions.
2 changes: 2 additions & 0 deletions frontend/src/main/scala/bloop/Cli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ object Cli {
debugFilter
)

// Set the proxy settings right before loading the state of the build
bloop.util.ProxySetup.updateProxySettings(commonOpts.env.toMap, logger)
val currentState = State.loadActiveStateFor(configDirectory, pool, cliOptions.common, logger)

if (Files.exists(configDirectory.underlying)) {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/main/scala/bloop/Server.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package bloop

import java.net.InetAddress

import bloop.util.ProxyEnvironment
import bloop.util.ProxySetup
import com.martiansoftware.nailgun.{Alias, NGContext, NGServer}

import scala.util.Try
Expand All @@ -19,7 +19,7 @@ object Server {
val addr = InetAddress.getLoopbackAddress
val server = new NGServer(addr, port)
registerAliases(server)
ProxyEnvironment.init()
ProxySetup.init()
server
}

Expand Down
1 change: 0 additions & 1 deletion frontend/src/main/scala/bloop/engine/Interpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ object Interpreter {
def execute(action: Action, stateTask: Task[State]): Task[State] = {
def execute(action: Action, stateTask: Task[State], inRecursion: Boolean): Task[State] = {
stateTask.flatMap { state =>
bloop.util.ProxyEnvironment.setJvmProxySettings(state)
action match {
// We keep it case because there is a 'match may not be exhaustive' false positive by scalac
// Looks related to existing bug report https://github.com/scala/bug/issues/10251
Expand Down
152 changes: 0 additions & 152 deletions frontend/src/main/scala/bloop/util/ProxyEnvironment.scala

This file was deleted.

155 changes: 155 additions & 0 deletions frontend/src/main/scala/bloop/util/ProxySetup.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package bloop.util

import java.net.URL
import java.net.MalformedURLException

import bloop.logging.Logger
import monix.execution.atomic.{Atomic, AtomicAny}

import scala.util.Try
import scala.collection.JavaConverters._

object ProxySetup {
private case class ProxySettings(host: String, port: Int, nonProxyHosts: Option[String])

/** Build map of startup proxy settings set via Java System properties in the server. */
private val configuredProxySettings: AtomicAny[Map[Proxy, ProxySettings]] = {
def getJvmProxyProperties(proxy: Proxy): Option[ProxySettings] = {
def sysprop(key: String): Option[String] = Option(System.getProperty(key))
val maybeVarHost = sysprop(proxy.propertyNameHost)
val maybeVarPort = sysprop(proxy.propertyNamePort).flatMap(prop => Try(prop.toInt).toOption)
val maybeNoProxy = proxy.propertyNameNoProxyHosts.flatMap(p => sysprop(p))
maybeVarHost.map { host =>
ProxySettings(host, maybeVarPort.getOrElse(proxy.defaultPort), maybeNoProxy)
}
}

lazy val env = System.getenv().asScala.toMap
Atomic {
Proxy.supportedProxySettings.flatMap { p =>
getJvmProxyProperties(p)
.orElse(getEnvProxyProperties(env, p.envVar, None))
.map(p -> _)
}.toMap
}
}

sealed trait Proxy {
val propertyNameHost: String
val propertyNamePort: String
val propertyNameNoProxyHosts: Option[String]
val defaultPort: Int
val envVar: String
}

object Proxy {
final case object HttpProxy extends Proxy {
val propertyNameHost = "http.proxyHost"
val propertyNamePort = "http.proxyPort"
val propertyNameNoProxyHosts = Some("http.nonProxyHosts")
val defaultPort = 80
val envVar = "http_proxy"
}

final case object FtpProxy extends Proxy {
val propertyNameHost = "ftp.proxyHost"
val propertyNamePort = "ftp.proxyPort"
val propertyNameNoProxyHosts = Some("ftp.nonProxyHosts")
val defaultPort = 80
val envVar = "ftp_proxy"
}

final case object HttpsProxy extends Proxy {
val propertyNameHost = "https.proxyHost"
val propertyNamePort = "https.proxyPort"
val propertyNameNoProxyHosts = None
val defaultPort = 443
val envVar = "https_proxy"
}

final case object SocksProxy extends Proxy {
val propertyNameHost = "socksProxyHost"
val propertyNamePort = "socksProxyHost"
val propertyNameNoProxyHosts = None
val defaultPort = 1080
val envVar = "socks_proxy"
}

val supportedProxySettings: Seq[Proxy] = List(HttpProxy, FtpProxy, HttpsProxy, SocksProxy)
}

/** Forces the initialization of environment variables, called in `Server`. */
def init(): Unit = ()

/**
* Update proxy settings per client environment variables.
*
* A bloop client can change its environment variables to modify the proxy settings
* globally in the bloop server. However, there is no mechanism to clean proxy
* settings. Users that want to do so must shut down the server and start it again.
* This policy is taken to avoid bad interactions with clients that run on different
* environments where proxy settings are not set (e.g. inside editors like emacs/vscode).
*
* The update process affects all the current bloop clients. Unfortunately, there is no
* way to mitigate this behavior because ivy/coursier/etc do not expose interfaces to
* configure the proxy settings directly. If users define different proxy settings in
* different clients, bloop will have an undefined behavior. Note that users are entitled
* to have a mix of clients with no proxy settings and with the same proxy settings,
* in which case bloop will consistently use the proxy settings set by one of the configured
* clients.
*
* Whenever remote compilation is added, the handling of environment variables must
* be disabled as it's unsafe (it's the only scenario where it's legit to have different
* proxy settings).
*
* Ref https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html
*
* @param environment The environment of the bloop client.
* @param logger A logger where we give feedback to the user.
*/
def updateProxySettings(environment: Map[String, String], logger: Logger): Unit = {
Proxy.supportedProxySettings.foreach { proxy =>
val settingsFromEnvironment =
getEnvProxyProperties(environment, proxy.envVar, Some(logger))
settingsFromEnvironment.foreach { proxySettings =>
configuredProxySettings.transform { settingsMap =>
settingsMap.get(proxy) match {
// Do nothing if the settings are the same, otherwise update globally
case Some(setSettings) if proxySettings == setSettings => settingsMap
case None =>
System.setProperty(proxy.propertyNameHost, proxySettings.host)
System.setProperty(proxy.propertyNamePort, proxySettings.port.toString())
proxy.propertyNameNoProxyHosts match {
case None => ()
case Some(k) =>
proxySettings.nonProxyHosts match {
case Some(v) => System.setProperty(k, v)
case None => System.clearProperty(k)
}
}
settingsMap + (proxy -> proxySettings)
}
}
}
}
}

/** Populate proxy settings from the environment variables. */
private def getEnvProxyProperties(
env: Map[String, String],
key: String,
logger: Option[Logger]
): Option[ProxySettings] = {
env.get(key).flatMap { proxyVar =>
try {
val url = new URL(proxyVar)
val maybeNoProxy = env.get("no_proxy").map(_.replace(',', '|'))
Some(ProxySettings(url.getHost(), url.getPort(), maybeNoProxy))
} catch {
case e: MalformedURLException =>
logger.foreach(_.warn(s"Expected valid URI format in proxy value of $proxyVar"))
None
}
}
}
}
21 changes: 18 additions & 3 deletions website/content/faq/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,24 @@ By default the sbt plugin exports only `Compile` and `Test` configurations.
If you want to export other sbt configurations too, please read about
[advanced sbt configuration]({{<relref "docs/sbt.md" >}}).

## Bloop can't download some file
You are maybe behind a proxy server.
Set `http_proxy` and `https_proxy` environments variable, Bool will used them to download needed ressources.
## Configure bloop behind a proxy

Bloop can be configured behind a proxy in two different ways:

1. Run the bloop server with the right proxy settings.
2. Set the proxy settings as environment variables in the terminal where
you invoke a bloop client (such as the command-line application).

You can change the proxy settings as many times as you want, however you
need to keep in mind the following considerations:

1. Changes in the proxy settings of a client affect all bloop clients.
2. It's not possible to clear proxy settings (if the proxy settings are
empty in one of the bloop clients, nothing is done).

To configure bloop behind a proxy, you can set `http_proxy` and
`https_proxy`. If you need a more advanced configuration, consult the
[Oracle Proxy documentation](https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html).

## Is Bloop open source?

Expand Down

0 comments on commit 07591f7

Please sign in to comment.