/
BooksApi.scala
70 lines (66 loc) · 3.37 KB
/
BooksApi.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.wlangiewicz.akkaetags
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.model.HttpResponse
import akka.http.scaladsl.model.StatusCodes.NotFound
import akka.http.scaladsl.server.Route
class BooksApi(booksService: BooksService) extends JsonFormats with ETags {
val routes: Route =
get {
// This is the implementation of the same endpoint that doesn't use ETags of Last-Modified optimizations, it will always fetch full data from the DB
// But it's also much more easier to understand
path("books" / IntNumber) { id =>
complete {
booksService.findById(id) match {
case Some(book) => book
case None => HttpResponse(NotFound, entity = "Not found")
}
}
} ~
// There are 3 cases to consider
path("books-etags" / IntNumber) { id =>
optionalHeaderValueByName("If-None-Match") {
case Some(_) =>
// First case, we get request with some value of "If-None-Match" header, right now we are unable to say if it's valid ETag
booksService.getBookLastUpdatedById(id) match {
case Some(lastUpdated) =>
println(s"book id: $id exists and If-None-Match was passed")
// "conditional" directive receives the ETag extracted from the request, and compares it to "lightweightBookETag(lastUpdated)"
// which was calculated only based on the date of the book - we didn't have to fetch full object from the DB
conditional(lightweightBookETag(lastUpdated), lastUpdated) {
// "conditional" directive will return 304 if ETag or "If-Modified-Since" were valid, in this case we don't need to fetch anything more from DB
complete {
// If ETag was invalid (for example outdated), we continue, this time fetching full object from DB (which is more time consuming) and return it normally
booksService.findById(id) match {
case Some(book) =>
println(s"returning complete Book object with id: ${book.id} - ETag was invalid")
book
case None =>
throw new RuntimeException("This shouldn't happen in sample project")
}
}
}
case None => complete {
// If resource doesn't exist we don't set any headers
HttpResponse(NotFound, entity = "Not found")
}
}
case None =>
// Second case, request doesn't contain "If-None-Match" header, we know that we have to return 200 with full body, so we do that (alternatively we return 404 if resource wasn't found)
booksService.findById(id) match {
case Some(book) =>
println(s"book id: $id exists and If-None-Match was NOT passed")
conditional(bookETag(book), book.lastUpdated) {
complete {
book
}
}
case None =>
complete {
HttpResponse(NotFound, entity = "Not found")
}
}
}
}
}
}