一、Request
request 与 response原理 我们知道tomcat会在第一次请求url来创建对应路径映射的servlet对象(如果设置servlet启动时自动创建,则请求url不会重复创建servlet对象) 同时,每一次请求,tomcat会先创建response和request对象,其中request对象中会封装请求消息数据 然后tomcat会将这两个对象传递给servlet实现类中的service方法,并调用 我们可以在service方法中,通过response对象来设置响应消息,最后用户通过浏览器可以获取到这些响应数据(这是省略前端的情况下,一般前端工程师会对响应拦截处理) 当然tomcat在响应数据前,会先获取response里面的数据,然后再响应给前端
如果你观察Tomcat的源码就会发现,tomcat有一个HttpServletRequest的实现类org.apache.catalina.connector包下的RequestFacade,同时此包下还有一个ResponseFacade是HttpServletResponse接口的实现类
1、获取请求消息数据
1、获取请求行数据
请求行格式 举例:GET /day1/demo1?name=zhangsan HTTP/1.1 GET这个位置:请求方法,可以是POST,DELETE,PUT等等,可通过String getMethod()
方法获取 /day1:虚拟目录,是在Tomcat设置的,可通过String getContextPath()
方法获取 /demo1:servlet路径,可通过String getServletPath()
方法获取 ?name=zhangsan:请求参数,可通过String getQueryString()
方法获取 /day1/demo1:这两个合起来是请求URI,可通过String getRequestURI()
方法获取 StringBuffer getRequestURL()
:和上面差不多,只不过它获取的路径会带http://ip地址
/day1/demo1HTTP/1.1:协议及版本,可通过String getProtocol()
方法获取 获取客户机ip地址,可通过String getRemoteAddr()
方法获取
package com. yzpnb. servlet ;
import javax. servlet. * ;
import javax. servlet. annotation. WebServlet ;
import javax. servlet. http. HttpServlet ;
import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import java. io. IOException ;
@WebServlet ( "/dome1" )
public class Dome1 extends HttpServlet {
@Override
protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
String method = req. getMethod ( ) ;
System . out. println ( "请求方法:" + method) ;
String contextPath = req. getContextPath ( ) ;
System . out. println ( "虚拟目录:" + contextPath) ;
String servletPath = req. getServletPath ( ) ;
System . out. println ( "servlet路径:" + servletPath) ;
String queryString = req. getQueryString ( ) ;
System . out. println ( "请求参数:" + queryString) ;
String requestURI = req. getRequestURI ( ) ;
StringBuffer requestURL = req. getRequestURL ( ) ;
System . out. println ( "请求路径:" + requestURI+ "========" + requestURL) ;
String protocol = req. getProtocol ( ) ;
System . out. println ( "协议及版本:" + protocol) ;
String remoteAddr = req. getRemoteAddr ( ) ;
System . out. println ( "客户机ip:" + remoteAddr) ;
}
}
2、获取请求头数据
方法 String getHeader(String name)
:通过请求头名称获取请求头(就是键值对的形式)Enumeration< String > getHeaderNames()
:获取所有请求头名称,返回值比较特殊,可以当Enumeration类型为迭代器
package com. yzpnb. servlet ;
import javax. servlet. * ;
import javax. servlet. annotation. WebServlet ;
import javax. servlet. http. HttpServlet ;
import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import java. io. IOException ;
import java. util. Enumeration ;
@WebServlet ( "/dome1" )
public class Dome1 extends HttpServlet {
@Override
protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
Enumeration < String > headerNames = req. getHeaderNames ( ) ;
System . out. println ( "所有请求头" ) ;
while ( headerNames. hasMoreElements ( ) ) {
String s = headerNames. nextElement ( ) ;
String header = req. getHeader ( s) ;
System . out. println ( s+ " " + header) ;
}
String userAgent = req. getHeader ( "user-agent" ) ;
if ( userAgent. contains ( "Chrome" ) ) {
System . out. println ( "很好,这是我们推荐的Chrome浏览器" ) ;
} else {
System . out. println ( "推荐使用谷歌Chrome浏览器" ) ;
}
String referer = req. getHeader ( "referer" ) ;
System . out. println ( "用户从:" + referer+ "来" ) ;
if ( referer != null ) {
if ( referer. contains ( "dome1" ) ) {
System . out. println ( "正常进入链接" ) ;
} else {
System . out. println ( "非正常进入" ) ;
}
} else {
System . out. println ( "直接输入网址进入" ) ;
}
}
}
3、请求体数据
post请求才有请求体 1、先获取输入流对象:BufferReader getReader():获取字符输入流
,ServletInputStream getInputStream():获取字节输入流
发送post请求,需要用到表单,或者用某些模拟浏览器,如果你不会,应该先去补习,而不是学这个
@Override
protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
BufferedReader reader = req. getReader ( ) ;
String line = null ;
while ( ( line = reader. readLine ( ) ) != null ) {
System . out. println ( line) ;
}
}
2、获取数据通用方法
1、获取请求参数通用方法
String getParameter(String name)
:根据参数名称获取参数值,比如username=zhangsan&age=14,那么我们通过参数名username可以获取对应值zhangsanString[] getParameterValues(String name)
:根据参数名称获取参数值数组,比如hobby=basketball&hobby=game,通过hobby可以获取数组[basketball,game]Enumeration< String > getParameterNames()
:获取所有请求参数名称Map< String,String[] > getParameterMap()
:获取所有参数的map集合
<%--
Created by IntelliJ IDEA.
User: dell
Date: 2020/10/4
Time: 16:42
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
< html>
< head>
< title> Title</ title>
</ head>
< body>
< form action = " /dome1" method = " post" >
< input type = " text" name = " username" placeholder = " 请输入用户名" /> < br/>
< input type = " text" name = " password" placeholder = " 请输入密码" /> < br/>
< input type = " checkbox" name = " hobby" value = " game" /> 游戏
< input type = " checkbox" name = " hobby" value = " basketball" /> 篮球< br/>
< input type = " submit" value = " 提交" />
</ form>
</ body>
</ html>
package com. yzpnb. servlet ;
import javax. servlet. * ;
import javax. servlet. annotation. WebServlet ;
import javax. servlet. http. HttpServlet ;
import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import java. io. BufferedReader ;
import java. io. IOException ;
import java. util. Enumeration ;
import java. util. Map ;
import java. util. Set ;
@WebServlet ( "/dome1" )
public class Dome1 extends HttpServlet {
@Override
protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
this . doPost ( req, resp) ;
}
@Override
protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
String username = req. getParameter ( "username" ) ;
System . out. println ( "username=" + username) ;
System . out. println ( "==============================" ) ;
String [ ] hobbies = req. getParameterValues ( "hobby" ) ;
if ( hobbies!= null ) {
System . out. print ( "hobby=[" ) ;
for ( String hobby: hobbies) {
System . out. print ( hobby+ "," ) ;
}
System . out. println ( "]" ) ;
System . out. println ( "==============================" ) ;
}
Enumeration < String > parameterNames = req. getParameterNames ( ) ;
while ( parameterNames. hasMoreElements ( ) ) {
String s = parameterNames. nextElement ( ) ;
System . out. print ( s+ "=" ) ;
String parameter = req. getParameter ( s) ;
System . out. println ( parameter) ;
}
System . out. println ( "==============================" ) ;
Map < String , String [ ] > parameterMap = req. getParameterMap ( ) ;
Set < String > keySet = parameterMap. keySet ( ) ;
for ( String key: keySet) {
String [ ] values = parameterMap. get ( key) ;
if ( hobbies!= null ) {
System . out. print ( key+ "=" ) ;
for ( String value: values) {
System . out. print ( value+ " " ) ;
}
System . out. println ( ) ;
}
}
}
}
req. setCharacterEncoding ( "utf-8" ) ;
这时你可能要问,如果用tomcat8以下版本怎么解决中文乱码,放心,就像人们现在都用智能手机,你还会专门回去用小诺基亚么?还会有人专门回去诺基亚研制触屏,上网么?
2、请求转发
请求转发 一种服务器跳转方式,通常我们一个servlet是完不成一项功能的,一般都是几个servlet配合完成,那么如何在servlet1处理完后跳转到servlet2继续处理呢?就需要请求转发 1、RequestDispatcher requestDispatcher = req.getRequestDispatcher(String path);
:通过request对象获取请求转发器对象RequestDispatcher 2、requestDispatcher.forward(ServletRequest servletRequest,ServletResponse servletResponse);
:使用请求转发器对象RequestDispatcher的forward方法转发
package com. yzpnb. servlet ;
import javax. servlet. * ;
import javax. servlet. annotation. WebServlet ;
import javax. servlet. http. HttpServlet ;
import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import java. io. BufferedReader ;
import java. io. IOException ;
import java. util. Enumeration ;
import java. util. Map ;
import java. util. Set ;
@WebServlet ( "/dome1" )
public class Dome1 extends HttpServlet {
@Override
protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
System . out. println ( "我是dome1Get" ) ;
req. getRequestDispatcher ( "/dome2" ) . forward ( req, resp) ;
}
@Override
protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
req. setCharacterEncoding ( "utf-8" ) ;
System . out. println ( "我是dome1Post" ) ;
req. getRequestDispatcher ( "/dome2" ) . forward ( req, resp) ;
}
}
package com. yzpnb. servlet ;
import javax. servlet. ServletException ;
import javax. servlet. annotation. WebServlet ;
import javax. servlet. http. HttpServlet ;
import javax. servlet. http. HttpServletRequest ;
import javax. servlet. http. HttpServletResponse ;
import java. io. IOException ;
@WebServlet ( "/dome2" )
public class Dome2 extends HttpServlet {
@Override
protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
System . out. println ( "我是dome2Post" ) ;
}
@Override
protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
System . out. println ( "我是dome2Get" ) ;
}
}
需要注意的点(面试可能问) 1、浏览器地址栏并不会发生改变 2、只能请求转发到服务器内部资源,而不能转发到外部,比如www.baidu.com 3、转发是一次请求,而不是多次,如果细心你会发现我们forward跳转时,都是将request和response对象传入的,也就是说,无论转发多少次,都是同一此请求
3、共享数据
域对象 一个有作用范围的对象,可以在范围内共享数据,不同域对象作用范围不同
request域 范围是一次请求,通常用于在多个转发资源中共享数据,比如servlet1转发到2,2转发到3,而就算转发10000次,也都在一次请求中,这就是request域 void setAttribute(String name,Object obj)
:存储一个数据到request域中,name是键,obj是数据,在同一个域中,可通过name获取objObject getAttribute(String name)
:通过键获取值void removeAttribute(String name)
:移除指定键值对
@Override
protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
req. setCharacterEncoding ( "utf-8" ) ;
System . out. println ( "我是dome1Post" ) ;
req. setAttribute ( "name" , "我是字符串" ) ;
req. setAttribute ( "age" , 15 ) ;
req. setAttribute ( "array" , new String [ ] { "1" , "2" , "3" } ) ;
req. getRequestDispatcher ( "/dome2" ) . forward ( req, resp) ;
}
@Override
protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
System . out. println ( "我是dome2Post" ) ;
String name = ( String ) req. getAttribute ( "name" ) ;
Integer age = ( Integer ) req. getAttribute ( "age" ) ;
String [ ] array = ( String [ ] ) req. getAttribute ( "array" ) ;
System . out. println ( name) ;
System . out. println ( age) ;
System . out. println ( array) ;
}
移除就不演示了,直接request.removeAttribute("name")这样的就可以将值移除出域
4、获取ServletContext
ServletContext 一个非常重要的对象,这里只讲怎么获取,后面会专门讲解此对象 ServletContext getServletContext()
:通过Request对象调用方法获取即可
二、HTTP响应协议
响应消息 前面我们学过请求消息,就是客户端发送给服务端的数据,由请求行,请求头,请求空行,请求体组成 响应消息:服务端发送给客户端的数据 响应消息由,响应行,响应头,响应空行,响应体组成
响应行组成:协议及版本 响应状态码 状态码描述 状态码:服务器告诉客户端浏览器本次请求与响应的状态,一般由3位数字组成,比如404,502,可以百度HTTP状态码来详细了解分类,下面简单介绍一下状态码分类 状态码分类(XX代表随机数字):1XX(表示服务端接收消息,接收了一半,没接收完,等了一段时间只能发送状态码1XX),2XX(表示成功),3XX(302代表重定向,比如我们平常登陆完成后出现5秒后跳转页面,这时跳转去的页面就是重定向页面。304代表访问缓存,比如你的头像,第一次访问后服务器反馈给你,下次你又来请求相同的内容,这时就没必要重复响应,服务器会告诉浏览器你本地有,去访问缓存就行了),4XX(表示客户端错误,404表示你请求的资源不存在,一般路径写错就会报这个错,405表示请求方法不对,比如你发Post请求,而后端只有处理Get请求的方法),5XX(表示服务器错误,一般后端代码出错会报这个错)
响应头 Content-Type:表示响应体数据格式以及编码格式 Content-disposition:表示以何种格式打开响应体,没有指定就使用默认值in-line
,表示在当前页面打开,一般我们需要指定时使用attachment;filename=XXX
,表示以附件形式打开响应体,比如下载时指定使用,filename指定文件名
三、Response
设置响应信息和设置请求信息差不多,就不多赘述了
设置响应行、响应头 setStatus(int sc)
:设置;响应行状态码setHeader(String name,String value)
:设置响应头
设置响应头 1、获取输出流(字符输出流PrintWriter getWriter()
,字节输出流ServletOutputStream getOutputStream()
。) 2、使用输出流将数据响应给客户端浏览器
1、response重定向
服务器端重定向 就是用户发送请求到一个servlet,服务器告诉用户这个servlet办不了,你去找X某某办 1、重定向需要给浏览器设置状态码302 2、需要告诉浏览器重定向路径,需设置location
@Override
protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
System . out. println ( "我是dome1Get" ) ;
this . doPost ( req, resp) ;
}
@Override
protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
req. setCharacterEncoding ( "utf-8" ) ;
System . out. println ( "我是dome1Post" ) ;
resp. sendRedirect ( "dome2" ) ;
}
重定向特点(与请求转发完全相反) 地址栏会随着重定向而改变 重定向几次就发送几次请求,也就是不能用request域来传递数据了,因为都不是同一次请求 重定向可以访问到其它站点,而不局限与本服务器资源
2、response相对/绝对路径
因为这个是初学者非常容易犯的错,所有特地挑出来讲解 绝对地址:http://ip地址/资源路径 相对地址:/资源路径 以/开头的是相对路径,不以/开头的是绝对路径 相对路径会相对于当前路径范围下,寻找指定资源 绝对路径会根据路径直接寻找资源 建议给客户端使用的重定向,加上虚拟目录,虚拟目录最好不要写死,以后改一下,要改很多地方,推荐使用request.getContextPath()动态获取虚拟目录
3、response输出数据
最大的问题 就是服务器响应与浏览器编码不一样,会乱码 那么响应头中Content-Type参数可以设置编码格式 通过设置响应头来规定编码
@Override
protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
System . out. println ( "我是dome1Get" ) ;
this . doPost ( req, resp) ;
}
@Override
protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
req. setCharacterEncoding ( "utf-8" ) ;
resp. setContentType ( "text/html;charset=utf-8" ) ;
System . out. println ( "我是dome1Post" ) ;
ServletOutputStream outputStream = resp. getOutputStream ( ) ;
outputStream. write ( "alksdjflkasjfl案例可使肌肤鲁大师" . getBytes ( "utf-8" ) ) ;
}
4、response验证码
@Override
protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
System . out. println ( "我是dome1Get" ) ;
this . doPost ( req, resp) ;
}
@Override
protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
req. setCharacterEncoding ( "utf-8" ) ;
int width = 100 ;
int height = 50 ;
BufferedImage bufferedImage = new BufferedImage ( width, height, BufferedImage . TYPE_INT_RGB) ;
Graphics graphics = bufferedImage. getGraphics ( ) ;
graphics. setColor ( Color . PINK) ;
graphics. fillRect ( 0 , 0 , width, height) ;
graphics. setColor ( Color . BLUE) ;
graphics. drawRect ( 0 , 0 , width- 1 , height- 1 ) ;
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ;
Random random = new Random ( ) ;
for ( int i = 1 ; i < 5 ; i++ ) {
int index = random. nextInt ( str. length ( ) ) ;
char ch = str. charAt ( index) ;
graphics. drawString ( ch+ "" , width/ 5 * i, height/ 2 ) ;
}
ImageIO . write ( bufferedImage, "jpg" , resp. getOutputStream ( ) ) ;
}
四、ServletContext对象
ServletContext 代表整个web应用,可以和整个程序的容器(比如servlet,Tomcat等等)通信 可以获取MIME类型 可以作为域对象,共享数据 获取文件的真实路径(服务器路径)
获取 1、通过request对象获取:request.getServletContext(); 2、直接通过HttpServlet获取(因为HttpServlet继承Servlet接口):this.getServletContext();
@Override
protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
System . out. println ( "我是dome1Get" ) ;
this . doPost ( req, resp) ;
}
@Override
protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
req. setCharacterEncoding ( "utf-8" ) ;
ServletContext servletContext1 = req. getServletContext ( ) ;
ServletContext servletContext2 = this . getServletContext ( ) ;
System . out. println ( "两种方式获取的ServletContext是否是同一个对象:" + ( servletContext1 == servletContext2) ) ;
String fileName = "a.jpg" ;
String MimeType = servletContext1. getMimeType ( fileName) ;
System . out. println ( "文件a.jpg对应的MIME类型为:" + MimeType ) ;
servletContext1. setAttribute ( "name" , "zhangsan" ) ;
String realPath = servletContext1. getRealPath ( "/index.jsp" ) ;
System . out. println ( realPath) ;
File file = new File ( realPath) ;
}
五、文件下载
步骤 1、设置响应头,告诉浏览器文件打开方式为附件打开,Content-disposition:attachment;filename=XXX
,表示以附件形式打开响应体 2、用输入流读取文件,转到输出流输出
@Override
protected void doGet ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
System . out. println ( "我是dome1Get" ) ;
this . doPost ( req, resp) ;
}
@Override
protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
req. setCharacterEncoding ( "utf-8" ) ;
ServletContext servletContext = req. getServletContext ( ) ;
String realPath = servletContext. getRealPath ( "/index.jsp" ) ;
FileInputStream fileInputStream = new FileInputStream ( realPath) ;
String mimeType = servletContext. getMimeType ( "index.jsp" ) ;
resp. setHeader ( "content-type" , mimeType) ;
resp. setHeader ( "content-disposition" , "attachment;filename=index.jsp" ) ;
ServletOutputStream outputStream = resp. getOutputStream ( ) ;
byte [ ] bytes = new byte [ 1024 * 8 ] ;
int len = 0 ;
while ( ( len = fileInputStream. read ( bytes) ) != - 1 ) {
outputStream. write ( bytes, 0 , len) ;
}
fileInputStream. close ( ) ;
}
1、中文文件名乱码问题
中文文件名问题,中文文件名无法正常获取显示 1、获取客户端浏览器版本信息 2、根据不同版本信息,响应不同的数据(设置filename文件名的编码方式)
package com. yzpnb. utils ;
import sun. misc. BASE64Encoder ;
import java. io. UnsupportedEncodingException ;
import java. net. URLEncoder ;
public class DownloadUtils {
public static String getFileName ( String agent, String filename) throws UnsupportedEncodingException {
if ( agent. contains ( "MSIE" ) ) {
filename = URLEncoder . encode ( filename, "utf-8" ) ;
filename = filename. replace ( "+" , " " ) ;
} else if ( agent. contains ( "Firefox" ) ) {
BASE64Encoder base64Encoder = new BASE64Encoder ( ) ;
filename = "=?utf-8?B?" + base64Encoder. encode ( filename. getBytes ( "utf-8" ) ) + "?=" ;
} else {
filename = URLEncoder . encode ( filename, "utf-8" ) ;
}
return filename;
}
}
@Override
protected void doPost ( HttpServletRequest req, HttpServletResponse resp) throws ServletException , IOException {
req. setCharacterEncoding ( "utf-8" ) ;
ServletContext servletContext = req. getServletContext ( ) ;
String fileName = "你好.txt" ;
String realPath = servletContext. getRealPath ( "/" + fileName) ;
FileInputStream fileInputStream = new FileInputStream ( realPath) ;
String mimeType = servletContext. getMimeType ( fileName) ;
resp. setHeader ( "content-type" , mimeType) ;
String userAgent = req. getHeader ( "user-agent" ) ;
fileName = DownloadUtils . getFileName ( userAgent, fileName) ;
resp. setHeader ( "content-disposition" , "attachment;filename=" + fileName) ;
ServletOutputStream outputStream = resp. getOutputStream ( ) ;
byte [ ] bytes = new byte [ 1024 * 8 ] ;
int len = 0 ;
while ( ( len = fileInputStream. read ( bytes) ) != - 1 ) {
outputStream. write ( bytes, 0 , len) ;
}
fileInputStream. close ( ) ;
}