2013-12-12 21 views
11

Câu hỏi của tôi là, làm cách nào tôi có thể đọc được phần thân của yêu cầu Ring nếu nó đã được đọc?Đọc chuông yêu cầu nội dung khi đã đọc

Đây là nền. Tôi đang viết một trình xử lý lỗi cho một ứng dụng Ring. Khi xảy ra lỗi, tôi muốn ghi lại lỗi, bao gồm tất cả thông tin có liên quan mà tôi có thể cần để tạo lại và sửa lỗi. Một phần thông tin quan trọng là phần thân của yêu cầu. Tuy nhiên, trạng thái của giá trị :body (vì nó là một loại đối tượng java.io.InputStream) gây ra sự cố.

Cụ thể, những gì xảy ra là một số trung gian (các ring.middleware.json/wrap-json-body middleware trong trường hợp của tôi) làm một slurp trên đối tượng cơ thể InputStream, làm thay đổi trạng thái nội bộ của đối tượng mà các cuộc gọi trong tương lai để slurp trở lại một chuỗi rỗng. Do đó, nội dung [nội dung của] bị mất hiệu quả từ bản đồ yêu cầu.

Giải pháp duy nhất tôi có thể nghĩ là phải sao chép trước đối tượng InputStream đối tượng trước khi có thể đọc được nội dung, chỉ trong trường hợp tôi có thể cần nó sau này. Tôi không thích cách tiếp cận này bởi vì có vẻ vụng về để làm một số công việc trên mọi yêu cầu chỉ trong trường hợp có thể có lỗi sau này. Có cách tiếp cận tốt hơn không?

Trả lời

6

Tôi có một thư viện hút nội dung, thay thế bằng một luồng có nội dung giống hệt nhau và lưu trữ bản gốc để nó có thể bị xì hơi sau đó.

groundhog

Đây không phải là đầy đủ cho các dòng vô thời hạn mở, và là một ý tưởng tồi nếu cơ thể là upload của một số đối tượng lớn. Nhưng nó giúp kiểm tra và tái tạo các điều kiện lỗi như là một phần của quá trình gỡ lỗi.

Nếu tất cả những gì bạn cần là bản sao của luồng, bạn có thể sử dụng chức năng tee-stream từ groundhog làm cơ sở cho phần mềm trung gian của riêng bạn.

+0

Cách tiếp cận tôi thực hiện dựa trên 'tee-stream'. Cảm ơn vì điều đó, và cho 'groundhog'. Tôi chấp nhận câu trả lời này, và tôi sẽ trình bày chi tiết cách tiếp cận của tôi trong một câu trả lời riêng biệt. –

1

Tôi nghĩ bạn đang mắc kẹt với một số loại chiến lược "giữ bản sao xung quanh trường hợp". Đáng tiếc là nó trông giống như :body theo yêu cầu must be an InputStream và không có gì khác (trên phản ứng nó có thể là một String hoặc những thứ khác đó là lý do tôi đề cập đến nó)

Phác thảo: Trong một middleware rất sớm, quấn :body InputStream trong một InputStream tự đặt lại khi đóng (example). Không phải tất cả InputStream có thể được đặt lại, vì vậy bạn có thể cần phải thực hiện một số sao chép tại đây. Sau khi được bọc, luồng có thể được đọc lại gần và bạn tốt. Có nguy cơ bộ nhớ ở đây nếu bạn có yêu cầu khổng lồ.

Cập nhật: đây là một nỗ lực nửa nướng, lấy cảm hứng từ một phần bởi tee-stream trong trò chơi yêu thích.

(require '[clojure.java.io :refer [copy]]) 
(defn wrap-resettable-body 
    [handler] 
    (fn [request] 
    (let [orig-body (:body request) 
      baos (java.io.ByteArrayOutputStream.) 
      _ (copy orig-body baos) 
      ba (.toByteArray baos) 
      bais (java.io.ByteArrayInputStream. ba) 
      ;; bais doesn't need to be closed, and supports resetting, so wrap it 
      ;; in a delegating proxy that calls its reset when closed. 
      resettable (proxy [java.io.InputStream] [] 
         (available [] (.available bais)) 
         (close [] (.reset bais)) 
         (mark [read-limit] (.mark bais read-limit)) 
         (markSupported [] (.markSupported bais)) 
         ;; exercise to reader: proxy with overloaded methods... 
         ;; (read [] (.read bais)) 
         (read [b off len] (.read bais b off len)) 
         (reset [] (.reset bais)) 
         (skip [n] (.skip bais))) 
      updated-req (assoc request :body resettable)] 
     (handler updated-req)))) 
+0

Ý tưởng hay; cách tiếp cận đó sẽ cho phép tái 'slurp'ing trong suốt. Thật không may cho tôi, đối tượng 'InputStream' thực tế cụ thể hơn là một đối tượng' org.eclipse.jetty.server.HttpInput', không phải là 'reset'table. Nhưng tôi nghĩ rằng cách tiếp cận của bạn là âm thanh. Tôi sẽ chấp nhận câu trả lời này nếu bạn phác họa một giải pháp hoạt động trong trường hợp không thể đặt lại, hoặc nếu không ai khác làm như vậy trong một vài ngày. –

+0

@ JeffTerrell Tôi nghĩ rằng bạn có thể bọc HttpInput trong một BufferedInputStream và tiếp tục bọc đó trong một resettable.Tôi tò mò và sẽ thử nó. – overthink

+0

clojure.java.io/input-stream sẽ trả về BufferedInputStream cho bạn. – Alex

3

Tôi đã sử dụng phương pháp tiếp cận cơ bản của @ noisesmith với một vài sửa đổi, như được hiển thị bên dưới. Mỗi chức năng này có thể được sử dụng làm phần mềm trung gian Ring.

(defn with-request-copy 
    "Transparently store a copy of the request in the given atom. 
    Blocks until the entire body is read from the request. The request 
    stored in the atom (which is also the request passed to the handler) 
    will have a body that is a fresh (and resettable) ByteArrayInputStream 
    object." 
    [handler atom] 
    (fn [{orig-body :body :as request}] 
    (let [{body :stream} (groundhog/tee-stream orig-body) 
      request-copy (assoc request :body body)] 
     (reset! atom request-copy) 
     (handler request-copy)))) 

(defn wrap-error-page 
    "In the event of an exception, do something with the exception 
    (e.g. report it using an exception handling service) before 
    returning a blank 500 response. The `handle-exception` function 
    takes two arguments: the exception and the request (which has a 
    ready-to-slurp body)." 
    [handler handle-exception] 
    ;; Note that, as a result of this top-level approach to 
    ;; error-handling, the request map sent to Rollbar will lack any 
    ;; information added to it by one of the middleware layers. 
    (let [request-copy (atom nil) 
     handler (with-request-copy handler request-copy)] 
    (fn [request] 
     (try 
     (handler request) 
     (catch Throwable e 
      (.reset (:body @request-copy)) 
      ;; You may also want to wrap this line in a try/catch block. 
      (handle-exception e @request-copy) 
      {:status 500}))))) 
Các vấn đề liên quan