差異處

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

連向這個比對檢視

Both sides previous revision 前次修改
下次修改
前次修改
java:web:resteasy:fetch_raw_message [2018/07/01 20:16]
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
行 51: 行 53:
  // .. skip  // .. skip
 </​code>​ </​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的類別,去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~~