Skip to content

Commit

Permalink
Add basic support for thread interruption, and add protection for lon…
Browse files Browse the repository at this point in the history
…g running requests in web app
  • Loading branch information
srowen committed Jul 20, 2022
1 parent 2369986 commit 45df470
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 3 deletions.
6 changes: 6 additions & 0 deletions core/src/main/java/com/google/zxing/MultiFormatReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ public void reset() {
private Result decodeInternal(BinaryBitmap image) throws NotFoundException {
if (readers != null) {
for (Reader reader : readers) {
if (Thread.currentThread().isInterrupted()) {
throw NotFoundException.getNotFoundInstance();
}
try {
return reader.decode(image, hints);
} catch (ReaderException re) {
Expand All @@ -179,6 +182,9 @@ private Result decodeInternal(BinaryBitmap image) throws NotFoundException {
// Calling all readers again with inverted image
image.getBlackMatrix().flip();
for (Reader reader : readers) {
if (Thread.currentThread().isInterrupted()) {
throw NotFoundException.getNotFoundInstance();
}
try {
return reader.decode(image, hints);
} catch (ReaderException re) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ private static void processImage(BufferedImage image,
savedException = re;
}

if (results.isEmpty()) {
if (results.isEmpty() && !Thread.currentThread().isInterrupted()) {
try {
// Look for pure barcode
Result theResult = reader.decode(bitmap, HINTS_PURE);
Expand All @@ -403,7 +403,7 @@ private static void processImage(BufferedImage image,
}
}

if (results.isEmpty()) {
if (results.isEmpty() && !Thread.currentThread().isInterrupted()) {
try {
// Look for normal barcode in photo
Result theResult = reader.decode(bitmap, HINTS);
Expand All @@ -415,7 +415,7 @@ private static void processImage(BufferedImage image,
}
}

if (results.isEmpty()) {
if (results.isEmpty() && !Thread.currentThread().isInterrupted()) {
try {
// Try again with other binarizer
BinaryBitmap hybridBitmap = new BinaryBitmap(new HybridBinarizer(source));
Expand Down
92 changes: 92 additions & 0 deletions zxingorg/src/main/java/com/google/zxing/web/TimeoutFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2022 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.zxing.web;

import com.google.common.util.concurrent.SimpleTimeLimiter;
import com.google.common.util.concurrent.TimeLimiter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeUnit;

/**
* Protect the decode endpoint from long-running requests.
*/
@WebFilter(urlPatterns = {"/w/decode"}, initParams = {
@WebInitParam(name = "timeoutSec", value = "10"),
})
public final class TimeoutFilter implements Filter {

private ExecutorService executorService;
private TimeLimiter timeLimiter;
private int timeoutSec;

@Override
public void init(FilterConfig filterConfig) {
executorService = Executors.newCachedThreadPool();
timeLimiter = SimpleTimeLimiter.create(executorService);
timeoutSec = Integer.parseInt(filterConfig.getInitParameter("timeoutSec"));
}

@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
timeLimiter.callWithTimeout(new Callable<Void>() {
@Override
public Void call() throws Exception {
chain.doFilter(request, response);
return null;
}
}, timeoutSec, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException e) {
HttpServletResponse servletResponse = (HttpServletResponse) response;
servletResponse.setStatus(HttpServletResponse.SC_REQUEST_TIMEOUT);
servletResponse.getWriter().write("Request took too long");
} catch (ExecutionException e) {
if (e.getCause() instanceof ServletException) {
throw (ServletException) e.getCause();
}
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
}
throw new ServletException(e.getCause());
}
}

@Override
public void destroy() {
if (executorService != null) {
executorService.shutdownNow();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2022 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.zxing.web;

import org.junit.Assert;
import org.junit.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockFilterConfig;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.GenericServlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

/**
* Tests {@link TimeoutFilter}.
*/
public final class TimeoutFilterTestCase extends Assert {

@Test
public void testTimeout() throws Exception {
MockFilterConfig config = new MockFilterConfig();
config.addInitParameter("timeoutSec", "1");
Filter filter = new TimeoutFilter();
filter.init(config);

FilterChain chain = new MockFilterChain(new GenericServlet() {
@Override
public void service(ServletRequest req, ServletResponse res) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// continue
}
}
});
HttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(new MockHttpServletRequest(), response, chain);
filter.destroy();
assertEquals(HttpServletResponse.SC_REQUEST_TIMEOUT, response.getStatus());
}

}

0 comments on commit 45df470

Please sign in to comment.