文件的上传与下载-tomcat

文件的上传与下载

文件上传

1.1前台页面

在做文件上传时,会有一个上传文件的页面,首先我们需要一个表单,并且表单的请请求方式为Post;
其次我们的from表单的enctype要设为"multipart/form-data" ,即 enctype=“multipart/form-data”
(意思是设置表单的类型为文件上传表单. )
默认下表单的类型为 “application/x-www-form-urlencoded” (不能用于文件上传)

前端表单配置

示例代码:

<!--  文件上传表单
      1.表单提交类型为 method="post"
      2.表单类型 enctype="multipart/form-data"
      3.表单元素类型  文件域设置name 属性值
-->
<form action="/upload" method="post" enctype="multipart/form-data">
    姓名<input type="text" name="name"><br>
    文件:<input type="file" name="file"><br>
    <input type="submit" value="上传">
</form>
/**  
 *  使用注释@MultipartConfig 将一个Servlet标识为支持文件上传  
 *  Servlet 将multipart / form-data 的Post请求封装为Part,通过Part对上传的文件进行操作  
 *  
 */@WebServlet("/uploadServlet")  
@MultipartConfig  
public class UploadServlet extends HttpServlet {  
    @Override  
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  
     System.out.println("文件上传");  
     //  获得请求的编码格式  
     req.setCharacterEncoding("UTF-8");  
     //  获取普通类单项(获取参数  
     String uname = req.getParameter("uname");  
     System.out.println("uname"+uname);  
  
     //  获取Part对象 (Servlet 将 multipart/form-data 的 POST 请求封装为Part对象  
     Part part = req.getPart("file");  
     //  通过Part对象得到上传的文件名  
     String fileName = part.getSubmittedFileName();  
     System.out.println("上传的文件名"+fileName);  
     //  得到文件存放的路径  
     String  filePath = req.getServletContext().getRealPath("/");  
     System.out.println("文件存放的路径"+filePath);  
     //  上传文件到指定目录  
     part.write(filePath+"/"+fileName)  ;  
    }  
}

![[Pasted image 20250308154352.png]]

文件下载

超链接下载

当我们在HTML或jsp页面使用a标签是, 愿意是希望能够进行跳转,当但超链接遇到浏览器不能识别的资源时会自动下载,当遇到浏览器能够直接显示的资源时,浏览器会默认的显现出来,比如 txt png jpg

当然也可以通过download属性规定浏览器下载.但有些浏览器并不支持

默认下载

当但超链接遇到浏览器不能识别的资源时会自动下载,当遇到浏览器能够直接显示的资源时,浏览器会默认的显现出来

<%@ page contentType="text/html;charset=UTF-8" language="java" %>  
<html>  
<head>  
    <title>文件下载</title>  
</head>  
<body>  
  
<!--浏览器能识别的资源-->  
<a href="">文本文件</a>  
<a href="down/2135.jpg_wh300.jpg">图片文件</a>  
<!-- 浏览器不能识别的资源-->  
<a href="">压缩文件</a>  
</body>  
</html>

指定download属性下载

<!--浏览器能识别的资源-->  
<a href="xxx.txt">文本文件</a>  
<a href="down/2135.jpg_wh300.jpg">图片文件</a>  
<!-- 浏览器不能识别的资源-->  
<a href="xxx.zip">压缩文件</a>  
  
<hr>  
<a href="">文本文件</a>  
<a href="down/2135.jpg_wh300.jpg" download="图片">图片文件</a>

![[Pasted image 20250308170113.png]]

download属性可以不写任何信息,会使用默认文件名.如果设置了download属性的值,则使用设置的值为文件名.当用户打开浏览器点击链接时会直接下载

后台下载

实现步骤

设置响应头

  1. 需要通过resp.setContentType 方法设置 Content-type 头字段的值 , 为浏览器无法使用某种方法或激活某个程序来处理的MIME类型,例如"application/octet-stream"或"application/x-msdownload"等
  2. 需要通过 resp.setHeader方法设置Content-Dispostion头的值为"attadchment;filename=文件名"(Content-Disposition:指定 attachment 模式,强制浏览器触发下载行为)
    读取文件并写入响应流
  3. 读取下载文件(通过文件输入流(FileInputStream)读取本地文件)
  4. 调用 响应输出流(resp.getOutputStream方法) 向客户端写传输文件内容
// 1. 设置请求编码
//设置请求体的编码为 UTF-8,确保能正确解析中文文件名等参数。
        request.setCharacterEncoding("UTF-8");
        
 // 2. 获取文件路径
 //获取 Web 应用根目录的物理路径
        String path = getServletContext().getRealPath("/");
        
// 3. 获取文件名参数

        String name = request.getParameter("filename");  // 原图错误:Tjlename
        
