diff --git a/readme.md b/readme.md index eb5c577..5c8efef 100644 --- a/readme.md +++ b/readme.md @@ -37,6 +37,7 @@ For a hands-on introduction to this library, take a look at the following blog p - [Compression](#compression) - [Cookies](#cookies) - [Redirects](#redirects) + - [Proxies](#proxies) - [Client Side Certificates](#client-side-certificates) - [Sessions](#sessions) - [Why Requests-Scala?](#why-requests-scala) @@ -428,6 +429,36 @@ a linked list of `Response` objects until the earliest response has a value of `None`. You can crawl up this linked list if you want to inspect the headers or other metadata of the intermediate redirects that brought you to your final value. +### Proxies + +Usage of proxies is supported via the `proxy` parameter: +```scala +val proxyHost = "some.proxy.host" +val proxyPort = 8001 + +val r = requests.get( + "https://httpbin.org", + proxy=(proxyHost, proxyPort) +) +``` + +For proxies that requires username/password authentication, you can provide them +via `proxyAuth`: + +```scala +val proxyHost = "some.proxy.host" +val proxyPort = 8001 + +val proxyUsername = "foo" +val proxyPassword = "bar" + +val r = requests.get( + "https://httpbin.org", + proxy=(proxyHost, proxyPort), + proxyAuth=(proxyUsername, proxyPassword) +) +``` + ### Client Side Certificates To use client certificate you need a PKCS 12 archive with private key and certificate. diff --git a/requests/src/requests/Model.scala b/requests/src/requests/Model.scala index 797500c..1a12471 100644 --- a/requests/src/requests/Model.scala +++ b/requests/src/requests/Model.scala @@ -51,6 +51,7 @@ case class Request(url: String, readTimeout: Int = 0, connectTimeout: Int = 0, proxy: (String, Int) = null, + proxyAuth: (String, String) = null, cert: Cert = null, sslContext: SSLContext = null, cookies: Map[String, HttpCookie] = Map(), @@ -234,7 +235,7 @@ case class StreamHeaders(url: String, } /** * Different ways you can authorize a HTTP request; by default, HTTP Basic - * auth and Proxy auth are supported + * and Bearer auth are supported */ trait RequestAuth{ def header: Option[String] @@ -248,9 +249,6 @@ object RequestAuth{ class Basic(username: String, password: String) extends RequestAuth{ def header = Some("Basic " + java.util.Base64.getEncoder.encodeToString((username + ":" + password).getBytes())) } - case class Proxy(username: String, password: String) extends RequestAuth{ - def header = Some("Proxy-Authorization " + java.util.Base64.getEncoder.encodeToString((username + ":" + password).getBytes())) - } case class Bearer(token: String) extends RequestAuth { def header = Some(s"Bearer $token") } diff --git a/requests/src/requests/Requester.scala b/requests/src/requests/Requester.scala index 54f345a..7e8126f 100644 --- a/requests/src/requests/Requester.scala +++ b/requests/src/requests/Requester.scala @@ -15,6 +15,7 @@ trait BaseSession{ def connectTimeout: Int def auth: RequestAuth def proxy: (String, Int) + def proxyAuth: (String, String) def cert: Cert def sslContext: SSLContext def maxRedirects: Int @@ -72,7 +73,8 @@ case class Requester(verb: String, * or MultiPart form data. * @param readTimeout How long to wait for data to be read before timing out * @param connectTimeout How long to wait for a connection before timing out - * @param proxy Host and port of a proxy you want to use + * @param proxy Host, port of HTTP proxy to use + * @param proxyAuth (Username, Password) authentication for HTTP proxy * @param cert Client certificate configuration * @param sslContext Client sslContext configuration * @param cookies Custom cookies to send up with this request @@ -90,6 +92,7 @@ case class Requester(verb: String, readTimeout: Int = sess.readTimeout, connectTimeout: Int = sess.connectTimeout, proxy: (String, Int) = sess.proxy, + proxyAuth: (String, String) = sess.proxyAuth, cert: Cert = sess.cert, sslContext: SSLContext = sess.sslContext, cookies: Map[String, HttpCookie] = Map(), @@ -106,9 +109,9 @@ case class Requester(verb: String, var streamHeaders: StreamHeaders = null val w = stream( url, auth, params, data.headers, headers, data, readTimeout, - connectTimeout, proxy, cert, sslContext, cookies, cookieValues, maxRedirects, - verifySslCerts, autoDecompress, compress, keepAlive, check, chunkedUpload, - onHeadersReceived = sh => streamHeaders = sh + connectTimeout, proxy, proxyAuth, cert, sslContext, cookies, cookieValues, + maxRedirects, verifySslCerts, autoDecompress, compress, keepAlive, check, + chunkedUpload, onHeadersReceived = sh => streamHeaders = sh ) w.writeBytesTo(out) @@ -150,6 +153,7 @@ case class Requester(verb: String, readTimeout: Int = sess.readTimeout, connectTimeout: Int = sess.connectTimeout, proxy: (String, Int) = sess.proxy, + proxyAuth: (String, String) = sess.proxyAuth, cert: Cert = sess.cert, sslContext: SSLContext = sess.sslContext, cookies: Map[String, HttpCookie] = Map(), @@ -230,6 +234,18 @@ case class Requester(verb: String, for((k, v) <- compress.headers) connection.setRequestProperty(k, v) connection.setReadTimeout(readTimeout) + + // Apply proxy auth headers, if any + if (proxy != null && proxyAuth != null) { + // @TODO This should support other auth types (digest, etc.) + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authorization + val (proxyUser, proxyPass) = proxyAuth + connection.setRequestProperty( + "Proxy-Authorization", + "Basic " + java.util.Base64.getEncoder.encodeToString((proxyUser + ":" + proxyPass).getBytes()) + ) + } + auth.header.foreach(connection.setRequestProperty("Authorization", _)) connection.setConnectTimeout(connectTimeout) connection.setUseCaches(false) @@ -309,9 +325,9 @@ case class Requester(verb: String, val newUrl = current.headers("location").head stream( new java.net.URL(url1, newUrl).toString, auth, params, blobHeaders, - headers, data, readTimeout, connectTimeout, proxy, cert, sslContext, cookies, - cookieValues, maxRedirects - 1, verifySslCerts, autoDecompress, - compress, keepAlive, check, chunkedUpload, Some(current), + headers, data, readTimeout, connectTimeout, proxy, proxyAuth, cert, + sslContext, cookies, cookieValues, maxRedirects - 1, verifySslCerts, + autoDecompress, compress, keepAlive, check, chunkedUpload, Some(current), onHeadersReceived ).readBytesThrough(f) }else{ @@ -379,6 +395,7 @@ case class Requester(verb: String, r.readTimeout, r.connectTimeout, r.proxy, + r.proxyAuth, r.cert, r.sslContext, r.cookies, @@ -408,6 +425,7 @@ case class Requester(verb: String, r.readTimeout, r.connectTimeout, r.proxy, + r.proxyAuth, r.cert, r.sslContext, r.cookies, diff --git a/requests/src/requests/Session.scala b/requests/src/requests/Session.scala index e1b4633..c9c602f 100644 --- a/requests/src/requests/Session.scala +++ b/requests/src/requests/Session.scala @@ -16,6 +16,7 @@ import scala.collection.mutable * @param readTimeout How long to wait for data to be read before timing out * @param connectTimeout How long to wait for a connection before timing out * @param proxy Host and port of a proxy you want to use + * @param proxyAuth Username and password of HTTP proxy * @param cookies Custom cookies to send up with this request * @param maxRedirects How many redirects to automatically resolve; defaults to 5. * You can also set it to 0 to prevent Requests from resolving @@ -27,6 +28,7 @@ case class Session(headers: Map[String, String] = BaseSession.defaultHeaders, cookies: mutable.Map[String, HttpCookie] = mutable.LinkedHashMap.empty[String, HttpCookie], auth: RequestAuth = RequestAuth.Empty, proxy: (String, Int) = null, + proxyAuth: (String, String) = null, cert: Cert = null, sslContext: SSLContext = null, persistCookies: Boolean = true, diff --git a/requests/src/requests/package.scala b/requests/src/requests/package.scala index 655f5b0..cc13813 100644 --- a/requests/src/requests/package.scala +++ b/requests/src/requests/package.scala @@ -13,6 +13,8 @@ package object requests extends _root_.requests.BaseSession { def proxy = null + def proxyAuth = null + def cert: Cert = null def sslContext: SSLContext = null