Use GZIP, JSON responses and JQuery - java

However, I want to compress my responses with GZIP wheren possible. I tried using the Compression filter code available for free download in the headfirst site. It works great for html, images, css and javascript.
I post the filter next. It checks if GZIP is an accepted encoding and it adds gzip as Content-Encoding. See: wrappedResp.setHeader("Content-Encoding", "gzip");
public class CompressionFilter implements Filter {
private ServletContext ctx;
private FilterConfig cfg;
/**
* The init method saves the config object and a quick reference to the
* servlet context object (for logging purposes).
*/
public void init(FilterConfig cfg)
throws ServletException {
this.cfg = cfg;
ctx = cfg.getServletContext();
//ctx.log(cfg.getFilterName() + " initialized.");
}
/**
* The heart of this filter wraps the response object with a Decorator
* that wraps the output stream with a compression I/O stream.
* Compression of the output stream is only performed if and only if
* the client includes an Accept-Encoding header (specifically, for gzip).
*/
public void doFilter(ServletRequest req,
ServletResponse resp,
FilterChain fc)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// Dose the client accept GZIP compression?
String valid_encodings = request.getHeader("Accept-Encoding");
if ( (valid_encodings != null) && (valid_encodings.indexOf("gzip") > -1) ) {
// Then wrap the response object with a compression wrapper
// We'll look at this class in a minute.
CompressionResponseWrapper wrappedResp = new CompressionResponseWrapper(response);
// Declare that the response content is being GZIP encoded.
wrappedResp.setHeader("Content-Encoding", "gzip");
// Chain to the next component (thus processing the request)
fc.doFilter(request, wrappedResp);
// A GZIP compression stream must be "finished" which also
// flushes the GZIP stream buffer which sends all of its
// data to the original response stream.
GZIPOutputStream gzos = wrappedResp.getGZIPOutputStream();
gzos.finish();
// The container handles the rest of the work.
//ctx.log(cfg.getFilterName() + ": finished the request.");
} else {
fc.doFilter(request, response);
//ctx.log(cfg.getFilterName() + ": no encoding performed.");
}
}
public void destroy() {
// nulling out my instance variables
cfg = null;
ctx = null;
}
}
I was using the next code to send JSON responses in Struts web application.
public ActionForward get(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) {
JSONObject json = // Do some logic here
RequestUtils.populateWithJSON(response, json);
return null;
}
public static void populateWithJSON(HttpServletResponse response,JSONObject json) {
if(json!=null) {
response.setContentType("text/x-json;charset=UTF-8");
response.setHeader("Cache-Control", "no-cache");
try {
response.getWriter().write(json.toString());
} catch (IOException e) {
throw new ApplicationException("IOException in populateWithJSON", e);
}
}
}
It works fine without compression but if I compress JSON responses, I can not see my JSON objects anymore. I handle JSON Ajax calls with JQuery with code snippets as follows:
$.post(url,parameters, function(json) {
// Do some DOM manipulation with the data contained in the JSON Object
}, "json");
If I see the response with Firebug it is empty.
Should I refractor my compression filter to skip compression in JSON responses? or there is a workaround to this?
For me, it looks like JQuery does not recognize the response as JSON because I am adding the Gzip compression.

If I see the response with Firebug it
is empty.
There's your clue - it's not a JQuery problem, it's server-side. (I'm afraid I can't help you with that, other than to suggest you stop looking at the client-side)
There's no problem gzipping ajax responses - if you can't see the response in Firebug, then JQuery can't see it either.

you have to add one more header "content-encoding: gzip" if you are compressing it.

Have you tried with an explicit java-based client to ensure it's a problem with jQuery or browser? If java client fails, something is wrong with server response.
But I am guessing that whereas browser can deal with uncompression with direct requests, this is perhaps not applied to Ajax calls.
It's an interesting question, I hope we'll get a more definitive answer. :)

Related

How to handle compressed (gzip) HTTP requests (NOT response) in JAVA servlet - Simple Example?

