2

I'm struggling with my proof of concept. Here is the main idea:

  • On one side, the REST API Server shall allow to get and send "stream" (as InputStream, not File)
  • On other side, the client shall do the same, reversely, again as InpuStream (possibly into/from File, but having an InputStream will answear all cases)

First, I know that Quarkus Reactive framework allow to send an AsyncFile, but that's not an option there:

  • The content (request/response) is then forwarded to/get from another service (object storage)
  • Not File again since, as it is a microservice, having huge files stored even temporarily within this service is against architecture logic
  • Not in Memory of course for obvious reasons (microservice will not be allowed to have 1 TB of RAM)

I try to find out how to do it, of course taking into account back-pressure to avoid everything (or almost) being in memory, whatever the way (client <-> server). My main issue is currently with Server side when sending InputStream back to client in response.

So my main question is: How can I send through REST HTTP service server side a huge stream without putting everything in memory or into a File with Quarkus? Then the next questions would be:

  • The same but on Client side (probably through WebClient and pipe but difficult to see how to do it since I've got an InputStream as response and would like to have a InputStream to consume, not a WriteStream)?
  • And finally on revese way (receiving InputStream on server side, sent by client)?

I've tried so far several ways:

  • Using Direct sending/receiving of InputStream but leads to Out of Memory
  • Using Reactive sending/receiving of InputStream (using Uni) but same issue
  • Using Reactive sending/receiving of Multi<Buffer> or Multi<byte[]> but same issue
  • Trying to implement back-pressure through request but did not worked out (since HTTP not HTTP/2 I believe)
  • Tring to figure out how to use overflow with Mutiny, but did not found how to do it (to not drop buffers of course)
  • Trying to create a AsyncInputStream, as AsyncFile from vert.x and Mutiny, but cannot get it working
  • Having on Server and Client definition something like (so using RestEasy Mutiny way, both for Server and Client) (note on client, I tried also the vert.x Web client too, but the main issue is on Server side).
@Path("/objects/{name}"}
@POST
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
public Uni<MyResponse> createObject(@RestPath String name, InputStream content) {
   // For the moment not the main issue, but I believed the InpuStream rely in memory, which is very bad
}

@Path("/objects/{name}"}
@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Uni<InputStream> getObject1(@RestPath String name) {
  // OOME
}

@Path("/objects2/{name}"}
@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Multi<Buffer> getObject2(@RestPath String name) {
  // OOME or Blocked
}

@Path("/objects2/{name}"}
@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Multi<byte[]> getObject3(@RestPath String name) {
  // OOME or Blocked
}

So focusing on Server side, I've tried for instance: (pseudo code)

  • Using directly InputStream: limitation to 10 MB (Quarkus default) and in Memory, above is faulty (inputStream is null on client side and no clue if the server sends everything or not, probably not in respect of response time).
  • Without back-pressure: Out of Memory
Multi.createFrom().emitter(em -> {
  // Functional interface as "sendAsRequested"
  while (InputStream as something) {
     byte[] bufferBytes = read from InpuStream
     if (read ended) {
        em.complete();
        break;
     }
     Buffer buffer = Buffer.buffer().appendBytes(bufferBytes);
     em.emit(buffer);    
  }
}).onCompletion().invoke(() -> {
    Close the InputStream
});
  • With back-pressure: But requests never come back to server, even if on Client, for each item I send a request(1)
Multi.createFrom().emitter(em -> {
  // Functional interface as "sendAsRequested"
  long nb = em.requested();
  for (loop on nb) {
     byte[] bufferBytes = read from InpuStream
     if (read ended) {
        em.complete();
        break;
     }
     Buffer buffer = Buffer.buffer().appendBytes(bufferBytes);
     em.emit(buffer);    
  }
}).onRequest().invoke(() -> {
    // will relaunch the functional interface "sendAsRequested"
}).onCompletion().invoke(() -> {
    Close the InputStream
});

And on client side: (seems that requests never reached the server)

Multi<Buffer> received;
received.subscribe().withSubcriber(new MultiSubscriber<Buffer>() {
  Subscription s;
  @Override
  public void onSubscribe(Subscription s) {
    this.s = s;
    s.request(1);
  }
  @Override
  public void onItem(Buffer buffer) {
    s.request(1);
    consume buffer
  }
});
  • Looking at AsyncFile implementation to try to write some "AsyncInputStream", but
    • It seems that current Mutiny implementation does not allow to add a new "type" (various types hard coded)
    • Trying to extend the AsyncFile to "mimic" its behavior for InpuStream backed, but not sure if I'm taking the right direction.

I'm a bit curious that default Quarkus said 10 MB as a limit for body (chunked body is not limited). And it seems that all efforts were going to small items or small streams (as in Json aspect), but none to streams such as InputStream (at the exception of File, but not possible in traditional container microservice way where memory and disk are too small for such File way). I understand the reason (80% of the needs), but it seems that InputStream is lacking of support right now in Quarkus (except if less than 10 MB).

Does someone has any ideas or suggestions?

Frederic Brégier
  • 2,108
  • 1
  • 18
  • 25
  • Just to let you know: using WebClient from VertX seems OK using "pipe" `BodyCodec.pipe(WriteSteam)` (right now dummy client just reading all buffers, doing nothing with it except throw them all). However using "Mutiny" Client seems to not have the "pipe" function...? And of course, now I have to transform WriteStream to InputStream... – Frederic Brégier Jun 06 '22 at 16:14
  • For anyone trying to figure out, I've put a Github test code to show the various issues... https://github.com/fredericBregier/TestInputStreamQuarkus – Frederic Brégier Jul 13 '22 at 15:57

0 Answers0