下面是将HttpServletRequest的inputStream上传到S3并随后下载的代码段。
public void init() {
s3Client = AmazonS3ClientBuilder.defaultClient();
//create bucket if not exists
}
@PostMapping( Consumes - Multipart/form-data)
public String send(HttpServletRequest request) {
//validate request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if(!isMultipart) // reject
if (Long.parseLong(request.getHeader("Content-length")) > maxLength) // reject
//Put request in S3
ObjectMetaData objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(Long.parseLong(request.getHeader("Content-length")));
objectMetadata.setContentType(request.getHeader("Content-type"));
s3Client.putObject(bucket, key, request.getInputStream, objectMetadata);
CustomServletRequestContext ctx = new CustomServletRequestContext(is, request.getCharacterEncoding(), request.getContentLength(), request.getContentType());
}
public void download(CustomServletRequestContext ctx) {
// Get data from S3
GetObjectRequest getObjectRequest = new GetObjectRequest(bucket, key);
S3Object s3Object = s3Client.getObject(getObjectRequest);
InputStream is = s3Object.getObjectContent();
ServletFileUpload upload = new ServletFileUpload();
upload.setSizeMax(maxLength);
FileItemIterator iterator = upload.getItemIterator(ctx); // need context object
while (iterator.hasNext()) {
FileItemStream item = iterator.next();
String name = item.getFieldName();
InputStream stream = item.openStream();
if (!item.isFormField()) {
process IOUtils.toString(stream, "UTF-8") // throws randomly Premature end of Stream exception
} else {
process Streams.asString(stream);
}
}
}
CustomServletRequestContext-需要为apache-commons-fileupload api创建此文件,以在读取期间获取Parts Iterator,这需要如上所述的Context。
class CustomServletRequestContext implements UploadContext {
private final InputStream is;
private final String characterEncoding;
private final int contentLength;
private final String contentType;
public MyServletRequestContext(InputStream is, String characterEncoding, int contentLength, String contentType) {
this.is = is;
this.characterEncoding = characterEncoding;
this.contentLength = contentLength;
this.contentType = contentType;
}
public String getCharacterEncoding() {
return characterEncoding;
}
public String getContentType() {
return contentType;
}
public int getContentLength() {
return contentLength;
}
public long contentLength() {
return contentLength;
}
public InputStream getInputStream() throws IOException {
return is;
}
public String toString() {
return String.format("ContentLength=%s, ContentType=%s", this.contentLength(), this.getContentType());
}
}
发现无法打开HttpServletRequest inputStream进行任何需要在放入S3之前需要读取inputStream的验证。否则,这将导致内容长度不匹配,因为由于已经读取inputStream并且偏移量从0增加,因此大小将不匹配。
那么在放入S3之前,如何对每个部分的内容类型进行验证,例如它们都是图像?或者它们都是纯文本。是否需要在创建存储桶策略级别设置此设置?尝试传递“ content_type_starts_with
”和“ content_range
”的ObjectMetadata标头没有成功。原本希望S3会拒绝看跌请求,但这成功完成了。
objectMetadata.setHeader("content_type_starts_with","image/");
objectMetadata.setHeader("content_range","1..100");
第二个问题是,在IoUtils.toString()中的非表单字段进行迭代时,随机获取Stream异常的提早结束。这主要发生在调试或强制在两次迭代之间进行GC期间。虽然,我将S3Client作为类变量,但不确定是什么导致了此异常。但是,如果将流直接读入String并从String打开另一个InputStream,则不会出现此错误。
上述方法的优点是,在将文件上传到S3时,不使用临时位置,并且还保存了内存,这是流对流的写操作。另外,由于某些部分小于5MB的限制,因此不使用multipart s3 api进行上传。但是,无论如何,我们都可以修复这些2。
在上载到S3之前或放置请求时,具有诸如内容类型检查之类的验证。我认为S3存储桶策略可以提供帮助,但一些参考/示例会有所帮助
修复了在此处使用注释读取S3对象时流过早结束的错误 https://commons.apache.org/proper/commons-fileupload/streaming.html