Play WS - check compression headers - scala

I enabled gzip compression for all the responses in my web service (Play 2.4) by following those instructions. Easy to set up, and I can see it works like a charm having checked with curl and wireshark that the responses are sent compressed.
Now I want to be a good developer and add an integration test to make sure no one breaks HTTP compression next week. Here's where the fun begins! My test looks like this:
"use HTTP compression" in {
forAll(endPoints) { endPoint =>
val response = await(
WS.url(Localhost + port + "/api" + endPoint).withHeaders("Accept-Encoding" -> "gzip").get()
)
response.header("Content-Encoding") mustBe Some("gzip")
}
}
However, the test fails as WS's response headers don't include content enconding information, and the body is returned as plain text, uncompressed.
[info] - should use HTTP compression *** FAILED ***
[info] forAll failed, because:
[info] at index 0, None was not equal to Some("gzip") (ApplicationSpec.scala:566)
Checking the traffic in wireshark when running this test I can clearly see the server is returning a gzip-encoded response, so it looks like WS is somehow transparently decompressing the response and stripping the content-encoding headers? Is there a way I can get the plain, compressed response with full headers so I can check whether the response is compressed or not?

I don't think you can do that. If I'm not mistaken , the problem here is that Netty return the content already uncompressed, so the header is removed also.
There is a configuration in AsyncHTTPClient to set that (setKeepEncoding), but unfortunately this only works in version 2.0 and newer, and Play 2.4 WS lib uses version 1.9.x.
Either way, the client Play gives you is already configured, and I don't know if you are able to tweak it. But you can create a new client to emulate that behavior:
// Converted from Java code: I have never worked with those APi's in Scala
val cfg = new AsyncHttpClientConfig.Builder().addResponseFilter(new ResponseFilter {
override def filter[T](ctx: FilterContext[T]): FilterContext[T] = {
val headers = ctx.getRequest.getHeaders
if (headers.containsKey("Accept-Encoding")) {
ctx.getResponseHeaders.getHeaders.put("Content-Encoding", List("gzip"))
}
ctx
}
}).build()
val client: NingWSClient = NingWSClient(cfg)
client.url("...") // (...)
Again, this is just emulating the result you need. Also, probably a more clever logic than just add gzip as Content-Encoding (ex: put the first algorithm requested in "Accepts Encoding") is advised.

Turns out we can't really use Play-WS for this specific test because it already returns the content uncompressed and stripped of the header (see #Salem's insightful answer), so there's no way to check whether the response is compressed.
However it's easy enough to write a test that checks for HTTP compression using standard Java classes. All we care about is whether the server answers in (valid) GZIP form when sending a request with Accept-Encoding: gzip. Here's what I ended up with:
forAll(endPoints) { endPoint =>
val url = new URL(Localhost + port + "/api/" + endPoint)
val connection = url.openConnection().asInstanceOf[HttpURLConnection]
connection.setRequestProperty("Accept-Encoding", "gzip")
Try {
new GZIPInputStream(connection.getInputStream)
} must be a 'success
}

Related

Explicitly setting `Content-Length` header in `java.net.http.HttpRequest`

I want to upload a file from InputStream over HTTP, and for this, I am using the new HttpClient provided as part of JDK11. When I try to set the Content-Length while creating a post request I get an IllegalArgumentException saying restricted header name: "Content-Length" and if I try to upload a file from InputStream without the Content-Length header I get an internal server error from the server where I want to upload the file. Is there any option to set the Content-Length in the request in Java 11?
CodeI am using for creating HttpRequest:
var postRequest = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofInputStream(() -> inputStream))
.uri(new URI(url))
.header(HttpHeaders.CONTENT_LENGTH, Long.toString(inputStreamSupplier.getFileSize()))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
.build();
Note: It won't be possible to update to Java 12 to allow the restricted headers.
I could also use another library, just wanted to know if there is an option to use the classes from JDK before switching to RestTemplate from Spring. (yes, it's deprecated, As the alternative uses spring boot can't use it at the moment)
Simply use fromPublisher:
var bodyPublisher = BodyPublishers
.fromPublisher(BodyPublishers.ofInputStream(()-> inputStream)), length);
Note that you must ensure that the InputStream delivers exactly length bytes.