I struggled with this problem for quite some time; and after finding a simple solution... wanted to ask a question & answer!!
The question has been asked in different ways multiple times on stack overflow, and the accepted solutions are either partially correct and complex or talk about response compression.
Aggregating some old Q&A on this topic:
wrong accepted ans/partially correct & complex
subsequent ans.
HTTP request compression
How to decode Gzip compressed request body in Spring MVC: (Almost correct 2nd ans, but complex overkill)
The accepted ans is wrong. It's about RESPONSE compression and not REQUEST.
Can I compress HTTP Requests using GZIP?
Compress an HTTP Request in java
Similar Questions - terminated at "NO request compression"
Does Java HTTP Client handle compression
A specific question & ans for Spring RestTemplate framework: How to zip- compress HTTP request with Spring RestTemplate?
A simple solution is by using a filter. (See servlet-filter tutorial)
Create a Servlet Filter:
Make sure that the filter is called either first/before any filters which use request body.
I. Register filter in web.xml:
<filter>
<filter-name>GzipRequestFilter</filter-name>
<filter-class>com...pkg...GzipRequestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GzipRequestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
II. Code for filter class:
public class GzipRequestFilter implements Filter {
// Optional but recommended.
private static final Set<String> METHODS_TO_IGNORE = ImmutableSet.of("GET", "OPTIONS", "HEAD");
#Override
public void doFilter(
final ServletRequest request,
final ServletResponse response,
final FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String method = httpServletRequest.getMethod().toUpperCase();
String encoding = Strings.nullToEmpty(
httpServletRequest.getHeader(HttpHeaders.CONTENT_ENCODING));
if (METHODS_TO_IGNORE.contains(method) || !encoding.contains("application/gzip")) {
chain.doFilter(request, response); // pass through
return;
}
HttpServletRequestWrapper requestInflated = new GzippedInputStreamWrapper(httpServletRequest);
chain.doFilter(requestInflated, response);
}
#Override
public void init(final FilterConfig filterConfig) throws ServletException {}
#Override
public void destroy() {}
}
III. Followed by code for GzipInputStream wrapper:
// Simple Wrapper class to inflate body of a gzipped HttpServletRequest.
final class GzippedInputStreamWrapper extends HttpServletRequestWrapper {
private GZIPInputStream inputStream;
GzippedInputStreamWrapper(final HttpServletRequest request) throws IOException {
super(request);
inputStream = new GZIPInputStream(request.getInputStream());
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
// NOTE: Later versions of javax.servlet library may require more overrides.
public int read() throws IOException {
return inputStream.read();
}
public void close() throws IOException {
super.close();
inputStream.close();
}
};
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(inputStream));
}
}
Now what remains is how to send a compressed request?
Postman does not yet support sending compressed HttpRequest bodies. You can still make it work by using the binary option and use a gzipped file containing the properly encoded request body.
One way is using a nodejs script with pako compression library. For a multipart/form-data request see form-data library
const pako = require('pako')
const axios = require('axios')
var params = qs.stringify({
'num': 42,
'str': 'A string param',
});
data = pako.gzip(Buffer.from(params));
var config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Encoding': 'application/gzip';
},
}
await axios.post(
'http://url-for-post-api-accepting-urlencoded',
data,
config,
).then((res) => {
console.log(`status: ${res.status} | data: ${res.data}`)
}).catch((error) => {
console.error(error)
})
NOTES:
We are using Content-Encoding: application/gzip header to specify that a request is compressed. Yes this is standard.
Do not use Content-Type as it will not work with multipart/form-data.
The HTTP protocol has been running under the assumption that size of HttpRequests are dwarfed by HttpResponses.
Further, due to assumed limited computing power in browser/client side, the norm has been to compress response and not requests. Browsers cannot natively compress but can do decompression natively.
But, unfortunately after years of many developers pushing code; some HTTP APIs evolve to consume large strings/data!!
It's a piece of cake to allow java servlets to have the option of working with compressed requests.

Does a HttpServlet have to respond to a request?

