CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-squareup-okhttp3--mockwebserver

A scriptable web server for testing HTTP clients with support for HTTP/1.1, HTTP/2, WebSockets, and SSL/TLS

Pending
Overview
Eval results
Files

advanced-features.mddocs/

Advanced Features

Advanced MockWebServer capabilities including custom dispatchers for dynamic response logic, HTTP/2 server push promises, duplex streaming, SSL/TLS configuration, protocol negotiation, and WebSocket upgrades.

Capabilities

Custom Dispatchers

Create dynamic response logic by extending the Dispatcher class or using QueueDispatcher with advanced configurations.

/**
 * Base class for handling mock server requests with custom logic
 */
abstract class Dispatcher {
    /**
     * Process incoming request and return appropriate response
     * @param request RecordedRequest containing request details
     * @returns MockResponse to send back to client
     */
    abstract fun dispatch(request: RecordedRequest): MockResponse
    
    /**
     * Get preview of next response without consuming it
     * @returns MockResponse that would be returned by next dispatch call
     */
    open fun peek(): MockResponse
    
    /**
     * Release any resources held by the dispatcher
     */
    open fun shutdown()
}

/**
 * Default dispatcher that serves responses from a FIFO queue
 */
class QueueDispatcher : Dispatcher() {
    /**
     * Add response to end of queue
     * @param response MockResponse to enqueue
     */
    open fun enqueueResponse(response: MockResponse)
    
    /**
     * Enable/disable fail-fast mode for empty queue
     * @param failFast If true, return error response when queue is empty
     */
    open fun setFailFast(failFast: Boolean)
    
    /**
     * Set specific response for fail-fast mode
     * @param failFastResponse Response to return when queue is empty (null for default 404)
     */
    open fun setFailFast(failFastResponse: MockResponse?)
}

Usage Examples:

// Custom dispatcher for RESTful API simulation
class ApiDispatcher : Dispatcher() {
    private val users = mutableMapOf(
        1 to """{"id": 1, "name": "Alice"}""",
        2 to """{"id": 2, "name": "Bob"}"""
    )
    
    override fun dispatch(request: RecordedRequest): MockResponse {
        val path = request.path ?: return MockResponse().setResponseCode(400)
        
        return when {
            path.matches(Regex("/api/users/\\d+")) && request.method == "GET" -> {
                val userId = path.substringAfterLast("/").toIntOrNull()
                val user = users[userId]
                if (user != null) {
                    MockResponse()
                        .setResponseCode(200)
                        .addHeader("Content-Type", "application/json")
                        .setBody(user)
                } else {
                    MockResponse().setResponseCode(404)
                }
            }
            
            path == "/api/users" && request.method == "GET" -> {
                val usersList = users.values.joinToString(",", "[", "]")
                MockResponse()
                    .setResponseCode(200)
                    .addHeader("Content-Type", "application/json")
                    .setBody(usersList)
            }
            
            path == "/api/users" && request.method == "POST" -> {
                val body = request.body.readUtf8()
                // Simulate user creation
                MockResponse()
                    .setResponseCode(201)
                    .addHeader("Content-Type", "application/json")
                    .addHeader("Location", "/api/users/3")
                    .setBody("""{"id": 3, "name": "New User"}""")
            }
            
            else -> MockResponse().setResponseCode(404)
        }
    }
}

// Apply custom dispatcher
val server = MockWebServer()
server.dispatcher = ApiDispatcher()
server.start()

QueueDispatcher Advanced Configuration:

val server = MockWebServer()
val queueDispatcher = QueueDispatcher()

// Configure fail-fast behavior
queueDispatcher.setFailFast(true) // Return 404 when queue is empty
// Or provide custom fail-fast response
queueDispatcher.setFailFast(
    MockResponse()
        .setResponseCode(503)
        .setBody("Service temporarily unavailable")
)

server.dispatcher = queueDispatcher
server.start()

HTTP/2 Server Push

Configure HTTP/2 server push promises to simulate modern web server behavior.

/**
 * Represents an HTTP/2 server push promise
 */
