2015-09-09 14 views
20

Tôi cần đăng nhập các yêu cầu máy khách http của Akka cũng như các câu trả lời của họ. Mặc dù có vẻ là một gợi ý về API để ghi lại các yêu cầu này nhưng không có tài liệu rõ ràng về cách thực hiện. Biện pháp của tôi đã được để tạo ra một yêu cầu đăng nhập mà minh bạch kết thúc tốt đẹp Http().singleRequest(req) như sau:Làm thế nào để đăng nhập yêu cầu khách hàng HTTP Akka

def loggedRequest(req: HttpRequest) 
        (implicit system: ActorSystem, ctx: ExecutionContext, m: Materializer): Future[HttpResponse] = { 

    Http().singleRequest(req).map { resp ⇒ 
    Unmarshal(resp.entity).to[String].foreach{s ⇒ 
     system.log.info(req.toString) 
     system.log.info(resp.toString + "\n" + s) 
    } 
    resp 
    } 
} 

Thật không may, tôi phải lấy tương lai hoặc thông qua các unmarshal hoặc chỉ đơn giản yêu cầu resp.entity.dataBytes để thu hồi phần thân của phản ứng. Tôi nhận được khai thác gỗ nhưng lời hứa được hoàn thành và tôi không còn có thể so sánh thực thể với dữ liệu thực tế nữa. Một giải pháp làm việc sẽ đăng nhập theo yêu cầu và đáp ứng và vượt qua kiểm tra trường hợp này mà không có một IllegalStateException với "Promise đã hoàn thành" bị ném:

describe("Logged rest requests") { 

    it("deliver typed responses") { 
    val foo = Rest.loggedRequest(Get(s"http://127.0.0.1:9000/some/path")) 
    val resp = foo.futureValue(patience) 
    resp.status shouldBe StatusCodes.OK 
    val res = Unmarshal(resp.entity).to[MyClass].futureValue 
    } 
} 

Ý tưởng chào đón.

+1

Tôi đang cố gắng thực hiện tương tự. Bạn đã tìm thấy một giải pháp? –

Trả lời

22

Một trong những giải pháp tôi đã tìm thấy là sử dụng một:

import akka.http.scaladsl.server.directives.DebuggingDirectives 

val clientRouteLogged = DebuggingDirectives.logRequestResult("Client ReST", Logging.InfoLevel)(clientRoute) 
Http().bindAndHandle(clientRouteLogged, interface, port) 

Mà có thể dễ dàng đăng nhập theo yêu cầu và kết quả là liệu định dạng (byte). Vấn đề là các bản ghi đó hoàn toàn không đọc được. Và đây là nơi nó trở nên phức tạp.

Đây là ví dụ của tôi mã hóa thực thể của yêu cầu/phản hồi và ghi nó vào trình ghi nhật ký.

Bạn có thể vượt qua một chức năng để:

DebuggingDirectives.logRequestResult 

def logRequestResult(magnet: LoggingMagnet[HttpRequest ⇒ RouteResult ⇒ Unit]) 

Đó là chức năng được viết bằng magnet pattern:

LoggingMagnet[HttpRequest ⇒ RouteResult ⇒ Unit] 

đâu:

LoggingMagnet[T](f: LoggingAdapter ⇒ T) 

Nhờ đó chúng ta có quyền truy cập vào tất cả các phần chúng ta cần phải ghi lại yêu cầu và kết quả. Chúng tôi có LoggingAdapter, HttpRequest và RouteResult

Trong trường hợp của tôi, tôi đã tạo một hàm bên trong. Tôi không muốn vượt qua tất cả các thông số một lần nữa.

def logRequestResult(level: LogLevel, route: Route) 
         (implicit m: Materializer, ex: ExecutionContext) = { 
    def myLoggingFunction(logger: LoggingAdapter)(req: HttpRequest)(res: Any): Unit = { 
    val entry = res match { 
     case Complete(resp) => 
     entityAsString(resp.entity).map(data ⇒ LogEntry(s"${req.method} ${req.uri}: ${resp.status} \n entity: $data", level)) 
     case other => 
     Future.successful(LogEntry(s"$other", level)) 
    } 
    entry.map(_.logTo(logger)) 
    } 
    DebuggingDirectives.logRequestResult(LoggingMagnet(log => myLoggingFunction(log)))(route) 
} 

Phần quan trọng nhất là dòng cuối cùng tôi đặt chức năngLoggingFunction vào logRequestResult.

Hàm có tên myLoggingFunction, đơn giản khớp với kết quả tính toán máy chủ và tạo LogEntry dựa trên nó.

Điều cuối cùng là phương pháp cho phép giải mã thực thể kết quả từ luồng.

def entityAsString(entity: HttpEntity) 
        (implicit m: Materializer, ex: ExecutionContext): Future[String] = { 
entity.dataBytes 
    .map(_.decodeString(entity.contentType().charset().value)) 
    .runWith(Sink.head) 
} 

Phương pháp này có thể dễ dàng thêm vào bất kỳ tuyến đường akka-http nào.

val myLoggedRoute = logRequestResult(Logging.InfoLevel, clinetRoute) 
Http().bindAndHandle(myLoggedRoute, interface, port) 
+1

Vui lòng sửa lỗi chính tả. Vui lòng chỉnh sửa sự chú ý. Và có thể thêm một chút thông tin về chính xác mã của bạn đang làm gì. –

+4

AFAICS câu hỏi là về yêu cầu ghi nhật ký và phản hồi tại máy khách, trong khi câu trả lời này là về yêu cầu ghi nhật ký và phản hồi tại máy chủ, phải không? –

+1

Đây là quá nhiều công việc chỉ cho mối quan tâm cắt chéo như khai thác gỗ. Nó phải đơn giản. Bên cạnh đó, tôi đồng ý với Arnout rằng điều này không cung cấp giải pháp về việc ghi lại các yêu cầu của khách hàng. – vijar

4

Đối với một giải pháp khác, mã này ghi lại yêu cầu IP và liên kết một số ngẫu nhiên với mỗi yêu cầu và phản hồi để chúng có thể được liên kết trong nhật ký. Nó cũng ghi lại thời gian phản ứng.

Vì yêu cầu có thể mất một lúc để xử lý và có thể không thành công, tôi muốn xem yêu cầu ngay lập tức và xem phản hồi nếu và thời điểm yêu cầu trả lại.

RequestFields chỉ là dữ liệu tôi quan tâm từ yêu cầu. Có rất nhiều tiếng ồn theo mặc định.

val logRequestResponse: Directive0 = 
    extractRequestContext flatMap { ctx => 
    extractClientIP flatMap { ip => 
     val id = scala.math.abs(rand.nextLong).toString 
     onSuccess(RequestFields.fromIdIpAndRequest(id, ip, ctx.request)) flatMap { req => 
     logger.info("request", req.asJson) 
     val i = Instant.now() 
     mapRouteResultWith { result => 
      Result.fromIdStartTimeAndRouteResult(id, i, result) map { res => 
      logger.info("response", res.asJson) 
      result 
     } 
     } 
    } 
    } 
} 
Các vấn đề liên quan