問題
有時候我們會透過網頁後端程式直接輸出檔案,因此需要在HTTP Response Header裡面添加Content-Disposition
參數,它告訴瀏覽器這個檔案要用瀏覽器直接開啟,例如Content-Disposition: inline
。也可以讓瀏覽器給使用者另存新檔,並且提供預設的檔案名稱,例如Content-Disposition: attachment; filename="File.txt"
。
通常我使用Spring作為網頁伺服器的後端,例如以下使用Spring MVC做一個GET API,用來下載某個檔案。
/** https://klab.tw */
@GetMapping
public ResponseEntity<Resource> downalod() {
String disp = "attachment; filename=\"檔案名稱.txt\"";
String mime = "text/plain";
Resource res = new UrlResource(...);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, disp)
.header(HttpHeaders.CONTENT_TYPE, mime)
.body(res);
}
然後就會看到Java報錯以下資訊:
java.lang.IllegalArgumentException: The Unicode character [檔] at code point [20,351] cannot be encoded as it is outside the permitted range of 0 to 255′
解決方式
這個不只是Java會遇到,因為HTTP規範中Header只接受ISO-8859-1的字元集,所以無法接受中文、日文、韓文、俄文等各種語言。解決方式URL編碼來將UTF-8字元集的文字,變成網址上常見的百分比開頭的文字,然後filanme
要變成filename*=utf-8''
。
例如filename="檔案名稱.txt"
就會變成filename*=utf-8''%E6%AA%94%E6%A1%88%E5%90%8D%E7%A8%B1.txt
(不能用雙引號保住檔名),以下直接提供一個快速的轉換方式。
public static String filenameEncode(String name) {
try {
return java.net.URLEncoder.encode(str, "UTF-8").replace("+", "%20");
} catch (java.io.UnsupportedEncodingException e) {
e.printStackTrace();
return name;
}
}
然後將上面的Get Method改成以下方式就可以運作囉。
@GetMapping
public ResponseEntity<Resource> downalod() {
String disp = "attachment; filename*=utf-8''" + filenameEncode("檔案名稱.txt");
String mime = "text/plain";
Resource res = new UrlResource(...);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, disp)
.header(HttpHeaders.CONTENT_TYPE, mime)
.body(res);
}
這邊提供的是Java搭配Spring的例子,使用其他套件或是其他語言只要尋找對應的URL Encoder方法就可以了。有些URL Encoder會把半形空白變成加號,例如上面的java.net.URLEncoder
就是,所以要再把加號變成%20
。
參考
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition