Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/sorting/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Clojure 已读取时读取环请求正文_Clojure_Ring - Fatal编程技术网

Clojure 已读取时读取环请求正文

Clojure 已读取时读取环请求正文,clojure,ring,Clojure,Ring,我的问题是,如果已经阅读了Ring请求的正文,我如何习惯性地阅读它 这是背景。我正在为Ring应用程序编写错误处理程序。发生错误时,我希望记录错误,包括复制和修复错误可能需要的所有相关信息。请求的主体是一条重要的信息。但是,:body值的状态性(因为它是java.io.InputStream对象的一种类型)会导致问题 具体来说,发生的情况是一些中间件(在我的例子中是ring.middleware.json/wrap json body中间件)在bodyInputStream对象上执行slurp,

我的问题是,如果已经阅读了Ring请求的正文,我如何习惯性地阅读它

这是背景。我正在为Ring应用程序编写错误处理程序。发生错误时,我希望记录错误,包括复制和修复错误可能需要的所有相关信息。请求的主体是一条重要的信息。但是,
:body
值的状态性(因为它是
java.io.InputStream
对象的一种类型)会导致问题

具体来说,发生的情况是一些中间件(在我的例子中是
ring.middleware.json/wrap json body
中间件)在body
InputStream
对象上执行
slurp
,这会更改对象的内部状态,以便将来对
slurp
的调用返回空字符串。因此,正文的[内容]实际上从请求映射中丢失


我能想到的唯一解决方案是在读取主体之前先复制主体
InputStream
对象,以防以后需要它。我不喜欢这种方法,因为在每个请求上做一些工作似乎很笨拙,以防以后出现错误。有更好的方法吗?

我想你一直在坚持某种“留一份副本以防万一”的策略。不幸的是,在请求上它看起来像
:body
,而在响应上它可以是
字符串
或其他东西,这就是我提到它的原因)

草图:在一个非常早期的中间件中,将
:body
InputStream包装在一个
InputStream
中,它会在关闭时重置自身()。并非所有的
InputStream
s都可以重置,因此您可能需要在此处进行一些复制。一旦包装好,可以近距离重读流,这很好。如果你有巨大的请求,这里会有内存风险

更新:这是一个半生不熟的尝试,部分灵感来自《土拨鼠》中的
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))))

我有一个lib,它吸收了主体,用一个具有相同内容的流替换它,并存储原始内容,以便以后可以对其进行压缩

这对于无限开放的流来说是不够的,如果主体是某个大型对象的上传,这是一个坏主意。但作为调试过程的一部分,它有助于测试和重新创建错误条件


如果您只需要复制流,您可以使用groundhog的函数作为您自己中间件的基础。

我采用了@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})))))

好主意;这种方法将允许透明的重新编码。不幸的是,实际的
InputStream
对象更具体地说是
org.eclipse.jetty.server.HttpInput
对象,它不是
reset
表。但我认为你的方法是正确的。如果你草拟了一个在不可重置的情况下有效的解决方案,或者如果在几天内没有其他人这样做,我会接受这个答案。@JeffTerrell我认为你可以将HttpInput包装在BufferedInputStream中,然后再包装在可重置的流中。我很好奇,打算试试。clojure.java.io/input-stream应该为您返回一个BufferedInputStream。我喜欢用可重置的输入流替换输入流的建议。我可能会在我的土拨鼠项目中借用这个想法(目前我存储/泄气,但自动重置似乎是一个不错的行为)。@noisesmith这种方法的一个缺点,我正在考虑:谁真正关闭了原始流?它被包装在ResettableStream中,其close()被覆盖。我想我更喜欢你的T恤流方法!我采用的方法是基于
tee-stream
。谢谢你,还有土拨鼠。我接受这个答案,我会在一个单独的答案中详细说明我的方法。也许我很傻,但我不太明白这是怎么回事。tee流的全部要点是它返回流的内容和一个新流,但是您忽略了返回的内容,那么它实现了什么呢?