@@ -3,6 +3,7 @@ package akkahttp
3
3
import akkahttp .ReverseProxy .Mode .Mode
4
4
import com .typesafe .config .{ConfigFactory , ConfigValueFactory }
5
5
import io .circe .*
6
+ import org .apache .pekko .NotUsed
6
7
import org .apache .pekko .actor .ActorSystem
7
8
import org .apache .pekko .http .scaladsl .model .*
8
9
import org .apache .pekko .http .scaladsl .model .Uri .Authority
@@ -13,9 +14,11 @@ import org.apache.pekko.http.scaladsl.settings.ServerSettings
13
14
import org .apache .pekko .http .scaladsl .{Http , HttpExt }
14
15
import org .apache .pekko .pattern .{CircuitBreaker , CircuitBreakerOpenException }
15
16
import org .apache .pekko .stream .ThrottleMode
16
- import org .apache .pekko .stream .scaladsl .{Sink , Source }
17
+ import org .apache .pekko .stream .scaladsl .{Flow , Sink , Source }
18
+ import org .apache .pekko .util .ByteString
17
19
import org .slf4j .{Logger , LoggerFactory }
18
20
21
+ import java .security .MessageDigest
19
22
import java .util .concurrent .ConcurrentHashMap
20
23
import java .util .concurrent .atomic .AtomicInteger
21
24
import scala .collection .parallel .CollectionConverters .ImmutableIterableIsParallelizable
@@ -28,10 +31,11 @@ import scala.util.{Failure, Success}
28
31
* https://github.com/mathieuancelin/akka-http-reverse-proxy
29
32
*
30
33
* Features ReverseProxy:
31
- * - Weighted round robin load balancing
34
+ * - Weighted round- robin load balancing
32
35
* - Retry on HTTP 5xx from target servers
33
36
* - CircuitBreaker per target server to avoid overload
34
37
* - HTTP Header `X-Correlation-ID` for tracing (only for Mode.local)
38
+ * - HTTP Header `X-Content-Hash` as an example of an on-the-fly processing scenario
35
39
*
36
40
* Mode.local:
37
41
* HTTP client(s) --> ReverseProxy --> local target server(s)
@@ -42,7 +46,7 @@ import scala.util.{Failure, Success}
42
46
* Remarks:
43
47
* - The target server selection is via the "Host" HTTP header
44
48
* - Local/Remote target servers are designed to be flaky to show Retry/CircuitBreaker behavior
45
- * - On top of the built in client, you may also try other clients
49
+ * - On top of the built- in client, you may also try other clients
46
50
* - This PoC may not scale well, possible bottlenecks are:
47
51
* - Combination of Retry/CircuitBreaker
48
52
* - Round robin impl. with `requestCounter` means shared state
@@ -164,6 +168,12 @@ object ReverseProxy extends App {
164
168
uri
165
169
}
166
170
171
+ def computeHashWithPayloadAndPayloadLength : Flow [ByteString , (MessageDigest , ByteString , Int ), NotUsed ] =
172
+ Flow [ByteString ].fold((MessageDigest .getInstance(" SHA-256" ), ByteString .empty, 0 )) { (acc, chunk) =>
173
+ acc._1.update(chunk.toByteBuffer)
174
+ (acc._1, acc._2 ++ chunk, acc._3 + chunk.length)
175
+ }
176
+
167
177
services.get(mode) match {
168
178
case Some (rawSeq) =>
169
179
val seq = rawSeq.flatMap(t => (1 to t.weight).map(_ => t))
@@ -179,8 +189,22 @@ object ReverseProxy extends App {
179
189
// If not, clients get 503 from pekko-http
180
190
callTimeout = 10 .seconds,
181
191
resetTimeout = 10 .seconds))
182
- val proxyReq = request.withUri(uri(target)).withHeaders(headers(target))
183
- circuitBreaker.withCircuitBreaker(http.singleRequest(proxyReq))
192
+
193
+ // Example of an on-the-fly processing scenario
194
+ val hashFuture = request.entity.dataBytes
195
+ .via(computeHashWithPayloadAndPayloadLength)
196
+ .runWith(Sink .head)
197
+ .map { case (digest, _, _) =>
198
+ RawHeader (" X-Content-Hash" , digest.digest().map(" %02x" .format(_)).mkString)
199
+ }
200
+
201
+ hashFuture.flatMap { hashHeader =>
202
+ val proxyReq = request
203
+ .withUri(uri(target))
204
+ .withHeaders(headers(target) :+ hashHeader)
205
+ circuitBreaker.withCircuitBreaker(http.singleRequest(proxyReq))
206
+ }
207
+
184
208
}.recover {
185
209
case _ : CircuitBreakerOpenException => BadGateway (id, " Circuit breaker opened" )
186
210
case _ : TimeoutException => GatewayTimeout (id)
0 commit comments