class PushPromise(
    /**
     * HTTP method for the pushed request
     */
    val method: String,
    
    /**
     * Path for the pushed request
     */
    val path: String,
    
    /**
     * Headers for the pushed request
     */
    val headers: Headers,
    
    /**
     * Response to be sent for the pushed request
     */
    val response: MockResponse
)

/**
 * Add HTTP/2 server push promise to response
 * @param promise PushPromise to add
 * @returns This MockResponse for method chaining
 */
fun withPush(promise: PushPromise): MockResponse

/**
 * List of server push promises (read-only)
 */
val pushPromises: List<PushPromise>

Usage Examples:

val server = MockWebServer()

// Main page response with pushed resources
val cssContent = "body { font-family: Arial; }"
val jsContent = "console.log('App loaded');"

val mainPageResponse = MockResponse()
    .setResponseCode(200)
    .addHeader("Content-Type", "text/html")
    .setBody("""
        <!DOCTYPE html>
        <html>
        <head>
            <link rel="stylesheet" href="/style.css">
            <script src="/app.js"></script>
        </head>
        <body><h1>Hello World</h1></body>
        </html>
    """.trimIndent())
    .withPush(
        PushPromise(
            method = "GET",
            path = "/style.css",
            headers = headingsOf("Content-Type", "text/css"),
            response = MockResponse()
                .setResponseCode(200)
                .addHeader("Content-Type", "text/css")
                .setBody(cssContent)
        )
    )
    .withPush(
        PushPromise(
            method = "GET",
            path = "/app.js",
            headers = headersOf("Content-Type", "application/javascript"),
            response = MockResponse()
                .setResponseCode(200)
                .addHeader("Content-Type", "application/javascript")
                .setBody(jsContent)
        )
    )

server.enqueue(mainPageResponse)
server.start()

Duplex Streaming (HTTP/2)

Advanced HTTP/2 bidirectional streaming capabilities for testing duplex communication.

/**
 * Interface for duplex HTTP/2 response bodies (bidirectional streaming)
 */
interface DuplexResponseBody {
    /**
     * Handle incoming duplex request with HTTP/2 stream
     * @param request RecordedRequest with request details
     * @param http2Stream HTTP/2 stream for bidirectional communication
     */
    fun onRequest(request: RecordedRequest, http2Stream: Http2Stream)
}

/**
 * Scriptable duplex response for HTTP/2 bidirectional streaming
 */
class MockDuplexResponseBody : DuplexResponseBody {
    /**
     * Expect specific request content
     * @param expected Expected request content string
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun receiveRequest(expected: String): MockDuplexResponseBody
    
    /**
     * Expect request stream to be exhausted
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun exhaustRequest(): MockDuplexResponseBody
    
    /**
     * Cancel stream with HTTP/2 error code
     * @param errorCode HTTP/2 error code for cancellation
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun cancelStream(errorCode: ErrorCode): MockDuplexResponseBody
    
    /**
     * Expect IOException when reading request
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun requestIOException(): MockDuplexResponseBody
    
    /**
     * Send response data to client
     * @param s Response data string
     * @param responseSent Optional CountDownLatch for synchronization
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun sendResponse(s: String, responseSent: CountDownLatch = CountDownLatch(0)): MockDuplexResponseBody
    
    /**
     * Close response stream
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun exhaustResponse(): MockDuplexResponseBody
    
    /**
     * Add delay in conversation
     * @param duration Delay duration
     * @param unit Time unit for duration
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun sleep(duration: Long, unit: TimeUnit): MockDuplexResponseBody
    
    /**
     * Wait for entire duplex conversation to complete successfully
     */
    fun awaitSuccess()
}

/**
 * Whether response is duplex (HTTP/2 bidirectional, read-only)
 */
val isDuplex: Boolean

Usage Examples:

val server = MockWebServer()

// Create duplex response body for chat-like interaction
val duplexBody = MockDuplexResponseBody()
    .receiveRequest("HELLO")
    .sendResponse("WELCOME")
    .receiveRequest("GET_DATA")
    .sendResponse("DATA: [1,2,3]")
    .receiveRequest("BYE")
    .sendResponse("GOODBYE")
    .exhaustRequest()
    .exhaustResponse()

