Play应用代理服务器错误处理测试与逻辑实现问询
解决方案:Play代理服务器故障转移到备用服务器
我来帮你搞定这个Play代理服务器的故障转移问题,同时解决测试环境无法捕获错误的难题!
一、实现自定义错误处理器完成故障转移
首先我们需要扩展Play的默认错误处理器,重写onServerError方法,在服务器A发生错误时自动请求服务器B,并根据B的响应返回结果。这里用Scala代码示例:
import play.api.http.DefaultHttpErrorHandler import play.api.mvc._ import play.api._ import scala.concurrent._ import play.api.libs.ws._ import scala.concurrent.duration._ class CustomErrorHandler( env: Environment, config: Configuration, sourceMapper: OptionalSourceMapper, router: Provider[Router], wsClient: WSClient ) extends DefaultHttpErrorHandler(env, config, sourceMapper, router) { // 从配置文件读取服务器B的地址,避免硬编码 private val backendServerUrl = config.get[String]("servers.backend") override def onServerError(request: RequestHeader, exception: Throwable): Future[Result] = { // 构建转发到服务器B的请求,携带原请求的方法、头信息 val backendRequest = wsClient.url(s"$backendServerUrl${request.uri}") .withHttpHeaders(request.headers.toSimpleMap.toSeq: _*) .withRequestTimeout(5.seconds) // 设置超时,避免请求卡住 // 根据原请求方法执行对应请求 val responseFuture = request.method match { case "GET" => backendRequest.get() case "POST" => request.body.asBytes.map(bytes => backendRequest.post(bytes)).getOrElse(backendRequest.post("")) case "PUT" => request.body.asBytes.map(bytes => backendRequest.put(bytes)).getOrElse(backendRequest.put("")) case other => backendRequest.execute(other) } responseFuture.map { response => // 原样返回服务器B的响应 Result( response.status, response.headers.map { case (k, vs) => (k, vs.toSeq) }, response.bodyAsSource ) }.recover { // 如果服务器B也请求失败,返回Internal Server Error case _ => InternalServerError("Service Unavailable") } } }
然后在application.conf里注册这个错误处理器:
play.http.errorHandler = "com.yourpackage.CustomErrorHandler" servers.backend = "http://server-b:8080" # 替换成你的服务器B地址
二、解决测试环境无法捕获错误的问题
Play的测试环境默认会使用专门的错误处理器,不会触发我们自定义的逻辑,所以需要手动配置启用自定义错误处理器:
1. 配置测试环境的错误处理器
在conf/application.test.conf中添加:
play.http.errorHandler = "com.yourpackage.CustomErrorHandler"
2. 编写测试用例验证逻辑
用MockWS模拟服务器B的响应,确保故障转移逻辑正常工作:
import org.scalatestplus.play._ import play.api.inject.guice.GuiceApplicationBuilder import play.api.test._ import play.api.libs.ws._ import scala.concurrent.Future class CustomErrorHandlerSpec extends PlaySpec with OneAppPerTest { // 自定义MockWS客户端,模拟服务器B的不同响应 private def mockBackendWS(successResponse: Boolean): WSClient = new WSClient { override def url(url: String): WSRequest = new WSRequest { override def execute(method: String): Future[WSResponse] = { if (successResponse) { Future.successful(WSResponse(200, Map.empty, "Response from Server B", "")) } else { Future.failed(new Exception("Backend server down")) } } // 实现其他WSRequest的必要方法,这里简化处理 override def get(): Future[WSResponse] = execute("GET") override def post(body: String): Future[WSResponse] = execute("POST") override def put(body: String): Future[WSResponse] = execute("PUT") // 省略其他方法的默认实现... } override def close(): Unit = () } override def fakeApplication(): Application = { new GuiceApplicationBuilder() .configure("play.http.errorHandler" -> "com.yourpackage.CustomErrorHandler") .configure("servers.backend" -> "http://mock-server-b") .build() } "CustomErrorHandler" should { "forward request to Server B when Server A throws error" in { val app = new GuiceApplicationBuilder() .overrides(bind[WSClient].toInstance(mockBackendWS(successResponse = true))) .configure("play.http.errorHandler" -> "com.yourpackage.CustomErrorHandler") .build() running(app) { // 模拟一个会抛出服务器错误的请求 val errorRequest = FakeRequest(GET, "/error-endpoint").withHandler { throw new RuntimeException("Server A error") } val result = route(app, errorRequest).get status(result) mustBe OK contentAsString(result) mustBe "Response from Server B" } } "return 500 when both Server A and Server B fail" in { val app = new GuiceApplicationBuilder() .overrides(bind[WSClient].toInstance(mockBackendWS(successResponse = false))) .configure("play.http.errorHandler" -> "com.yourpackage.CustomErrorHandler") .build() running(app) { val errorRequest = FakeRequest(GET, "/error-endpoint").withHandler { throw new RuntimeException("Server A error") } val result = route(app, errorRequest).get status(result) mustBe INTERNAL_SERVER_ERROR } } } }
三、额外注意点
- 请求body转发:如果你的代理需要处理POST/PUT等带body的请求,要确保原请求的body被正确转发给服务器B,代码里已经做了基本处理,你可以根据实际需求调整。
- 超时与重试:可以给WS请求添加重试逻辑,避免因网络波动导致不必要的500。
- 日志记录:建议在错误处理器中添加日志,记录服务器A的错误信息和转发到B的请求情况,方便排查问题。
内容的提问来源于stack exchange,提问作者mrdoubtful
相关产品推荐
相关产品推荐

