Skip to content

Commit

Permalink
Another try at CLOSE_WAIT: simplify by removing HttpClient, always co…
Browse files Browse the repository at this point in the history
…nsume every byte of input in all cases, and force GCs to clear stuck sockets

git-svn-id: https://zxing.googlecode.com/svn/trunk@2240 59b500cc-1b3d-0410-9834-0bbf25fbcc57
  • Loading branch information
srowen committed Mar 30, 2012
1 parent 758c9c3 commit 73345ae
Show file tree
Hide file tree
Showing 14 changed files with 73 additions and 889 deletions.
5 changes: 2 additions & 3 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ Apache License. Contributors are listed under:
http://barcode4j.sourceforge.net/contributors.html

--------------------------------------------------------------------------------
NOTICES FOR APACHE HTTPCOMPONENTS, COMMONS IO, COMMONS LANG,
COMMONS LOGGING, COMMONS FILEUPLOAD
NOTICES FOR APACHE COMMONS FILEUPLOAD, IO, LANG
--------------------------------------------------------------------------------

Copyright 1999-2012 The Apache Software Foundation
Copyright 2002-2010 The Apache Software Foundation

This product includes software developed by
The Apache Software Foundation (http://www.apache.org/).
143 changes: 70 additions & 73 deletions zxingorg/src/com/google/zxing/web/DecodeServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,14 @@
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.http.Header;
import org.apache.http.HttpMessage;
import org.apache.http.HttpResponse;
import org.apache.http.HttpEntity;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.BasicClientConnectionManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;

import java.awt.color.CMMException;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
Expand All @@ -67,6 +54,8 @@
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -86,15 +75,16 @@
*/
public final class DecodeServlet extends HttpServlet {

private static final Logger log = Logger.getLogger(DecodeServlet.class.getName());

// No real reason to let people upload more than a 2MB image
private static final long MAX_IMAGE_SIZE = 2000000L;
// No real reason to deal with more than maybe 2 megapixels
private static final int MAX_PIXELS = 1 << 21;

private static final Logger log = Logger.getLogger(DecodeServlet.class.getName());

static final Map<DecodeHintType,Object> HINTS;
static final Map<DecodeHintType,Object> HINTS_PURE;
private static final byte[] REMAINDER_BUFFER = new byte[8192];
private static final long GC_HACK_INTERVAL_MS = 60 * 1000;
private static final Map<DecodeHintType,Object> HINTS;
private static final Map<DecodeHintType,Object> HINTS_PURE;

static {
HINTS = new EnumMap<DecodeHintType,Object>(DecodeHintType.class);
Expand All @@ -105,18 +95,27 @@ public final class DecodeServlet extends HttpServlet {
}

private DiskFileItemFactory diskFileItemFactory;
private Timer gcHackTimer;

@Override
public void init(ServletConfig servletConfig) {
Logger logger = Logger.getLogger("com.google.zxing");
logger.addHandler(new ServletContextLogHandler(servletConfig.getServletContext()));
gcHackTimer = new Timer();
gcHackTimer.schedule(new TimerTask() {
@Override
public void run() {
System.gc(); // Hack: GC may close these weird stuck CLOSE_WAIT sockets?
}
}, GC_HACK_INTERVAL_MS, GC_HACK_INTERVAL_MS);
diskFileItemFactory = new DiskFileItemFactory();
log.info("DecodeServlet configured");
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {

String imageURIString = request.getParameter("u");
if (imageURIString == null || imageURIString.isEmpty()) {
log.fine("URI was empty");
Expand All @@ -141,30 +140,17 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
return;
}

HttpUriRequest getRequest = new HttpGet(imageURI);
getRequest.addHeader("Connection", "close"); // Avoids CLOSE_WAIT socket issue?

HttpParams params = new BasicHttpParams();
DefaultHttpClient.setDefaultHttpParams(params);
params.setIntParameter(CoreConnectionPNames.SO_LINGER, 5); // Avoids CLOSE_WAIT socket issue?
params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000);
params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 5000);
HttpURLConnection connection = (HttpURLConnection) imageURI.toURL().openConnection();
connection.setAllowUserInteraction(false);
connection.setReadTimeout(5000);
connection.setConnectTimeout(5000);
connection.setRequestProperty("User-Agent", "zxing.org");
connection.setRequestProperty("Connection", "close");

ClientConnectionManager connectionManager = new BasicClientConnectionManager();
HttpClient client = new DefaultHttpClient(connectionManager, params);
try {

HttpResponse getResponse;
try {
getResponse = client.execute(getRequest);
} catch (IllegalArgumentException iae) {
// Thrown if hostname is bad or null
if (log.isLoggable(Level.FINE)) {
log.fine(iae.toString());
}
getRequest.abort();
response.sendRedirect("badurl.jspx");
return;
connection.connect();
} catch (IOException ioe) {
// Encompasses lots of stuff, including
// java.net.SocketException, java.net.UnknownHostException,
Expand All @@ -174,40 +160,60 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
if (log.isLoggable(Level.FINE)) {
log.fine(ioe.toString());
}
getRequest.abort();
response.sendRedirect("badurl.jspx");
return;
}

if (getResponse.getStatusLine().getStatusCode() != HttpServletResponse.SC_OK) {
if (log.isLoggable(Level.FINE)) {
log.fine("Unsuccessful return code: " + getResponse.getStatusLine().getStatusCode());
InputStream is = null;
try {

is = connection.getInputStream();

if (connection.getResponseCode() != HttpServletResponse.SC_OK) {
if (log.isLoggable(Level.FINE)) {
log.fine("Unsuccessful return code: " + connection.getResponseCode());
}
response.sendRedirect("badurl.jspx");
return;
}
if (connection.getHeaderFieldInt("Content-Length", 0) > MAX_IMAGE_SIZE) {
log.fine("Too large");
response.sendRedirect("badimage.jspx");
return;
}
response.sendRedirect("badurl.jspx");
return;
}
if (!isSizeOK(getResponse)) {
log.fine("Too large");
response.sendRedirect("badimage.jspx");
return;
}

log.info("Decoding " + imageURI);
HttpEntity entity = getResponse.getEntity();
InputStream is = entity.getContent();
try {
log.info("Decoding " + imageURI);
processStream(is, request, response);

} catch (IOException ioe) {
if (log.isLoggable(Level.FINE)) {
log.fine(ioe.toString());
}
response.sendRedirect("badurl.jspx");
} finally {
EntityUtils.consume(entity);
is.close();
if (is != null) {
consumeRemainder(is);
is.close();
}
}

} finally {
connectionManager.shutdown();
connection.disconnect();
}

}

private static void consumeRemainder(InputStream is) {
try {
int available;
while ((available = is.available()) > 0) {
is.read(REMAINDER_BUFFER, 0, available); // don't care about value, or collision
}
} catch (IOException ioe) {
// continue
}
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Expand Down Expand Up @@ -249,8 +255,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)

}

private static void processStream(InputStream is, ServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
private static void processStream(InputStream is,
ServletRequest request,
HttpServletResponse response) throws ServletException, IOException {

BufferedImage image;
try {
Expand Down Expand Up @@ -383,20 +390,10 @@ private static void handleException(ReaderException re, HttpServletResponse resp
}
}

private static boolean isSizeOK(HttpMessage getResponse) {
Header lengthHeader = getResponse.getLastHeader("Content-Length");
if (lengthHeader != null) {
long length = Long.parseLong(lengthHeader.getValue());
if (length > MAX_IMAGE_SIZE) {
return false;
}
}
return true;
}

@Override
public void destroy() {
log.config("DecodeServlet shutting down...");
gcHackTimer.cancel();
}

}
2 changes: 1 addition & 1 deletion zxingorg/src/com/google/zxing/web/DoSFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public final class DoSFilter implements Filter {
private static final int MAX_ACCESSES_PER_IP_PER_TIME = 10;
private static final long MAX_ACCESS_INTERVAL_MSEC = 10L * 1000L;
private static final long UNBAN_INTERVAL_MSEC = 60L * 60L * 1000L;
public static final String BAD_IPS_INIT_PARAM = "bad.ips";
private static final String BAD_IPS_INIT_PARAM = "bad.ips";

private final IPTrie numRecentAccesses;
private final Timer timer;
Expand Down
Binary file removed zxingorg/web/WEB-INF/lib/commons-codec-1.4.jar
Binary file not shown.
Loading

0 comments on commit 73345ae

Please sign in to comment.