差異處

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

連向這個比對檢視

Both sides previous revision 前次修改
下次修改
前次修改
java:web:resteasy:fetch_raw_message [2018/07/01 21:24]
tony [ReadInterceptor & InputStreamSniffer]
java:web:resteasy:fetch_raw_message [2023/06/25 09:48] (目前版本)
行 1: 行 1:
 {{tag>​resteasy}} {{tag>​resteasy}}
-====== How to fetch raw message after the request is failed? ​(working) ​======+====== ​RESTEasy - How to fetch raw message after the request is failed? ======
 ===== Problem ===== ===== Problem =====
-RESTEasy jackson provider讓我們可以輕鬆的將第三方服務回應的json內容,轉成我們自己的物件,讓程式碼只專注於資源物件的操作;然而,第三方服務並非永遠都如你預期,它可能突然更新甚至吐出你不認得的訊息:+RESTEasy jackson provider讓我們可以輕鬆的將第三方服務回應的json內容,轉成我們自己的物件,讓程式碼只專注於資源物件的操作;然而,第三方服務並非永遠都如你預期,它可能突然更新吐出無法處理的訊息:
 <​code>​ <​code>​
 javax.ws.rs.ProcessingException:​ RESTEASY003145:​ Unable to find a MessageBodyReader of content-type text/​html;​charset=iso-8859-1 javax.ws.rs.ProcessingException:​ RESTEASY003145:​ Unable to find a MessageBodyReader of content-type text/​html;​charset=iso-8859-1
 </​code>​ </​code>​
-我有幸遇到這樣的問題,也讓我思考著該如何將原始資料記錄下來以回報第三方服務提供者。本篇文章分享可能的作法,可依照你的需求自行挑選。+我有幸遇到這樣的問題,也讓我思考著該如何將原始資料記錄下來以回報第三方服務提供者。本篇文章分享可能的作法,可依照你的需求自行挑選。\\ 
 +\\ 
 +(完整程式內容可以從github上clone:​ [[https://​github.com/​frank007love/​ResteasyPractice|link]])
 ===== How to resolve? ===== ===== How to resolve? =====
 ==== ReadInterceptor & InputStreamSniffer ==== ==== ReadInterceptor & InputStreamSniffer ====
-一開始遇到這個問題時,我的想法是:​ 是否可以透過[[https://​blog.wfyvv.com/​blog/​2018/​03/​jax-rs/​|interceptor]]去處理?​ 我所提供的第一個方法,是註冊一個ReadInterceptor,並且wrap原始的InputStream,將讀取的內容給記錄下來。首先是ReaderInterceptor,它會從Context中取出InputStream,並將其wrap成InputStreamSniffer,代整個讀取流程執行後,可以透過getReadContent去拿到讀取的內容:​+一開始遇到這個問題時,我的想法是:​ 是否可以透過[[https://​blog.wfyvv.com/​blog/​2018/​03/​jax-rs/​|interceptor]]去處理?​ 我所提供的第一個方法,是註冊一個ReadInterceptor,並且wrap原始的InputStream,將讀取的內容給記錄下來。首先是ReaderInterceptor,它會從Context中取出InputStream,並將其wrap成InputStreamSniffer,讀取流程執行後,可以透過getReadContent去拿到讀取的內容:​
 <code java> <code java>
 @Provider @Provider
行 70: 行 72:
 } }
 </​code>​ </​code>​
-答案是不行,這是由於RESTEasy。+答案是不行,這是由於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的類別,去implement 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 ===== ===== Reference =====
   - [[https://​blog.wfyvv.com/​blog/​2018/​03/​jax-rs/​|JAX-RS 2.0 简介及其过滤器与拦截器]]   - [[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~~