servlet/rest controller 下载 log4j 日志文件内容没有完全结束
servlet / rest controller download log4j log file contents doesn't cleanly end
在一个 Spring 网络应用程序中(虽然我不认为这是 Spring-related),我制作了一个 REST GET 资源,允许下载当前的 log4j2 文件内容,但是请求(来自浏览器和 cUrl)都不会随着部署在远程生产服务器上的 webapp 而终止——而它们通常会在我的 Windows 开发机器上结束。
此外,cUrl 表示还有字节要接收:
* transfer closed with 3 bytes remaining to read
* stopped the pause stream!
* Closing connection 0
curl: (18) transfer closed with 3 bytes remaining to read
这是相关代码:
// ...
import org.springframework.http.HttpHeaders;
// ...
@RestController
@RequestMapping(path="/logs", produces="application/json")
public class LogController {
// ...
@GetMapping(path="/{appenderName}/contents", produces="text/plain")
public void download(@PathVariable String appenderName, HttpServletResponse response) {
// ...
org.apache.logging.log4j.Logger rootLogger = LogManager.getRootLogger();
if(rootLogger instanceof Logger) {
Logger l = (Logger) rootLogger;
if(l.getAppenders().containsKey(appenderName)) {
Appender appender = l.getAppenders().get(appenderName);
if(appender instanceof FileAppender) {
((FileAppender) appender).getManager().flush();
final File f = new File(((FileAppender) appender).getFileName());
response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + f.getName() + "\"");
Path tempCopy = Files.createTempFile("log-", null);
Files.copy(f.toPath(), tempCopy, StandardCopyOption.REPLACE_EXISTING);
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(tempCopy.toFile().length()));
Reader reader = new FileReader(tempCopy.toFile());
IOUtils.copy(reader, response.getWriter());
reader.close();
tempCopy.toFile().delete();
// ...
我认为问题出在 Content-length
header 上:在 Linux (Ubuntu) 服务器上,它只有在我减去 3
时才有效到文件长度:
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(tempCopy.toFile().length() - 3));
我试过在下载前将原文件复制到一个临时文件中,但似乎没有帮助。
当然,如果我不发送任何东西,一切都很好Content-Length
header。
看起来像 cURL 相关问题,这是因为 Content-Disposition
header 的 filename
部分(这里的问题是关于日志的文件名格式,主要是日期部分)。
Content-Disposition
的形成方式最初由 RFC 2047 and RFC 2231 描述;设置 filename
部分的正确方法是对其进行编码:
String fileName = URLEncoder.encode(f.getName(), "UTF-8");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"");
附加信息
顺便说一句,后来 RFC 5987 引入了在可选的“扩展”参数中使用编码的可能性(filename*
在我们的例子中);它受到现代浏览器的支持。
它使您可以添加可选的“扩展”参数:
Content-Disposition: attachment;
filename="EURO rates";
filename*=utf-8''%e2%82%ac%20rates
此处支持 RFC 5987 的用户代理将使用 filename*
参数,旧用户代理将忽略它并使用 filename
代替。
我决定在 Content-Type
header:
中指定字符集
response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain; charset=utf-8");
没有它,Linux 服务器返回:
Content-Type: text/plain;charset=ISO-8859-1
在一个 Spring 网络应用程序中(虽然我不认为这是 Spring-related),我制作了一个 REST GET 资源,允许下载当前的 log4j2 文件内容,但是请求(来自浏览器和 cUrl)都不会随着部署在远程生产服务器上的 webapp 而终止——而它们通常会在我的 Windows 开发机器上结束。
此外,cUrl 表示还有字节要接收:
* transfer closed with 3 bytes remaining to read
* stopped the pause stream!
* Closing connection 0
curl: (18) transfer closed with 3 bytes remaining to read
这是相关代码:
// ...
import org.springframework.http.HttpHeaders;
// ...
@RestController
@RequestMapping(path="/logs", produces="application/json")
public class LogController {
// ...
@GetMapping(path="/{appenderName}/contents", produces="text/plain")
public void download(@PathVariable String appenderName, HttpServletResponse response) {
// ...
org.apache.logging.log4j.Logger rootLogger = LogManager.getRootLogger();
if(rootLogger instanceof Logger) {
Logger l = (Logger) rootLogger;
if(l.getAppenders().containsKey(appenderName)) {
Appender appender = l.getAppenders().get(appenderName);
if(appender instanceof FileAppender) {
((FileAppender) appender).getManager().flush();
final File f = new File(((FileAppender) appender).getFileName());
response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + f.getName() + "\"");
Path tempCopy = Files.createTempFile("log-", null);
Files.copy(f.toPath(), tempCopy, StandardCopyOption.REPLACE_EXISTING);
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(tempCopy.toFile().length()));
Reader reader = new FileReader(tempCopy.toFile());
IOUtils.copy(reader, response.getWriter());
reader.close();
tempCopy.toFile().delete();
// ...
我认为问题出在 Content-length
header 上:在 Linux (Ubuntu) 服务器上,它只有在我减去 3
时才有效到文件长度:
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(tempCopy.toFile().length() - 3));
我试过在下载前将原文件复制到一个临时文件中,但似乎没有帮助。
当然,如果我不发送任何东西,一切都很好Content-Length
header。
看起来像 cURL 相关问题,这是因为 Content-Disposition
header 的 filename
部分(这里的问题是关于日志的文件名格式,主要是日期部分)。
Content-Disposition
的形成方式最初由 RFC 2047 and RFC 2231 描述;设置 filename
部分的正确方法是对其进行编码:
String fileName = URLEncoder.encode(f.getName(), "UTF-8");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"");
附加信息
顺便说一句,后来 RFC 5987 引入了在可选的“扩展”参数中使用编码的可能性(filename*
在我们的例子中);它受到现代浏览器的支持。
它使您可以添加可选的“扩展”参数:
Content-Disposition: attachment;
filename="EURO rates";
filename*=utf-8''%e2%82%ac%20rates
此处支持 RFC 5987 的用户代理将使用 filename*
参数,旧用户代理将忽略它并使用 filename
代替。
我决定在 Content-Type
header:
response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain; charset=utf-8");
没有它,Linux 服务器返回:
Content-Type: text/plain;charset=ISO-8859-1