2012-01-19 29 views
25

Tôi đã tạo một bộ lọc trong máy chủ web java của tôi (appengine thực sự) ghi nhật ký các tham số của một yêu cầu gửi đến. Tôi cũng muốn ghi lại phản hồi kết quả mà máy chủ web của tôi viết. Mặc dù tôi có quyền truy cập vào đối tượng phản hồi, tôi không chắc chắn làm cách nào để nhận được phản hồi chuỗi/nội dung thực tế từ nó.Cách đọc và sao chép nội dung luồng đầu ra phản hồi servlet HTTP để ghi nhật ký

Bất kỳ ý tưởng nào?

+0

Bạn viết câu trả lời bằng cách nào? 'response.getWriter(). write (yourResponseString)' ??? Hay bạn đang làm điều gì đó khác biệt? Bạn có muốn viết lỗi không? (Nói cách khác, bạn có muốn ghi lại phản hồi khi bạn đang thực hiện 'response.sendError (yourError)' ??) – Dave

+1

có lẽ http://java.sun.com/blueprints/corej2eepatterns/Patterns/InterceptingFilter.html này và http://docstore.mik.ua/orelly/xml/jxslt/ch08_04.htm này có thể cung cấp cho bạn gợi ý –

+0

@Dave chỉ sử dụng response.getWriter(). write (yourResponseString) như bạn đã đề cập và đó là kết quả cũ của tôi muốn chụp. – aloo

Trả lời

74

Bạn cần phải tạo một Filter trong đó bạn quấn luận ServletResponse với một thực hiện tùy chỉnh HttpServletResponseWrapper trong đó bạn ghi đè lên getOutputStream()getWriter() để trả về một thực hiện tùy chỉnh ServletOutputStream trong đó bạn sao chép các byte bằng văn bản (s) trong phương thức trừu tượng cơ sở OutputStream#write(int b). Sau đó, bạn vượt qua tùy chọn được bao bọc HttpServletResponseWrapper cho cuộc gọi FilterChain#doFilter() thay vào đó và cuối cùng bạn sẽ có thể nhận được phản hồi được sao chép sau cuộc gọi.

Nói cách khác, Filter sự:

@WebFilter("/*") 
public class ResponseLogger implements Filter { 

    @Override 
    public void init(FilterConfig config) throws ServletException { 
     // NOOP. 
    } 

    @Override 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { 
     if (response.getCharacterEncoding() == null) { 
      response.setCharacterEncoding("UTF-8"); // Or whatever default. UTF-8 is good for World Domination. 
     } 

     HttpServletResponseCopier responseCopier = new HttpServletResponseCopier((HttpServletResponse) response); 

     try { 
      chain.doFilter(request, responseCopier); 
      responseCopier.flushBuffer(); 
     } finally { 
      byte[] copy = responseCopier.getCopy(); 
      System.out.println(new String(copy, response.getCharacterEncoding())); // Do your logging job here. This is just a basic example. 
     } 
    } 

    @Override 
    public void destroy() { 
     // NOOP. 
    } 

} 

Tục HttpServletResponseWrapper:

public class HttpServletResponseCopier extends HttpServletResponseWrapper { 

    private ServletOutputStream outputStream; 
    private PrintWriter writer; 
    private ServletOutputStreamCopier copier; 

    public HttpServletResponseCopier(HttpServletResponse response) throws IOException { 
     super(response); 
    } 

    @Override 
    public ServletOutputStream getOutputStream() throws IOException { 
     if (writer != null) { 
      throw new IllegalStateException("getWriter() has already been called on this response."); 
     } 

     if (outputStream == null) { 
      outputStream = getResponse().getOutputStream(); 
      copier = new ServletOutputStreamCopier(outputStream); 
     } 

     return copier; 
    } 

    @Override 
    public PrintWriter getWriter() throws IOException { 
     if (outputStream != null) { 
      throw new IllegalStateException("getOutputStream() has already been called on this response."); 
     } 

     if (writer == null) { 
      copier = new ServletOutputStreamCopier(getResponse().getOutputStream()); 
      writer = new PrintWriter(new OutputStreamWriter(copier, getResponse().getCharacterEncoding()), true); 
     } 

     return writer; 
    } 

