下载文件
//下载文件Servlet
public class UploadFileServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
//获取读取本地文件的输入流
InputStream is;
ServletContext context = getServletContext();
is = context.getResourceAsStream("/NVRFE60YF.png");
//设置输出的MIME类型
resp.setContentType("application/force-download");
//设置响应头
resp.setHeader("Content-Length",String.valueOf(is.available()));//设置文件文件长度
resp.setHeader("Content-Disposition","attachment;filename=wenjian.png");//设置文件需要用户下载而不是展示(attachment)并且文件默认名为wenjian.png
//获取向客户端的输出流
ServletOutputStream os = resp.getOutputStream();
//缓冲512字节
byte[] bytes = new byte[512];
int len = -1;
//写到输出流
while((len = is.read(bytes))!=-1){
os.write(bytes,0,len);
}
//关闭流
is.close();
os.close();
}
}
上传文件
通过Apache开源类库
Apache提供了两个和软件上传相关的软件包
fileupload软件包和I/O软件包
fileupload的主要接口和类的类框图
对于一个请求正文为“multipar/form-data”类型的HTTP请求。upload软件包把请求正文的复合表单的每个子部分看成一个FileItem对象。
FileItem对象有两种类型formFild类型(普通表单域)非fromField类型(文件类型)
ServletFileUpload类是文件上传处理器。
//上传文件Servlet
public class DownLoadFileServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
//设置响应正文编码
resp.setCharacterEncoding("utf-8");
//创建一个DiskFileItemFactory对象(用来制造DiskFileItem对象)
DiskFileItemFactory factory = new DiskFileItemFactory();
//设置向硬盘写数据时所用的缓冲区大小
factory.setSizeThreshold(1024);
//获取Context对象
ServletContext context = getServletContext();
//获取临时目录
String tempPath = context.getRealPath("/temp");
//设置一个临时目录
factory.setRepository(new File(tempPath));
//创建一个文件上传处理器(与factory产生联系)
ServletFileUpload fileUpload = new ServletFileUpload(factory);
//设置最大上传文件大小
fileUpload.setSizeMax(10*1024*1024);
PrintWriter writer = resp.getWriter();
//遍历fileItem
try {
Map<String, List<FileItem>> listMap = fileUpload.parseParameterMap(req);
for (String s : listMap.keySet()) {
List<FileItem> fileItems = listMap.get(s);
for (FileItem fileItem : fileItems) {
if(fileItem.isFormField()){
//普通表单域
writer.println(fileItem.getFieldName()+":"+fileItem.getString());
}else {
//获取文件名
String fN = fileItem.getName();
//保存
fileItem.write(new File(context.getRealPath("/downFile")+"/"+fN));
writer.println(fN+"已经保存");
}
}
}
} catch (FileUploadException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
待解决:不知道为什么通过fileItem不能获取中文字符
读写Cookie
Cookie是服务器存放在客户端的一些信息。
Web服务器为了支持Cookie需要具备以下数据
1.在HTTP响应结果中添加Cookie数据
2.解析HTTP请求中的Cookie信息
浏览器为了支持Cookie,需要具备以下功能。
1.解析HTTP响应中的Cookie数据
2.把Cookie数据保存到本地硬盘
3.读取本地硬盘上的Cookie数据,把他添加到HTTP请求中。
添加Cookie
//创建一个Cookie对象
Cookie cookie = new Cookie("key","value");
//设置失效时间(单位:秒)1.大于0时,代表浏览器在客户硬盘上保存Cookie的时间 2.等于0,告诉浏览器删除客户端中的这个Cookie 3.小于0,不把Cookie存入硬盘,该Cookie生命周期和此浏览器进程相当。
//默认为-1
cookie.setMaxAge(1);
设置cookie共享范围
//tomcat根目录下所有Web应用间可访问
cookie.setPath("/");
//app1应用可访问
cookie.setPath("/app1");
//.baidu.com域名下可访问
cookie.setDomain(".baidu.com");
获取Cookie对象
//获取HTTP请求中的所有Cookie信息
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {
System.out.println(cookie.getName());
System.out.println(cookie.getValue());
System.out.println(cookie.getMaxAge());
}
获取RequestDispa对象
//方式1(绝对路径)
ServletContext servletContext = getServletContext();
servletContext.getRequestDispatcher("路径");
//方式2(绝对路径/相对路径)
req.getRequestDispatcher("路径");
方式1的路径是绝对路径
方式2的路径可以是绝对路径也可以是相对路径
以/开头的为绝对路径。Web应用的根目录
请求转发和包含
由于无法通过Servlet获取其他的Servlet。因此当我们需要在一次请求中调用多个Servlet参与的话。。就可以通过请求转发或包含来完成
特点:
1.原组件和目标组件处理的都是一个请求。
2.目标组件可以是Servlet JSP或HTML
请求转发:
forward方法:把请求转发给其他组件,并由其他组件完成后续的生成响应结果等操作。
在执行requestDispatcher.forwardf方法时,会清空用于存放响应正文的缓冲区。也就是说在源组件中不能向响应正文中添加数据。如果调用了与提交缓冲区相关的方法。会导致IllegalStateException异常。
包含:
include方法:把包含的组件产生的响应结果包含到自身的响应结果中。
特点:
1.源组件与被包含组件的输出数据都会被添加到响应结果中
2.在目标组件中对响应状态代码或者响应头所作的修改都会被忽略。
重定向:
运作流程
1.用户通过URL访问某个Web组件
2.服务器端的组件返回一个状态代码为302的响应结果。该结果的含义是让客户端再请求访问另一个Web组件
3.浏览器接收响应结果后自动访问另一个Web组件
4.浏览器端接收另一个Web组件的响应结果
重定向特点(response.sendRedirect方法)
1.源组件生成的响应结果不会被发送到客户端。只有重定向的Web组件的响应结果发送到客户端
2.如果源组件在重定向前调用了提交缓冲区的方法,在执行sendRedirect时,会抛出IllegalStateException异常。
3.源组件和重定向的web组件不是一次请求。因此他们不共享request和response
4.参数可以是任意有效的地址
异步处理HTTP请求
Servlet会为每个HTTP请求分配一个工作线程。即每次Servlet容器都会从主线程池中取出一个空闲的工作线程,由该线程从头到尾处理这次请求。如果在HTTP请求中涉及到I/O操作或者数据库的操作等耗时的操作,那么该工作线程就会被长时间占用。
因此我们可以采取异步的方式解决这一问题。
![](/qrcode.jpg)
Servlet异步处理机制
Servlet从HTTPServletRequest对象中获取一个AsyncContext对象。该对象表示异步处理上下文。AsyncContext把响应当前请求的任务传给一个新的线程。最初的由Servlet容器分配的主线程就可以先被释放掉。从而可以处理更多的请求。所以所谓的Servlet异步处理机制,就是把任务从Servlet容器的主线程传给另一个线程。
异步处理流程
一:首先需要配置Servlet为支持Async的Servlet
方法1注解
@WebServlet(
asyncSupported = true
)
方法2web.xml文件
<servlet>
<servlet-name>upload</servlet-name>
<servlet-class>com.sea.UploadFileServlet</servlet-class>
<async-supported>true</async-supported>//这里
</servlet>
二:获取AsyncContext对象
AsyncContext asyncContext = httpServletRequest.startAsync();
asyncContext为处理异步操作提供了上下文,他拥有的方法为:
三:启动异步线程
有多种方式启动异步线程,每种方式都有各自的特点
方式1:
AsyncContext asyncContext = req.startAsync();
//设置异步操作超时时间
asyncContext.setTimeout(60*5);
//开启异步线程
asyncContext.start(new MyThread(asyncContext));
asyncContext的start方法的实现方式和Servlet容器有关。有的是直接从Servlet的工作线程中取一个空闲的线程(相当于并发效率并没有改变,取一个放回一个)。另一种实现是维护一个专门的线程池来给他分配线程。
方式2
AsyncContext asyncContext = req.startAsync();
//开启异步线程
new Thread(new MyThread(asyncContext)).start();
手动创建新线程,如果访问量大的话,可能同时存在很多的线程,大大降低运行效率。
方式3
//创建一个线程池。
private static ThreadPoolExecutor pool = new ThreadPoolExecutor(
100,//线程池维护的线程的最小数量
200,//线程池维护的线程的最大数量
5000L,//线程池维护的线程所允许的空闲时间
TimeUnit.MILLISECONDS,//线程池维护的线程所允许的空闲时间的单位
new ArrayBlockingQueue<Runnable>(100)//线程池使用的缓冲队列
);
AsyncContext asyncContext = req.startAsync();
//开启线程
pool.execute(new MyThread(asyncContext));
处理请求的具体线程
public class MyThread implements Runnable{
//HTTP异步请求上下文
AsyncContext asyncContext = null;
public MyThread(AsyncContext context){
asyncContext = context;
}
@Override
public void run() {
System.out.println("异步线程执行耗时操作");
//执行完毕后调用该方法通知Servlet
asyncContext.complete();
}
}
异步监听器
AsyncContext asyncContext = req.startAsync();
//添加监听器
asyncContext.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent asyncEvent) throws IOException {
System.out.println("异步线程执行完时调用");
}
@Override
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
System.out.println("异步线程执行超时时调用");
}
@Override
public void onError(AsyncEvent asyncEvent) throws IOException {
System.out.println("异步线程出错时调用");
}
@Override
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
System.out.println("异步线程开始时");
}
});
非阻塞I/O
阻塞I/O:当要读取的数据还没准备好,或者要写的数据还没准备好,线程会进入阻塞状态。
非阻塞I/O:当要读取的数据还没准备好,或者要写的数据还没准备好,线程会直接退出读/写方法。
Servlet有两个监听器
ReadListener接口:
监听ServletInputservlet输入流的行为。
接口的方法有:
//ServletInputStrem监听器
public class MyReadListener implements ReadListener {
StringBuffer sb = new StringBuffer();
ServletInputStream is = null;
AsyncContext context = null;
public MyReadListener(ServletInputStream is,AsyncContext context){
this.context = context;
this.is = is;
}
//servletInputStream流中有数据时触发
@Override
public void onDataAvailable() throws IOException {
System.out.println("输入流中有可读数据");
int len = -1;
byte[] bytes = new byte[1024];
while(is.isReady()&&(len = is.read(bytes))!=-1){
System.out.println(len);
sb.append(new String(bytes,"utf-8"));
}
System.out.println("结束");
}
//读完
@Override
public void onAllDataRead() throws IOException {
System.out.println("读取完毕");
System.out.println(sb.toString());
//或者转发给其他组件
//context.dispatch("web组件");
}
@Override
public void onError(Throwable throwable) {
}
}
遇到的坑:is.isReady()&&(len = is.read(bytes))!=-1一定要写前面的is.isReady()他在is没留数据时会返回false。而这个is.read方法是重写过的,当没有数据可读时他会抛出异常。。。。
WriteListener接口:监听ServletOutputStream输出流的行为。
//ServletOutputStream监听器
public class MyWriteListener implements WriteListener {
AsyncContext context = null;
ServletOutputStream os = null;
public MyWriteListener(AsyncContext context,ServletOutputStream os){
this.context = context;
this.os = os;
}
@Override
public void onWritePossible() throws IOException {
System.out.println("可以向输出流写数据了");
}
@Override
public void onError(Throwable throwable) {
System.out.println("发生错误");
}
}
服务器端推送
//推送测试
@WebServlet(urlPatterns = "/push")
public class PushServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取pushBuilder对象
PushBuilder pushBuilder = req.newPushBuilder();
PrintWriter writer = resp.getWriter();
pushBuilder.path("NVRFE60YF.png").push();
if(pushBuilder!=null){
writer.write("<img src=\"NVRFE60YF.png\">");
}else {
writer.write("不支持");
}
}
}
(由于我的tomcat是8.5.51不支持推送,所以推送的代码我没有具体试过。若想支持推送需要配置tomcat9)