1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-14 07:22:20 +03:00

MockWebServer wrapper (#6326)

This is step 1 of a plan to break MockWebServer's dependency on JUnit 4.

1. New module, mockwebserverwrapper: implement almost all of the mockwebserver
   API by delegating to the real mockwebserver API.

2. Swap the names. Rename the package okhttp3.mockwebserver to mockwebserver3,
   and rename okhttp3.mockwebserverwrapper to okhttp3.mockwebserver. With this
   rename all users of MockWebServer are now using the wrapper instead.

3. Change mockwebserver3 to not depend on JUnit 4.

4. Migrate callers from the okhttp3.mockwebserver wrapper to mockwebserver3,
   dropping their JUnit dependency along the way. It may take a very long time
   for callers to migrate. That's okay; the wrapper isn't much code to maintain.

Notable breaks in binary compatibility:

 - Duplex is not supported in the wrapper. This was never intended as public
   API.
 - QueueDispatcher cannot be subclassed in the wrapper. Supporting this is
   rather annoying because we need to support reverse wrapping and unwrapping.
   I think it's not worth the effort.
This commit is contained in:
Jesse Wilson
2020-10-18 07:02:17 -04:00
committed by GitHub
parent 190e071283
commit 02ac2f38e8
11 changed files with 1524 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
jar {
manifest {
attributes('Automatic-Module-Name': 'okhttp3.mockwebserverwrapper')
}
}
dependencies {
api project(':okhttp')
api project(':mockwebserver')
api deps.junit
testImplementation project(':okhttp-testing-support')
testImplementation project(':okhttp-tls')
testImplementation deps.assertj
}
afterEvaluate { project ->
project.tasks.dokka {
outputDirectory = "$rootDir/docs/4.x"
outputFormat = 'gfm'
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.mockwebserverwrapper
abstract class Dispatcher {
@Throws(InterruptedException::class)
abstract fun dispatch(request: RecordedRequest): MockResponse
open fun peek(): MockResponse {
return MockResponse().apply { this.socketPolicy = SocketPolicy.KEEP_OPEN }
}
open fun shutdown() {}
}

View File

@@ -0,0 +1,248 @@
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.mockwebserverwrapper
import okhttp3.Headers
import okhttp3.WebSocketListener
import okhttp3.internal.addHeaderLenient
import okhttp3.internal.http2.Settings
import okio.Buffer
import java.util.concurrent.TimeUnit
class MockResponse : Cloneable {
@set:JvmName("status")
var status: String = ""
private var headersBuilder = Headers.Builder()
private var trailersBuilder = Headers.Builder()
@set:JvmName("headers")
var headers: Headers
get() = headersBuilder.build()
set(value) {
this.headersBuilder = value.newBuilder()
}
@set:JvmName("trailers")
var trailers: Headers
get() = trailersBuilder.build()
set(value) {
this.trailersBuilder = value.newBuilder()
}
private var body: Buffer? = null
var throttleBytesPerPeriod = Long.MAX_VALUE
private set
private var throttlePeriodAmount = 1L
private var throttlePeriodUnit = TimeUnit.SECONDS
@set:JvmName("socketPolicy")
var socketPolicy = SocketPolicy.KEEP_OPEN
@set:JvmName("http2ErrorCode")
var http2ErrorCode = -1
private var bodyDelayAmount = 0L
private var bodyDelayUnit = TimeUnit.MILLISECONDS
private var headersDelayAmount = 0L
private var headersDelayUnit = TimeUnit.MILLISECONDS
private var promises = mutableListOf<PushPromise>()
var settings: Settings = Settings()
private set
var webSocketListener: WebSocketListener? = null
private set
val pushPromises: List<PushPromise>
get() = promises
init {
setResponseCode(200)
setHeader("Content-Length", 0L)
}
public override fun clone(): MockResponse {
val result = super.clone() as MockResponse
result.headersBuilder = headersBuilder.build().newBuilder()
result.promises = promises.toMutableList()
return result
}
@JvmName("-deprecated_getStatus")
@Deprecated(
message = "moved to var",
replaceWith = ReplaceWith(expression = "status"),
level = DeprecationLevel.ERROR)
fun getStatus(): String = status
fun setStatus(status: String) = apply {
this.status = status
}
fun setResponseCode(code: Int): MockResponse {
val reason = when (code) {
in 100..199 -> "Informational"
in 200..299 -> "OK"
in 300..399 -> "Redirection"
in 400..499 -> "Client Error"
in 500..599 -> "Server Error"
else -> "Mock Response"
}
return apply { status = "HTTP/1.1 $code $reason" }
}
fun clearHeaders() = apply {
headersBuilder = Headers.Builder()
}
fun addHeader(header: String) = apply {
headersBuilder.add(header)
}
fun addHeader(name: String, value: Any) = apply {
headersBuilder.add(name, value.toString())
}
fun addHeaderLenient(name: String, value: Any) = apply {
addHeaderLenient(headersBuilder, name, value.toString())
}
fun setHeader(name: String, value: Any) = apply {
removeHeader(name)
addHeader(name, value)
}
fun removeHeader(name: String) = apply {
headersBuilder.removeAll(name)
}
fun getBody(): Buffer? = body?.clone()
fun setBody(body: Buffer) = apply {
setHeader("Content-Length", body.size)
this.body = body.clone() // Defensive copy.
}
fun setBody(body: String): MockResponse = setBody(Buffer().writeUtf8(body))
fun setChunkedBody(body: Buffer, maxChunkSize: Int) = apply {
removeHeader("Content-Length")
headersBuilder.add(CHUNKED_BODY_HEADER)
val bytesOut = Buffer()
while (!body.exhausted()) {
val chunkSize = minOf(body.size, maxChunkSize.toLong())
bytesOut.writeHexadecimalUnsignedLong(chunkSize)
bytesOut.writeUtf8("\r\n")
bytesOut.write(body, chunkSize)
bytesOut.writeUtf8("\r\n")
}
bytesOut.writeUtf8("0\r\n") // Last chunk. Trailers follow!
this.body = bytesOut
}
fun setChunkedBody(body: String, maxChunkSize: Int): MockResponse =
setChunkedBody(Buffer().writeUtf8(body), maxChunkSize)
@JvmName("-deprecated_getHeaders")
@Deprecated(
message = "moved to var",
replaceWith = ReplaceWith(expression = "headers"),
level = DeprecationLevel.ERROR)
fun getHeaders(): Headers = headers
fun setHeaders(headers: Headers) = apply { this.headers = headers }
@JvmName("-deprecated_getTrailers")
@Deprecated(
message = "moved to var",
replaceWith = ReplaceWith(expression = "trailers"),
level = DeprecationLevel.ERROR)
fun getTrailers(): Headers = trailers
fun setTrailers(trailers: Headers) = apply { this.trailers = trailers }
@JvmName("-deprecated_getSocketPolicy")
@Deprecated(
message = "moved to var",
replaceWith = ReplaceWith(expression = "socketPolicy"),
level = DeprecationLevel.ERROR)
fun getSocketPolicy() = socketPolicy
fun setSocketPolicy(socketPolicy: SocketPolicy) = apply {
this.socketPolicy = socketPolicy
}
@JvmName("-deprecated_getHttp2ErrorCode")
@Deprecated(
message = "moved to var",
replaceWith = ReplaceWith(expression = "http2ErrorCode"),
level = DeprecationLevel.ERROR)
fun getHttp2ErrorCode() = http2ErrorCode
fun setHttp2ErrorCode(http2ErrorCode: Int) = apply {
this.http2ErrorCode = http2ErrorCode
}
fun throttleBody(bytesPerPeriod: Long, period: Long, unit: TimeUnit) = apply {
throttleBytesPerPeriod = bytesPerPeriod
throttlePeriodAmount = period
throttlePeriodUnit = unit
}
fun getThrottlePeriod(unit: TimeUnit): Long =
unit.convert(throttlePeriodAmount, throttlePeriodUnit)
fun setBodyDelay(delay: Long, unit: TimeUnit) = apply {
bodyDelayAmount = delay
bodyDelayUnit = unit
}
fun getBodyDelay(unit: TimeUnit): Long =
unit.convert(bodyDelayAmount, bodyDelayUnit)
fun setHeadersDelay(delay: Long, unit: TimeUnit) = apply {
headersDelayAmount = delay
headersDelayUnit = unit
}
fun getHeadersDelay(unit: TimeUnit): Long =
unit.convert(headersDelayAmount, headersDelayUnit)
fun withPush(promise: PushPromise) = apply {
promises.add(promise)
}
fun withSettings(settings: Settings) = apply {
this.settings = settings
}
fun withWebSocketUpgrade(listener: WebSocketListener) = apply {
status = "HTTP/1.1 101 Switching Protocols"
setHeader("Connection", "Upgrade")
setHeader("Upgrade", "websocket")
body = null
webSocketListener = listener
}
override fun toString() = status
companion object {
private const val CHUNKED_BODY_HEADER = "Transfer-encoding: chunked"
}
}

View File

@@ -0,0 +1,218 @@
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.mockwebserverwrapper
import okhttp3.HttpUrl
import okhttp3.Protocol
import org.junit.rules.ExternalResource
import java.io.Closeable
import java.io.IOException
import java.net.InetAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit
import java.util.logging.Level
import java.util.logging.Logger
import javax.net.ServerSocketFactory
import javax.net.ssl.SSLSocketFactory
class MockWebServer : ExternalResource(), Closeable {
val delegate = okhttp3.mockwebserver.MockWebServer()
val requestCount: Int by delegate::requestCount
var bodyLimit: Long by delegate::bodyLimit
var serverSocketFactory: ServerSocketFactory? by delegate::serverSocketFactory
var dispatcher: Dispatcher = QueueDispatcher()
set(value) {
field = value
delegate.dispatcher = value.wrap()
}
val port: Int
get() {
before() // This implicitly starts the delegate.
return delegate.port
}
val hostName: String
get() {
before() // This implicitly starts the delegate.
return delegate.hostName
}
var protocolNegotiationEnabled: Boolean by delegate::protocolNegotiationEnabled
@get:JvmName("protocols") var protocols: List<Protocol>
get() = delegate.protocols
set(value) {
delegate.protocols = value
}
init {
delegate.dispatcher = dispatcher.wrap()
}
private var started: Boolean = false
@Synchronized override fun before() {
if (started) return
try {
start()
} catch (e: IOException) {
throw RuntimeException(e)
}
}
@JvmName("-deprecated_port")
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "port"),
level = DeprecationLevel.ERROR)
fun getPort(): Int = port
fun toProxyAddress(): Proxy {
before() // This implicitly starts the delegate.
return delegate.toProxyAddress()
}
@JvmName("-deprecated_serverSocketFactory")
@Deprecated(
message = "moved to var",
replaceWith = ReplaceWith(
expression = "run { this.serverSocketFactory = serverSocketFactory }"
),
level = DeprecationLevel.ERROR)
fun setServerSocketFactory(serverSocketFactory: ServerSocketFactory) {
delegate.serverSocketFactory = serverSocketFactory
}
fun url(path: String): HttpUrl {
before() // This implicitly starts the delegate.
return delegate.url(path)
}
@JvmName("-deprecated_bodyLimit")
@Deprecated(
message = "moved to var",
replaceWith = ReplaceWith(
expression = "run { this.bodyLimit = bodyLimit }"
),
level = DeprecationLevel.ERROR)
fun setBodyLimit(bodyLimit: Long) {
delegate.bodyLimit = bodyLimit
}
@JvmName("-deprecated_protocolNegotiationEnabled")
@Deprecated(
message = "moved to var",
replaceWith = ReplaceWith(
expression = "run { this.protocolNegotiationEnabled = protocolNegotiationEnabled }"
),
level = DeprecationLevel.ERROR)
fun setProtocolNegotiationEnabled(protocolNegotiationEnabled: Boolean) {
delegate.protocolNegotiationEnabled = protocolNegotiationEnabled
}
@JvmName("-deprecated_protocols")
@Deprecated(
message = "moved to var",
replaceWith = ReplaceWith(expression = "run { this.protocols = protocols }"),
level = DeprecationLevel.ERROR)
fun setProtocols(protocols: List<Protocol>) {
delegate.protocols = protocols
}
@JvmName("-deprecated_protocols")
@Deprecated(
message = "moved to var",
replaceWith = ReplaceWith(expression = "protocols"),
level = DeprecationLevel.ERROR)
fun protocols(): List<Protocol> = delegate.protocols
fun useHttps(sslSocketFactory: SSLSocketFactory, tunnelProxy: Boolean) {
delegate.useHttps(sslSocketFactory, tunnelProxy)
}
fun noClientAuth() {
delegate.noClientAuth()
}
fun requestClientAuth() {
delegate.requestClientAuth()
}
fun requireClientAuth() {
delegate.requireClientAuth()
}
@Throws(InterruptedException::class)
fun takeRequest(): RecordedRequest {
return delegate.takeRequest().unwrap()
}
@Throws(InterruptedException::class)
fun takeRequest(timeout: Long, unit: TimeUnit): RecordedRequest? {
return delegate.takeRequest(timeout, unit)?.unwrap()
}
@JvmName("-deprecated_requestCount")
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "requestCount"),
level = DeprecationLevel.ERROR)
fun getRequestCount(): Int = delegate.requestCount
fun enqueue(response: MockResponse) {
delegate.enqueue(response.wrap())
}
@Throws(IOException::class)
@JvmOverloads fun start(port: Int = 0) {
started = true
delegate.start(port)
}
@Throws(IOException::class)
fun start(inetAddress: InetAddress, port: Int) {
started = true
delegate.start(inetAddress, port)
}
@Synchronized
@Throws(IOException::class)
fun shutdown() {
delegate.shutdown()
}
@Synchronized override fun after() {
try {
shutdown()
} catch (e: IOException) {
logger.log(Level.WARNING, "MockWebServer shutdown failed", e)
}
}
override fun toString(): String = delegate.toString()
@Throws(IOException::class)
override fun close() = delegate.close()
companion object {
private val logger = Logger.getLogger(MockWebServer::class.java.name)
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.mockwebserverwrapper
import okhttp3.Headers
class PushPromise(
@get:JvmName("method") val method: String,
@get:JvmName("path") val path: String,
@get:JvmName("headers") val headers: Headers,
@get:JvmName("response") val response: MockResponse
) {
@JvmName("-deprecated_method")
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "method"),
level = DeprecationLevel.ERROR)
fun method() = method
@JvmName("-deprecated_path")
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "path"),
level = DeprecationLevel.ERROR)
fun path() = path
@JvmName("-deprecated_headers")
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "headers"),
level = DeprecationLevel.ERROR)
fun headers() = headers
@JvmName("-deprecated_response")
@Deprecated(
message = "moved to val",
replaceWith = ReplaceWith(expression = "response"),
level = DeprecationLevel.ERROR)
fun response() = response
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.mockwebserverwrapper
class QueueDispatcher : Dispatcher() {
internal val delegate = okhttp3.mockwebserver.QueueDispatcher()
@Throws(InterruptedException::class)
override fun dispatch(request: RecordedRequest): MockResponse {
throw UnsupportedOperationException("unexpected call")
}
override fun peek(): MockResponse {
throw UnsupportedOperationException("unexpected call")
}
fun enqueueResponse(response: MockResponse) {
delegate.enqueueResponse(response.wrap())
}
override fun shutdown() {
delegate.shutdown()
}
fun setFailFast(failFast: Boolean) {
delegate.setFailFast(failFast)
}
fun setFailFast(failFastResponse: MockResponse?) {
delegate.setFailFast(failFastResponse?.wrap())
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.mockwebserverwrapper
import okhttp3.Handshake
import okhttp3.Handshake.Companion.handshake
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.TlsVersion
import okio.Buffer
import java.io.IOException
import java.net.Inet6Address
import java.net.Socket
import javax.net.ssl.SSLSocket
class RecordedRequest {
val requestLine: String
val headers: Headers
val chunkSizes: List<Int>
val bodySize: Long
val body: Buffer
val sequenceNumber: Int
val failure: IOException?
val method: String?
val path: String?
val handshake: Handshake?
val requestUrl: HttpUrl?
@get:JvmName("-deprecated_utf8Body")
@Deprecated(
message = "Use body.readUtf8()",
replaceWith = ReplaceWith("body.readUtf8()"),
level = DeprecationLevel.ERROR)
val utf8Body: String
get() = body.readUtf8()
val tlsVersion: TlsVersion?
get() = handshake?.tlsVersion
internal constructor(
requestLine: String,
headers: Headers,
chunkSizes: List<Int>,
bodySize: Long,
body: Buffer,
sequenceNumber: Int,
failure: IOException?,
method: String?,
path: String?,
handshake: Handshake?,
requestUrl: HttpUrl?
) {
this.requestLine = requestLine
this.headers = headers
this.chunkSizes = chunkSizes
this.bodySize = bodySize
this.body = body
this.sequenceNumber = sequenceNumber
this.failure = failure
this.method = method
this.path = path
this.handshake = handshake
this.requestUrl = requestUrl
}
@JvmOverloads
constructor(
requestLine: String,
headers: Headers,
chunkSizes: List<Int>,
bodySize: Long,
body: Buffer,
sequenceNumber: Int,
socket: Socket,
failure: IOException? = null
) {
this.requestLine = requestLine;
this.headers = headers
this.chunkSizes = chunkSizes
this.bodySize = bodySize
this.body = body
this.sequenceNumber = sequenceNumber
this.failure = failure
if (socket is SSLSocket) {
try {
this.handshake = socket.session.handshake()
} catch (e: IOException) {
throw IllegalArgumentException(e)
}
} else {
this.handshake = null
}
if (requestLine.isNotEmpty()) {
val methodEnd = requestLine.indexOf(' ')
val pathEnd = requestLine.indexOf(' ', methodEnd + 1)
this.method = requestLine.substring(0, methodEnd)
var path = requestLine.substring(methodEnd + 1, pathEnd)
if (!path.startsWith("/")) {
path = "/"
}
this.path = path
val scheme = if (socket is SSLSocket) "https" else "http"
val inetAddress = socket.localAddress
var hostname = inetAddress.hostName
if (inetAddress is Inet6Address && hostname.contains(':')) {
// hostname is likely some form representing the IPv6 bytes
// 2001:0db8:85a3:0000:0000:8a2e:0370:7334
// 2001:db8:85a3::8a2e:370:7334
// ::1
hostname = "[$hostname]"
}
val localPort = socket.localPort
// Allow null in failure case to allow for testing bad requests
this.requestUrl = "$scheme://$hostname:$localPort$path".toHttpUrlOrNull()
} else {
this.requestUrl = null
this.method = null
this.path = null
}
}
@Deprecated(
message = "Use body.readUtf8()",
replaceWith = ReplaceWith("body.readUtf8()"),
level = DeprecationLevel.WARNING)
fun getUtf8Body(): String = body.readUtf8()
fun getHeader(name: String): String? = headers.values(name).firstOrNull()
override fun toString(): String = requestLine
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.mockwebserverwrapper
enum class SocketPolicy {
SHUTDOWN_SERVER_AFTER_RESPONSE,
KEEP_OPEN,
DISCONNECT_AT_END,
UPGRADE_TO_SSL_AT_END,
DISCONNECT_AT_START,
DISCONNECT_AFTER_REQUEST,
DISCONNECT_DURING_REQUEST_BODY,
DISCONNECT_DURING_RESPONSE_BODY,
DO_NOT_READ_REQUEST_BODY,
FAIL_HANDSHAKE,
SHUTDOWN_INPUT_AT_END,
SHUTDOWN_OUTPUT_AT_END,
STALL_SOCKET_AT_START,
NO_RESPONSE,
RESET_STREAM_AT_START,
EXPECT_CONTINUE,
CONTINUE_ALWAYS
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.mockwebserverwrapper
import java.util.concurrent.TimeUnit.MILLISECONDS
internal fun Dispatcher.wrap(): okhttp3.mockwebserver.Dispatcher {
if (this is QueueDispatcher) return this.delegate
val delegate = this
return object : okhttp3.mockwebserver.Dispatcher() {
override fun dispatch(
request: okhttp3.mockwebserver.RecordedRequest
): okhttp3.mockwebserver.MockResponse {
return delegate.dispatch(request.unwrap()).wrap()
}
override fun peek(): okhttp3.mockwebserver.MockResponse {
return delegate.peek().wrap()
}
override fun shutdown() {
delegate.shutdown()
}
}
}
internal fun MockResponse.wrap(): okhttp3.mockwebserver.MockResponse {
val result = okhttp3.mockwebserver.MockResponse()
val copyFromWebSocketListener = webSocketListener
if (copyFromWebSocketListener != null) {
result.withWebSocketUpgrade(copyFromWebSocketListener)
}
val body = getBody()
if (body != null) result.setBody(body)
for (pushPromise in pushPromises) {
result.withPush(pushPromise.wrap())
}
result.withSettings(settings)
result.status = status
result.headers = headers
result.trailers = trailers
result.socketPolicy = socketPolicy.wrap()
result.http2ErrorCode = http2ErrorCode
result.throttleBody(throttleBytesPerPeriod, getThrottlePeriod(MILLISECONDS), MILLISECONDS)
result.setBodyDelay(getBodyDelay(MILLISECONDS), MILLISECONDS)
result.setHeadersDelay(getHeadersDelay(MILLISECONDS), MILLISECONDS)
return result
}
private fun PushPromise.wrap(): okhttp3.mockwebserver.PushPromise {
return okhttp3.mockwebserver.PushPromise(
method = method,
path = path,
headers = headers,
response = response.wrap()
)
}
internal fun okhttp3.mockwebserver.RecordedRequest.unwrap(): RecordedRequest {
return RecordedRequest(
requestLine = requestLine,
headers = headers,
chunkSizes = chunkSizes,
bodySize = bodySize,
body = body,
sequenceNumber = sequenceNumber,
failure = failure,
method = method,
path = path,
handshake = handshake,
requestUrl = requestUrl
)
}
private fun SocketPolicy.wrap(): okhttp3.mockwebserver.SocketPolicy {
return okhttp3.mockwebserver.SocketPolicy.valueOf(name)
}

View File

@@ -0,0 +1,629 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.mockwebserverwrapper;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.HttpsURLConnection;
import okhttp3.Handshake;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.Protocol;
import okhttp3.RecordingHostnameVerifier;
import okhttp3.TestUtil;
import okhttp3.testing.PlatformRule;
import okhttp3.tls.HandshakeCertificates;
import okhttp3.tls.HeldCertificate;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.asList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static okhttp3.tls.internal.TlsUtil.localhost;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@SuppressWarnings({"ArraysAsListWithZeroOrOneArgument", "deprecation"})
public final class MockWebServerTest {
@Rule public PlatformRule platform = new PlatformRule();
@Rule public final MockWebServer server = new MockWebServer();
@Rule public Timeout globalTimeout = Timeout.seconds(30);
@Before public void checkPlatforms() {
platform.assumeNotBouncyCastle();
}
@Test public void defaultMockResponse() {
MockResponse response = new MockResponse();
assertThat(headersToList(response)).containsExactly("Content-Length: 0");
assertThat(response.getStatus()).isEqualTo("HTTP/1.1 200 OK");
}
@Test public void setResponseMockReason() {
String[] reasons = {
"Mock Response",
"Informational",
"OK",
"Redirection",
"Client Error",
"Server Error",
"Mock Response"
};
for (int i = 0; i < 600; i++) {
MockResponse response = new MockResponse().setResponseCode(i);
String expectedReason = reasons[i / 100];
assertThat(response.getStatus()).isEqualTo(("HTTP/1.1 " + i + " " + expectedReason));
assertThat(headersToList(response)).containsExactly("Content-Length: 0");
}
}
@Test public void setStatusControlsWholeStatusLine() {
MockResponse response = new MockResponse().setStatus("HTTP/1.1 202 That'll do pig");
assertThat(headersToList(response)).containsExactly("Content-Length: 0");
assertThat(response.getStatus()).isEqualTo("HTTP/1.1 202 That'll do pig");
}
@Test public void setBodyAdjustsHeaders() throws IOException {
MockResponse response = new MockResponse().setBody("ABC");
assertThat(headersToList(response)).containsExactly("Content-Length: 3");
assertThat(response.getBody().readUtf8()).isEqualTo("ABC");
}
@Test public void mockResponseAddHeader() {
MockResponse response = new MockResponse()
.clearHeaders()
.addHeader("Cookie: s=square")
.addHeader("Cookie", "a=android");
assertThat(headersToList(response)).containsExactly("Cookie: s=square", "Cookie: a=android");
}
@Test public void mockResponseSetHeader() {
MockResponse response = new MockResponse()
.clearHeaders()
.addHeader("Cookie: s=square")
.addHeader("Cookie: a=android")
.addHeader("Cookies: delicious");
response.setHeader("cookie", "r=robot");
assertThat(headersToList(response)).containsExactly("Cookies: delicious", "cookie: r=robot");
}
@Test public void mockResponseSetHeaders() {
MockResponse response = new MockResponse()
.clearHeaders()
.addHeader("Cookie: s=square")
.addHeader("Cookies: delicious");
response.setHeaders(new Headers.Builder().add("Cookie", "a=android").build());
assertThat(headersToList(response)).containsExactly("Cookie: a=android");
}
@Test public void regularResponse() throws Exception {
server.enqueue(new MockResponse().setBody("hello world"));
URL url = server.url("/").url();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Accept-Language", "en-US");
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in, UTF_8));
assertThat(connection.getResponseCode()).isEqualTo(HttpURLConnection.HTTP_OK);
assertThat(reader.readLine()).isEqualTo("hello world");
RecordedRequest request = server.takeRequest();
assertThat(request.getRequestLine()).isEqualTo("GET / HTTP/1.1");
assertThat(request.getHeader("Accept-Language")).isEqualTo("en-US");
// Server has no more requests.
assertThat(server.takeRequest(100, MILLISECONDS)).isNull();
}
@Test public void redirect() throws Exception {
server.enqueue(new MockResponse()
.setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
.addHeader("Location: " + server.url("/new-path"))
.setBody("This page has moved!"));
server.enqueue(new MockResponse().setBody("This is the new location!"));
URLConnection connection = server.url("/").url().openConnection();
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in, UTF_8));
assertThat(reader.readLine()).isEqualTo("This is the new location!");
RecordedRequest first = server.takeRequest();
assertThat(first.getRequestLine()).isEqualTo("GET / HTTP/1.1");
RecordedRequest redirect = server.takeRequest();
assertThat(redirect.getRequestLine()).isEqualTo("GET /new-path HTTP/1.1");
}
/**
* Test that MockWebServer blocks for a call to enqueue() if a request is made before a mock
* response is ready.
*/
@Test public void dispatchBlocksWaitingForEnqueue() throws Exception {
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
server.enqueue(new MockResponse().setBody("enqueued in the background"));
}).start();
URLConnection connection = server.url("/").url().openConnection();
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in, UTF_8));
assertThat(reader.readLine()).isEqualTo("enqueued in the background");
}
@Test public void nonHexadecimalChunkSize() throws Exception {
server.enqueue(new MockResponse()
.setBody("G\r\nxxxxxxxxxxxxxxxx\r\n0\r\n\r\n")
.clearHeaders()
.addHeader("Transfer-encoding: chunked"));
URLConnection connection = server.url("/").url().openConnection();
InputStream in = connection.getInputStream();
try {
in.read();
fail();
} catch (IOException expected) {
}
}
@Test public void responseTimeout() throws Exception {
server.enqueue(new MockResponse()
.setBody("ABC")
.clearHeaders()
.addHeader("Content-Length: 4"));
server.enqueue(new MockResponse().setBody("DEF"));
URLConnection urlConnection = server.url("/").url().openConnection();
urlConnection.setReadTimeout(1000);
InputStream in = urlConnection.getInputStream();
assertThat(in.read()).isEqualTo('A');
assertThat(in.read()).isEqualTo('B');
assertThat(in.read()).isEqualTo('C');
try {
in.read(); // if Content-Length was accurate, this would return -1 immediately
fail();
} catch (SocketTimeoutException expected) {
}
URLConnection urlConnection2 = server.url("/").url().openConnection();
InputStream in2 = urlConnection2.getInputStream();
assertThat(in2.read()).isEqualTo('D');
assertThat(in2.read()).isEqualTo('E');
assertThat(in2.read()).isEqualTo('F');
assertThat(in2.read()).isEqualTo(-1);
assertThat(server.takeRequest().getSequenceNumber()).isEqualTo(0);
assertThat(server.takeRequest().getSequenceNumber()).isEqualTo(0);
}
@Ignore("Not actually failing where expected")
@Test public void disconnectAtStart() throws Exception {
server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START));
server.enqueue(new MockResponse()); // The jdk's HttpUrlConnection is a bastard.
server.enqueue(new MockResponse());
try {
server.url("/a").url().openConnection().getInputStream();
fail();
} catch (IOException expected) {
}
server.url("/b").url().openConnection().getInputStream(); // Should succeed.
}
/**
* Throttle the request body by sleeping 500ms after every 3 bytes. With a 6-byte request, this
* should yield one sleep for a total delay of 500ms.
*/
@Test public void throttleRequest() throws Exception {
TestUtil.assumeNotWindows();
server.enqueue(new MockResponse()
.throttleBody(3, 500, TimeUnit.MILLISECONDS));
long startNanos = System.nanoTime();
URLConnection connection = server.url("/").url().openConnection();
connection.setDoOutput(true);
connection.getOutputStream().write("ABCDEF".getBytes(UTF_8));
InputStream in = connection.getInputStream();
assertThat(in.read()).isEqualTo(-1);
long elapsedNanos = System.nanoTime() - startNanos;
long elapsedMillis = NANOSECONDS.toMillis(elapsedNanos);
assertThat(elapsedMillis).isBetween(500L, 1000L);
}
/**
* Throttle the response body by sleeping 500ms after every 3 bytes. With a 6-byte response, this
* should yield one sleep for a total delay of 500ms.
*/
@Test public void throttleResponse() throws Exception {
TestUtil.assumeNotWindows();
server.enqueue(new MockResponse()
.setBody("ABCDEF")
.throttleBody(3, 500, TimeUnit.MILLISECONDS));
long startNanos = System.nanoTime();
URLConnection connection = server.url("/").url().openConnection();
InputStream in = connection.getInputStream();
assertThat(in.read()).isEqualTo('A');
assertThat(in.read()).isEqualTo('B');
assertThat(in.read()).isEqualTo('C');
assertThat(in.read()).isEqualTo('D');
assertThat(in.read()).isEqualTo('E');
assertThat(in.read()).isEqualTo('F');
assertThat(in.read()).isEqualTo(-1);
long elapsedNanos = System.nanoTime() - startNanos;
long elapsedMillis = NANOSECONDS.toMillis(elapsedNanos);
assertThat(elapsedMillis).isBetween(500L, 1000L);
}
/** Delay the response body by sleeping 1s. */
@Test public void delayResponse() throws IOException {
TestUtil.assumeNotWindows();
server.enqueue(new MockResponse()
.setBody("ABCDEF")
.setBodyDelay(1, SECONDS));
long startNanos = System.nanoTime();
URLConnection connection = server.url("/").url().openConnection();
InputStream in = connection.getInputStream();
assertThat(in.read()).isEqualTo('A');
long elapsedNanos = System.nanoTime() - startNanos;
long elapsedMillis = NANOSECONDS.toMillis(elapsedNanos);
assertThat(elapsedMillis).isGreaterThanOrEqualTo(1000L);
in.close();
}
@Test public void disconnectRequestHalfway() throws Exception {
server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_DURING_REQUEST_BODY));
// Limit the size of the request body that the server holds in memory to an arbitrary
// 3.5 MBytes so this test can pass on devices with little memory.
server.setBodyLimit(7 * 512 * 1024);
HttpURLConnection connection = (HttpURLConnection) server.url("/").url().openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setFixedLengthStreamingMode(1024 * 1024 * 1024); // 1 GB
connection.connect();
OutputStream out = connection.getOutputStream();
byte[] data = new byte[1024 * 1024];
int i;
for (i = 0; i < 1024; i++) {
try {
out.write(data);
out.flush();
if (i == 513) {
// pause slightly after half way to make result more predictable
Thread.sleep(100);
}
} catch (IOException e) {
break;
}
}
// Halfway +/- 0.5%
assertThat((float) i).isCloseTo(512f, offset(5f));
}
@Test public void disconnectResponseHalfway() throws IOException {
server.enqueue(new MockResponse()
.setBody("ab")
.setSocketPolicy(SocketPolicy.DISCONNECT_DURING_RESPONSE_BODY));
URLConnection connection = server.url("/").url().openConnection();
assertThat(connection.getContentLength()).isEqualTo(2);
InputStream in = connection.getInputStream();
assertThat(in.read()).isEqualTo('a');
try {
int byteRead = in.read();
// OpenJDK behavior: end of stream.
assertThat(byteRead).isEqualTo(-1);
} catch (ProtocolException e) {
// On Android, HttpURLConnection is implemented by OkHttp v2. OkHttp
// treats an incomplete response body as a ProtocolException.
}
}
private List<String> headersToList(MockResponse response) {
Headers headers = response.getHeaders();
int size = headers.size();
List<String> headerList = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
headerList.add(headers.name(i) + ": " + headers.value(i));
}
return headerList;
}
@Test public void shutdownWithoutStart() throws IOException {
MockWebServer server = new MockWebServer();
server.shutdown();
}
@Test public void closeViaClosable() throws IOException {
Closeable server = new MockWebServer();
server.close();
}
@Test public void shutdownWithoutEnqueue() throws IOException {
MockWebServer server = new MockWebServer();
server.start();
server.shutdown();
}
@Test public void portImplicitlyStarts() {
assertThat(server.getPort()).isGreaterThan(0);
}
@Test public void hostnameImplicitlyStarts() {
assertThat(server.getHostName()).isNotNull();
}
@Test public void toProxyAddressImplicitlyStarts() {
assertThat(server.toProxyAddress()).isNotNull();
}
@Test public void differentInstancesGetDifferentPorts() throws IOException {
MockWebServer other = new MockWebServer();
assertThat(other.getPort()).isNotEqualTo(server.getPort());
other.shutdown();
}
@Test public void statementStartsAndStops() throws Throwable {
final AtomicBoolean called = new AtomicBoolean();
Statement statement = server.apply(new Statement() {
@Override public void evaluate() throws Throwable {
called.set(true);
server.url("/").url().openConnection().connect();
}
}, Description.EMPTY);
statement.evaluate();
assertThat(called.get()).isTrue();
try {
server.url("/").url().openConnection().connect();
fail();
} catch (ConnectException expected) {
}
}
@Test public void shutdownWhileBlockedDispatching() throws Exception {
// Enqueue a request that'll cause MockWebServer to hang on QueueDispatcher.dispatch().
HttpURLConnection connection = (HttpURLConnection) server.url("/").url().openConnection();
connection.setReadTimeout(500);
try {
connection.getResponseCode();
fail();
} catch (SocketTimeoutException expected) {
}
// Shutting down the server should unblock the dispatcher.
server.shutdown();
}
@Test public void requestUrlReconstructed() throws Exception {
server.enqueue(new MockResponse().setBody("hello world"));
URL url = server.url("/a/deep/path?key=foo%20bar").url();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in, UTF_8));
assertThat(connection.getResponseCode()).isEqualTo(HttpURLConnection.HTTP_OK);
assertThat(reader.readLine()).isEqualTo("hello world");
RecordedRequest request = server.takeRequest();
assertThat(request.getRequestLine()).isEqualTo(
"GET /a/deep/path?key=foo%20bar HTTP/1.1");
HttpUrl requestUrl = request.getRequestUrl();
assertThat(requestUrl.scheme()).isEqualTo("http");
assertThat(requestUrl.host()).isEqualTo(server.getHostName());
assertThat(requestUrl.port()).isEqualTo(server.getPort());
assertThat(requestUrl.encodedPath()).isEqualTo("/a/deep/path");
assertThat(requestUrl.queryParameter("key")).isEqualTo("foo bar");
}
@Test public void shutdownServerAfterRequest() throws Exception {
server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.SHUTDOWN_SERVER_AFTER_RESPONSE));
URL url = server.url("/").url();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
assertThat(connection.getResponseCode()).isEqualTo(HttpURLConnection.HTTP_OK);
HttpURLConnection refusedConnection = (HttpURLConnection) url.openConnection();
try {
refusedConnection.getResponseCode();
fail("Second connection should be refused");
} catch (ConnectException e) {
assertThat(e.getMessage()).contains("refused");
}
}
@Test public void http100Continue() throws Exception {
server.enqueue(new MockResponse().setBody("response"));
URL url = server.url("/").url();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Expect", "100-Continue");
connection.getOutputStream().write("request".getBytes(UTF_8));
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in, UTF_8));
assertThat(reader.readLine()).isEqualTo("response");
RecordedRequest request = server.takeRequest();
assertThat(request.getBody().readUtf8()).isEqualTo("request");
}
@Test public void testH2PriorKnowledgeServerFallback() {
try {
server.setProtocols(asList(Protocol.H2_PRIOR_KNOWLEDGE, Protocol.HTTP_1_1));
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected.getMessage()).isEqualTo(
("protocols containing h2_prior_knowledge cannot use other protocols: "
+ "[h2_prior_knowledge, http/1.1]"));
}
}
@Test public void testH2PriorKnowledgeServerDuplicates() {
try {
// Treating this use case as user error
server.setProtocols(asList(Protocol.H2_PRIOR_KNOWLEDGE, Protocol.H2_PRIOR_KNOWLEDGE));
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected.getMessage()).isEqualTo(
("protocols containing h2_prior_knowledge cannot use other protocols: "
+ "[h2_prior_knowledge, h2_prior_knowledge]"));
}
}
@Test public void testMockWebServerH2PriorKnowledgeProtocol() {
server.setProtocols(asList(Protocol.H2_PRIOR_KNOWLEDGE));
assertThat(server.protocols().size()).isEqualTo(1);
assertThat(server.protocols().get(0)).isEqualTo(Protocol.H2_PRIOR_KNOWLEDGE);
}
@Test public void https() throws Exception {
HandshakeCertificates handshakeCertificates = localhost();
server.useHttps(handshakeCertificates.sslSocketFactory(), false);
server.enqueue(new MockResponse().setBody("abc"));
HttpUrl url = server.url("/");
HttpsURLConnection connection = (HttpsURLConnection) url.url().openConnection();
connection.setSSLSocketFactory(handshakeCertificates.sslSocketFactory());
connection.setHostnameVerifier(new RecordingHostnameVerifier());
assertThat(connection.getResponseCode()).isEqualTo(HttpURLConnection.HTTP_OK);
BufferedReader reader =
new BufferedReader(new InputStreamReader(connection.getInputStream(), UTF_8));
assertThat(reader.readLine()).isEqualTo("abc");
RecordedRequest request = server.takeRequest();
assertThat(request.getRequestUrl().scheme()).isEqualTo("https");
Handshake handshake = request.getHandshake();
assertThat(handshake.tlsVersion()).isNotNull();
assertThat(handshake.cipherSuite()).isNotNull();
assertThat(handshake.localPrincipal()).isNotNull();
assertThat(handshake.localCertificates().size()).isEqualTo(1);
assertThat(handshake.peerPrincipal()).isNull();
assertThat(handshake.peerCertificates().size()).isEqualTo(0);
}
@Test public void httpsWithClientAuth() throws Exception {
assumeFalse(getPlatform().equals("conscrypt"));
HeldCertificate clientCa = new HeldCertificate.Builder()
.certificateAuthority(0)
.build();
HeldCertificate serverCa = new HeldCertificate.Builder()
.certificateAuthority(0)
.build();
HeldCertificate serverCertificate = new HeldCertificate.Builder()
.signedBy(serverCa)
.addSubjectAlternativeName(server.getHostName())
.build();
HandshakeCertificates serverHandshakeCertificates = new HandshakeCertificates.Builder()
.addTrustedCertificate(clientCa.certificate())
.heldCertificate(serverCertificate)
.build();
server.useHttps(serverHandshakeCertificates.sslSocketFactory(), false);
server.enqueue(new MockResponse().setBody("abc"));
server.requestClientAuth();
HeldCertificate clientCertificate = new HeldCertificate.Builder()
.signedBy(clientCa)
.build();
HandshakeCertificates clientHandshakeCertificates = new HandshakeCertificates.Builder()
.addTrustedCertificate(serverCa.certificate())
.heldCertificate(clientCertificate)
.build();
HttpUrl url = server.url("/");
HttpsURLConnection connection = (HttpsURLConnection) url.url().openConnection();
connection.setSSLSocketFactory(clientHandshakeCertificates.sslSocketFactory());
connection.setHostnameVerifier(new RecordingHostnameVerifier());
assertThat(connection.getResponseCode()).isEqualTo(HttpURLConnection.HTTP_OK);
BufferedReader reader =
new BufferedReader(new InputStreamReader(connection.getInputStream(), UTF_8));
assertThat(reader.readLine()).isEqualTo("abc");
RecordedRequest request = server.takeRequest();
assertThat(request.getRequestUrl().scheme()).isEqualTo("https");
Handshake handshake = request.getHandshake();
assertThat(handshake.tlsVersion()).isNotNull();
assertThat(handshake.cipherSuite()).isNotNull();
assertThat(handshake.localPrincipal()).isNotNull();
assertThat(handshake.localCertificates().size()).isEqualTo(1);
assertThat(handshake.peerPrincipal()).isNotNull();
assertThat(handshake.peerCertificates().size()).isEqualTo(1);
}
@Test
public void shutdownTwice() throws IOException {
MockWebServer server2 = new MockWebServer();
server2.start();
server2.shutdown();
try {
server2.start();
fail();
} catch (IllegalArgumentException iae) {
// expected
}
server2.shutdown();
}
public static String getPlatform() {
return System.getProperty("okhttp.platform", "jdk8");
}
}

View File

@@ -1,6 +1,7 @@
rootProject.name = 'okhttp-parent'
include ':mockwebserver'
include ':mockwebserverwrapper'
if (properties.containsKey('android.injected.invoked.from.ide') ||
System.getenv('ANDROID_SDK_ROOT') != null) {