Skip to content

Commit bfd977d

Browse files
authored
Merge pull request #471 from kachayev/fix-non-existing-file
Catch FileNotFoundException when sending file body, do not print exception stacktrace into HTTP response body
2 parents b5fffea + 9ccb973 commit bfd977d

File tree

3 files changed

+108
-72
lines changed

3 files changed

+108
-72
lines changed

src/aleph/http/core.clj

+77-50
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@
6565
AtomicBoolean]
6666
[javax.net.ssl SSLHandshakeException]))
6767

68+
(def ^CharSequence server-name (HttpHeaders/newEntity "Server"))
69+
(def ^CharSequence connection-name (HttpHeaders/newEntity "Connection"))
70+
(def ^CharSequence date-name (HttpHeaders/newEntity "Date"))
71+
6872
(def non-standard-keys
6973
(let [ks ["Content-MD5"
7074
"ETag"
@@ -275,6 +279,17 @@
275279
(defn chunked-writer-enabled? [^Channel ch]
276280
(some? (-> ch netty/channel .pipeline (.get ChunkedWriteHandler))))
277281

282+
(def default-error-response
283+
{:status 500
284+
:headers {"content-type" "text/plain"}
285+
:body "Internal server error"})
286+
287+
;; Logs exception and returns default 500 response
288+
;; not to expose internal logic to the client
289+
(defn error-response [^Throwable e]
290+
(log/error e "error in HTTP handler")
291+
default-error-response)
292+
278293
(defn send-streaming-body [ch ^HttpMessage msg body]
279294

280295
(HttpUtil/setTransferEncodingChunked msg (boolean (not (has-content-length? msg))))
@@ -441,6 +456,33 @@
441456
(.close raf)
442457
(.close fc)))))
443458

459+
(defn send-contiguous-body [ch ^HttpMessage msg body]
460+
(let [omitted? (identical? :aleph/omitted body)
461+
body (if (or (nil? body) omitted?)
462+
empty-last-content
463+
(DefaultLastHttpContent. (netty/to-byte-buf ch body)))
464+
length (-> ^HttpContent body .content .readableBytes)]
465+
466+
(when-not omitted?
467+
(if (instance? HttpResponse msg)
468+
(let [code (-> ^HttpResponse msg .status .code)]
469+
(when-not (or (<= 100 code 199) (= 204 code))
470+
(try-set-content-length! msg length)))
471+
(try-set-content-length! msg length)))
472+
473+
(netty/write ch msg)
474+
(netty/write-and-flush ch body)))
475+
476+
(defn send-internal-error [ch ^HttpResponse msg]
477+
(let [raw-headers (.headers msg)
478+
headers {:server (.get raw-headers server-name)
479+
:connection (.get raw-headers connection-name)
480+
:date (.get raw-headers date-name)}
481+
resp (-> default-error-response
482+
(update :headers merge headers))
483+
msg' (ring-response->netty-response resp)]
484+
(send-contiguous-body ch msg' (:body resp))))
485+
444486
(defn send-chunked-file [ch ^HttpMessage msg ^HttpFile file]
445487
(let [raf (RandomAccessFile. ^File (.-fd file) "r")
446488
cf (ChunkedFile. raf
@@ -467,34 +509,17 @@
467509
(defn send-file-body [ch ssl? ^HttpMessage msg ^HttpFile file]
468510
(cond
469511
ssl?
470-
(send-streaming-body ch msg
471-
(-> file
472-
(bs/to-byte-buffers {:chunk-size (.-chunk-size file)})
473-
s/->source))
512+
(let [source (-> file
513+
(bs/to-byte-buffers {:chunk-size 1e6})
514+
s/->source)]
515+
(send-streaming-body ch msg source))
474516

475517
(chunked-writer-enabled? ch)
476518
(send-chunked-file ch msg file)
477519

478520
:else
479521
(send-file-region ch msg file)))
480522

481-
(defn send-contiguous-body [ch ^HttpMessage msg body]
482-
(let [omitted? (identical? :aleph/omitted body)
483-
body (if (or (nil? body) omitted?)
484-
empty-last-content
485-
(DefaultLastHttpContent. (netty/to-byte-buf ch body)))
486-
length (-> ^HttpContent body .content .readableBytes)]
487-
488-
(when-not omitted?
489-
(if (instance? HttpResponse msg)
490-
(let [code (-> ^HttpResponse msg .status .code)]
491-
(when-not (or (<= 100 code 199) (= 204 code))
492-
(try-set-content-length! msg length)))
493-
(try-set-content-length! msg length)))
494-
495-
(netty/write ch msg)
496-
(netty/write-and-flush ch body)))
497-
498523
(let [ary-class (class (byte-array 0))
499524

500525
;; extracted to make `send-message` more inlineable
@@ -511,35 +536,37 @@
511536
(defn send-message
512537
[ch keep-alive? ssl? ^HttpMessage msg body]
513538

514-
(let [f (cond
515-
516-
(or
517-
(nil? body)
518-
(identical? :aleph/omitted body)
519-
(instance? String body)
520-
(instance? ary-class body)
521-
(instance? ByteBuffer body)
522-
(instance? ByteBuf body))
523-
(send-contiguous-body ch msg body)
524-
525-
(instance? ChunkedInput body)
526-
(send-chunked-body ch msg body)
527-
528-
(instance? File body)
529-
(send-file-body ch ssl? msg (http-file body))
530-
531-
(instance? Path body)
532-
(send-file-body ch ssl? msg (http-file body))
533-
534-
(instance? HttpFile body)
535-
(send-file-body ch ssl? msg body)
536-
537-
:else
538-
(let [class-name (.getName (class body))]
539-
(try
540-
(send-streaming-body ch msg body)
541-
(catch Throwable e
542-
(log/error e "error sending body of type " class-name)))))]
539+
(let [f (try
540+
(cond
541+
(or
542+
(nil? body)
543+
(identical? :aleph/omitted body)
544+
(instance? String body)
545+
(instance? ary-class body)
546+
(instance? ByteBuffer body)
547+
(instance? ByteBuf body))
548+
(send-contiguous-body ch msg body)
549+
550+
(instance? ChunkedInput body)
551+
(send-chunked-body ch msg body)
552+
553+
(instance? File body)
554+
(send-file-body ch ssl? msg (http-file body))
555+
556+
(instance? Path body)
557+
(send-file-body ch ssl? msg (http-file body))
558+
559+
(instance? HttpFile body)
560+
(send-file-body ch ssl? msg body)
561+
562+
:else
563+
(send-streaming-body ch msg body))
564+
(catch Throwable e
565+
(let [class-name (.getName (class body))]
566+
(log/errorf "error sending body of type %s: %s"
567+
class-name
568+
(.getMessage ^Throwable e)))
569+
(send-internal-error ch msg)))]
543570

544571
(when-not keep-alive?
545572
(handle-cleanup ch f))

src/aleph/http/server.clj

+11-22
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,7 @@
103103
TimeUnit/MILLISECONDS)
104104
(.get ref))))
105105

106-
(defn error-response [^Throwable e]
107-
(log/error e "error in HTTP handler")
108-
{:status 500
109-
:headers {"content-type" "text/plain"}
110-
:body (let [w (java.io.StringWriter.)]
111-
(.printStackTrace e (java.io.PrintWriter. w))
112-
(str w))})
113-
114-
(let [[server-name connection-name date-name]
115-
(map #(HttpHeaders/newEntity %) ["Server" "Connection" "Date"])
116-
117-
[server-value keep-alive-value close-value]
106+
(let [[server-value keep-alive-value close-value]
118107
(map #(HttpHeaders/newEntity %) ["Aleph/0.4.6" "Keep-Alive" "Close"])]
119108
(defn send-response
120109
[^ChannelHandlerContext ctx keep-alive? ssl? rsp]
@@ -123,27 +112,27 @@
123112
[(http/ring-response->netty-response rsp)
124113
(get rsp :body)]
125114
(catch Throwable e
126-
(let [rsp (error-response e)]
115+
(let [rsp (http/error-response e)]
127116
[(http/ring-response->netty-response rsp)
128117
(get rsp :body)])))]
129118

130119
(netty/safe-execute ctx
131120
(let [headers (.headers rsp)]
132121

133-
(when-not (.contains headers ^CharSequence server-name)
134-
(.set headers ^CharSequence server-name server-value))
122+
(when-not (.contains headers ^CharSequence http/server-name)
123+
(.set headers ^CharSequence http/server-name server-value))
135124

136-
(when-not (.contains headers ^CharSequence date-name)
137-
(.set headers ^CharSequence date-name (date-header-value ctx)))
125+
(when-not (.contains headers ^CharSequence http/date-name)
126+
(.set headers ^CharSequence http/date-name (date-header-value ctx)))
138127

139-
(.set headers ^CharSequence connection-name (if keep-alive? keep-alive-value close-value))
128+
(.set headers ^CharSequence http/connection-name (if keep-alive? keep-alive-value close-value))
140129

141130
(http/send-message ctx keep-alive? ssl? rsp body))))))
142131

143132
;;;
144133

145134
(defn invalid-value-response [req x]
146-
(error-response
135+
(http/error-response
147136
(IllegalArgumentException.
148137
(str "cannot treat "
149138
(pr-str x)
@@ -174,7 +163,7 @@
174163
(try
175164
(rejected-handler req')
176165
(catch Throwable e
177-
(error-response e)))
166+
(http/error-response e)))
178167
{:status 503
179168
:headers {"content-type" "text/plain"}
180169
:body "503 Service Unavailable"})))
@@ -183,15 +172,15 @@
183172
(try
184173
(handler req')
185174
(catch Throwable e
186-
(error-response e))))]
175+
(http/error-response e))))]
187176

188177
(-> previous-response
189178
(d/chain'
190179
netty/wrap-future
191180
(fn [_]
192181
(netty/release req)
193182
(-> rsp
194-
(d/catch' error-response)
183+
(d/catch' http/error-response)
195184
(d/chain'
196185
(fn [rsp]
197186
(when (not (-> req' ^AtomicBoolean (.websocket?) .get))

test/aleph/http_test.clj

+20
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,26 @@
423423
:body
424424
bs/to-string)))))
425425

426+
(defn non-existing-file-handler [req]
427+
{:status 200
428+
:body (File. "this-file-does-not-exist.sure")})
429+
430+
(defn- assert-file-error []
431+
(is (= 500 (-> (http/get (str "http://localhost:" port)
432+
{:throw-exceptions? false})
433+
(d/timeout! 1e3)
434+
deref
435+
:status))))
436+
437+
(deftest test-sending-non-existing-file
438+
(testing "sending FileRegion"
439+
(with-handler non-existing-file-handler
440+
(assert-file-error)))
441+
442+
(testing "sending chunked body"
443+
(with-compressed-handler non-existing-file-handler
444+
(assert-file-error))))
445+
426446
;;;
427447

428448
(defn get-netty-client-event-threads []

0 commit comments

Comments
 (0)