val duplexResponse = MockResponse()
    .setResponseCode(200)
    .withDuplexBody(duplexBody)

server.enqueue(duplexResponse)
server.start()

// Client would establish HTTP/2 connection and stream data...
// After client interaction completes:
duplexBody.awaitSuccess() // Verify conversation completed as scripted

SSL/TLS Configuration

Configure HTTPS with SSL certificates and client authentication options.

/**
 * Enable HTTPS with SSL socket factory
 * @param sslSocketFactory SSL socket factory for creating SSL connections
 * @param tunnelProxy Whether to tunnel through proxy
 */
fun useHttps(sslSocketFactory: SSLSocketFactory, tunnelProxy: Boolean)

/**
 * Disable SSL client authentication (default)
 */
fun noClientAuth()

/**
 * Request (but don't require) SSL client authentication
 */
fun requestClientAuth()

/**
 * Require SSL client authentication
 */
fun requireClientAuth()

Usage Examples:

val server = MockWebServer()

// Create SSL context with self-signed certificate
val sslContext = SSLContext.getInstance("TLS")
val keyManager = createSelfSignedKeyManager() // Your implementation
val trustManager = createTrustAllManager() // Your implementation
sslContext.init(arrayOf(keyManager), arrayOf(trustManager), SecureRandom())

// Enable HTTPS
server.useHttps(sslContext.socketFactory, false)
server.requestClientAuth() // Optional client certificates

server.enqueue(MockResponse().setBody("Secure response"))
server.start()

// Now server accepts HTTPS connections
val httpsUrl = server.url("/secure").newBuilder().scheme("https").build()

Protocol Negotiation

Configure HTTP protocol version support and ALPN negotiation.

/**
 * Whether ALPN protocol negotiation is enabled (default: true)
 */
var protocolNegotiationEnabled: Boolean

/**
 * Supported protocols for ALPN (default: HTTP/2, HTTP/1.1)
 */
var protocols: List<Protocol>

Usage Examples:

val server = MockWebServer()

// Configure protocol support
server.protocolNegotiationEnabled = true
server.protocols = listOf(Protocol.HTTP_2, Protocol.HTTP_1_1)

// Or disable HTTP/2, use only HTTP/1.1
server.protocols = listOf(Protocol.HTTP_1_1)

server.start()

WebSocket Upgrades

Enable WebSocket protocol upgrades for testing WebSocket clients.

/**
 * Enable WebSocket upgrade with listener
 * @param listener WebSocketListener for handling WebSocket events
 * @returns This MockResponse for method chaining
 */
fun withWebSocketUpgrade(listener: WebSocketListener): MockResponse

Usage Examples:

val server = MockWebServer()

val webSocketListener = object : WebSocketListener() {
    override fun onOpen(webSocket: WebSocket, response: Response) {
        webSocket.send("Welcome to WebSocket")
    }
    
    override fun onMessage(webSocket: WebSocket, text: String) {
        when (text) {
            "ping" -> webSocket.send("pong")
            "close" -> webSocket.close(1000, "Normal closure")
            else -> webSocket.send("Echo: $text")
        }
    }
}

val webSocketResponse = MockResponse()
    .withWebSocketUpgrade(webSocketListener)

server.enqueue(webSocketResponse)
server.start()

// Client connects with WebSocket upgrade request
val request = Request.Builder()
    .url(server.url("/websocket"))
    .addHeader("Upgrade", "websocket")
    .addHeader("Connection", "Upgrade")
    .addHeader("Sec-WebSocket-Key", "generated-key")
    .addHeader("Sec-WebSocket-Version", "13")
    .build()

HTTP/2 Settings and Informational Responses

Configure HTTP/2 settings frames and 1xx informational responses.

/**
 * Set HTTP/2 settings frame
 * @param settings HTTP/2 settings to apply
 * @returns This MockResponse for method chaining
 */
fun withSettings(settings: Settings): MockResponse