I have several servlets that do things server side. On a few I just encode some unnecessary data and send it back, which seems pointless. Do you have to respond ? What happens when you just say return ? I've done that before and nothing seems to go wrong but I am relatively new to servlets. Are there consequences for simply returning that go above my head ? And what exactly happens when you return;
if(request.getParameter("name").equals("saveusedcards")) {
String sessId = request.getSession().getId();
//encode request with confirmation that cards were successfully updated
if(usersUpdatedCards.get(sessId).isEmpty()){
//no cards were seen
}
boolean success = DataDAO.updateCards(usersUpdatedCards.get(sessId));
if(success){
System.out.println("Data base update successfull!");
String responseMessage = new Gson().toJson("card successfully udpated");
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
System.out.println("updated cards response message: "+responseMessage);
response.getWriter().write(responseMessage);
return;
} else {
System.out.println("Data base update failed...");
String responseMessage = new Gson().toJson("card was not successfully updated");
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
System.out.println("updated cards response message: "+responseMessage);
response.getWriter().write(responseMessage);
return;
}
}
The servlet must produce an HTTP response for the client, however it is perfectly acceptable to return no content in the response body. When doing so your servlet should make this clear to the client by sending a response code of 204 (no content). Reference: https://httpstatuses.com/204
Here is an example of how you would set the response code from the doGet method. You could do the same from doPost or service methods.
#Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
// Do whatever work you need to do here...
res.setStatus(HttpServletResponse. SC_NO_CONTENT); // This returns a 204
}

Authorization redirect on session expiration does not work on submitting a JSF form, page stays the same

I am using JSF2. I have implemented a custom faces servlet like so:
public class MyFacesServletWrapper extends MyFacesServlet {
// ...
}
wherein I'm doing some authorization checks and sending a redirect when the user is not logged in:
public void service(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (...) {
String loginURL = req.getContextPath() + "/LoginPage.faces";
res.sendRedirect(loginURL);
}
}
This works when the user tries to navigate to another page. However, this does not work when a JSF form is submitted by a JSF command link/button. The line sendRedirect() line is hit and executed, no exception is been thrown, but the user stays at the same page. Basically, there's no visual change at all.
Why does this work on page navigation, but not on form submit?
Your concrete problem is most likely caused because your JSF command link/button is actually sending an ajax request which in turn expects a special XML response. If you're sending a redirect as response to an ajax request, then it would just re-send the ajax request to that URL. This in turn fails without feedback because the redirect URL returns a whole HTML page instead of a special XML response. You should actually be returning a special XML response wherein the JSF ajax engine is been instructed to change the current window.location.
But you've actually bigger problems: using the wrong tool for the job. You should use a servlet filter for the job, not a homegrown servlet and for sure not one which supplants the FacesServlet who is the responsible for all the JSF works.
Assuming that you're performing the login in a request/view scoped JSF backing bean as follows (if you're using container managed authentication, see also 2nd example of Performing user authentication in Java EE / JSF using j_security_check):
externalContext.getSessionMap().put("user", user);
Then this kickoff example of a filter should do:
#WebFilter("/*") // Or #WebFilter(servletNames={"facesServlet"})
public class AuthorizationFilter implements Filter {
private static final String AJAX_REDIRECT_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<partial-response><redirect url=\"%s\"></redirect></partial-response>";
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
String loginURL = request.getContextPath() + "/login.xhtml";
boolean loggedIn = (session != null) && (session.getAttribute("user") != null);
boolean loginRequest = request.getRequestURI().equals(loginURL);
boolean resourceRequest = request.getRequestURI().startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER + "/");
boolean ajaxRequest = "partial/ajax".equals(request.getHeader("Faces-Request"));
if (loggedIn || loginRequest || resourceRequest)) {
if (!resourceRequest) { // Prevent browser from caching restricted resources. See also https://stackoverflow.com/q/4194207/157882
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
response.setDateHeader("Expires", 0); // Proxies.
}
chain.doFilter(request, response); // So, just continue request.
}
else if (ajaxRequest) {
response.setContentType("text/xml");
response.setCharacterEncoding("UTF-8");
response.getWriter().printf(AJAX_REDIRECT_XML, loginURL); // So, return special XML response instructing JSF ajax to send a redirect.
}
else {
response.sendRedirect(loginURL); // So, just perform standard synchronous redirect.
}
}
// ...
}
See also:
Using JSF 2.0 / Facelets, is there a way to attach a global listener to all AJAX calls?
FullAjaxExceptionHandler does not show session expired error page on ajax button
FacesContext.getCurrentInstance().getExternalContext().redirect("newpage.xhtml"); try this.... in place of res.sendredirect(cpath).

