假设一台服务器部署了一个 Java 应用程序,需要将本机的数据文件(可能是文本也可能是图像)上传到远端的另外一台服务器,注意这个不是通过前端进行PUT请求来完成的,需要怎么做呢?
需要上传的一方实现一个 FileIOService.java
// 这里把图片的常用后缀采用列举的方式做了设置,可以换个其他更简单的图像判别方法
private static final String[] IMAGE_VALUES = new String[]{"jpg", "BMP", "bmp", "JPG", "wbmp", "jpeg", "png", "PNG", "JPEG", "WBMP", "GIF", "gif"};
private static final HashSet<String> IMAGE_TYPE = new HashSet<>(Arrays.asList(IMAGE_VALUES));
/**
* 将文件转换成 byte[]
*
* @param filePath 文件路径
* @return buffer
*/
private byte[] getBytes(String filePath) throws IOException {
byte[] buffer = new byte[0];
FileInputStream fileInputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
File file = new File(filePath);
fileInputStream = new FileInputStream(file);
byteArrayOutputStream = new ByteArrayOutputStream(1024);
byte[] b = new byte[1024];
int n;
while ((n = fileInputStream.read(b)) != -1) {
byteArrayOutputStream.write(b, 0, n);
}
buffer = byteArrayOutputStream.toByteArray();
} catch (Exception e) {
log.error(e);
} finally {
if (fileInputStream != null) fileInputStream.close();
if (byteArrayOutputStream != null) byteArrayOutputStream.close();
}
return buffer;
}
// 获取文件字节数组
private byte[] getFileBytes(File file) throws IOException {
String fileName = file.getName();
String[] names = fileName.split("\\.");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
if (names.length > 1) {
String fileType = names[names.length - 1];
if (IMAGE_TYPE.contains(fileType)) {
log.info("The file {} is a picture.", fileName);
BufferedImage bi;
try {
bi = ImageIO.read(file);
ImageIO.write(bi, fileType, byteArrayOutputStream);
byte[] bytes = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
return bytes;
} catch (IOException e) {
log.error(e);
}
}
}
return getBytes(file.getAbsolutePath());
}
/**
* 将文件流发送至另外服务器的方法
*
* @param bytes 文件字节
* @param fileName 文件路径
* @return 从服务器端响应的流 可通过 new String(bytes); 转换
*/
private void httpPost(byte[] bytes, String fileName) throws IOException {
try {
URL console = new URL(LOCALIZATION_UPLOAD_URL);
HttpURLConnection conn = (HttpURLConnection) console.openConnection();
conn.addRequestProperty("fileName", fileName);
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setUseCaches(false);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("POST");
conn.connect();
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
log.info(bytes.length);
out.write(bytes);
out.flush();
out.close();
InputStream is = conn.getInputStream();
if (is != null) {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
is.close();
conn.disconnect();
byte[] response = outStream.toByteArray();
if (response.length > 0) {
log.info("Upload response: {}", new String(response));
}
}
conn.disconnect();
} catch (Exception e) {
log.error("Upload failed: {}", e);
}
}
// 支持多文件上传
private void multipleFileUpload(File file) {
if (file.isFile()) {
log.info("upload file name {}", file.getName());
uploadFile(file, file.getName());
return;
}
File[] files = file.listFiles();
if (files == null) return;
for (File f : files) {
multipleFileUpload(f);
}
}
// 上传单文件
private void uploadFile(File file) {
try {
byte[] bytes = getFileBytes(file);
log.info("Upload file : {}", file.getName());
httpPost(bytes, file.getName());
} catch (IOException e) {
log.error(e);
}
}
远端服务器服务接受数据流并持久化到磁盘
// FileIOUtils.java 下面会用到的获取服务器上根目录所在位置的工具类
public class FileIOUtils {
private FileIOUtils() {
throw new IllegalStateException("Utility class");
}
public static File getRootPath() throws FileNotFoundException {
File path = new File(ResourceUtils.getURL("classpath:").getPath());
if(!path.exists()) {
path = new File("");
}
return path;
}
}
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
/**
* 判断指定文件夹是否存在,不存在则创建
* @param path 路径前缀
*/
private void createDirIfNoExist(String path) {
File file = new File(path, FilenameUtils.getName(path));
if(!file.exists()) {
boolean res = file.mkdirs();
log.info("set writable {}", file.setWritable(true, false));
log.info("create dir {} : {}", file.getAbsoluteFile(), res);
}
}
@RequestMapping("upload")
public void receiveFile(HttpServletRequest request) throws IOException {
ServletInputStream in = request.getInputStream();
try {
String fileName = request.getHeader("fileName");
String path = FileIOUtils.getRootPath().getAbsolutePath() + "/static/upload/";
String name = FilenameUtils.getName(fileName);
String filePath = path + fileName.replace(name, "");
createDirIfNoExist(filePath);
File file = new File(filePath , FilenameUtils.getName(fileName));
if (!file.exists()) {
log.info("create file {} : {}", fileName, file.createNewFile());
}
log.info("set file writable {}", file.setWritable(true, false));
FileUtils.copyInputStreamToFile(in, file);
} catch (Exception e) {
log.error(e);
}
}
在 SpringBoot 框架下,可能会有异常: request.getInputStream()
获取不到数据(返回空值),这是因为在框架里,HttpServletRequest 已经被框架在接收 HTTP Request 时读取过一遍了,然后我们自己要读取的话,数据流已经被处理过(处理过一次,InputStream的 read index 已经为 -1,再直接读就为空了),这种情况下需要加一个Wrapper类,在第一次读取时对数据进行一次备份
InputStreamHttpServletRequestWrapper.java
public class InputStreamHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
private static final int BUFFER_SIZE = 4096;
public InputStreamHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = inputStream2Byte(request.getInputStream());
}
private byte[] inputStream2Byte(InputStream inputStream) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] bytes = new byte[BUFFER_SIZE];
int length;
while ((length = inputStream.read(bytes, 0, BUFFER_SIZE)) != -1) {
outputStream.write(bytes, 0, length);
}
return outputStream.toByteArray();
}
@Override
public ServletInputStream getInputStream() throws IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
log.info("set readListener");
}
@Override
public int read() throws IOException {
return inputStream.read();
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}