2013-12-12 6 views
11

내 질문에, 이미 읽은 링 요청의 본문을 어떻게 읽을 수 있습니까?이미 읽었을 때 링 요청 본문을 읽는 중

여기에 배경이 있습니다. 링 응용 프로그램에 대한 오류 처리기를 쓰고 있습니다. 오류가 발생하면 오류를 재생산하고 해결하는 데 필요한 모든 관련 정보를 포함하여 오류를 기록하려고합니다. 중요한 정보 중 하나가 요청의 본문입니다. 그러나 java.io.InputStream 개체의 형식이므로 :body 값의 상태 저장에 문제가 있습니다.

구체적으로 무슨 일 일부 미들웨어 (내 경우에는 ring.middleware.json/wrap-json-body 미들웨어) 등 slurp 반환 빈 문자열 미래를 호출하는 객체의 내부 상태를 변경 몸 InputStream 객체에 slurp을 수행한다는 것이다. 따라서 요청 본문에서 [본문 내용]이 효과적으로 손실됩니다.

제가 생각할 수있는 유일한 해결책은 시체를 미리 읽을 수 있도록하기 전에 시체를 미리 복사하는 것입니다. 나중에 필요할 수도 있습니다. 나중에이 오류가 발생할 수있는 경우를 대비하여 모든 요청에 ​​대해 일부 작업을 수행하는 것이 어색하기 때문에이 방법이 마음에 들지 않습니다. 더 나은 접근 방법이 있습니까?

답변

6

나는, 몸을 빨아 동일한 내용 스트림으로 대체하고, 나중에 수축 할 수 있도록 원본을 저장하는 LIB 있습니다.

groundhog

이 무한정 열려있는 스트림에 적합하지 않고, 신체 일부 대형 개체의 업로드의 경우 나쁜 생각이다. 그러나 디버깅 프로세스의 일부로 오류 조건을 테스트하고 다시 작성하는 데 도움이됩니다.

스트림의 복제본이 필요하면 자신의 미들웨어의 기초로 groundhog의 tee-stream 기능을 사용할 수 있습니다.

+0

내가 취한 접근 방식은'tee-stream'을 기반으로합니다. 고마워, 그리고 'groundhog'. 나는이 대답을 받아 들일 것이고, 나는 나의 접근법을 별도의 대답으로 자세히 설명 할 것이다. –

1

나는 당신이 일종의 "상황에 맞게 사본을 보관하십시오"전략에 고착되어 있다고 생각합니다. 아주 초기 미들웨어에서, InputStream:body의 InputStream을 포장 : 불행하게도가 요청 must be an InputStream 및 아무것도에 :body처럼 보이는

스케치 (응답에 내가 그것을 언급 왜 이는 String 또는 다른 것들 일 수있다) (example) 닫을 때 자동으로 재설정됩니다. 모든 InputStream을 재설정 할 수있는 것은 아니므로 여기에서 일부 복사를해야 할 수도 있습니다. 래핑 된 스트림은 닫을 때 스트림을 다시 읽을 수 있습니다. 거대한 요청이 있으면 여기 메모리 위험이 있습니다.

업데이트 : 여기 부분적으로는 tee-stream에 의해 영감을 얻은 예기치 않은 시도입니다.

(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

그 접근법은 투명한 재발행을 허용 할 것이다. 불행히도 나를 위해, 실제'InputStream' 객체는 좀 더 구체적으로'reset' 테이블이 아닌'org.eclipse.jetty.server.HttpInput' 객체입니다. 하지만 당신의 접근 방식이 건전하다고 생각합니다. 재설정 불가능한 경우에 작동하는 솔루션을 스케치하거나 며칠 내에 다른 사람이 아무 것도하지 않으면이 대답을 받아 들일 것입니다. –

+0

@JeffTerrell 나는 당신이 BufferedInputStream에있는 HttpInput을 감쌀 수 있다고 생각하고 그것을 다시 설정할 수있는 것으로 감싼다.나는 호기심이 많고 그것을 시도 할 것이다. – overthink

+0

clojure.java.io/input-stream은 BufferedInputStream을 반환합니다. – Alex

3

다음과 같이 @ noisesmith의 기본 접근 방식을 약간 수정하여 채택했습니다. 이러한 각 기능은 링 미들웨어로 사용할 수 있습니다.

(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})))))