Servlet mixes header and content and writing twice same in output?

I have implemented servlet which behaves not stable, sometimes it mixes header in content and writing same twice.
and sometimes it is returning file which contains response header mixed by content like this:
Server: Apache-Coyote/1.1
: W/"43-1353687036000"
DatCCoonntenntt--DDiissppoosittiioonn: : atatatacehnmte;n tf;i lfenlaemnea=m20=12201112211127325421_4W1_Wirnkgi_nSgc_Seern.xnlsx
sx
Content-Typ-eT: ype: applaipcatciaoti/on/toctestt-rstare
am
ConCtoententy-pTeype: appalicatcion/oon/octet-setarm
m
CCoonntent-Lnegtht h: 4199
Date: te: FriF,r i2,3 2No vNo2v0 120162: 215:25 :G4M2T
....
File content bytes ...
And again same header and content
UPDATE
*This situation happens on Tomcat7*
I have tested also on Tomcat6 and Jetty, in both cases there is no injection of HTTP-Header to response content but HTTP-Header is wrong and returns wrong file name, the file content is correct file. I have noticed that wrong return from servlet happens when
returns transfer-encoding is chunked.
When I am removing header stuff, and second part of bytes, it is valid file.
Is it possible that is synchronization issue ?
UPDATE
Here is full source of servlet :
public class ExcelDownloadServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
private static final Logger LOG = Logger
.getLogger (ExcelDownloadServlet.class);
#Override
protected void doGet (HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
try
{
TransactionId transactionId = getTransactionId (request);
String fileName =
request.getParameter (GlobalConstants.EXCEL_FILE);
ExcelDownloadType downloadType =
ExcelDownloadType
.valueOf (request
.getParameter (GlobalConstants.EXCEL_DOWNLOAD_TYPE));
ActionContextFactory actionContextFactory =
ApplicationContext.getContext ()
.getActionContextFactory ();
//suppress warning. HttpServletRequest.getLocales does not support generics
#SuppressWarnings("unchecked")
ActionContext actionContext =
actionContextFactory.create (request.getSession ()
.getId (), Collections.<Locale> list (request
.getLocales ()));
GetExcelDataResponse dataResponse =
new GetExcelData (transactionId, fileName, downloadType)
.execute (actionContext);
writeToResponse (response, dataResponse.getFileName (),
dataResponse.getData ());
}
catch (InvalidSessionException e)
{
LOG.error ("Invalid session in Excel download", e);
throw new ServletException (e);
}
catch (ActionException e)
{
LOG.error ("Could not download into excel.", e);
throw new ServletException (e);
}
}
protected TransactionId getTransactionId (HttpServletRequest request)
{
return RequestParameterDeserializer.<TransactionId> deserialize (
request, GlobalConstants.TRANSACTION_ID);
}
protected void writeToResponse (HttpServletResponse response,
String rawFileName, byte[] data) throws IOException
{
ServletOutputStream sout = null;
try
{
response.setContentType ("application/octet-stream");
response.setContentLength (data.length);
// removing blanks from the file name, since FF cuts file names
// otherwise.
String fileNameWithTime = rawFileName.replaceAll (" ", "_");
response.setHeader ("Content-Disposition", "attachment; filename="
+ fileNameWithTime);
sout = response.getOutputStream ();
sout.write (data, 0, data.length);
}
finally
{
if (sout != null)
{
sout.close ();
}
}
}
UPDATE
*The call comes from GWT application when is generating the URL of servlet with required parameters and sets in IFrame, then servlet calls and file is downloading. Are there any suggestions ?*
I had a similar issue a long time ago.
It turned out that closing the ServletOutputStream triggered an unexpected behaviour on the request flow.
Servlets are not supposed to close the container provided OutputStream.
Another issue could be manually setting the content length, it is responsibility of the container producing the correct value.
To summarize, try removing out.close() and response.setContentLength()

