I have a stream of fs2 streams and I'd like to create a compressed stream ready to be written into file with *.zip extension or for downloading.
The problem is that stream never terminates. Here is the code:
package backup
import java.io.OutputStream
import cats.effect._
import cats.effect.implicits._
import cats.implicits._
import fs2.{Chunk, Pipe, Stream, io}
import java.util.zip.{ZipEntry, ZipOutputStream}
import fs2.concurrent.Queue
import scala.concurrent.{ExecutionContext, SyncVar}
// https://github.com/slamdata/fs2-gzip/blob/master/core/src/main/scala/fs2/gzip/package.scala
// https://github.com/scalavision/fs2-helper/blob/master/src/main/scala/fs2helper/zip.scala
// https://github.com/eikek/sharry/blob/2f1dbfeae3c73bf2623f65c3591d0b3e0691d4e5/modules/common/src/main/scala/sharry/common/zip.scala
object Fs2Zip {
private def writeEntry[F[_]](zos: ZipOutputStream)(implicit F: Concurrent[F],
blockingEc: ExecutionContext,
contextShift: ContextShift[F]): Pipe[F, (String, Stream[F, Byte]), Unit] =
_.flatMap {
case (name, data) =>
val createEntry = Stream.eval(F.delay {
zos.putNextEntry(new ZipEntry(name))
})
val writeEntry = data.through(io.writeOutputStream(F.delay(zos.asInstanceOf[OutputStream]), blockingEc, closeAfterUse = false))
val closeEntry = Stream.eval(F.delay(zos.closeEntry()))
createEntry ++ writeEntry ++ closeEntry
}
private def zipP1[F[_]](implicit F: ConcurrentEffect[F],
blockingEc: ExecutionContext,
contextShift: ContextShift[F]): Pipe[F, (String, Stream[F, Byte]), Byte] = entries => {
Stream.eval(Queue.unbounded[F, Option[Chunk[Byte]]]).flatMap { q =>
Stream.suspend {
val os = new java.io.OutputStream {
private def enqueueChunkSync(a: Option[Chunk[Byte]]) = {
println(s"enqueueChunkSync $a")
val done = new SyncVar[Either[Throwable, Unit]]
q.enqueue1(a).start.flatMap(_.join).runAsync(e => IO(done.put(e))).unsafeRunSync
done.get.fold(throw _, identity)
println(s"enqueueChunkSync done $a")
}
#scala.annotation.tailrec
private def addChunk(c: Chunk[Byte]): Unit = {
val free = 1024 - bufferedChunk.size
if (c.size > free) {
enqueueChunkSync(Some(Chunk.vector(bufferedChunk.toVector ++ c.take(free).toVector)))
bufferedChunk = Chunk.empty
addChunk(c.drop(free))
} else {
bufferedChunk = Chunk.vector(bufferedChunk.toVector ++ c.toVector)
}
}
private var bufferedChunk: Chunk[Byte] = Chunk.empty
override def close(): Unit = {
// flush remaining chunk
enqueueChunkSync(Some(bufferedChunk))
bufferedChunk = Chunk.empty
// terminate the queue
enqueueChunkSync(None)
}
override def write(bytes: Array[Byte]): Unit =
Chunk.bytes(bytes)
override def write(bytes: Array[Byte], off: Int, len: Int): Unit =
addChunk(Chunk.bytes(bytes, off, len))
override def write(b: Int): Unit =
addChunk(Chunk.singleton(b.toByte))
}
val write: Stream[F, Unit] = Stream
.bracket(F.delay(new ZipOutputStream(os)))((zos: ZipOutputStream) => F.delay(zos.close()))
.flatMap((zos: ZipOutputStream) => entries.through(writeEntry(zos)))
val read = q.dequeue
.unNoneTerminate
.flatMap(Stream.chunk(_))
read.concurrently(write)
}
}
}
def zip[F[_]: ConcurrentEffect: ContextShift](entries: Stream[F, (String, Stream[F, Byte])])(
implicit ec: ExecutionContext): Stream[F, Byte] =
entries.through(zipP1)
}
The code is shamelessly copied from https://github.com/eikek/sharry/blob/master/modules/common/src/main/scala/sharry/common/zip.scala
and updated to compile with the latest fs2 and cats-effect
I narrowed the problem to enqueueChunkSync:
private def enqueueChunkSync(a: Option[Chunk[Byte]]) = {
val done = new SyncVar[Either[Throwable, Unit]]
q.enqueue1(a).start.flatMap(_.join).runAsync(e => IO(done.put(e))).unsafeRunSync
done.get.fold(throw _, identity)
}
which blocks on the last chunk. When I put a println in there and make the buffer smaller I see that chunks are flushed successfully until the last one.
When I remove the blocking bit done.get.fold(throw _, identity) it seems to work, but then I imagine the bytes are flushed to the stream all at once?
How is the last chunk different from the previous ones?
Related
I am working with Firebase so I have a lot of return types of type ApiFuture<A>. I'd like to turn them into a Task[A] to work with ZIO effects.
We can create a method to convert all of them using Typeclasses:
trait EffectUtils[F[_]] {
def toEffect[A](a: F[A]): Task[A]
}
object EffectUtils {
implicit val apiFuture: EffectUtils[ApiFuture] = new EffectUtils[ApiFuture] {
override def toEffect[A](a: ApiFuture[A]): Task[A] = Task.effectAsync[A]( cb =>
ApiFutures.addCallback(a, new ApiFutureCallback[A] {
override def onFailure(t: Throwable): Unit = cb(Task.fail(t))
override def onSuccess(result: A): Unit = cb(Task.succeed(result))
})
)
}
implicit class ApiFutureOps[A](f: ApiFuture[A]) {
def toEffect(implicit instance: EffectUtils[ApiFuture]) = instance.toEffect(f)
}
}
Now, when we make an API request and want to convert the result to a ZIO type, it's easy:
import EffectUtils._
object App {
// calling a Firebase function
val record: Task[UserRecord] = firebase.getInstance().auth().getUserByEmailAsync(email).toEffect
I cannot figure out how to replace setTargetFragment() in the code sample below, which is from my Preferencesfragment obviously:
override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat,
pref: Preference
): Boolean {
// Instantiate the new Fragment
val args = pref.extras
val fragment = supportFragmentManager.fragmentFactory.instantiate(
classLoader,
pref.fragment
).apply {
arguments = args
setTargetFragment(caller, 0) // <-- DEPRICATED CODE
}
// Replace the existing Fragment with the new Fragment
supportFragmentManager.beginTransaction()
.replace(R.id.settings, fragment)
.addToBackStack(null)
.commit()
title = pref.title
return true
}
Too many unknowns for my level of knowledge of Android Studio! This reference helps, but still confused:
How to replace setTargetFragment() now that it is deprecated
Well, apparently this works, but I'm not sure that I really understand what is going on:
override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat,
pref: Preference
): Boolean {
// Instantiate the new Fragment
val args = pref.extras
val fragment: Fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment)
fragment.arguments = args
supportFragmentManager.beginTransaction().replace(R.id.settings, fragment).addToBackStack(null).commit()
supportFragmentManager.setFragmentResultListener("requestKey", fragment) { _, _ -> }
return true
}
I am using Vertx. 4.0.3 and trying to stream a request body directly to a file. For that purpose I am using the following (Kotlin) code:
router.post("/upload").handler { ctx ->
val startTime = System.currentTimeMillis()
val response = ctx.response()
val request = ctx.request()
val fs = vertx.fileSystem()
fs.open("data.bin", OpenOptions()) { res ->
if (res.succeeded()) {
val asyncFile = res.result()
request.pipeTo(asyncFile).onComplete { writeResult ->
if(writeResult.succeeded()) {
response.end("${System.currentTimeMillis() - startTime}")
} else {
response.setStatusCode(500).end(res.cause().stackTraceToString())
}
}
} else {
response.setStatusCode(500).end(res.cause().stackTraceToString())
}
}
}
Unfortunately I am getting an exception like:
java.lang.IllegalStateException: Request has already been read
at io.vertx.core.http.impl.Http1xServerRequest.checkEnded(Http1xServerRequest.java:628)
at io.vertx.core.http.impl.Http1xServerRequest.endHandler(Http1xServerRequest.java:334)
at io.vertx.core.http.impl.Http1xServerRequest.endHandler(Http1xServerRequest.java:60)
at io.vertx.core.streams.impl.PipeImpl.<init>(PipeImpl.java:35)
at io.vertx.core.streams.ReadStream.pipeTo(ReadStream.java:119)
at io.vertx.ext.web.impl.HttpServerRequestWrapper.pipeTo(HttpServerRequestWrapper.java:410)
at fileupload.AppKt$main$2$1.handle(App.kt:60)
at fileupload.AppKt$main$2$1.handle(App.kt)
at io.vertx.core.impl.future.FutureImpl$3.onSuccess(FutureImpl.java:124)
at io.vertx.core.impl.future.FutureBase.lambda$emitSuccess$0(FutureBase.java:54)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:497)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:834)
Since I do nothing to the request I have no idea where my request is already read. Can someone please give me some insights into this? Thanks!
This happens because by the time the callback of fs.open is invoked, the request has been fully read already.
You must pause the request before opening the file and resume it after:
router.post("/upload").handler { ctx ->
val startTime = System.currentTimeMillis()
val response = ctx.response()
val request = ctx.request()
val fs = vertx.fileSystem()
// Pause
request.pause()
fs.open("data.bin", OpenOptions()) { res ->
// Resume
request.resume()
if (res.succeeded()) {
val asyncFile = res.result()
request.pipeTo(asyncFile).onComplete { writeResult ->
if(writeResult.succeeded()) {
response.end("${System.currentTimeMillis() - startTime}")
} else {
response.setStatusCode(500).end(res.cause().stackTraceToString())
}
}
} else {
response.setStatusCode(500).end(res.cause().stackTraceToString())
}
}
}
Vert.x for Kotlin provide a equivalent set of suspend functions. In your case you may want to implement the equivalent openAwait and pipeToAwait functions in order to avoid the "callback hell". Now your code might look like this:
router.post("/upload").handler { ctx ->
val startTime = System.currentTimeMillis()
val response = ctx.response()
val request = ctx.request()
val fs = vertx.fileSystem()
val asyncFile = fs.openAwait("data.bin", OpenOptions())
val result = request.pipeToAwait(asyncFile)
// code for sending http response
}
I'm trying to expose an API for a CorDapp and the functions are not displaying. When looking at an example (https://github.com/roger3cev/obligation-cordapp), I receive the following page: https://imgur.com/a/ifOdrAd, however when I load my CorDapp, it says there are no installed Cordapps and /api on the localhost returns a 404. In the project, I feel the problem lies somewhere here: https://github.com/PronoyC/InsureFlight/tree/master/cordapp/src/main/kotlin/com/insureflight. I know this is very vague but I cannot find any errors that indicate a specific area. Any help would be appreciated.
InsureFlightApi.kt:
package com.insureflight
import net.corda.core.contracts.Amount
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import com.insureflight.flows.IssuePolicy
import com.insureflight.flows.PayoutPolicy
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalances
import net.corda.finance.flows.CashIssueFlow
import java.util.*
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.QueryParam
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
import javax.ws.rs.core.Response.Status.BAD_REQUEST
import javax.ws.rs.core.Response.Status.CREATED
#Path("insureflight")
class InsureFlightApi(val rpcOps: CordaRPCOps) {
private val myIdentity = rpcOps.nodeInfo().legalIdentities.first()
#GET
#Path("me")
#Produces(MediaType.APPLICATION_JSON)
fun me() = mapOf("me" to myIdentity)
#GET
#Path("peers")
#Produces(MediaType.APPLICATION_JSON)
fun peers() = mapOf("peers" to rpcOps.networkMapSnapshot()
.filter { nodeInfo -> nodeInfo.legalIdentities.first() != myIdentity }
.map { it.legalIdentities.first().name.organisation })
#GET
#Path("policies")
#Produces(MediaType.APPLICATION_JSON)
fun policies() = rpcOps.vaultQuery(Policy::class.java).states
#GET
#Path("cash")
#Produces(MediaType.APPLICATION_JSON)
fun cash() = rpcOps.vaultQuery(Cash.State::class.java).states
#GET
#Path("cash-balances")
#Produces(MediaType.APPLICATION_JSON)
fun getCashBalances() = rpcOps.getCashBalances()
#GET
#Path("self-issue-cash")
fun selfIssueCash(#QueryParam(value = "amount") amount: Int,
#QueryParam(value = "currency") currency: String): Response {
// 1. Prepare issue request.
val issueAmount = Amount(amount.toLong(), Currency.getInstance(currency))
val notary = rpcOps.notaryIdentities().firstOrNull() ?: throw IllegalStateException("Could not find a notary.")
val issueRef = OpaqueBytes.of(0)
val issueRequest = CashIssueFlow.IssueRequest(issueAmount, issueRef, notary)
// 2. Start flow and wait for response.
val (status, message) = try {
val flowHandle = rpcOps.startFlowDynamic(CashIssueFlow::class.java, issueRequest)
val result = flowHandle.use { it.returnValue.getOrThrow() }
CREATED to result.stx.tx.outputs.single().data
} catch (e: Exception) {
BAD_REQUEST to e.message
}
// 3. Return the response.
return Response.status(status).entity(message).build()
}
#GET
#Path("issue-policy")
fun issuePolicy(#QueryParam(value = "premium") premium: Int,
#QueryParam(value = "currency") currency: String,
#QueryParam(value = "client") client: String,
#QueryParam(value = "underwriter") underwriter: String,
#QueryParam(value = "flight") flight: String,
#QueryParam(value = "fStatus") fStatus: String): Response {
// 1. Create a premium object.
val issuePremium = Amount(premium.toLong() * 100, Currency.getInstance(currency))
// 2. Start the IssuePolicy flow. We block and wait for the flow to return.
val (status, message) = try {
val flowHandle = rpcOps.startFlowDynamic(
IssuePolicy.Initiator::class.java,
issuePremium,
client,
underwriter,
flight,
fStatus,
true
)
val result = flowHandle.use { it.returnValue.getOrThrow() }
CREATED to "Transaction id ${result.id} committed to ledger.\n${result.tx.outputs.single().data}"
} catch (e: Exception) {
BAD_REQUEST to e.message
}
// 3. Return the result.
return Response.status(status).entity(message).build()
}
#GET
#Path("payout-policy")
fun settlePolicy(#QueryParam(value = "id") id: String,
#QueryParam(value = "delayedMinutes") delayedMinutes: Int,
#QueryParam(value = "fStatus") fStatus: String): Response {
// 1. Get party objects for the counterparty.
val linearId = UniqueIdentifier.fromString(id)
// 2. Start the SettlePolicy flow. We block and wait for the flow to return.
val (status, message) = try {
val flowHandle = rpcOps.startFlowDynamic(
PayoutPolicy.Initiator::class.java,
linearId,
delayedMinutes,
fStatus,
true
)
flowHandle.use { flowHandle.returnValue.getOrThrow() }
CREATED to "Policy $linearId has been settled."
} catch (e: Exception) {
BAD_REQUEST to e.message
}
// 3. Return the result.
return Response.status(status).entity(message).build()
}
}
InsureFlightPlugin.kt (ObligationPlugin.kt is very similar to this):
package com.insureflight
import net.corda.core.messaging.CordaRPCOps
import net.corda.webserver.services.WebServerPluginRegistry
import java.util.function.Function
class InsureFlightPlugin : WebServerPluginRegistry {
override val webApis: List<Function<CordaRPCOps, out Any>> = listOf(Function(::InsureFlightApi))
override val staticServeDirs: Map<String, String> = mapOf(
"policy" to javaClass.classLoader.getResource("policyWeb").toExternalForm()
)
}
Kid101 is correct. You've to register the InsureFlightPlugin, like done here: obligation-cordapp/kotlin-source/src/main/resources/META-INF/services/net.corda.webserver.services.WebServerPluginRegistry
I am trying to create an HTTP filter that logs some info about the request, say headers, and a limited (so the memory doesn't explode) part of the request body in case of an error.
To do that I've followed the docs (https://www.playframework.com/documentation/2.6.x/ScalaHttpFilters) and came up with something like this:
class RequestErrorLogFilter #Inject()(actorSystem: ActorSystem)(implicit ec: ExecutionContext)
extends EssentialFilter {
private val logger = org.slf4j.LoggerFactory.getLogger("application.AccumulatorFlowFilter")
private implicit val logging = Logging(actorSystem.eventStream, logger.getName)
override def apply(next: EssentialAction): EssentialAction = new EssentialAction {
override def apply(request: RequestHeader): Accumulator[ByteString, Result] = {
val accumulator: Accumulator[ByteString, Result] = next(request)
val data = ArrayBuffer.empty[ByteString]
var totalSize = 0
val maxSize = 1024
val flow: Flow[ByteString, ByteString, NotUsed] = Flow[ByteString]
.map((in: ByteString) => {
val left = maxSize - totalSize
if (left > 0) {
val takeNow =
if (in.size > left) {
in.slice(0, left)
} else {
in
}
data.append(takeNow)
totalSize += takeNow.size
}
in
})
val accumulatorWithResult = accumulator.through(flow).map { result =>
// this code doesn't get executed in case of an exception in a controller
logger.info(s"The flow has completed and the result is $result")
if (result.header.status >= 400) {
val headerText = data.map(_.utf8String).mkString("")
logger.warn(s"There was an error. Request head: $headerText")
}
result
}
accumulatorWithResult
}
}
}
This works fine for client errors (like 400 - bad request), or for any error returned from a controller, but in case of an exception inside of a controller, filter's "callback" isn't executed, so there's no opportunity to log what happened.
And there's a same problem with a much simpler "AccessLogHttpFilter" which I think is a recommended solution to create an access log with play applications:
class LoggingFilter #Inject() (val mat: Materializer, implicit val ec: ExecutionContext)
extends Filter {
def apply(nextFilter: RequestHeader => Future[Result])
(requestHeader: RequestHeader): Future[Result] = {
val startTime = System.currentTimeMillis
nextFilter(requestHeader).map { result =>
val endTime = System.currentTimeMillis
val requestTime = endTime - startTime
Logger.info(s"${requestHeader.method} ${requestHeader.uri} took ${requestTime}ms and " +
s"returned ${result.header.status}")
result.withHeaders("Request-Time" -> requestTime.toString)
}
}
}
Is there a way to make play invoke http filter code even in case of exceptions?
Is there some other workaround?
Figured it out.
For an EssentialFilter to handle an error you need to add a .recover() call to an accumulator:
class RequestErrorLogFilter #Inject()(actorSystem: ActorSystem)(implicit ec: ExecutionContext)
extends EssentialFilter {
private val logger = org.slf4j.LoggerFactory.getLogger("application.AccumulatorFlowFilter")
private implicit val logging = Logging(actorSystem.eventStream, logger.getName)
override def apply(next: EssentialAction): EssentialAction = new EssentialAction {
override def apply(request: RequestHeader): Accumulator[ByteString, Result] = {
val accumulator: Accumulator[ByteString, Result] = next(request)
val data = ArrayBuffer.empty[ByteString]
var totalSize = 0
val maxSize = 1024
val flow: Flow[ByteString, ByteString, NotUsed] = Flow[ByteString]
.map((in: ByteString) => {
val left = maxSize - totalSize
if (left > 0) {
val takeNow =
if (in.size > left) {
in.slice(0, left)
} else {
in
}
data.append(takeNow)
totalSize += takeNow.size
}
in
})
val accumulatorWithResult: Accumulator[ByteString, Result] = accumulator.through(flow).map { result =>
logger.info(s"The flow has completed and the result is $result")
if (result.header.status >= 400) {
val headerText = data.map(_.utf8String).mkString("")
logger.warn(s"There was an error. Request head: $headerText")
}
result
}
accumulatorWithResult.recover {
case error =>
val headerText = data.map(_.utf8String).mkString("")
logger.warn(s"There was an error: $error. Request head: $headerText")
throw error
}
}
}
}
And for a simple Filter you need a .failed.foreach call on the result future:
class LoggingFilter #Inject() (val mat: Materializer, implicit val ec: ExecutionContext)
extends Filter {
def apply(nextFilter: RequestHeader => Future[Result])
(requestHeader: RequestHeader): Future[Result] = {
val startTime = System.currentTimeMillis
val eventualResult = nextFilter(requestHeader)
eventualResult.failed.foreach { error: Throwable =>
val endTime = System.currentTimeMillis
val requestTime = endTime - startTime
Logger.info(s"${requestHeader.method} ${requestHeader.uri} took ${requestTime}ms and " +
s"returned 500 $error")
}
eventualResult.map { result =>
val endTime = System.currentTimeMillis
val requestTime = endTime - startTime
Logger.info(s"${requestHeader.method} ${requestHeader.uri} took ${requestTime}ms and " +
s"returned ${result.header.status}")
result.withHeaders("Request-Time" -> requestTime.toString)
}
}
}