關於jetty和webx對於HttpServletResponse getWriter和getOutputStream的處理
阿新 • • 發佈:2019-01-06
這個異常經過在jetty的一個簡單程式的測試驗證,確定問題及分析如下:
這個程式在使用response輸出結果時,先呼叫response的getWriter獲得PrintWrite物件後輸出內容,然後再呼叫getOutputStream方法獲得outputStream物件後輸出二進位制內容,然後就跑出上面那個異常了。 這兩個方法在jetty容易中是這麼處理: org.eclipse.jetty.server.Response繼承自j2ee裡面的HttpServletResponse.java類 org.eclipse.jetty.server.Response.java類裡面 public ServletOutputStream getOutputStream() throws IOException { if (_outputState!=NONE && _outputState!=STREAM) 如果狀態為WRITER狀態,則丟擲異常 throw new IllegalStateException("WRITER"); _outputState=STREAM; 把response狀態改為STREAM流狀態 return _connection.getOutputStream(); } public PrintWriter getWriter() throws IOException { if (_outputState!=NONE && _outputState!=WRITER) 如果狀態為STREAM,則丟擲異常 throw new IllegalStateException("STREAM"); /* if there is no writer yet */ if (_writer==null) { /* get encoding from Content-Type header */ String encoding = _characterEncoding; if (encoding==null) { /* implementation of educated defaults */ if(_mimeType!=null) encoding = null; // TODO getHttpContext().getEncodingByMimeType(_mimeType); if (encoding==null) encoding = StringUtil.__ISO_8859_1; setCharacterEncoding(encoding); } /* construct Writer using correct encoding */ _writer = _connection.getPrintWriter(encoding); } _outputState=WRITER; 把response狀態改為WRITER狀態, return _writer; } 也就是說在j2ee,web應用裡面不能同時開啟PrintWriter和OutputStream,否則就是丟擲上面那個異常。 jetty的response裡面有三種狀態: public static final int NONE=0, 未呼叫getPrintWriter和getOutputStream之前的預設狀態 STREAM=1, 二進位制流狀態 呼叫getOutputStream之後的狀態 WRITER=2; 字元流狀態 解決方法: 1.在應用中只使用一個,要麼都用getPrintWriter,要麼都用getOutputStream。 2.在webx 中的com.alibaba.citrus.service.requestcontext.buffered.impl.BufferedResponseImpl.java類中有下面這麼解決方案: /** * 取得輸出流。 * * @return response的輸出流 * @throws IOException 輸入輸出失敗 */ @Override public ServletOutputStream getOutputStream() throws IOException { if (stream != null) { return stream; } if (writer != null) { // 如果getWriter方法已經被呼叫,則將writer轉換成OutputStream // 這樣做會增加少量額外的記憶體開銷,但標準的servlet engine不會遇到這種情形, // 只有少數servlet engine需要這種做法(resin)。 if (writerAdapter != null) { return writerAdapter; } else { log.debug("Attampt to getOutputStream after calling getWriter. This may cause unnecessary system cost."); writerAdapter = new WriterOutputStream(writer, getCharacterEncoding()); return writerAdapter; } } if (buffering) { // 注意,servletStream一旦建立,就不改變, // 如果需要改變,只需要改變其下面的bytes流即可。 if (bytesStack == null) { bytesStack = new Stack<ByteArrayOutputStream>(); } ByteArrayOutputStream bytes = new ByteArrayOutputStream(); bytesStack.push(bytes); stream = new BufferedServletOutputStream(bytes); log.debug("Created new byte buffer"); } else { stream = super.getOutputStream(); } return stream; } /** * 取得輸出字元流。 * * @return response的輸出字元流 * @throws IOException 輸入輸出失敗 */ @Override public PrintWriter getWriter() throws IOException { if (writer != null) { return writer; } if (stream != null) { // 如果getOutputStream方法已經被呼叫,則將stream轉換成PrintWriter。 // 這樣做會增加少量額外的記憶體開銷,但標準的servlet engine不會遇到這種情形, // 只有少數servlet engine需要這種做法(resin)。 if (streamAdapter != null) { return streamAdapter; } else { log.debug("Attampt to getWriter after calling getOutputStream. This may cause unnecessary system cost."); streamAdapter = new PrintWriter(new OutputStreamWriter(stream, getCharacterEncoding()), true); return streamAdapter; } } if (buffering) { // 注意,servletWriter一旦建立,就不改變, // 如果需要改變,只需要改變其下面的chars流即可。 if (charsStack == null) { charsStack = new Stack<StringWriter>(); } StringWriter chars = new StringWriter(); charsStack.push(chars); writer = new BufferedServletWriter(chars); log.debug("Created new character buffer"); } else { writer = super.getWriter(); } return writer; } 所以在我們自己的應用中就不要再呼叫完j2ee的原生response的getPrintWriter之後再呼叫原生的getOutputStream(),或者呼叫原生的response的getOutputStream之後再呼叫getPrintWriter。