/**
 * Add 1xx informational response before main response
 * @param informationalResponse MockResponse with 1xx status code
 * @returns This MockResponse for method chaining
 */
fun addInformationalResponse(informationalResponse: MockResponse): MockResponse

Usage Examples:

// HTTP/2 settings configuration
val settingsFrame = Settings().apply {
    set(Settings.MAX_CONCURRENT_STREAMS, 100)
    set(Settings.INITIAL_WINDOW_SIZE, 65535)
}

val http2Response = MockResponse()
    .setResponseCode(200)
    .withSettings(settingsFrame)
    .setBody("HTTP/2 response with custom settings")

// 1xx informational responses
val mainResponse = MockResponse()
    .addInformationalResponse(
        MockResponse().setResponseCode(100) // 100 Continue
    )
    .addInformationalResponse(
        MockResponse().setResponseCode(102) // 102 Processing
    )
    .setResponseCode(200)
    .setBody("Final response after informational responses")

server.enqueue(mainResponse)

Duplex Streaming

MockDuplexResponseBody enables scriptable bidirectional HTTP/2 streaming for testing duplex communication patterns.

/**
 * A scriptable request/response conversation for HTTP/2 duplex streaming.
 * Create conversation scripts by calling methods in the sequence they should execute.
 */
class MockDuplexResponseBody : DuplexResponseBody {
    /**
     * Expect to receive specific request data
     * @param expected Expected request content as string
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun receiveRequest(expected: String): MockDuplexResponseBody
    
    /**
     * Expect the request body to be exhausted (no more data)
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun exhaustRequest(): MockDuplexResponseBody
    
    /**
     * Cancel the HTTP/2 stream with specified error code
     * @param errorCode HTTP/2 error code for stream cancellation
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun cancelStream(errorCode: ErrorCode): MockDuplexResponseBody
    
    /**
     * Expect an IOException when reading request data
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun requestIOException(): MockDuplexResponseBody
    
    /**
     * Send response data to client
     * @param s Response content as string
     * @param responseSent Optional latch signaled when response is sent
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun sendResponse(s: String, responseSent: CountDownLatch = CountDownLatch(0)): MockDuplexResponseBody
    
    /**
     * Close the response stream (no more data will be sent)
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun exhaustResponse(): MockDuplexResponseBody
    
    /**
     * Sleep for specified duration during conversation
     * @param duration Duration to sleep
     * @param unit Time unit for duration
     * @returns This MockDuplexResponseBody for method chaining
     */
    fun sleep(duration: Long, unit: TimeUnit): MockDuplexResponseBody
    
    /**
     * Wait for duplex conversation to complete successfully
     * Blocks until all scripted actions are executed
     */
    fun awaitSuccess()
}

Usage Examples:

import okhttp3.internal.http2.ErrorCode
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

// Create duplex conversation script
val duplexBody = MockDuplexResponseBody()
    .receiveRequest("Initial request data")
    .sendResponse("Server acknowledges")
    .receiveRequest("Follow-up data")
    .sendResponse("Processing complete")
    .exhaustRequest()
    .exhaustResponse()

// Create duplex response
val duplexResponse = MockResponse()
    .setResponseCode(200)
    .addHeader("Content-Type", "application/octet-stream")
    .setDuplexResponseBody(duplexBody)

server.enqueue(duplexResponse)

// Client performs duplex communication...

// Wait for conversation to complete
duplexBody.awaitSuccess()

// Stream cancellation example
val cancellingDuplexBody = MockDuplexResponseBody()
    .receiveRequest("Start streaming")
    .sendResponse("Streaming started")
    .cancelStream(ErrorCode.CANCEL)

// Error handling example  
val errorDuplexBody = MockDuplexResponseBody()
    .receiveRequest("Valid data")
    .sendResponse("Data received")
    .requestIOException() // Expect IOException on next read
    .exhaustResponse()

Install with Tessl CLI

npx tessl i tessl/maven-com-squareup-okhttp3--mockwebserver

docs

advanced-features.md

index.md

request-verification.md

response-configuration.md

server-management.md

tile.json