1. 程式人生 > >關於jetty和webx對於HttpServletResponse getWriter和getOutputStream的處理

關於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。