Skip to main content

airframe-http-recorder: Web Request/Response Recorder

airframe-http-recorder is an HTTP server for recording and replaying HTTP responses. This is useful for testing HTTP server interactions in an environment with limited resources (e.g., CI servers)

With HttpRecorder, you can:

  • Create a proxy server to the actual server and record the responses automatically.
    • This reduces the burden of writing mock servers and dummy responses.
  • Record custom HTTP request/response pairs.
    • This is useful for simulating server failures (e.g., returning 5xx responses)
  • Replay recorded responses, stored in SQLite databases.
    • This is useful for running HTTP server/client integration tests on CI, without actually accessing the real destination servers.

Usage​

build.sbt

Maven Central

libraryDependencies += "org.wvlet.airframe" %% "airframe-http-recorder" %% (version)

Record & Replay​

import wvlet.airframe.control.Control._
import wvlet.airframe.http.{Http,HttpStatus}
import wvlet.airframe.http.recorder.{HttpRecorder,HttpRecorderConfig}
import wvlet.airframe.http.finagle.FinagleClient

// Create a proxy server that will record responses for matching requests,
// and make actual requests the destination for non-recorded requests.
val recorderConfig = HttpRecorderConfig(destUri = "https://www.google.com")
withResource(HttpRecorder.createRecorderProxy(recorderConfig)) { server =>

// Create an HTTP client
val client = Http.client.newSyncClient(server.localAddress) // "localhost:(port number)"

// Requests to the local server will be recorded
val response = client.send(Http.GET("/"))

// You can record your own dummy responses
server.record(Http.GET("/dummy"), Http.response(HttpStatus.NotFound_404))
}


// Create a proxy server only for recording server responses
withResource(HttpRecorder.createRecordOnlyServer(recorderConfig)) { server =>
val addr = server.localAddress // "localhost:(port number)"
// Requests to the local server will be recorded

// If necessary, add custom request-response pairs
server.recordIfNotExists(Http.GET("/dummy"), Http.response(HttpStatus.Ok_200))
}

// Create a replay server that returns recorded responses for matching requests
withResource(HttpRecorder.createReplayOnlyServer(recorderConfig)) { server =>
val addr = server.localAddress // "localhost:(port number)"
// Requests to the local server will return the recorded responses
}

Programmable HttpRecorderServer​

import wvlet.airframe.http.Http
import wvlet.airframe.http.recorder._
import wvlet.airframe.control.Control._

val recorderConfig = HttpRecorderConfig(sessionName="my-recording")
val response = withResource(HttpRecorder.createProgrammableServer(recorderConfig)) { server =>
// Add custom server responses
val request = Http.GET("/index.html")
val response = Http.response().withContent("Hello World!")
server.record(request, response)

val addr = server.localAddress // "localhost:(port number)"
// Requests to the local server will return the programmed responses
}

If you don't need to persist your recoded responsoe (e.g., in unit tests), use HttpRecorder.createInMemoryProgrammableServer. The recorded responses wiil be discarded after closing the server.

Customize Request Matcher​

By default, http-recorder finds records that have the same method type (e.g., GET, POST, etc.), path, query string, request body, and HTTP headers. Some unstable HTTP headers like Date, User-Agent, etc. (defined in defaultExcludeHeaderPrefixes) will not be used for matching.

If this matching is too strict, we can use PathOnlyMatcher (checks only method types and uri) or your own implementation to compute hash values for matching requests:

import wvlet.airframe.http.recorder._
import wvlet.airframe.control.Control._

val config = HttpRecorderConfig(requestMatcher = HttpRequestMatcher.PathOnlyMatcher)
withResource(HttpRecorder.createInMemoryServer(config)) { server =>
//
}