本文档主要讨论如下内容:
- 使用 Java Servlet 接口的服务端;
- 使用 Java HttpClient 工具的客户端;
- 使用 Curl 工具的 C 语言客户端;
- 文件名非 ASCII 字符问题
概述
根据 HTTP 协议的普适的实现,使用 Content-Type 和 Content-Disposition 两个 Header 属性,并将数据作为二进制流写入 Response Body。这样使用可以大部分已有的工具或者类库,便于开发和调试。
Header 写法如下:
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="sample.txt"
这样实现, HTTP URL 直接在浏览器地址栏直接输入,下载的文件也会自动命名为 sample.txt。
使用 Java Servlet 接口的服务端
代码如下:
@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
private final int ARBITARY_SIZE = 1048;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/plain");
resp.setHeader("Content-disposition", "attachment; filename=sample.txt");
try(InputStream in = req.getServletContext().getResourceAsStream("/WEB-INF/sample.txt");
OutputStream out = resp.getOutputStream()) {
byte[] buffer = new byte[ARBITARY_SIZE];
int numBytesRead;
while ((numBytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, numBytesRead);
}
}
}
}
使用 Java HttpClient 工具的客户端
HttpEntity entity = response.getEntity();
if (entity != null) {
String name = response.getFirstHeader('Content-Disposition').getValue();
String fileName = disposition.replaceFirst("(?i)^.*filename=\"([^\"]+)\".*$", "$1");
FileOutputStream fos = new FileOutputStream("C:\\" + fileName);
entity.writeTo(fos);
fos.close();
}
使用 Curl 工具的 C 语言客户端
Curl 工具版本 7.21.2 以上,if you specify --remote-header-name
/ -J
,下载的文件也会自动按照 Header 里的信息命名。
参考 https://github.com/curl/curl/blob/0c76795cafe0fccab41b3adc1be08cb81d55024f/src/toolcbhdr.c#L157。
文件名非 ASCII 字符问题
在 RFC 5987 中,Character Set and Language Encoding for Hypertext Transfer Protocol (HTTP) Header Field Parameters 章节讨论了字符集问题。
如下代码实现了 encode 的功能:
public static String rfc5987_encode(final String s) throws UnsupportedEncodingException {
final byte[] s_bytes = s.getBytes("UTF-8");
final int len = s_bytes.length;
final StringBuilder sb = new StringBuilder(len << 1);
final char[] digits = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
final byte[] attr_char = {'!','#','$','&','+','-','.','0','1','2','3','4','5','6','7','8','9', 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','^','_','`', 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','|', '~'};
for (int i = 0; i < len; ++i) {
final byte b = s_bytes[i];
if (Arrays.binarySearch(attr_char, b) >= 0)
sb.append((char) b);
else {
sb.append('%');
sb.append(digits[0x0f & (b >>> 4)]);
sb.append(digits[b & 0x0f]);
}
}
return sb.toString();
}
也可以参考 https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/http/ContentDisposition.java
附录
参考:
- Do I need Content-Type: application/octet-stream for file download? https://stackoverflow.com/questions/20508788/do-i-need-content-type-application-octet-stream-for-file-download
- Content-Disposition https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition
- Example of Downloading file https://www.baeldung.com/servlet-download-file
- Curl to grab remote filename after following location https://stackoverflow.com/questions/6881034/curl-to-grab-remote-filename-after-following-location
- handling filename* parameters with spaces via RFC 5987 results in '+' in filenames
- Character Set and Language Encoding for Hypertext Transfer Protocol (HTTP) Header Field Parameters