使用ServletInputStream()输入流读取图片方式

时间:2021-07-05 06:14:52

问题描述

最近遇到需要用到上传图片到服务器上,学习了一下原生servlet中的form上传图片保存到指定目录的情况

思路:前端提交–servlet获取inputstream–输出到本地

获取输入流后输出到本地一直打不开提示损坏/0kb.从网上看到有说需要apache的两个包io和fileupload包.我想的是不借助第三方工具包处理(tomcat也是第三方呵呵,纯的应该是利用socket吧)

项目结构

如图所示:并未使用其余组件,创建了一个动态java项目即可

使用ServletInputStream()输入流读取图片方式

问题原因

网上查到一片文章,大概意思是,上传文件不是单纯的文件流,其与本地io不同其中多了些东西.按照本地上传下载的方式无法解析出来.包括些分隔符\和表单的一些信息,需要重新处理

解决方法

手动解析出图片的流,并把其中的多余东西去掉,然后将得到的纯文件流输出到指定位置

总结回顾

该问题居然查不到当前时间的帖子,一般都是2-3年前的让我有点意外.知其然而不知其所以然早晚被人家掣肘.在框架琳琅满目的当下抄抄写写确实能解决问题而且真的是事半功倍.

另写代码要多查多看api文档

多动手敲代码,不然不知道所以然,这么点破问题弄了个周末

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
package server;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@SuppressWarnings("serial")
@WebServlet(name = "streams",urlPatterns = "/UploadServlet.do")
public class CsvTest extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
            this.doPost(request, response);
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        final int NONE = 0; // 状态码,表示没有特殊操作
        final int DATAHEADER = 1; // 表示下一行要读到报头信息
        final int FILEDATA = 2; // 表示下面要读的是上传文件和二进制数据
        final int FIELDDATA = 3; // 表示下面要读到表单域的文本值
        // 请求消息实体的总长度(请求消息中除消息头之外的数据长度)
        int totalbytes = request.getContentLength();
        
        File f; // 上传文件储存在服务器上
        // 容纳请求消息实体的字节数组
        byte[] dataOrigin = new byte[totalbytes];
        // 对于post多个文件的表单,b作为原始数据的副本提供提取文件数据的操作
        byte[] b = new byte[totalbytes];
        // 请求消息类型
        String contentType = request.getContentType();       
        String fieldname = ""; // 表单域的名称
        String fieldvalue = ""; // 表单域的值
        String fileFormName = ""; // 上传的文件再表单中的名称
        String fileRealName = ""; // 上传文件的真实名字
        String boundary = ""; // 分界符字符串
        String lastboundary = ""; // 结束分界符字符串
        
        int fileSize = 0; // 文件长度       
        // 容纳表单域的名称/值的哈希表
        Map<String, String> formfieldsTable = new HashMap<String, String>();
        // 容纳文件域的名称/文件名的哈希表
        Map<String, String> filenameTable = new HashMap<String, String>();
        
        // 在消息头类型中找到分界符的定义
        int pos = contentType.indexOf("boundary=");
        int pos2; // position2
        
        if (pos != -1) {
        pos += "boundary=".length();
        boundary = "--" + contentType.substring(pos); // 解析出分界符
        lastboundary = boundary + "--"; // 得到结束分界符
        }
        
        int state = NONE; // 起始状态为NONE
        
        // 得到请求消息的数据输入流
        DataInputStream in = new DataInputStream(request.getInputStream());
        in.readFully(dataOrigin); // 根据长度,将消息实体的内容读入字节数组dataOrigin中
        in.close(); // 关闭数据流
        String reqcontent = new String(dataOrigin); // 从字节数组中得到表示实体的字符串
        
        // 从字符串中得到输出缓冲流
        BufferedReader reqbuf = new BufferedReader(new StringReader(reqcontent));
        
        // 设置循环标志
        boolean flag = true;
        // int i = 0;
        while (flag == true) {
        String s = reqbuf.readLine();
        if (s == lastboundary || s == null)
        break;
        switch (state) {
        case NONE:
        if (s.startsWith(boundary)) {
        // 如果读到分界符,则表示下一行一个头信息
        state = DATAHEADER;
        // i += 1;
        }
        break;
        case DATAHEADER:
        pos = s.indexOf("filename=");
        // 先判断出这是一个文本表单域的头信息,还是一个上传文件的头信息
        if (pos == -1) {
        // 如果是文本表单域的头信息,解析出表单域的名称
        pos = s.indexOf("name=");
        pos += "name=".length() + 1; // 1表示后面的"的占位
        s = s.substring(pos);
        int l = s.length();
        s = s.substring(0, l - 1); // 应该是"
        fieldname = s; // 表单域的名称放入fieldname
        out.print("fieldname=" + fieldname);
        state = FIELDDATA; // 设置状态码,准备读取表单域的值
        } else {
        // 如果是文件数据的头,先存储这一行,用于在字节数组中定位
        String temp = s;
        // 先解析出文件名
        pos = s.indexOf("name=");
        pos += "name=".length() + 1; // 1表示后面的"的占位
        pos2 = s.indexOf("filename=");
        String s1 = s.substring(pos, pos2 - 3); // 3表示";加上一个空格
        fileFormName = s1;
        pos2 += "filename=".length() + 1; // 1表示后面的"的占位
        s = s.substring(pos2);
        int l = s.length();
        s = s.substring(0, l - 1);
        pos2 = s.lastIndexOf("\\"); // 对于IE浏览器的设置
        s = s.substring(pos2 + 1);
        fileRealName = s;
        out.print("fileRealName=" + fileRealName + "<br>");
        out.print("fileRealName.length()=" + fileRealName.length() + "<br>");
        if (fileRealName.length() != 0) { // 确定有文件被上传
        // 下面这一部分从字节数组中取出文件的数据
        b = dataOrigin; // 复制原始数据以便提取文件
        pos = byteIndexOf(b, temp, 0); // 定位行
        
        // 定位下一行,2 表示一个回车和一个换行占两个字节
        b = subBytes(b, pos + temp.getBytes().length + 2,
        b.length);
        
        // 再读一行信息,是这一部分数据的Content-type
        s = reqbuf.readLine();
        
        // 设置文件输入流,准备写文件
        f = new File("C:" + File.separator +"Users" + File.separator +"Administrator" + File.separator +"Desktop" + File.separator +fileRealName);
        DataOutputStream fileout = new DataOutputStream(
        new FileOutputStream(f));
        
        // 字节数组再往下一行,4表示两回车换行占4个字节,本行的回车换行2个字节,Content-type的下
        // 一行是回车换行表示的空行,占2个字节
        // 得到文件数据的起始位置
        b = subBytes(b, s.getBytes().length + 4, b.length);
        pos = byteIndexOf(b, boundary, 0); // 定位文件数据的结尾
        b = subBytes(b, 0, pos - 1); // 取得文件数据
        fileout.write(b, 0, b.length - 1); // 将文件数据存盘
        fileout.close();
        fileSize = b.length - 1; // 文件长度存入fileSize
        out.print("fileFormName=" + fileFormName + " filename="
        + fileRealName + " fileSize=" + fileSize
        + "<br>");
        filenameTable.put(fileFormName, fileRealName);
        state = FILEDATA;
        }
        }
        break;
        case FIELDDATA:
        // 读取表单域的值
        s = reqbuf.readLine();
        fieldvalue = s; // 存入fieldvalue
        out.print(" fieldvalue=" + fieldvalue + "<br>");
        formfieldsTable.put(fieldname, fieldvalue);
        state = NONE;
        break;
        case FILEDATA:
        // 如果是文件数据不进行分析,直接读过去
        while ((!s.startsWith(boundary))
        && (!s.startsWith(lastboundary))) {
        s = reqbuf.readLine();
        if (s.startsWith(boundary)) {
        state = DATAHEADER;
        } else {
        break;
        }
        }
        break;
        }       
        }
        // 指定内容类型,并且可以显示中文
        out.println("<HTML");
        out.println("<HEAD><TITLE>文件上传结果</TITLE></HEAD>");
        out.println("<BODY>");
        out.println("<H1>文件上传结果</H1><hr>");
        out.println("ID为" + formfieldsTable.get("FileID1") + "的文件"
        + filenameTable.get("FileData1") + "已经上传!<br>");
        out.println("ID为" + formfieldsTable.get("FileID2") + "的文件"
        + filenameTable.get("FileData2") + "已经上传!<br>");
        // out.println("i = " + i + "<br>");
        out.println("</BODY>");
        out.println("</HTML>");
        }       
        
        private static int byteIndexOf(byte[] b, String s, int start) {
        return byteIndexOf(b, s.getBytes(), start);
        }       
        
        private static int byteIndexOf(byte[] b, byte[] s, int start) {
        int i;
        if (s.length == 0) {
        return 0;
        }
        int max = b.length - s.length;
        if (max < 0) {
        return -1;
        }
        if (start > max) {
        return -1;
        }
        if (start < 0) {
        start = 0;
        }
        // 在b中找到s的第一个元素
        search: for (i = start; i <= max; i++) {
        if (b[i] == s[0]) {
        // 找到了s中的第一个元素后,比较剩余的部分是否相等
        int k = 1;
        while (k < s.length) {
        if (b[k + i] != s[k]) {
        continue search;
        }
        k++;
        }
        return i;
        }
        }
        return -1;
        }       
        
        private static byte[] subBytes(byte[] b, int from, int end) {
        byte[] result = new byte[end - from];
        System.arraycopy(b, from, result, 0, end - from);
        return result;
        }
        
        private static String subBytesString(byte[] b, int from, int end) {
        return new String(subBytes(b, from, end));
        }       
        }

以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/weixin_43671743/article/details/106616327