差異處
這裏顯示兩個版本的差異處。
java:wiremock:record-disconnection-behavior [2021/08/09 12:26] tony [方法1] |
java:wiremock:record-disconnection-behavior [2023/06/25 09:48] |
||
---|---|---|---|
行 1: | 行 1: | ||
- | {{tag>wiremock}} | ||
- | ====== WireMock - Record Disconnection Behavior ====== | ||
- | ===== Problem ===== | ||
- | 我有一隻待測程式(SUT)會相依於外部服務(External Service),操作流程如下: | ||
- | - SUT觸發一個async command。 | ||
- | - 接著外部服務將會重置而導致SUT無法連線。 | ||
- | - SUT會不停的Get去等待外部服務再次連線。 | ||
- | {{:java:wiremock:wm_test_connection_reset.png|}}\\ | ||
- | 在我導入WireMock要替代外部服務並錄製腳本過程中,發現WireMock只要發生網路連線問題就會回應以下內容: | ||
- | <code> | ||
- | Network failure trying to make a proxied request from WireMock to https://10.146.125.169/api/v1 | ||
- | Connect to 10.146.125.169:443 [/10.146.125.169] failed: Connection timed out: connect | ||
- | </code> | ||
- | {{:java:wiremock:wm_test_connection_reset_and_get_500_error_code.png|}}\\ | ||
- | \\ | ||
- | 這導致SUT接到非預期的500 status code,使工作的執行無法順利完成。本篇文章主要分享我的解決方法。 | ||
- | ===== How to? ===== | ||
- | (對嘗試過程沒興趣可以直接看方法2) | ||
- | ===== 方法1 ===== | ||
- | 我的第一個方法,是直接找到造成500問題的地方([[https://github.com/tomakehurst/wiremock/blob/1e7c07e9ff845b3253c8177159775ddd070c22a4/src/main/java/com/github/tomakehurst/wiremock/http/ProxyResponseRenderer.java|link]] ProxyResponseRenderer),修改WireMock程式碼直接retry。可會讓工作能夠繼續執行並完成。最後我透過修改錄製出來的腳本,加上長時間的response delay強迫client timeout去模擬類似情境: | ||
- | <code json> | ||
- | "response": { | ||
- | "status": 200, | ||
- | "fixedDelayMilliseconds": 120000 | ||
- | } | ||
- | </code> | ||
- | Note. 嘗試過在HttpClientFactory加socket timeout是沒有幫助的,這受到作業系統設定限制。 | ||
- | ===== 方法2 ===== | ||
- | 後來研究了一下[[http://wiremock.org/docs/extending-wiremock/|Exteding WireMock]]內容後,覺得應該有機會可以透過擴充的方式去達到我的目的,因此有了方法2。 | ||
- | ==== Extend ResponseTransformer ==== | ||
- | 首先我看上了ResponseTransformer,它可以讓你對Response加工。在[[http://wiremock.org/docs/simulating-faults/|Simulating Faults]]內容有提及WireMock模擬Fault的一些方式,其中這部分內容激起了我的靈感: | ||
- | <code json> | ||
- | { | ||
- | "request": { | ||
- | "method": "GET", | ||
- | "url": "/fault" | ||
- | }, | ||
- | "response": { | ||
- | "fault": "MALFORMED_RESPONSE_CHUNK" | ||
- | } | ||
- | } | ||
- | </code> | ||
- | 我想應該可以根據connection timed out這種特定錯誤情境,讓WireMock產生Fault的Response,在這裡我使用了Fault.CONNECTION_RESET_BY_PEER: | ||
- | <code java> | ||
- | public class SocketTimedOutResponseTransformer extends ResponseTransformer { | ||
- | @Override | ||
- | public Response transform(Request request, Response response, FileSource files, Parameters parameters) { | ||
- | if(response.getStatus()==500&&response.getBodyAsString().contains("timed out")) { | ||
- | return Response.Builder.like(response) | ||
- | .body(String.valueOf(Fault.CONNECTION_RESET_BY_PEER)) | ||
- | .fault(Fault.CONNECTION_RESET_BY_PEER).build(); | ||
- | } | ||
- | return response; | ||
- | } | ||
- | |||
- | @Override | ||
- | public String getName() { | ||
- | return "SocketTimedOutResponseTransformer"; | ||
- | } | ||
- | } | ||
- | </code> | ||
- | response我除了模擬fault以外,也在body塞Fault.CONNECTION_RESET_BY_PEER;這是另一段故事,請讓我在下一段做說明。 | ||
- | ==== Extend StubMappingTransformer ==== | ||
- | 在套用上面方法後,工作是能夠如下圖執行完畢:\\ | ||
- | {{:java:wiremock:wm_simulate_connection_reset.png|}}\\ | ||
- | 但產生出來的腳本並不包含fault動作,也代表著無法重現整個流程。我Trace了一下WireMock程式碼,發現LoggedResponse轉為ResponseDefinition時,並沒有取用Fault欄位,這導致了資料的遺失:\\ | ||
- | {{:java:wiremock:wm_src_apply_loggedresponse.png|}}\\ | ||
- | 因此我將腦筋動到了另外一個Extension StubMappingTransformer上。與前一個extension成對,當收到500且有Fault的ResponseDefinition時,就會把body內的字串轉為Fault並產生新的ResponseDefinition,最後塞到StubMapping讓它有辦法產生我們預期的內容: | ||
- | <code java> | ||
- | public class FaultStubMappingTransformer extends StubMappingTransformer { | ||
- | |||
- | |||
- | @Override | ||
- | public String getName() { | ||
- | return "FaultStubMappingTransformer"; | ||
- | } | ||
- | |||
- | private Fault getFault(ResponseDefinition responseDef) { | ||
- | String body = Objects.toString(responseDef.getBody(), ""); | ||
- | try { | ||
- | return Fault.valueOf(body); | ||
- | } catch (IllegalArgumentException e) { | ||
- | return null; | ||
- | } | ||
- | } | ||
- | | ||
- | private void setupFaultResponseDef(StubMapping stubMapping) { | ||
- | ResponseDefinition responseDef = stubMapping.getResponse(); | ||
- | Fault fault = getFault(responseDef); | ||
- | if( fault == null ) | ||
- | return; | ||
- | |||
- | ResponseDefinition faultResponseDef = ResponseDefinitionBuilder.like(responseDef).withFault(fault).build(); | ||
- | stubMapping.setResponse(faultResponseDef); | ||
- | } | ||
- | | ||
- | @Override | ||
- | public StubMapping transform(StubMapping stubMapping, FileSource files, Parameters parameters) { | ||
- | ResponseDefinition responseDef = stubMapping.getResponse(); | ||
- | |||
- | if( responseDef.getStatus() == 500) { | ||
- | setupFaultResponseDef(stubMapping); | ||
- | } | ||
- | return stubMapping; | ||
- | } | ||
- | } | ||
- | </code> | ||
- | 最後產生的mapping file終於有fault的字眼: | ||
- | <code json> | ||
- | "response" : { | ||
- | "status" : 500, | ||
- | "base64Body" : "Q09OTkVDVElPTl9SRVNFVF9CWV9QRUVS", | ||
- | "fault" : "CONNECTION_RESET_BY_PEER" | ||
- | } | ||
- | </code> | ||
- | |||
- | ===== ===== | ||
- | ---- | ||
- | \\ | ||
- | ~~DISQUS~~ |