Need help with creating custom HttpServletResponse

Suddenly stuck on generating custom servlet response. I want to replace servlet response with predefined one:
public class MyCustomResponse extends HttpServletResponseWrapper {
private String customOutput;
public MyCustomResponse(String customOutput, HttpServletResponse response) {
super(response);
// PrintWriter and Outputstream should stream this variable as output
this.customOutput = customOutput;
}
//
// Below I need to override something
//
}
and filter code snipped as follows:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//
//
MyCustomResponse customResponse = new MyCustomResponse("Hello world!", (HttpServletResponse) response);
chain.doFilter(request, customResponse);
}
Shame on me, but i'm really stuck on coding this simple task :(
Any help would be appreciated.
UPDATE:
All I want is to implement custom response wrapper which, once it's put into filter chain, would always respond with some predefined text. I know how to write custom data from within doFilter() method, but I want MyCustomResponse to be responsible for that - just instantiate and put in chain. Any well-reasoned responses "You cant do that because..." are also welcome.
As quoted in one of your comments :
"I want my custom response to return a
string in response to getWriter or
getOutputStream method invocation"
For that, you have to provide your own implementation for getWriter() & getOutputStream() by overriding them.
//---
private PrintWriter printWriter = null;
private ServletOutputStream outputStream = null;
public PrintWriter getWriter( ) throws IOException {
if (this.outputStream != null) {
throw new IllegalStateException(
"Cannot call getWriter( ) after getOutputStream( )");
}
if (this.printWriter == null) {
// initialize printWriter
}
return this.printWriter;
}
public ServletOutputStream getOutputStream( ) throws IOException {
if (this.printWriter != null) {
throw new IllegalStateException(
"Cannot call getOutputStream( ) after getWriter( )");
}
if (this.outputStream == null) {
// initialize outputStream
}
return this.outputStream;
}
//---
I am sorry, but
it is not clear what is your
problem. You code is written, so? It
does not work? what exactly does not work?
Why do you want to do this? The "right" solution is to pass information as session attribute.
I do not believe this can work. Really, you do not call directly the next filter in chain. You are kindly asking the app. server to do this. And you are not expected to replace the servlet request/response by your own. Use method explained above (#2)
Your response wrapper is useless as is, since it only stores a string in the Java object used to model the actual HTTP response.
The actual HTTP response that the client receives is the stream of bytes (resp. characters) sent via the output stream (resp. writer) of the HttpServletResponse object (and the headers, cookies, etc. stored in the HttpServletResponse object).
If you want to send a custom output string to the client, just use response.getWriter().print("Hello worlds!").
Passing the response to the rest of the filter chain is questionable, since the rest of the chain will probably want to add its own data to the response stream.
If you want to hard-code the response to send to the client to your custom output, but be able to still pass the response to the chain and ignore whatever the rest of the chain puts in the response, you could try to add the following to your wrapper :
private ServletOutputStream fakeOutputStream =
new ServletOutputStream() {
#Override
public void write(int b) throws IOException {
// do nothing. Everything written to this stream is ignored
}
}
private PrintWriter fakeWriter = new PrintWriter(fakeOutputStream);
public MyCustomResponse(String customOutput, HttpServletResponse response) {
super(response);
response.getWriter().print(customOutput);
}
#Override
public ServletOutputStream getOutputStream() {
return fakeOutputStream;
}
#Override
public PrintWriter getWriter() {
return fakeWriter;
}
I don't see the reason of what you want to do, but if you want to use your wrapper, my suggestion would be:
Create your own servlet that uses your wrapper and register it in web.xml, in something like this:
Extend javax.servlet.GenericServlet and override the service(ServletRequest, ServletResponse) method. Then you use the Template Method pattern to create a service(ServletRequest, ServletResponseWrapper). OR
Extend javax.servlet.HttpServlet and override service(HttpServletRequest, HttpServletResponse) method. Use the Template Method pattern to create a service(HttpServletRequest, HttpServletResponseWrapper). This will require that you don't use the doGet, doPost, doPut, doTrace methods already provided by HttpServlet but, instead create your own that uses your wrapper.
Hope this helps.

Categories

Resources