【发布时间】:2019-03-15 18:32:51
【问题描述】:
我正在开发一个提供 REST 端点的 Spring 应用程序。其中一个端点本质上充当 HTML 客户端和第三方云存储提供商之间的代理。此端点从存储提供程序检索文件并将它们代理回客户端。类似于以下内容(请注意,同一端点有同步和异步版本):
@Controller
public class CloudStorageController {
...
@RequestMapping(value = "/fetch-image/{id}", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public ResponseEntity<byte[]> fetchImageSynchronous(@PathVariable final Long id) {
final byte[] imageFileContents = this.fetchImage(id);
return ResponseEntity.ok().body(imageFileContents);
}
@RequestMapping(value = "/fetch-image-async/{id}", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public Callable<ResponseEntity<byte[]>> fetchImageAsynchronous(@PathVariable final Long id) {
return () -> {
final byte[] imageFileContents = this.fetchImage(id);
return ResponseEntity.ok().body(imageFileContents);
};
}
private byte[] fetchImage(final long id) {
// fetch the file from cloud storage and return as byte array
...
}
...
}
由于客户端应用程序 (HTML5 + ajax) 的性质以及此端点的使用方式,用户身份验证提供给此端点的方式与其他端点不同。为了处理这个问题,开发了一个 HandlerInterceptor 来处理这个端点的身份验证:
@Component("cloudStorageAuthenticationInterceptor")
public class CloudStorageAuthenticationInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) {
// examine the request for the authentication information and verify it
final Authentication authenticated = ...
if (authenticated == null) {
try {
pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} catch (IOException e) {
throw new RuntimeException(e);
}
return false;
}
else {
try {
request.login(authenticated.getName(), (String) authenticated.getCredentials());
} catch (final ServletException e) {
throw new BadCredentialsException("Bad credentials");
}
}
return true;
}
}
拦截器是这样注册的:
@Configuration
@EnableWebMvc
public class ApiConfig extends WebMvcConfigurerAdapter {
@Autowired
@Qualifier("cloudStorageAuthenticationInterceptor")
private HandlerInterceptor cloudStorageAuthenticationInterceptor;
@Override
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(this.cloudStorageAuthenticationInterceptor)
.addPathPatterns(
"/fetch-image/**",
"/fetch-image-async/**"
);
}
@Override
public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(this.asyncThreadPoolCoreSize);
executor.setMaxPoolSize(this.asyncThreadPoolMaxSize);
executor.setQueueCapacity(this.asyncThreadPoolQueueCapacity);
executor.setThreadNamePrefix(this.asyncThreadPoolPrefix);
executor.initialize();
configurer.setTaskExecutor(executor);
super.configureAsyncSupport(configurer);
}
}
理想情况下,图像获取将异步完成(使用 /fetch-image-asyc/{id} 端点),因为它必须调用可能有一些延迟的第三方 Web 服务。
同步端点 (/fetch-image/{id}) 适用于所有浏览器。但是,如果使用异步端点 (/fetch-image-async/{id}),Chrome 和 Firefox 可以正常工作。
但是,如果客户端是 Microsoft IE 或 Microsoft Edge,我们似乎有些奇怪的行为。端点被正确调用并且响应成功发送(至少从服务器的角度来看)。但是,浏览器似乎正在等待一些额外的东西。在 IE/Edge DevTools 窗口中,图像的网络请求显示为等待 30 秒,然后似乎超时,更新成功,图像成功显示。似乎与服务器的连接仍然打开,因为数据库连接等服务器端资源没有释放。在其他浏览器中,异步响应的接收和处理在一秒钟或更短的时间内完成。
如果我删除了 HandlerInterceptor 并且只是硬连线了一些用于调试的凭据,那么该行为就会消失。所以这似乎与 HandlerInterceptor 和异步控制器方法之间的交互有关,并且只针对部分客户端展示。
有人对为什么 IE/Edge 的语义会导致这种行为有任何建议吗?
【问题讨论】:
标签: spring spring-mvc internet-explorer asynchronous microsoft-edge