关于jetty和webx对于HttpServletResponse getWriter和getOutputStream的处理
这个异常经过在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。