Skip to content

Commit 31be5f9

Browse files
authored
Merge pull request http4s#1784 from rossabaker/issue-1783
Tighten up logging of errors thrown by services
2 parents 3526427 + 71184e3 commit 31be5f9

File tree

4 files changed

+46
-37
lines changed

4 files changed

+46
-37
lines changed

blaze-server/src/main/scala/org/http4s/server/blaze/Http1ServerStage.scala

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package org.http4s
22
package server
33
package blaze
44

5-
import cats.effect.{Effect, IO}
5+
import cats.data.OptionT
6+
import cats.effect.{Effect, IO, Sync}
67
import cats.implicits._
78
import fs2._
89
import java.nio.ByteBuffer
@@ -60,6 +61,7 @@ private[blaze] class Http1ServerStage[F[_]](
6061

6162
// micro-optimization: unwrap the service and call its .run directly
6263
private[this] val serviceFn = service.run
64+
private[this] val optionTSync = Sync[OptionT[F, ?]]
6365

6466
// both `parser` and `isClosed` are protected by synchronization on `parser`
6567
private[this] val parser = new Http1ServerParser[F](logger, maxRequestLineLen, maxHeadersLen)
@@ -140,19 +142,21 @@ private[blaze] class Http1ServerStage[F[_]](
140142
parser.collectMessage(body, requestAttrs) match {
141143
case Right(req) =>
142144
executionContext.execute(new Runnable {
143-
def run(): Unit =
144-
F.runAsync {
145-
try serviceFn(req)
146-
.getOrElse(Response.notFound)
147-
.handleErrorWith(serviceErrorHandler(req))
148-
catch serviceErrorHandler(req)
149-
} {
150-
case Right(resp) =>
151-
IO(renderResponse(req, resp, cleanup))
145+
def run(): Unit = {
146+
val action = optionTSync
147+
.suspend(serviceFn(req))
148+
.getOrElse(Response.notFound)
149+
.recoverWith(serviceErrorHandler(req))
150+
.flatMap(resp => F.delay(renderResponse(req, resp, cleanup)))
151+
152+
F.runAsync(action) {
153+
case Right(()) => IO.unit
152154
case Left(t) =>
153-
IO(internalServerError(s"Error running route: $req", t, req, cleanup))
155+
IO(logger.error(t)(s"Error running request: $req")).attempt *> IO(
156+
closeConnection())
154157
}
155158
.unsafeRunSync()
159+
}
156160
})
157161
case Left((e, protocol)) =>
158162
badMessage(e.details, new BadRequest(e.sanitized), Request[F]().withHttpVersion(protocol))

blaze-server/src/main/scala/org/http4s/server/blaze/Http2NodeStage.scala

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ package org.http4s
22
package server
33
package blaze
44

5-
import cats.effect.{Effect, IO}
5+
import cats.data.OptionT
6+
import cats.effect.{Effect, IO, Sync}
67
import cats.implicits._
78
import fs2._
89
import fs2.Stream._
910
import java.util.Locale
1011
import org.http4s.{Headers => HHeaders, Method => HMethod}
1112
import org.http4s.Header.Raw
12-
import org.http4s.Status._
1313
import org.http4s.blaze.http.Headers
1414
import org.http4s.blaze.http.http20.{Http2StageTools, NodeMsg}
1515
import org.http4s.blaze.http.http20.Http2Exception._
@@ -33,6 +33,10 @@ private class Http2NodeStage[F[_]](
3333
import Http2StageTools._
3434
import NodeMsg.{DataFrame, HeadersFrame}
3535

36+
// micro-optimization: unwrap the service and call its .run directly
37+
private[this] val serviceFn = service.run
38+
private[this] val optionTSync = Sync[OptionT[F, ?]]
39+
3640
override def name = "Http2NodeStage"
3741

3842
override protected def stageStartup(): Unit = {
@@ -185,21 +189,20 @@ private class Http2NodeStage[F[_]](
185189
val hs = HHeaders(headers.result())
186190
val req = Request(method, path, HttpVersion.`HTTP/2.0`, hs, body, attributes)
187191
executionContext.execute(new Runnable {
188-
def run(): Unit =
189-
F.runAsync {
190-
try service(req)
191-
.getOrElse(Response.notFound)
192-
.recoverWith(serviceErrorHandler(req))
193-
.handleError(_ => Response(InternalServerError, req.httpVersion))
194-
.map(renderResponse)
195-
catch serviceErrorHandler(req)
196-
} {
197-
case Right(_) =>
198-
IO.unit
199-
case Left(t) =>
200-
IO(logger.error(t)("Error rendering response"))
201-
}
202-
.unsafeRunSync()
192+
def run(): Unit = {
193+
val action = optionTSync
194+
.suspend(serviceFn(req))
195+
.getOrElse(Response.notFound)
196+
.recoverWith(serviceErrorHandler(req))
197+
.flatMap(resp => renderResponse(resp))
198+
199+
F.runAsync(action) {
200+
case Right(()) => IO.unit
201+
case Left(t) =>
202+
IO(logger.error(t)(s"Error running request: $req")).attempt *> IO(
203+
shutdownWithCommand(Cmd.Disconnect))
204+
}
205+
}.unsafeRunSync()
203206
})
204207
}
205208
}

server/src/main/scala/org/http4s/server/package.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import cats.implicits._
77
import org.http4s.headers.{Connection, `Content-Length`}
88
import org.http4s.syntax.string._
99
import org.log4s.getLogger
10-
import scala.util.control.NonFatal
1110

1211
package object server {
1312

@@ -101,7 +100,7 @@ package object server {
101100
s"""Message failure handling request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
102101
.getOrElse("<unknown>")}""")
103102
mf.toHttpResponse(req.httpVersion)
104-
case NonFatal(t) =>
103+
case t if !t.isInstanceOf[VirtualMachineError] =>
105104
serviceErrorLogger.error(t)(
106105
s"""Error servicing request: ${req.method} ${req.pathInfo} from ${req.remoteAddr.getOrElse(
107106
"<unknown>")}""")

servlet/src/main/scala/org/http4s/servlet/Http4sServlet.scala

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.http4s
22
package servlet
33

4+
import cats.data.OptionT
45
import cats.effect._
56
import cats.implicits.{catsSyntaxEither => _, _}
67
import fs2.async
@@ -29,6 +30,7 @@ class Http4sServlet[F[_]](
2930

3031
// micro-optimization: unwrap the service and call its .run directly
3132
private[this] val serviceFn = service.run
33+
private[this] val optionTSync = Sync[OptionT[F, ?]]
3234

3335
object ServletRequestKeys {
3436
val HttpSession: AttributeKey[Option[HttpSession]] = AttributeKey[Option[HttpSession]]
@@ -97,10 +99,11 @@ class Http4sServlet[F[_]](
9799
bodyWriter: BodyWriter[F]): F[Unit] = {
98100
ctx.addListener(new AsyncTimeoutHandler(request, bodyWriter))
99101
// Note: We're catching silly user errors in the lift => flatten.
100-
val response = Async.shift(executionContext) *> F
101-
.delay(serviceFn(request).getOrElse(Response.notFound))
102-
.flatten
103-
.handleErrorWith(serviceErrorHandler(request))
102+
val response = Async.shift(executionContext) *>
103+
optionTSync
104+
.suspend(serviceFn(request))
105+
.getOrElse(Response.notFound)
106+
.recoverWith(serviceErrorHandler(request))
104107

105108
val servletResponse = ctx.getResponse.asInstanceOf[HttpServletResponse]
106109
renderResponse(response, servletResponse, bodyWriter)
@@ -122,9 +125,9 @@ class Http4sServlet[F[_]](
122125
s"Async context timed out, but response was already committed: ${request.method} ${request.uri.path}")
123126
F.pure(())
124127
}
125-
} { _ =>
126-
ctx.complete()
127-
IO.unit
128+
} {
129+
case Right(()) => IO(ctx.complete())
130+
case Left(t) => IO(logger.error(t)("Error timing out async context")) *> IO(ctx.complete())
128131
}
129132
}
130133
}

0 commit comments

Comments
 (0)