Asynchronous HTTP client for Play Framework with OAuth, OpenID, and SSL/TLS support
—
Ning AsyncHttpClient-based implementation providing the actual HTTP transport layer with advanced configuration options. This is the default implementation used by Play WS for HTTP operations.
Main Ning-based WSClient implementation.
/**
* WSClient implementation using Ning AsyncHttpClient
* @param config AsyncHttpClient configuration
*/
case class NingWSClient(config: AsyncHttpClientConfig) extends WSClient {
def underlying[T]: T
def url(url: String): WSRequest
def close(): Unit
}
/**
* Ning WSClient factory
*/
object NingWSClient {
/** Create client with default configuration */
def apply(): NingWSClient
/** Create client with custom configuration */
def apply(config: NingWSClientConfig): NingWSClient
}Basic Ning Client Usage:
import play.api.libs.ws.ning._
// Create client with default configuration
val client = NingWSClient()
// Use the client
val response = client.url("https://api.example.com/data").get()
// Always close the client when done
client.close()Ning-specific WSRequest implementation with additional methods.
/**
* Ning-specific WSRequest implementation
*/
case class NingWSRequest(
client: NingWSClient,
url: String,
method: String = "GET",
body: WSBody = EmptyBody,
headers: Map[String, Seq[String]] = Map.empty,
queryString: Map[String, Seq[String]] = Map.empty,
calc: Option[WSSignatureCalculator] = None,
auth: Option[(String, String, WSAuthScheme)] = None,
followRedirects: Option[Boolean] = None,
requestTimeout: Option[Int] = None,
virtualHost: Option[String] = None,
proxyServer: Option[WSProxyServer] = None
) extends WSRequest {
// Additional Ning-specific methods
/** Get current request headers */
def requestHeaders: Map[String, Seq[String]]
/** Get specific request header */
def requestHeader(name: String): Option[String]
/** Get current query parameters */
def requestQueryParams: Map[String, Seq[String]]
/** Get current URL (may include signature parameters) */
def requestUrl: String
/** Get request body as byte array */
def getBody: Option[Array[Byte]]
}Ning-specific WSResponse implementation.
/**
* Ning-specific WSResponse implementation
* @param ahcResponse Underlying AsyncHttpClient response
*/
case class NingWSResponse(ahcResponse: AHCResponse) extends WSResponse {
def status: Int
def statusText: String
def header(key: String): Option[String]
def allHeaders: Map[String, Seq[String]]
def cookies: Seq[WSCookie]
def cookie(name: String): Option[WSCookie]
def body: String
def xml: Elem
def json: JsValue
def bodyAsBytes: Array[Byte]
def underlying[T]: T
}Comprehensive configuration options for the Ning AsyncHttpClient.
/**
* Ning WSClient configuration
*/
case class NingWSClientConfig(
wsClientConfig: WSClientConfig = WSClientConfig(),
allowPoolingConnection: Boolean = true,
allowSslConnectionPool: Boolean = true,
ioThreadMultiplier: Int = 2,
maxConnectionsPerHost: Int = -1,
maxConnectionsTotal: Int = -1,
maxConnectionLifetime: Duration = Duration.Inf,
idleConnectionInPoolTimeout: Duration = 1.minute,
webSocketIdleTimeout: Duration = 15.minutes,
maxNumberOfRedirects: Int = 5,
maxRequestRetry: Int = 5,
disableUrlEncoding: Boolean = false
)Advanced Ning Configuration:
import scala.concurrent.duration._
val ningConfig = NingWSClientConfig(
wsClientConfig = WSClientConfig(
connectionTimeout = 10.seconds,
requestTimeout = 30.seconds,
followRedirects = true
),
allowPoolingConnection = true,
maxConnectionsPerHost = 20,
maxConnectionsTotal = 100,
maxConnectionLifetime = 5.minutes,
idleConnectionInPoolTimeout = 2.minutes,
maxNumberOfRedirects = 3,
maxRequestRetry = 2
)
val client = NingWSClient(ningConfig)Factory for creating Ning configurations from WS configurations.
/**
* Factory for creating Ning configurations
*/
object NingWSClientConfigFactory {
/**
* Create Ning configuration from WSClient configuration
*/
def forClientConfig(config: WSClientConfig): NingWSClientConfig
}Builder for creating AsyncHttpClient configurations.
/**
* Builder for AsyncHttpClient configuration
*/
class NingAsyncHttpClientConfigBuilder(ningConfig: NingWSClientConfig) {
/** Configure the AsyncHttpClient builder */
def configure(): AsyncHttpClientConfig.Builder
/** Build the final configuration */
def build(): AsyncHttpClientConfig
/** Modify the underlying builder */
def modifyUnderlying(
modify: AsyncHttpClientConfig.Builder => AsyncHttpClientConfig.Builder
): NingAsyncHttpClientConfigBuilder
}Custom AsyncHttpClient Configuration:
import com.ning.http.client.AsyncHttpClientConfig
val builder = new NingAsyncHttpClientConfigBuilder(ningConfig)
.modifyUnderlying { ahcBuilder =>
ahcBuilder
.setUserAgent("MyApp/1.0")
.setCompressionEnforced(true)
.setFollowRedirect(true)
}
val ahcConfig = builder.build()
val client = NingWSClient(ahcConfig)Play Framework dependency injection support for Ning WS client.
/**
* Play module for Ning WS dependency injection
*/
class NingWSModule extends Module {
def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]]
}
/**
* Injectable WSAPI implementation using Ning
*/
class NingWSAPI @Inject()(client: WSClient) extends WSAPI {
def client: WSClient
def url(url: String): WSRequest
}
/**
* Components trait for manual dependency injection
*/
trait NingWSComponents {
def wsClient: WSClient
def wsApi: WSAPI
}Manual Dependency Injection Setup:
import play.api.libs.ws.ning._
trait MyApplicationComponents extends NingWSComponents {
// Provide WSClient implementation
lazy val wsClient: WSClient = {
val config = NingWSClientConfig()
NingWSClient(config)
}
// Provide WSAPI implementation
lazy val wsApi: WSAPI = new NingWSAPI(wsClient)
// Don't forget to close client on app shutdown
override def applicationLifecycle: ApplicationLifecycle = {
val lifecycle = super.applicationLifecycle
lifecycle.addStopHook(() => Future.successful(wsClient.close()))
lifecycle
}
}Managing connection pools with Ning client.
// Configuration for connection pooling
val poolConfig = NingWSClientConfig(
allowPoolingConnection = true,
allowSslConnectionPool = true,
maxConnectionsPerHost = 50, // Max connections per host
maxConnectionsTotal = 200, // Max total connections
maxConnectionLifetime = 10.minutes, // Max connection age
idleConnectionInPoolTimeout = 5.minutes // Idle timeout
)
val client = NingWSClient(poolConfig)
// The client will automatically manage the connection pool
// Connections are reused for better performance
val response1 = client.url("https://api.example.com/endpoint1").get()
val response2 = client.url("https://api.example.com/endpoint2").get()
// Both requests can reuse the same connection to api.example.comConfigure automatic request retries for failed requests.
val retryConfig = NingWSClientConfig(
maxRequestRetry = 3, // Retry failed requests up to 3 times
maxNumberOfRedirects = 5 // Follow up to 5 redirects
)
val client = NingWSClient(retryConfig)
// This request will be automatically retried on failure
val response = client.url("https://unreliable-api.com/data").get()Configure WebSocket idle timeout (for when using WS client with WebSockets).
val wsConfig = NingWSClientConfig(
webSocketIdleTimeout = 30.minutes // Close idle WebSocket connections after 30 minutes
)
val client = NingWSClient(wsConfig)Optimize Ning client for different scenarios.
High Throughput Configuration:
val highThroughputConfig = NingWSClientConfig(
wsClientConfig = WSClientConfig(
connectionTimeout = 5.seconds,
requestTimeout = 30.seconds
),
allowPoolingConnection = true,
allowSslConnectionPool = true,
ioThreadMultiplier = 4, // More I/O threads for high concurrency
maxConnectionsPerHost = 100,
maxConnectionsTotal = 500,
maxConnectionLifetime = 30.minutes,
idleConnectionInPoolTimeout = 10.minutes
)Low Latency Configuration:
val lowLatencyConfig = NingWSClientConfig(
wsClientConfig = WSClientConfig(
connectionTimeout = 1.second,
requestTimeout = 5.seconds
),
allowPoolingConnection = true,
maxConnectionsPerHost = 10, // Smaller pool for faster connection reuse
maxConnectionsTotal = 50,
idleConnectionInPoolTimeout = 1.minute, // Shorter idle timeout
maxRequestRetry = 1 // Fewer retries for lower latency
)Resource-Constrained Configuration:
val resourceConstrainedConfig = NingWSClientConfig(
allowPoolingConnection = true,
maxConnectionsPerHost = 5, // Limit connections
maxConnectionsTotal = 20, // Lower total pool size
maxConnectionLifetime = 2.minutes, // Shorter connection lifetime
idleConnectionInPoolTimeout = 30.seconds, // Quick idle cleanup
ioThreadMultiplier = 1 // Fewer threads
)Access underlying Ning objects for advanced processing.
client.url("https://api.example.com/data").get().map { response =>
// Access underlying Ning response
val ningResponse = response.underlying[com.ning.http.client.Response]
// Get additional response information
val responseTime = ningResponse.getResponseTime
val uri = ningResponse.getUri
val contentLength = ningResponse.getContentLength
println(s"Response time: ${responseTime}ms")
println(s"Content length: $contentLength bytes")
}import play.api.libs.ws.ning._
import play.api.libs.ws.ssl._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
// Create production-ready Ning configuration
val productionConfig = NingWSClientConfig(
wsClientConfig = WSClientConfig(
connectionTimeout = 10.seconds,
idleTimeout = 60.seconds,
requestTimeout = 30.seconds,
followRedirects = true,
useProxyProperties = true,
userAgent = Some("MyApp/2.0"),
compressionEnabled = true,
ssl = SSLConfig(
protocol = "TLSv1.2",
enabledProtocols = Some(Seq("TLSv1.2"))
)
),
allowPoolingConnection = true,
allowSslConnectionPool = true,
ioThreadMultiplier = 2,
maxConnectionsPerHost = 20,
maxConnectionsTotal = 100,
maxConnectionLifetime = 10.minutes,
idleConnectionInPoolTimeout = 5.minutes,
maxNumberOfRedirects = 3,
maxRequestRetry = 2,
disableUrlEncoding = false
)
// Create and use the client
val client = NingWSClient(productionConfig)
// Make requests
val future = for {
response1 <- client.url("https://api.service1.com/data").get()
response2 <- client.url("https://api.service2.com/data").get()
} yield {
(response1.json, response2.json)
}
future.map { case (json1, json2) =>
// Process responses
println("Data retrieved successfully")
}.recover { case ex =>
println(s"Request failed: ${ex.getMessage}")
}.andThen { case _ =>
// Always clean up resources
client.close()
}Install with Tessl CLI
npx tessl i tessl/maven-com-typesafe-play--play-ws-2-10