    @Override 
    public void flushBuffer() throws IOException { 
     if (writer != null) { 
      writer.flush(); 
     } else if (outputStream != null) { 
      copier.flush(); 
     } 
    } 

    public byte[] getCopy() { 
     if (copier != null) { 
      return copier.getCopy(); 
     } else { 
      return new byte[0]; 
     } 
    } 

} 

Tục ServletOutputStream:

public class ServletOutputStreamCopier extends ServletOutputStream { 

    private OutputStream outputStream; 
    private ByteArrayOutputStream copy; 

    public ServletOutputStreamCopier(OutputStream outputStream) { 
     this.outputStream = outputStream; 
     this.copy = new ByteArrayOutputStream(1024); 
    } 

    @Override 
    public void write(int b) throws IOException { 
     outputStream.write(b); 
     copy.write(b); 
    } 

    public byte[] getCopy() { 
     return copy.toByteArray(); 
    } 

} 
+0

Tên hàm tạo cho 'HttpServletResponseCopier' không chính xác, tôi không thể chỉnh sửa vì chỉnh sửa phải dài hơn 6 ký tự và tôi không muốn thay đổi bất kỳ điều gì khác về câu trả lời. – vahidg

+0

@Chishi: cảm ơn bạn, tôi đã khắc phục lỗi đánh máy. – BalusC

+1

tự hỏi tại sao nó quá phức tạp để có được cơ thể phản ứng. Nó phải giống như response.getContent(). Phải là một số lý do vững chắc đằng sau nó :) – antnewbee

2

Tôi không quen thuộc với appengine nhưng bạn cần một cái gì đó Access Log Valve trong Tomcat. Thuộc tính của nó mẫu; một bố cục định dạng xác định các trường thông tin khác nhau từ yêu cầu và đáp ứng được ghi lại, hoặc từ chung hoặc kết hợp để chọn một định dạng chuẩn.

Có vẻ appengine đã tích hợp chức năng cho log filtering.

apply a servlet filter

1

Trong khi BalusC's answer sẽ làm việc trong hầu hết các tình huống bạn phải hãy cẩn thận với flush gọi - nó cam kết phản ứng và không có văn bản khác để nó là có thể, ví dụ như. qua các bộ lọc sau. Chúng tôi đã tìm thấy một số vấn đề với cách tiếp cận rất tương tự trong môi trường Websphere nơi phản hồi được phân phối chỉ là một phần.

Theo số this question không nên gọi xả, bạn nên để nó được gọi nội bộ.

Tôi đã giải quyết vấn đề xả bằng cách sử dụng TeeWriter (nó chia luồng thành 2 luồng) và sử dụng các luồng không đệm trong "luồng phân nhánh" cho mục đích ghi nhật ký. Nó là không cần thiết để gọi flush sau đó.

private HttpServletResponse wrapResponseForLogging(HttpServletResponse response, final Writer branchedWriter) { 
    return new HttpServletResponseWrapper(response) { 
     PrintWriter writer; 

     @Override 
     public synchronized PrintWriter getWriter() throws IOException { 
      if (writer == null) { 
       writer = new PrintWriter(new TeeWriter(super.getWriter(), branchedWriter)); 
      } 
      return writer; 
     } 
    }; 
} 

Sau đó, bạn có thể sử dụng nó theo cách này:

protected void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException { 
    //... 
    StringBuilderWriter branchedWriter = new org.apache.commons.io.output.StringBuilderWriter(); 
    try { 
     chain.doFilter(request, wrapResponseForLogging(response, branchedWriter)); 
    } finally { 
     log.trace("Response: " + branchedWriter); 
    } 
} 

Mã này được đơn giản hóa cho brewity.

6

Giải pháp BalusC là ok, nhưng ít lỗi thời. Mùa xuân hiện có tính năng cho nó. Tất cả những gì bạn cần làm là sử dụng [ContentCachingResponseWrapper], có phương thức public byte[] getContentAsByteArray().

Tôi đề nghị tạo WrapperFactory để cho phép cấu hình, cho dù sử dụng ResponseWrapper hoặc ContentCachingResponseWrapper mặc định.

Các vấn đề liên quan