I'd like to gzip content before passing it to the client (if client supports it) using the embedded Jetty server with unfiltered. However, I didn't manage to figure out how to do this.Any suggestions?
Hi there,
while the GZipResponseString generally works, I'm not really satisfied, since gzipping content is something that should be generally applied to all routes.
I think the place for that is a Plan.
These bindings are meant to abstract out the server backends, not to
contain application logic. You can write application logic above them in
filters, or higher than that in the server configuration, but most
application logic will go into the intent partial function.
Intents are composable, like any partial function. If you want to do
something for every request then you do it in the outermost function.
You see a bit of this in the SillyStore example:
http://unfiltered.databinder.net/Silly+Store.html
The first Path match has been factored out and made available to a
nested partial function. It isn't transforming the return value of that
function, but it easily could be.
We should start to exploit this in the core library more so that it's
clearer to people what they can do in applications, and to provide more
functionality that is reusable across all backends. I need to think a
little more about what it should be called and what exactly the types
are, but this is certainly feasible:
def intent = unfiltered.intents.Gzip {
case Path(...) => ResponseString(...)
case Path(...) => ResponseString(...)
}
Gzip#apply being a function that takes an intent partial function and
returns an intent partial function.
Nathan
def intent = unfiltered.intents.Gzip {
case Path(...) => ResponseString(...)
case Path(...) => ResponseString(...)
}
I left out handling PassAndThen within the apply() function above, since I was not sure, what to do exactly. I now think wrapping it into a GZipResponseFunction would have too many side effects, so one should simply pass it along:
It may be necessary to wrap it for other use case, though.
Yeah... unfiltered.response.GZip won't work with the Scalate responder
at all, the way it's set up right now.
This reveals a bigger gap in the current response function hierarchy,
and now I see why Gerd was motivated to try to replace the streams in
the response binding. We don't really have much support for doing
streaming output the right way for arbitrarily large responses.
The idea was that you would extend ResponseStreamer and provide your own
implementation of `stream` to write to the outputstream. In writers you
have ResponseWriter. Those are both fine interfaces, but in practice we
have higher level responders (now including the gzip responder) that
serialize the output into one string or byte array.
So I see two problems: first that the provided response functions don't
seem to be meshing very well with common use cases, and second that we
don't give people an easy way to insert a FilterOutputStream into the
pipeline.
I think I have a good solution for the second problem, which is to
provide a copy function in the response binding trait similar to what
you have with case classes, so that you can effectively wrap and replace
the raw outputstream with another which will be used by any response
functions that compose around it. (I *knew* there was a reason I made
the type of these HttpResponse => HttpResponse.)
But that is still tricky to abstract out into an intent helper of the
type that we've been talking about.
def intent = unfiltered.intents.Gzip {
case Path(...) => ResponseString(...)
case Path(...) => ResponseString(...)
}
We need to cut to the front of the line of response functions... and we
can do that. I just need to come up with a way to structure and name all
of this that doesn't look like a total hack, because it's really not. :)
Nathan
It's not... I'm thinking of deprecating PassAndThen entirely, unless someone objects.