Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add request decompression to server GZip middleware #7119

Open
wants to merge 1 commit into
base: series/0.23
Choose a base branch
from

Conversation

matkob
Copy link

@matkob matkob commented May 17, 2023

Fixes #7072.
Relates to #4981 but doesn't cover client side middleware.

@mergify mergify bot added series/0.23 PRs targeting 0.23.x module:server docs Relates to our website or tutorials labels May 17, 2023
req: Request[F],
): Request[F] = {
val decompressPipe = Compression[F].gunzip(bufferSize = bufferSize).andThenF(_.content)
logger.trace("GZip middleware decoding content").unsafeRunSync()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love running this effect like this, though I see that it is status quo.

Perhaps in 1.x we should move the logging out to a separate wrapper method. Something like,

object Gzip extends GzipPlatform {

  def withLogging[F[_]: Functor, G[_]: Compression: Sync](...):` Http[F, G]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of asking for Sync we should ask for a LoggerFactory. There are old mistakes but let's not repeat them in new code 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see. Makes sense.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like out of scope of this PR, doesn't it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing existing code is out-of-scope. Avoiding writing new code with anti-pattterns (established as they may be) is in-scope :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it! I'll change it a little

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change seems to have some repercussions. Biggest one being breaking compatibility but also when working on it I had an issue with GZipPlatform I couldn't resolve - I couldn't provide enough evidence to create GZip middleware.
How about removing those trace logs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't provide enough evidence to create GZip middleware.

What's the error?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I change the signature of Gzip::apply to something like def apply[F[_]: LoggerFactory: Monad, G[_]: Compression](...) then the arguments of GZipPlatform::apply must change as well. Currently it's

def apply[F[_], G[_]](
      http: Http[F, G],
      bufferSize: Int,
      level: DeflateParams.Level,
      isZippable: Response[G] => Boolean,
      F: Functor[F],
      G: Sync[G],
  ): Http[F, G]

But F: Functor[F] is not enough. I tried various approaches but can't find anything that would work.

@@ -34,12 +34,14 @@ object GZip extends GZipPlatform {
bufferSize: Int = 32 * 1024,
level: DeflateParams.Level = DeflateParams.Level.DEFAULT,
isZippable: Response[G] => Boolean = defaultIsZippable[G](_: Response[G]),
isZipped: Request[G] => Boolean = defaultIsZipped[G](_: Request[G]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matkob you are going to need to add an overload to not break bincompat, e.g.

 def apply[F[_]: Functor, G[_]: Compression](
      http: Http[F, G],
      bufferSize: Int = 32 * 1024,
      level: DeflateParams.Level = DeflateParams.Level.DEFAULT,
      isZippable: Response[G] => Boolean = defaultIsZippable[G](_: Response[G]),
      isZipped: Request[G] => Boolean = defaultIsZipped[G](_: Request[G]),
  ): Http[F, G] = apply(http, bufferSize, level, isZippable, defaultIsZipped[G])

 def apply[F[_]: Functor, G[_]: Compression](
      http: Http[F, G],
      bufferSize: Int = 32 * 1024,
      level: DeflateParams.Level = DeflateParams.Level.DEFAULT,
      isZippable: Response[G] => Boolean = defaultIsZippable[G](_: Response[G]),
      isZipped: Request[G] => Boolean = defaultIsZipped[G](_: Request[G]),
  ): Http[F, G] = ...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So actually, I am not sure if it makes sense to put "Gzip" and "Gunzip" together. Shouldn't they be exposed as different middlewares?

I know there's the issue on the client side, but this PR is about the server.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@armanbilge Yeah, I think that makes sense.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from the user perspective here. I assumed that adding GZip middleware would cover everything related to gzip in my http4s server, so both incoming requests and outgoing responses. I was really surprised it did not.

In #4981 I saw a discussion about that but I'm not convinced there should be separate middlewares for decompressing and compressing, especially because this requires some further renaming and deprecations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a fair point. So for logging we have RequestLogger and ResponseLogger and then Logger which does both. Maybe we need something similar here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are going to need to add an overload to not break bincompat

I wanted to add that overload but it didn't play well with its optional arguments - compiler was complaining about the ambiguity of the signatures. Also, after I removed this additional isZipped argument, bincompat shouldn't be an issue.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for logging we have RequestLogger and ResponseLogger and then Logger which does both. Maybe we need something similar here?

Time will tell, I guess. And if there's need, anyone can contribute such enhanced granularity.

Copy link
Contributor

@wjoel wjoel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would strongly suggest adding a "max uncompressed body size" parameter to this middleware to protect it from gzip bombs.

Adding it as a separate request-only middleware would make the trace thing easy to fix as bincompat wouldn't be an issue.

bufferSize: Int,
req: Request[F],
): Request[F] = {
val decompressPipe = Compression[F].gunzip(bufferSize = bufferSize).andThenF(_.content)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the .handleErrorWith in #5546 (comment) is a good idea to throw MalformedMessageBodyFailure for streams that aren't valid gzip streams, but maybe it could be more specific (and I should have added a test for it).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Relates to our website or tutorials module:server series/0.23 PRs targeting 0.23.x
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add request decompression middleware
4 participants