差異處

這裏顯示兩個版本的差異處。

連向這個比對檢視

java:web:resteasy:fetch_raw_message [2018/07/01 22:55]
tony
java:web:resteasy:fetch_raw_message [2023/06/25 09:48]
行 1: 行 1:
-{{tag>​resteasy}} 
-====== RESTEasy - How to fetch raw message after the request is failed? ====== 
-===== Problem ===== 
-RESTEasy jackson provider讓我們可以輕鬆的將第三方服務回應的json內容,轉成我們自己的物件,讓程式碼只專注於資源物件的操作;然而,第三方服務並非永遠都如你預期,它可能突然更新而吐出無法處理的訊息:​ 
-<​code>​ 
-javax.ws.rs.ProcessingException:​ RESTEASY003145:​ Unable to find a MessageBodyReader of content-type text/​html;​charset=iso-8859-1 
-</​code>​ 
-我有幸遇到這樣的問題,也讓我思考著該如何將原始資料記錄下來以回報第三方服務提供者。本篇文章分享可能的作法,可依照你的需求自行挑選。 
-===== How to resolve? ===== 
-==== ReadInterceptor & InputStreamSniffer ==== 
-一開始遇到這個問題時,我的想法是:​ 是否可以透過[[https://​blog.wfyvv.com/​blog/​2018/​03/​jax-rs/​|interceptor]]去處理?​ 我所提供的第一個方法,是註冊一個ReadInterceptor,並且wrap原始的InputStream,將讀取的內容給記錄下來。首先是ReaderInterceptor,它會從Context中取出InputStream,並將其wrap成InputStreamSniffer,待讀取流程執行後,就可以透過getReadContent去拿到讀取的內容:​ 
-<code java> 
-@Provider 
-public class SnifferReadInterceptor implements ReaderInterceptor { 
- 
- private InputStreamSniffer inputStreamSniffer = null; 
-  
- @Override 
- public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException,​ WebApplicationException { 
- InputStream inputStream = context.getInputStream();​ 
-  
- inputStreamSniffer = new InputStreamSniffer(inputStream);​ 
- context.setInputStream(inputStreamSniffer);​ 
-  
- return context.proceed();​ 
- } 
- 
- public String getReadContent(){ 
- return inputStreamSniffer.getContent();​ 
- } 
-} 
-</​code>​ 
-InputStreamSniffer則是將讀取的動作delegate給原始的InputStream,並悄悄地記下讀取內容:​ 
-<code java> 
-public class InputStreamSniffer extends InputStream { 
- 
- private InputStream srcInputStream;​ 
- private StringBuilder contentStringBuilder;​ 
-  
- public InputStreamSniffer(InputStream inputStream){ 
- srcInputStream = inputStream;​ 
- contentStringBuilder = new StringBuilder();​ 
- } 
-  
- @Override 
- public int read() throws IOException { 
- int read_c = srcInputStream.read();​ 
- contentStringBuilder.append((char)read_c);​ 
- return read_c; 
- } 
- // .. skip 
-</​code>​ 
-我簡單的寫了一個測試,想要確認將message轉為json物件發生問題時,readInterceptor是否可以拿到message:​ 
-<code java> 
-@Test 
-public void testSnifferReadInterceptorWithReadFailed(){ 
- Invocation invocation = initInvocation();​ 
-  
- Response response = null; 
- try { 
- response =  invocation.invoke();​ 
- response.readEntity(TestModel.class);​ 
- fail("​should be failed "​);​ 
- } catch( ProcessingException e ) { 
- // Can't find valid messageBodyReader 
- assertTrue(readInterceptor.getReadContent().isEmpty());​ 
- } finally { 
- response.close();​ 
- } 
-} 
-</​code>​ 
-答案是不行,這是由於RESTEasy在讀取訊息前,會先根據Response Header中宣告的MediaType去找尋是否有對應的MessageBodyRead。 
-==== Read All Content of ReadInterceptor ==== 
-由於我們無法於執行過程中解決這個問題,因此改在ReadInterceptor中,先將所有內容讀出來:​ 
-<code java> 
-public class DebugReadInterceptor ​ implements ReaderInterceptor{ 
- 
- private String content; 
- 
- @Override 
- public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException,​ WebApplicationException { 
- InputStream inputStream = context.getInputStream();​ 
- content = IOUtils.toString(inputStream);​ 
-  
- context.setInputStream(new ByteArrayInputStream(content.getBytes()));​ 
- return context.proceed();​ 
- 
-  
- public String getContent(){ 
- return content; 
- } 
-} 
-</​code>​ 
-這個方法是可以解決目前遇到的問題,但缺點是每次請求都一定會多處理一次內容。於是我把腦筋動到了MessageBodyReader身上。 
-==== Customized MessageBodyReader ==== 
-我新增一個名為CompositeMessageBodyProvider的類別,去extend MessageBodyReader;isReadable永遠回傳true,將搜尋合適MessageBodyReader動作交由readFrom處理,如果找不到就丟ProcessingException上去。Client可以攔截此例外決定要處理的方式:​ 
-<code java> 
-@Provider 
-@Consumes(MediaType.WILDCARD) 
-@Produces(MediaType.WILDCARD) 
-public class CompositeMessageBodyProvider implements MessageBodyReader<​Object>,​ MessageBodyWriter<​Object>​ { 
- 
- private List<​MessageBodyReader<​Object>>​ messageBodyReaders = new ArrayList<>​();​ 
-  
- @Override 
- public boolean isReadable(Class<?>​ type, Type genericType,​ Annotation[] annotations,​ MediaType mediaType) { 
- return true; 
- } 
-  
- private Optional<​MessageBodyReader<​Object>>​ findReader(Class<?>​ type, Type genericType,​ Annotation[] annotations,​ MediaType mediaType){ 
- return messageBodyReaders.stream().filter(writter->​writter.isReadable(type,​ genericType,​ annotations,​ mediaType)).findAny();​ 
- } 
- 
- @Override 
- public Object readFrom(Class<​Object>​ type, Type genericType,​ Annotation[] annotations,​ MediaType mediaType, 
- MultivaluedMap<​String,​ String> httpHeaders,​ InputStream entityStream) 
- throws IOException,​ WebApplicationException { 
-  
- Optional<​MessageBodyReader<​Object>>​ reader = findReader(type,​ genericType,​ annotations,​ mediaType); 
- if(reader.isPresent()){ 
- return reader.get().readFrom(type,​ genericType,​ annotations,​ mediaType, httpHeaders,​ entityStream);​ 
- } 
-  
- String output = IOUtils.toString(entityStream);​ 
- throw new ProcessingException("​Unexpected content: " + output); 
- } 
- // .. skip 
-</​code>​ 
-===== Summary ===== 
-以parse error、unexpected media type與unexpected json content(parse ok)三種錯誤來說:​ 
-  - ReadInterceptor & InputStreamSniffer:​ 適用於parse error與unexpected json content。 
-  - Read All Content of ReadInterceptor:​ 適用於以上提及的所有錯誤情形,但效率較差。 
-  - Customized MessageBodyReader:​ 適用於unexpected media type。 
-要使用哪種方式,可仔細評估後再決定。 
-===== Reference ===== 
-  - [[https://​blog.wfyvv.com/​blog/​2018/​03/​jax-rs/​|JAX-RS 2.0 简介及其过滤器与拦截器]] 
-  - [[https://​dennis-xlc.gitbooks.io/​restful-java-with-jax-rs-2-0-en/​cn/​part1/​chapter12/​ordering_filters_and_interceptors.html|Ordering Filters and Interceptors]] 
-=====    ===== 
----- 
-\\ 
-~~DISQUS~~ 
-