Play! Framework 2.6! Gzip Filter if the response size is greater than 50bytes

I am currently working with Play! Framework 2.6. I am looking into gzipping my response if they are greater than 80bytes. However, With the Framework there is no way to perform this. Based on this Documentation I can make use of the ff code snippet
new GzipFilter(shouldGzip = (request, response) =>
response.body.contentType.exists(_.startsWith("text/html")))
However it did not specify on where would I create this. Any idea how I can specify if it should a gzip a certain response if its greater than 50bytes?
By default, the response bodies are streamed which means you do not know how big the size of the response body will be.
If you know the size of the response body already (e.g. you're serving a file from Amazon S3 already know the file size) You can set the Content-Length header and check it in GzipFilter.
You will also likely need to implement your own GzipFilter and adapt it so it checks the Content-Length.

Is it possible that a simple Akka Http request is so much different from a curl one?

I'm trying to download data from the European Central Bank API. A simple curl works fine:
curl "https://sdw-wsrest.ecb.europa.eu/service/data/EXR/D.USD.EUR.SP00.A?startPeriod=2018-06-06&endPeriod=2018-06-06"
But when I try to achieve the same thing with Akka Http, all of a sudden I get a 500 error. Here is the code (you can run it in the browser and see it for yourself): https://scastie.scala-lang.org/ynqKN3ClQJmPUruybgR37g. Because the URLs are exactly the same, it means that the requests themselves must be different. How is that possible?
It looks like you need to add an Accept header to your request. This seems to work:
val httpRequest = HttpRequest(
method = HttpMethods.GET,
headers = List(Accept()),
uri = uri)
val futureResponse = Http().singleRequest(httpRequest)
It appears that the service you are calling requires the Accept header and curl adds an Accept: */* header by default.

Manually GZIP data over websocket?

I have a web socket proxy that accepts messages and passes them through to clients (browser and Flash).
In an attempt to optimize I was hoping to GZIP the data that goes over that web socket connection. Is this possible, and/or what are the other approaches that might work for this?
I know that there is a WebSocket extension being worked on according to this StockOverflow question.
My current approach within a Scala/Jetty application:
def compressBytes(bytes:Array[Byte]) = {
val bos = new ByteArrayOutputStream
val gzip = new GZIPOutputStream(bos)
gzip.write(bytes)
gzip.close
bos.toByteArray
}
sent to the client:
def onMessage(bytes:Array[Byte], offset:Int, length:Int) {
serverSocket.connection.sendMessage(compressBytes(bytes), offset, length)
}
Side note: I know that the Sec-WebSocket-Extensions: permessage-deflate is a possibility, but not yet full adopted (Jetty 9 has it I believe)
Thanks
With jetty, you can just drop in a servlet filter to do the job: http://www.eclipse.org/jetty/documentation/current/gzip-filter.html

HTTP response content type

I am implementing a file download feature via a servlet and tried using Files.probeContentType without success, it doesn't seem to pick up the right MIME type, so I use a default of application/octet-stream when that happens. In my testing, that setting seems to work ok with all the various file types like tar, gif, gz, mp4, xml, json, work as in the files are downloaded correctly and can be opened with their respective apps.
First question, if anyone can tell me what I am doing wrong with probeContentType or a better way to determine the mime type, that'll be most appreciated, here are the few lines of code in Scala
val is = new FileInputStream(file)
val mt = Files.probeContentType(file.toPath)
val mimetype = if (mt == null) "application/octet-stream" else mt
Regardless, is it ok to always set the HTTP response content-type to application/octet-stream? I have only tested with Chrome and Firefox.
UPDATE In case anyone is wondering, I ended up using MimeUtil courtesy of this post. It works great so far for all the file types I have thrown at it. Here's the snippet of Scala code
import eu.medsea.mimeutil.MimeUtil
val file = new File("path-to-your-file")
MimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector")
val mt = MimeUtil.getMimeTypes(file).toString
Here's a post with helpful code snippets for a few MIME type detection libraries.