Kotlin/Java Sending Gzip streaming response - java

I am trying to send a Response containing compressed (gzip) streamingOutput. My current code is :
#Path("/")
#Get
#Produces(MediaType.APPLICATION_JSON)
fun testRessource() : Response {
val streamingOutput = TestOutputStream()
val gzipStreamingOutput = CompressedHttpOutputStream(streamingOutput)
val response = Response.ok(gzipStreamingOutput)
response.setHeader("Content-Encoding", "gzip")
return response
}
class TestOutputStream() : StreamingOutput {
override fun write(outputStream: OutputStream) {
val writer = BufferedWriter(OutputStreamWriter(outputStream))
writer.write("{ "id" : 5 }")
writer.flush()
}
}
class CompressedHttpOutputStream(private val streamingOutput: StreamingOutput) : StreamingOutput {
override fun write(outputStream: OutputStream) {
val os = GZIPOutputStream(outputStream)
streamingOutput.write(os)
os.finish()
}
}
When I do request this service, I get gibberish data in my browser.
It seems like I am missing something even though my response have the following headers correctly set : Content-Encoding : gzip and Transfer-encoding : chunked.
In my unit tests with rest-assured, if I extract the body and read it through a GzipInputStream(), I am able to retrieve the json body.
When I replace :
val os = GZIPOutputStream(outputStream)
with
val os = DeflaterOutputStream(outputStream) and Content-Encoding : deflate
The output is correctly decompressed into json.
I am using Quarkus 2.6.0.Final.
Thank you for your help and insights !

Nevermind, There is no need of CompressedHttpOutputStream. Quarkus implements gzip support for rest endpoints.
I just need to add #GZIP annotation to the endpoint and quarkus.resteasy.gzip.enabled=true in the application.properties.

Related

Quarkus reactive client - sending Multipart from ByteArray

Im trying to use io.quarkus:quarkus-rest-client-reactive-jackson to send a multipart file.
Here is my client class
#RegisterRestClient(configKey = "foo")
interface FooClient {
#POST
#Path("/upload")
fun uploadFile(
#RestForm("file")
#PartType("application/octet-stream")
file: ByteArray
): Uni<String>
}
and here is how I invoke it
val file:ByteArray = storage.readAllBytes("foo", "foo")
fooClient.uploadFile(file = file)
.subscribe()
.with { log.info("upload file result : $it") }
but I always get internal server error
2022-12-24 03:11:55,135 ERROR [io.qua.mut.run.MutinyInfrastructure]
(vert.x-eventloop-thread-0) Mutiny had to drop the following
exception: org.jboss.resteasy.reactive.ClientWebApplicationException:
Received: 'Internal Server Error, status code 500' when invoking: Rest
Client method: 'foo.FooClient#uploadFile'
How to send a multipart file with ByteArray in quarkus reactive?

java.net.Http.HttpClient - LineSubscriber not triggered before Body InputStream is closed

I'm using the following setup to send data to a http server:
Method: Post
Headers:
Transfer-Encoding: Chunked
Expect: continue-100
The server responds with a 100 and I continue to send data to the body of the request
The server sends a response back when it has received a data fragment (that is a line of text)
I want to read the server's data fragment response when it arrives
I'm using the java.net.http.HttpClient in the following way:
val inputStream: PipedInputStream = PipedInputStream()
val outputStream = PipedOutputStream(inputStream)
val request = HttpRequest.newBuilder()
.POST(BodyPublishers.ofInputStream {inputStream})
.expectContinue(true)
.uri(url)
.build()
httpClient.sendAsync(request, BodyHandlers.fromLineSubscriber(MyResponseSubscriber()))
....
outputStream!!.write(msg.toByteArray())
This is the code for the LineSubscriber:
private class MyResponseSubscriber : Subscriber<String> {
private var subscription: Subscription? = null
override fun onSubscribe(subscription: Subscription) {
this.subscription = subscription
subscription.request(1)
}
override fun onNext(item: String?) {
println("-> Received response from server: $item")
subscription!!.request(1)
}
override fun onError(throwable: Throwable?) {
}
override fun onComplete() {
}
}
The problem: The LineSubscriber is not triggered until I close the outputStream, so I will only receive the server responses when the client is finished with sending data. What I really want is to react to the server's messages as soon as they come in. I can see that the onSubscribe method is only called, when outputStream.close was called.
// as soon as this is called, the LineSubscriber's subscription is initiated
outputStream.close()
Is there a configuration option in the HttpClient that I'm missing for this use case?

Response header has duplicated values

