AngularJS POST for PDF 数据总是失败
AngularJS POST for PDF data always fails
我正在尝试绕过 Internet Explorer 的 URL 限制(有关详细信息,请参阅 this Microsoft support article)以生成 PDF。
我的 client-side JS 中有一个表示 PDF 字节流的 base-64 编码字符串。对于 Firefox 和 Chrome,我通过在字符串前添加 data:application/pdf;base64,
来显示 PDF,浏览器会愉快地呈现数据 URI。
对于 Internet Explorer,我试图将字符串发送回 POST 中的 servlet,以便它可以 return 内容类型为 "application/pdf" 的字节流。除了我在客户端请求中传递实际的 PDF 内容外,我几乎遵循以下问题中提出的解决方案:
- AngularJS: Display Blob PDF in Angular App
- How to read pdf stream in AngularJS
我的 HTTP POST 如下所示:
var pdf; // base-64 encoded string in this variable
$http.post("/convert/pdf", {
params: {
base64pdf: pdf
}
}, {
responseType : "arraybuffer"
}).then( function onSuccess(result) {
var file = new Blob([result.data], {type: 'application/pdf'});
window.navigator.msSaveOrOpenBlob(file, "result.pdf");
},
function onFailure() { /* log and do failure case stuff */ });
我的服务器是 运行 一个 Java spring-webmvc 服务。 Convert 控制器如下所示:
@Controller
@RequestMapping(value = "/convert", produces = "application/pdf")
public class ConvertController {
@RequestMapping(value = "/pdf", method = RequestMethod.POST)
public ResponseEntity<byte[]> generatePdf(
@RequestParam(value="base64pdf", required=true) String base64encodedDataUri,
HttpServletRequest request) {
byte[] bytes = Base64.decodeBase64(base64encodedDataUri);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/pdf"));
String filename = "test.pdf";
headers.setContentDispositionFormData(filename, filename);
ResponseEntity<byte[]> response = new ResponseEntity<>(bytes, headers, HttpStatus.OK);
return response;
}
// other methods
}
控制器代码基于 this answer。我认为这没有问题;它在单元测试中测试良好,并通过 Swagger API 接口直接 POST 请求。
问题是这样的。每当我在 IE 中触发此 $http.post
时,结果是 always 错误回调,它是 always HTTP 400 错误。
我观察到以下情况:
使用 IE 11 的开发者工具网络流量捕获,我可以观察到正在发出的请求。该请求在参数中包含完整且正确的 base-64 编码字符串,该字符串存在于请求 Body.
中
请求 Header 看起来像这样:
Request POST /convert/pdf HTTP/1.1
Content-Type application/json;charset=utf-8
Accept application/json, text/plain, */*
Referer localhost:8080/
Accept-Language en-US
Accept-Encoding gzip, deflate
User-Agent Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Host localhost:8080
Content-Length 5699
DNT 1
Connection Keep-Alive
Cache-Cont
rol no-cache
- 响应 header 显示:
Response HTTP/1.1 400 Bad Request
Server Apache-Coyote/1.1
Access-Control-Allow-Origin *
Access-Control-Allow-Methods POST,GET,OPTIONS,DELETE
Access-Control-Max-Age 3600
Access-Control-Allow-Headers x-requested-with
Content-Type application/pdf
Content-Length 98
Date Mon, 07 Dec 2015 21:54:32 GMT
Connection close
响应 body 包含无效的 PDF。好吧,从技术上讲,开发人员工具说它无法呈现它,当您保存它时,保存的 PDF 不是有效的 PDF。
编辑: 我登录 Tomcat8 并观察到 Spring 抱怨 "base64pdf"请求中缺少参数:
2015-12-08 02:29:32,514 INFO [http-nio-8080-exec-4] INFO Required String parameter 'base64pdf' is not present
org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'base64pdf' is not present
所以我真的有两个问题:
我怎么会丢失 base64pdf
参数? 参数有最大长度吗?我尝试了这个问题中显示的原始内容,并按照下面用户 chenzhenjia 的建议通过 data: { base64pdf : pdf }
传递参数。
如何让这个 PDF 调用在 IE 中工作? 虽然一方面我想知道我在上面做错了什么,但坦率地说,我愿意 "the right answer" 了解如何将客户端的这个 base-64 编码字符串转换为 PDF。 (也就是说,如果这是 XY problem 而我找错了树,我也想知道。)
我正在使用:
- AngularJS 1.4.8
- Spring 4.2.3.RELEASE
- Java 8
- Tomcat 8
尝试将方法上的 @RequestMapping
注释更改为:
@RequestMapping(value = "/convert", produces = "application/pdf",method = RequestMethod.POST)
或者,尝试以下 AngularJS POST:
var req = { method: 'POST', url: 'example.com', headers: { 'Content-Type': 'application/pdf' }, data: { base64pdf: pdf } }
$http(req).then(function(){...}, function(){...});
我最终通过在请求正文中发送 base64pdf 字符串来实现它。
在Java中,我创建了一个简单的模型:
public class PayloadHolder {
private String payload;
// getters and setters
}
然后我更新了控制器的方法签名:
@RequestMapping(value = "/pdf", method = RequestMethod.POST, produces = "application/pdf")
public ResponseEntity<byte[]> generatePdf(
@RequestBody(required = true) PayloadHolder payload,
HttpServletRequest request)
最后更改了 AngularJS 请求:
var pdf; // base-64 encoded string in this variable
var request = {
payload : pdf
};
var config = {
headers: { 'Accept': 'application/pdf' },
responseType : "arrayBuffer"
};
$http.post("/convert/pdf", request, config)
.then( function onSuccess(result) {
var file = new Blob([result.data], {type: 'application/pdf'});
window.navigator.msSaveOrOpenBlob(file, "result.pdf");
},
function onFailure() { /* log and do failure case stuff */ });
我不确定为什么 POST 不能使用参数工作,特别是因为我在文档中找不到任何表明它不应该工作的内容。我会 post 解决这个问题的后续问题。
对于 $http.post 如果您没有在服务器端指定内容类型 (spring mvc) 则将内容类型设置为 url-form-encoded 请求的其他内容类型,因为 angular 可能与其他内容类型一起发送。
示例:
$http({
method: "post",
url: URL,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: $.param({base64pdf: pdf})
}).success(function (result){
var file = new Blob([result.data], {type: 'application/pdf'});
window.navigator.msSaveOrOpenBlob(file, "result.pdf");
});
我正在尝试绕过 Internet Explorer 的 URL 限制(有关详细信息,请参阅 this Microsoft support article)以生成 PDF。
我的 client-side JS 中有一个表示 PDF 字节流的 base-64 编码字符串。对于 Firefox 和 Chrome,我通过在字符串前添加 data:application/pdf;base64,
来显示 PDF,浏览器会愉快地呈现数据 URI。
对于 Internet Explorer,我试图将字符串发送回 POST 中的 servlet,以便它可以 return 内容类型为 "application/pdf" 的字节流。除了我在客户端请求中传递实际的 PDF 内容外,我几乎遵循以下问题中提出的解决方案:
- AngularJS: Display Blob PDF in Angular App
- How to read pdf stream in AngularJS
我的 HTTP POST 如下所示:
var pdf; // base-64 encoded string in this variable
$http.post("/convert/pdf", {
params: {
base64pdf: pdf
}
}, {
responseType : "arraybuffer"
}).then( function onSuccess(result) {
var file = new Blob([result.data], {type: 'application/pdf'});
window.navigator.msSaveOrOpenBlob(file, "result.pdf");
},
function onFailure() { /* log and do failure case stuff */ });
我的服务器是 运行 一个 Java spring-webmvc 服务。 Convert 控制器如下所示:
@Controller
@RequestMapping(value = "/convert", produces = "application/pdf")
public class ConvertController {
@RequestMapping(value = "/pdf", method = RequestMethod.POST)
public ResponseEntity<byte[]> generatePdf(
@RequestParam(value="base64pdf", required=true) String base64encodedDataUri,
HttpServletRequest request) {
byte[] bytes = Base64.decodeBase64(base64encodedDataUri);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/pdf"));
String filename = "test.pdf";
headers.setContentDispositionFormData(filename, filename);
ResponseEntity<byte[]> response = new ResponseEntity<>(bytes, headers, HttpStatus.OK);
return response;
}
// other methods
}
控制器代码基于 this answer。我认为这没有问题;它在单元测试中测试良好,并通过 Swagger API 接口直接 POST 请求。
问题是这样的。每当我在 IE 中触发此 $http.post
时,结果是 always 错误回调,它是 always HTTP 400 错误。
我观察到以下情况:
使用 IE 11 的开发者工具网络流量捕获,我可以观察到正在发出的请求。该请求在参数中包含完整且正确的 base-64 编码字符串,该字符串存在于请求 Body.
中
请求 Header 看起来像这样:
Request POST /convert/pdf HTTP/1.1 Content-Type application/json;charset=utf-8 Accept application/json, text/plain, */* Referer localhost:8080/ Accept-Language en-US Accept-Encoding gzip, deflate User-Agent Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko Host localhost:8080 Content-Length 5699 DNT 1 Connection Keep-Alive Cache-Cont rol no-cache
- 响应 header 显示:
Response HTTP/1.1 400 Bad Request Server Apache-Coyote/1.1 Access-Control-Allow-Origin * Access-Control-Allow-Methods POST,GET,OPTIONS,DELETE Access-Control-Max-Age 3600 Access-Control-Allow-Headers x-requested-with Content-Type application/pdf Content-Length 98 Date Mon, 07 Dec 2015 21:54:32 GMT Connection close
响应 body 包含无效的 PDF。好吧,从技术上讲,开发人员工具说它无法呈现它,当您保存它时,保存的 PDF 不是有效的 PDF。
编辑: 我登录 Tomcat8 并观察到 Spring 抱怨 "base64pdf"请求中缺少参数:
2015-12-08 02:29:32,514 INFO [http-nio-8080-exec-4] INFO Required String parameter 'base64pdf' is not present
org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'base64pdf' is not present
所以我真的有两个问题:
我怎么会丢失
base64pdf
参数? 参数有最大长度吗?我尝试了这个问题中显示的原始内容,并按照下面用户 chenzhenjia 的建议通过data: { base64pdf : pdf }
传递参数。如何让这个 PDF 调用在 IE 中工作? 虽然一方面我想知道我在上面做错了什么,但坦率地说,我愿意 "the right answer" 了解如何将客户端的这个 base-64 编码字符串转换为 PDF。 (也就是说,如果这是 XY problem 而我找错了树,我也想知道。)
我正在使用:
- AngularJS 1.4.8
- Spring 4.2.3.RELEASE
- Java 8
- Tomcat 8
尝试将方法上的 @RequestMapping
注释更改为:
@RequestMapping(value = "/convert", produces = "application/pdf",method = RequestMethod.POST)
或者,尝试以下 AngularJS POST:
var req = { method: 'POST', url: 'example.com', headers: { 'Content-Type': 'application/pdf' }, data: { base64pdf: pdf } }
$http(req).then(function(){...}, function(){...});
我最终通过在请求正文中发送 base64pdf 字符串来实现它。
在Java中,我创建了一个简单的模型:
public class PayloadHolder {
private String payload;
// getters and setters
}
然后我更新了控制器的方法签名:
@RequestMapping(value = "/pdf", method = RequestMethod.POST, produces = "application/pdf")
public ResponseEntity<byte[]> generatePdf(
@RequestBody(required = true) PayloadHolder payload,
HttpServletRequest request)
最后更改了 AngularJS 请求:
var pdf; // base-64 encoded string in this variable
var request = {
payload : pdf
};
var config = {
headers: { 'Accept': 'application/pdf' },
responseType : "arrayBuffer"
};
$http.post("/convert/pdf", request, config)
.then( function onSuccess(result) {
var file = new Blob([result.data], {type: 'application/pdf'});
window.navigator.msSaveOrOpenBlob(file, "result.pdf");
},
function onFailure() { /* log and do failure case stuff */ });
我不确定为什么 POST 不能使用参数工作,特别是因为我在文档中找不到任何表明它不应该工作的内容。我会 post 解决这个问题的后续问题。
对于 $http.post 如果您没有在服务器端指定内容类型 (spring mvc) 则将内容类型设置为 url-form-encoded 请求的其他内容类型,因为 angular 可能与其他内容类型一起发送。
示例:
$http({
method: "post",
url: URL,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: $.param({base64pdf: pdf})
}).success(function (result){
var file = new Blob([result.data], {type: 'application/pdf'});
window.navigator.msSaveOrOpenBlob(file, "result.pdf");
});