// 4. 验证文件名合法性(新增:防止路径穿越攻击)
        if (name == null || name.contains("..") || name.contains("/")) {
            response.sendError(400, "Invalid filename");
            return;
        }    
// 5. 创建 File 对象
        File file = new File(path + name);
        
//判断文件是否存在并且是一个标准文件  
    if(!file.exists()&&!file.isFile()){  
    //设置响应头
        resp.setContentType("application/octet-stream");  
         //设置响应类型  
        resp.setHeader("Content-Disposition", "attachment; filename="+fileName); 
	//流操作
        //得到file文件的输入流  
        InputStream in = new FileInputStream(file);  
        //得到字节输出流  
        ServletOutputStream out = resp.getOutputStream();  
        //定义byte数组  
        byte[] bytes = new byte[1024];  
        //定义长度  
        int len=0;  
        //循环输出  
        while((len=in.read(bytes))!=-1){  
            //输出  
            out.write(bytes,0,len);  
        }  
        //关闭资源  
        out.close();  
        in.close();  
  
  
    }else{  
        resp.getWriter().write("文件不存在!,请重试");  
        resp.getWriter().close();  
    }  
  
}
<form action="down">  
  
    文件名:<input type="text" name="fileName" placeholder="请输入要下载的文件名">  
    <button>下载</button>  
  
</form>

响应头:控制浏览器行为

Content-Type:定义响应类型

作用:
告知浏览器响应体的数据类型(MIME 类型)。若设置为 text/html,浏览器会直接渲染 HTML;若设置为 application/octet-stream,浏览器会将其视为二进制流,触发下载行为。

Content-Disposition:定义下载行为

作用:
attachment 模式强制浏览器弹出下载对话框,filename 指定默认文件名。
为何必须设置:
若未设置此头,浏览器可能根据 URL 或内容猜测文件名(如 down?file=123 生成 down 作为文件名)。

获取 example.jar 的几种方法
example.jar 是一个示例的 JAR 文件,您可以通过以下方式获取或生成它:

流式传输

使用 FileInputStream 读取文件

为何必须使用输入流:
文件可能较大(如 GB 级),直接加载到内存会导致 内存溢出(OOM)。流式传输通过分块读写(缓冲区),逐段处理文件,降低内存压力。

通过 ServletOutputStream 写入响应

为何必须使用输出流:
HTTP 响应本质是字节流,必须通过流式传输将文件内容写入响应体。
类比解释
假设你要寄一封信:

信件内容:相当于文件内容(如 data.pdf 的二进制数据)。
信封:相当于 HTTP 响应头(Content-Type、Content-Disposition 等)。
邮局:相当于 ServletOutputStream,负责将信件内容(字节流)装入信封并发送
如果没有 ServletOutputStream:
相当于你直接告诉收件人“信在某个地址”,对方需要自己去取。这会暴露你的文件路径(如服务器上的 /files/data.pdf),导致安全风险。

为何不能直接操作文件路径?
错误示例
// 错误做法:直接重定向到文件路径
javaresponse.sendRedirect("/files/data.pdf");
后果:

  • 暴露服务器路径:客户端会看到服务器文件路径(如 http://yourserver.com/files/data.pdf)。
  • 路径遍历攻击:攻击者可构造 URL(如 http://yourserver.com/files/…/…/etc/passwd)窃取敏感文件。
  • 无法控制下载行为:浏览器可能直接显示文件(如图片),而非触发下载。

分块传输逻辑详解:

//得到file文件的输入流  
InputStream in = new FileInputStream(file);  
//得到字节输出流  
 ServletOutputStream out = resp.getOutputStream();  
 //定义byte数组  
byte[] bytes = new byte[1024];  
//定义长度  
int len=0;  
//循环输出  
while((len=in.read(bytes))!=-1){  
//输出  
    out.write(bytes,0,len);   // 分块写入响应流
}  

逐行解释
1.byte[] bytes= new byte[4096]

  • 创建一个 4KB 的缓冲区(内存块),用于临时存储从文件中读取的数据。
  • 为什么是 4KB?
    平衡内存占用和 IO 效率。缓冲区太小(如 1KB)会增加 IO 次数;太大(如 10MB)会浪费内存

2.in.read( bytes)

从文件输入流(FileInputStream)中读取数据,每次最多读取 4KB,返回实际读取的字节数(bytesRead)。
底层操作:
文件内容 → 读取到内存缓冲区 → 准备写入响应流。

3.out.write(bytes, 0, bytesRead)

将缓冲区中的数据写入响应输出流(ServletOutputStream)。
参数解释:

  • buffer:数据来源的字节数组。
  • 0:从缓冲区的第 0 个字节开始。
  • bytesRead:写入的实际长度(避免缓冲区未填满时的无效数据)

4.循环条件 while (… != -1)

  • in.read() 返回 -1 表示文件已读完,循环终止
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值