I have two simple microservices through which the download file flows, but for some unknown reason some header values are duplicated.
First service where is Response build:
#POST
#Path(PRINT_PREVIEW)
fun print(#Valid printRequest: PrintRequest): Response {
// printPreview is Pair<String, ByteArrayInputStream>
val printPreview = printService.printPreview(printRequest)
return Response.ok(printPreview.second)
.header("Content-Disposition", "attachment; filename=${printPreview.first}")
.type("application/pdf")
.build()
}
And in the second microservice the response is just reused:
CLIENT interface:
#POST
#Path(PRINT_PREVIEW)
fun printPreview(request: PrintRequest): Response
CONTROLLER:
#POST
#Path(PRINT_PREVIEW)
fun printPreview(#Valid request: NewAdhocContractRequest): Response {
log.info("printPreview: request=$request")
return contractService.printPreview(request)
}
And as a result, the response header contains duplicate values:
However, when I println what header the Response has, it looks ok, no duplicates.
println(printPreview.headers)
[Content-Disposition=attachment; filename=Test_KNZ_1BB__modelace_2022722_104020.pdf,Content-Type=application/pdf,transfer-encoding=chunked]
Any idea how to avoid it? Thanks.

How can I pass a JSON object from Javascript to Java

What I am trying to do is initiate an ajax call from my frontend code by user interaction. This calls a Java Restful service that I have written. And this Java function calls another service.
I need that java service in the middle because I need to send the inputs to other service in the format of "MyModel".
The problem is, the AJAX call works but it cannot get the JSON object that I send. You see in the Java function below I create the "param1" : "asdasd" for the second time there. That's because it cannot get the JSON data from front-end. It should be dynamically created with the argument of sendInputs function.
By the way when I debug the value String input is like this: ""
Javascript AJAX call:
var paramData = {"param1" : "asdasd"};
$.ajax({
type : 'GET',
url : "/api/v2/proxy",
dataType : "json",
headers : {
"Service-End-Point" : "http://localhost:9000/service/myService/sendInputs"
},
statusCode : {
200 : function(data) {
}
},
contentType : "application/json",
data : JSON.stringify(paramData),
error : function(error) {
}
});
Java consume:
#GET
#Path("/sendInputs")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public String sendInputs(String input) {
String result = null;
//define the service endpoint to be added the default URL
String serviceEndpoint = "otherService/tool/runTool";
List<MyModel> modelParameterList = new ArrayList<MyModel>();
MyModel inputParameter = null;
inputParameter = new MyModel("param1", "asdasd");
modelParameterList.add(inputParameter);
//convert the Java Map to a json string using Jackson ObjectMapper
String jsonStringOfInputParameters = toJSON(modelParameterList);
WebClient client = WebClient
.create("http://localhost:9000");
result = client.path(serviceEndpoint)
.query("tool", "myTool")
.query("input", jsonStringOfInputParameters)
.accept("application/json")
//tells cxf to convert the json to a string type upon return
.get(String.class);
// Return the json result as a string
return result;
}
your paramData variable is already a valid json. I do not think you need yo use JSON.Stringify() again.And what is this is the ajax call:
statusCode : {
200 : function(data) {
}
}
Status code is supposed to be coming from the server in response.
First, your ajax header should be like this:
headers: {
Accept: "application/json; charset=utf-8",
"Content-Type": "application/json; charset=utf-8"
},
url:
url: "http://localhost:9000/service/myService/sendInputs"
Second, you need to have MyModel with param1 field and Also setters and getters. And this can be your service method:
public String sendInputs(MyModel model)
{
//model.getParam1() will be "asdasd"
}

How to write a Restful client using the same Interface defined for the server

I am writing a Restful service using Scala.
On the server side, it has a interface:
trait ICustomerService {
#GET
#Path("/{id}")
#Produces(Array("application/xml"))
def getCustomer(#PathParam("id") id: Int): StreamingOutput
}
The service works fine and I tested it using web browser.
Now I want to write some automated test to this interface. The way I need to do is to write a RESTEasy client using the same interface:
class CustomerServiceProxy(url : String) {
RegisterBuiltin.register(ResteasyProviderFactory.getInstance());
val proxy = ProxyFactory.create(classOf[ICustomerService], url)
def getCustomer(id: Int): Customer = {
val streamingOutput = proxy.getCustomer(id)
<Problem here>
}
}
This code will not work as the streaming output only allow writing.
How do I write this test class so that I can get what the server write into the streamingoutput from the client side?
Many thanks
The StreamingOutput doesn't allow writing, it performs writing. All you have to do is make your own OutputStream to capture it:
/**
* Re-buffers data from a JAXRS StreamingOutput into a new InputStream
*/
def rebuffer(so: StreamingOutput): InputStream = {
val os = new ByteArrayOutputStream
so.write(os)
new ByteArrayInputStream(os.toByteArray())
}
def getCustomer(id: Int): Customer = {
val streamingOutput = proxy.getCustomer(id)
val inputStream = rebuffer(streamingOutput)
inputStream.read() // or pass it to an XML parser or whatever
}
Hope this helps!

Categories