文章目录
1、请求对象与响应对象介绍
- 用户在客户端输入网址(虚拟路径)时,开始发送一个
HTTP请求(请求行、请求头、请求体)
至服务器。服务器内的Tomcat引擎
会解析请求的地址,去找XML文件
,然后根据虚拟路径找Servlet的真实路径
,真实的Servlet
会将请求的信息封装成request(请求)对象
,然后再创建一个response(响应)对象
,(此时的response
内是空的)同时创建servlet对象
,并调用service方法
(或doGet和doPost方法)。这样就是把两个对象传给了服务器内的某个servlet的service方法
,通过这个方法,我们可以获得request的所有的信息,并且向response内设置信息。response.getwriter().write()
将内容写到response的缓冲区,这样service方法结束了,方法返回后,tomcat引擎会将从该response缓冲区中获取的设置信息封装成一个HTTP响应(响应行、响应头、响应体)
,发送给客户端,客户端解析响应回来的东西继而进行显示。 - 我们可以通过设置修改响应的信息进行相应的重定向(用户访问的网页不存在并跳转到其他网页上)、修改响应文本(需要修改浏览器和服务器两边的编码,并且还得处理兼容问题)。
- 需要强调的是:请求对象和响应对象都是由服务器创建,我们只是拿来用;
2、响应对象详解
HttpServletResponse接口
继承自ServletResponse接口
,由于HTTP响应消息分为状态行、响应消息头、消息体
三部分,因此,在HttpServletResponse接口
中定义了向客户端发送响应状态码、响应消息头、响应消息体
的方法。虽然HttpServletResponse接口
中的方法较多,但是我们常用的也就是那么几个,如果用到其他的方法了可以阅读响应的源码或者相关资料就行了;
发送状态码相关函数
方法 | 说明 |
---|---|
public void setStatus(int sc) | 设置响应消息状态码,Web服务器默认产生一个状态码为200的状态行 |
public void sendError(int sc) | 发送表示错误信息的状态码,第二个方法还增加了一个用于提示说明的文本信息 |
public void sendError(int sc, String msg) | 发送响应消息头相关函数 |
发送响应消息头相关函数
方法 | 说明 |
---|---|
public void addHeader(String name, String value) public void setHeader(String name, String value) | 设置HTTP响应头字段,name指定字段名称,value指定字段值。addHeader可以增加同名的响应头字段,setHeader则会覆盖同名的头字段 |
public void setContentLength(int len) | 设置响应消息的实体内容的大小,单位为字节,即设置Content-Length字段的值 |
public void setContentType(String type) | 设置Servlet输出内容的MIME类型,即设置Content-Type字段的值 |
public void setCharacterEncoding(String charset) | 设置输出内容字符编码,即设置Content-Type字段的值,注意,该方法优先级比setContentType的高 |
public void sendRedirect(String location) | Servlet请求重定向 |
发送响应消息体相关函数
方法 | 说明 |
---|---|
public ServletOutputStream getOutputStream() | 获取HttpServletResponse的字节输出流ServletOutputStram类型 |
public PrintWriter getWriter() | 获取HttpServletResponse的字符输出流ServletWriter类型 |
- 首先明确,后台给前台响应的数据分为:普通文本、HTML、JSON;
给前台相应普通文本:
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;
import java.io.PrintWriter;
@WebServlet(value = "/demo1")
public class MyServlet1 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取打印输出流
PrintWriter writer = response.getWriter();
//响应一段文本数据
writer.write("Hello Browser!");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
服务器开启之后,通过你在浏览器键入的URL,服务器默认创建HTTP GET请求,并通过配置文件中的类的全限定名称找到这个后台的Servlet,带回了后台相应给前台的一段文本数据,将他展示在了浏览器的页面上;
本质上,一个继承了
HttpServlet
类的servlet
中有两个核心的方法,doGet()和doPost()
,由于get请求和post请求有区别:get请求的参数参数在url里面,不安全,post请求的参数放在请求体中,安全;他们两种请求的逻辑应该有所差别,但HttpServlet底层已经做过了处理,因此这里代码简写;【后面介绍】
给前台相应HTML文本:
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;
import java.io.PrintWriter;
@WebServlet(name = "MyServlet2", value = "/demo2")
public class MyServlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取打印输出流
PrintWriter writer = response.getWriter();
//响应一段HTML数据
writer.write("<h1 style='color:red;'>Hello Browser!</font>");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
给前台相应JSON数据:
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(name = "MyServlet3", value = "/demo3")
public class MyServlet3 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
String jsonStr = "{\"username\":\"zhangsan\",\"password\":\"123456\"}";
response.getWriter().write(jsonStr);
}
}
一般异步请求的时候响应JSON数据,响应JSON数据的时候,后台设置MIME文件类型为:
response.setContentType("application/json;charset=utf-8");
或者response.setContentType("text/json;charset=utf-8");
关于中文乱码问题
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;
import java.io.PrintWriter;
@WebServlet(value = "/demo1")
public class MyServlet1 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取打印输出流
PrintWriter writer = response.getWriter();
//响应一段文本数据
writer.write("你好,世界!");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
为什么会乱码?
计算机中的数据都是以二进制形式存储的,因此,传输文本时,就会发生字符的字节之间的转换。字符与字节之间的转换时通过查码表完成的,字符转换为字节的过程称为编码
,字节转换为字符的过程称为解码
,如果编码和解码使用的码表不一样,则会出现乱码问题。
注意:HttpServletResponse对象
的字符输出流在编码时,默认采用的是ISO 8859-1编码
,该编码方式不兼容中文
,比如会将"中国"编码为"63 63"(在ISO 8959-1的码表中查不到的字符会显示63)。当浏览器对接收到的数据进行解码时,会默认采用Unicode编码
,将"63"解码为"?",浏览器就将"中国"两个字符解码为"??"。
- 怎么处理,我们给请求对象和响应对象设置编码方式:
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
给前台响应一张图片
如果我们把一张图片资源放在web下,只要服务器开启,谁都可以访问,这样没有任何逻辑;我们可以将图片资源放在WEB-INF下,这样前台不能通过键入URL访问,后台通过响应对象以流的方式响应给前台,有了针对性;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
@WebServlet(name = "MyServlet4", value = "/demo4")
public class MyServlet4 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String localName = request.getLocalName();
System.out.println(localName);
if(localName.equals("192.168,18.123")){
//做出逻辑判断,如果是该主机要求访问后台资源,结束方法
return;
}
ServletContext context = this.getServletContext();
String realPath = context.getRealPath("WEB-INF/google.png");
//图片资源使用字节流写出去
ServletOutputStream outputStream = response.getOutputStream();
FileInputStream fis = new FileInputStream(realPath);
int len = 0;
byte[] bytes = new byte[1024 * 8];
while ((len = fis.read(bytes)) != 0) {
outputStream.write(bytes, 0, len);
outputStream.flush();
}
fis.close();
outputStream.close();
}
}
绘制验证码响应给前台
我们在做登录与注册的时候,通常出现的验证码都是后台绘制好响应给前台;
import javax.imageio.ImageIO;
import javax.servlet.ServletContext;
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.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
@WebServlet(name = "DrawCode", value = "/code")
public class DrawCode extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int width = 200;
int height = 70;
//获取一个图片对象
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
//获取一只画笔
Graphics graphics = image.getGraphics();
//首先画一个矩形
graphics.drawRect(0, 0, width, height);
//填充背景色
graphics.setColor(getRandomColor());
graphics.fillRect(0, 0, width, height);
//画随机字符
graphics.setFont(new Font("微软雅黑", Font.BOLD, 40));
String str = "ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz";
String code = "";
Random random = new Random();
for (int i = 1; i <= 4; i++) {
int index = random.nextInt(str.length());
graphics.setColor(getRandomColor());
String s = String.valueOf(str.charAt(index));
code += s;
graphics.drawString(s, width / 5 * i, height / 5 * 3);
}
//画干扰线
for (int i = 0; i < 20; i++) {
int x1 = random.nextInt(width);
int y1 = random.nextInt(height);
int x2 = random.nextInt(width);
int y2 = random.nextInt(height);
graphics.setColor(getRandomColor());
graphics.drawLine(x1, y1, x2, y2);
}
//画干扰点
for (int i = 0; i < 10; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
graphics.setColor(getRandomColor());
graphics.drawString(".", x, y);
}
//将随机生成的验证码存放在全局域对象中
ServletContext context = this.getServletContext();
context.setAttribute("code", code);
//必须设置响应内容类型为图片,否则前台不识别
response.setContentType("image/png");
//获取文件输出流
OutputStream os = response.getOutputStream();
//输出图片流
ImageIO.write(image, "png", os);
os.flush();
os.close();//关闭流
}
/***
* 获取随机颜色
* @return
*/
private static Color getRandomColor() {
Random ran = new Random();
Color color = new Color(ran.nextInt(256), ran.nextInt(256), ran.nextInt(256));
return color;
}
}
3、请求对象详解
HttpServletRequest接口
继承ServletRequest接口
,专门用于封装HTTP请求
消息。由于HTTP请求信息包括请求行、请求头和请求体
三部分, 所以HttpServletRequest接口
定义了获取请求行、请求头和请求体的相关方法。
获取请求行的相关方法
方法 | 说明 |
---|---|
public String getMethod() | 获取HTTP请求方式,POST、GET等 |
public String getRequestURI() | 获取请求行中资源名称部分 |
public String getQueryString() | 获取请求行中的参数部分 |
public String getProtocol() | 获取请求行中协议名称和版本,如HTTP 1.1 |
public String getContextPath() | 获取请求URL中属于Web应用程序的路径 |
getQueryString()的使用:
由于该方法是返回包含在请求 URL 中路径后面的查询字符串。如果 URL 没有查询字符串,则此方法返回 null;因为对于POST请求来说,请求参数虽然也可以拼接在URL后面,但是我们一般不这样做,所以POST请求的参数一般放在请求体里面,也就是说使用POST请求URL后面是没有参数的,因此该方法的返回值一定是null,也即该方法
只对get请求有效
;
【注意】:在
tomcat8.0
以上的版本,GET请求,请求参数是中文不乱码;而对于POST请求,请求参数是中文还是会乱码;
代码示例:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String queryString = request.getQueryString();
System.out.println("请求参数为:" + queryString);
//请求参数为:username=zhangsan&pwd=123456
//请求参数为:username=%E5%BC%A0%E4%B8%89&pwd=123456
}
这里可以看到一个现象:如果是英文的用户名和密码,后台获得的是本来的字符串,但是如果采用中文的用户名和密码,后台得到的是编码过后的字符串,为什么?
因为网络标准对URL的编码做了硬性的规定,只能包含英文字母、特殊字符等存在,这意味着,如果URL中有汉字,就必须编码后使用。但是麻烦的是,
RFC 1738
没有规定具体的编码方法,而是交给应用程序(浏览器)自己决定。这导致"URL编码"成为了一个混乱的领域;
后台如果想得到用户输入的字符串,可以进行编解码;
解决方法:
Java中有编解码的对应类与方法:
public class URLEncoderextends Object
——编码
public static String encode(String s, String enc):将字符串s转为按照enc编码格式编码;
public class URLDecoderextends Object
——解码
public static String decode(String s, String enc):将字符串s按照enc编码格式解码;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String queryString = request.getQueryString();
String decode = URLDecoder.decode(queryString, "utf-8");
System.out.println("后台解码过后的请求参数为:" + decode);
String gbk = URLEncoder.encode(decode, "gbk");
System.out.println("编码过后的文字为:" + gbk);
/*后台解码过后的请求参数为:username=张三&pwd=123456
编码过后的文字为:username%3D%D5%C5%C8%FD%26pwd%3D123456*/
}
获取客户端信息以及上下文路径的方法
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(name = "MyServlet8", value = "/demo8")
public class MyServlet8 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取上下文路径
String contextPath = request.getContextPath();
System.out.println(contextPath);
//获取客户端IP
String addr = request.getRemoteAddr();
System.out.println(addr);
//获取主机名
String host = request.getRemoteHost();
System.out.println(host);
//获取端口
int port = request.getRemotePort();
System.out.println(port);
/*/servlet20200819_war_exploded
0:0:0:0:0:0:0:1
0:0:0:0:0:0:0:1
58312*/
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
获取请求消息头的相关方法
方法 | 说明 |
---|---|
public String getHeader(String name) | 获取指定字段的值,如果没有返回null,如果有多个返回第一个值 |
public Enumeration getHeaders(String name) | 返回一个指定字段的Enumeration集合对象 |
public Enumeration getHeaderNames() | 返回一个包含所有字段的Enumeration集合对象 |
public String getContentType() | 获取Content-Type字段的值 |
代码示例:
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.BufferedReader;
import java.io.IOException;
import java.net.URLDecoder;
@WebServlet(name = "MyServlet7", value = "/demo7")
public class MyServlet7 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取部分请求头
String referer = request.getHeader("Referer");
String header = request.getHeader("User-Agent");
System.out.println(referer);
System.out.println(header);
/*http://localhost:8080/servlet20200819_war_exploded/
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36*/
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
获取请求体的相关方法
方法 | 说明 |
---|---|
public ServletInputStream getInputStream() | 获取请求的ServletInputStream对象,如果实体内容为非文本,只能通过getInputStream方法获取请求体消息体 |
public BufferedReader getReader() | 获取请求的BufferedReader对象,该对象会将实体内容字节数据转换为指定字符集编码的文本字符串 |
public String getParameter(String name) | 获取指定的参数值,没有该参数返回null |
public Enumeration getParameterNames() | 返回一个包含所有参数名的Enumeration对象 |
public String[] getParameterValues(String name) | HTTP请求中可能有多个相同的参数,获取同一个参数名对应的所有参数值 |
代码示例:
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;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
@WebServlet(name = "MyServlet7", value = "/demo7")
public class MyServlet7 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求参数的两种方式
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//返回枚举类型
Enumeration<String> names = request.getParameterNames();
while (names.hasMoreElements()) {
String key = names.nextElement();
String value = request.getParameter(key);
System.out.println(key + "--------------" + value);
}
System.out.println("------------------------------------------");
Map<String, String[]> map = request.getParameterMap();
Set<Map.Entry<String, String[]>> entries = map.entrySet();
for (Map.Entry<String, String[]> entry : entries) {
//默认值的返回值是一个字符串数组,因为表单当中会有多选
System.out.println(entry.getKey() + "--------" + entry.getValue()[0]);
}
/*username--------------张三
pwd--------------123456
------------------------------------------
username--------张三
pwd--------123456*/
}
}
1、我们在做前后台交互的时候,前台文本框中的name属性最终被作为键提交给后台,后台就是通过这个键找到它对应的值;表单中表单项填写的太多了肯定不能通过
getParameter()
这个方法一个一个找,因此以上两种方法非常适合;
2、并且注意:getParameterMap()
中获取到的键都是数组,如果不是多选,只需要取出第一个元素,就是我们要获取的表单的值;
post请求获得请求参数的方式:getReader()
post请求的请求参数是放在请求体里面的,因此只能通过该方法读取,并且post请求的提交的数据大小没有限制,而get请求是有限制的;
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.BufferedReader;
import java.io.IOException;
import java.net.URLDecoder;
@WebServlet(name = "MyServlet7", value = "/demo7")
public class MyServlet7 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BufferedReader reader = request.getReader();
String s = reader.readLine();
String decode = URLDecoder.decode(s, "utf-8");
System.out.println("后台解码过后的请求参数为:" + decode);
//后台解码过后的请求参数为:username=张三&pwd=123456
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
URI与URL的区别
url
统一资源定位符,uri
统一资源标识符,他们之间有什么区别呢?看下代码打印的结果:
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(name = "MyServlet5", value = "/demo5")
public class MyServlet5 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String method = request.getMethod();
System.out.println("这一次的请求方式是:" + method);
//获取
// url 统一资源定位符
// uri 统一资源标识符
String requestURI = request.getRequestURI();
System.out.println("统一资源标识符:" + requestURI);
StringBuffer requestURL = request.getRequestURL();
System.out.println("统一资源定位符:" + requestURL);
/*这一次的请求方式是:GET
统一资源标识符:/servlet20200819_war_exploded/demo5
统一资源定位符:http://localhost:8080/servlet20200819_war_exploded/demo5
这一次的请求方式是:POST
统一资源标识符:/servlet20200819_war_exploded/demo5
统一资源定位符:http://localhost:8080/servlet20200819_war_exploded/demo5*/
}
}
可以发现统一资源标识符没有给出协议、主机名、端口号,只给出了资源标识符;看过别人的文档之后,自己的总结如下:
1、有一家公司的总经理给了你一张名片,上面写了他的头衔:北京XXX公司总经理张三
,还有他的办公室地址,北京市海淀区长安街35号北京XXX公司总经理办公室
;
2、那么这个头衔就和张三对应起来了,只要一说到这个头衔,大家都知道说的是张三,反应到网络世界,这个头衔就叫做URI
,只要你给我一个URI
,我就知道它代表了什么,比如,http://www.sina.com.cn
代表了新浪网,admin@qq.com
代表了某一个人的qq邮箱,你的qq号也是一个URI(腾讯服务器内可以识别就是你的QQ账户),URI就是网络资源的头衔,通过URI标记可以把网络世界里面的每一个事物都加以标记并区分开来。
3、好的,现在出现了一个问题,你现在知道北京XXX公司总经理是张三,但是我们找不到他,因为不知道他的地址,虽然有他的URI头衔,但是除此以外,你对他具体的情况一无所知;于是你还得到他的办公室地址,通过北京市海淀区长安街35号北京XXX公司总经理办公室
这个地址,你就找到了张三。反应到网络世界,网络世界里面的每一个资源不光有自己的头衔,还要能够被人访问。所以,网络地址是必须的,否则,这个网络资源的存在没有任何意义,这个地址就叫做URL
。
4、URI强调的是给资源标记命名
,URL强调的是给资源定位
,由于URL显然比URI包含信息更多,所以URL
也充当了WWW万维网
里面URI
的角色,但是他比URI多了一层意义,我不光知道你叫什么,我还知道你在哪里;
5、URL 是 URI 的子集,所有的URL都是URI,但不是每个URI都是URL,还有可能是URN;
4、this.doGet(req,res)的推导
- 上面所有的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;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
@WebServlet(name = "MyServlet7", value = "/demo7")
public class MyServlet7 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
…………
}
}
- 因为get请求和post请求的传递数据的方式是不一样的,因此数据取出来的方式也不一样,但是为什么可以写成上面的模式,这样怎么取出数据?
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.BufferedReader;
import java.io.IOException;
@WebServlet(name = "MyServlet8", value = "/demo8")
public class MyServlet8 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String string = request.getQueryString();
System.out.println("这是get请求读取到的数据:" + string);
//解析数据,展示数据
String[] split = string.split("&");
String[] one = split[0].split("=");
String[] two = split[1].split("=");
System.out.println(one[0] + ":" + one[1] + "," + two[0] + ":" + "," + two[1]);
/*这是post请求读取到的数据:username=zhangsan&pwd=123456
username:zhangsan,pwd:,123456*/
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BufferedReader reader = request.getReader();
String line = reader.readLine();
System.out.println("这是post请求读取到的数据:" + line);
//解析数据,展示数据
String[] split = line.split("&");
String[] one = split[0].split("=");
String[] two = split[1].split("=");
System.out.println(one[0] + ":" + one[1] + "," + two[0] + ":" + "," + two[1]);
/*这是get请求读取到的数据:username=zhangsan&pwd=123456
username:zhangsan,pwd:,123456*/
}
}
上面的代码只有最开始读取字符串那行代码不同,其他地方都一样,可以将所有代码抽取在doGet()或者doPost()中,获取当前请求的方式,进行判断,然后根据不同方式取出数据;
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.BufferedReader;
import java.io.IOException;
@WebServlet(name = "MyServlet8", value = "/demo8")
public class MyServlet8 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String method = request.getMethod();
String line = "";
if (method.equalsIgnoreCase("get")) {
line = request.getQueryString();
} else if (method.equalsIgnoreCase("post")) {
BufferedReader reader = request.getReader();
line = reader.readLine();
}
//解析数据,展示数据
String[] split = line.split("&");
String[] one = split[0].split("=");
String[] two = split[1].split("=");
System.out.println(one[0] + ":" + one[1] + "," + two[0] + ":" + "," + two[1]);
/*username:zhangsan,pwd:,123456*/
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
其实,
HttpServlet
里面已经做了处理,无论是get请求还是post请求,都可以直接使用请求对象HttpServletRequest对象
获取前台数据;
5、请求转发与重定向
请求转发(内部转发)
- Forward(请求转向):服务器程序内部请求转向,这个特性允许前一个程序用于处理请求,而后一个程序用来返回响应;也就是请求与响应都被分为了多份,交由几个servlet执行;
- 请求转发的特点:
1、一次请求,一次响应;
2、地址栏不发生变化;
3、请求转发只能访问内部的资源,不能访问该web工程以外的资源;
4、可以访问到WEB-INF
下的资源;
- 请求转发使用到的方法:
request.getRequestDispatcher("/要内部转发的路径").forward(request, response);
代码示例:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("a 收到请求了");
System.out.println("处理请求");
System.out.println("转发到b");
//内部转发
request.getRequestDispatcher("/b").forward(request, response);
System.out.println("回到a");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("b 收到请求了");
System.out.println("处理请求");
System.out.println("转发到c");
//内部转发
request.getRequestDispatcher("/c").forward(request, response);
System.out.println("回到b");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("c 收到请求了");
System.out.println("处理请求");
//内部转发
request.getRequestDispatcher("/home.jsp").forward(request, response);
}
a接收到请求之后,自己处理了一部分请求,但是把剩余的部分交给了b处理,b处理了部分请求,又把剩余的请求交给了c处理,最终页面跳转到欢迎页面;在回去的时候,响应还是从a中返回,相当于绕了一圈还是回到了a,也因此说,内部转发是一次请求,一次响应;
重定向
Redirect(重定向)
:服务端发送给客户端一个重定向的临时响应头,这个响应头包含重定向之后的URL,客户端用新的URL重新向服务器发送一个新的请求。
- 重定向的特点:
1、多次请求,多次响应;
2、地址栏发生变化;
3、可以访问内部资源,也可以访问外部资源;
4、不能访问WEB-INF
下的资源;
- 重定向用到的方法:
//设置状态码302重定向
response.setStatus(302);
//设置响应头location
response.setHeader("location","http://www.baidu.com");
//以上两步可以合成一个简单的方法
response.sendRedirect("http://www.baidu.com");
代码示例:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置状态码302重定向
response.setStatus(302);
//设置响应头location
response.setHeader("location","http://www.baidu.com");
//以上两步可以合成一个简单的方法
response.sendRedirect("http://www.baidu.com");
}
重定向的过程类似于这样的:当用户在地址栏键入URL,通过虚拟路径找到这个servlet,但是这个servlet内部是这样做的:首先设置状态码302,意在告诉浏览器我这里不能对你的响应作出处理,你可以去访问百度,并把这个百度的地址告诉了它;于是浏览器重新向百度发出请求,最终百度做出了回应;因此重定向是多次请求,多次响应的;并且由于再次访问的时候还是属于浏览器访问,因此不能访问到
WEB-INF
下的资源;
请求域
请求转发的出现引出了一个域对象的存在:
请求域request对象
,之前认识了一个全局域对象:ServletContext
,它的作用域是整个web工程;而请求域的范围是:请求转发中多个资源共享的数据,也就是仅限于本次请求与响应;
- 用到的方法:
void setAttribute(String name,Object obj):存储数据
Object getAttitude(String name):通过键获取值
void removeAttribute(String name):通过键移除键值对
6、web项目中路径书写的问题
学习web工程,会发现一个问题,关于资源路径的书写,有的时候需要加上项目的上下文路径,有的时候则不需要,怎么区分?
-
内部转发/请求转发
不需要写项目的上下文路径,这属于服务器内部资源的跳转; - HTML、JSP页面等前端页面中的路径都需要写项目的上下文路径;
- 重定向也需要写项目的上下文路径,因为新的请求是从浏览器发出;
- 访问别人的服务器,路径必须全部写完整,也就是协议、主机名、端口都要有;如果自己的电脑访问自己的服务器,写全路径肯定没有问题,但是注意此时的主机名不能写成IP,而应该写成
localhost
,这涉及到了跨域请求的问题;
7、前后台交互的登陆注册案例
画验证码的servlet:
import javax.imageio.ImageIO;
import javax.servlet.ServletContext;
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.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
@WebServlet(name = "DrawCode", value = "/code")
public class DrawCode extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int width = 200;
int height = 70;
//获取一个图片对象
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
//获取一只画笔
Graphics graphics = image.getGraphics();
//首先画一个矩形
graphics.drawRect(0, 0, width, height);
//填充背景色
graphics.setColor(getRandomColor());
graphics.fillRect(0, 0, width, height);
//画随机字符
graphics.setFont(new Font("微软雅黑", Font.BOLD, 40));
String str = "ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz";
String code = "";
Random random = new Random();
for (int i = 1; i <= 4; i++) {
int index = random.nextInt(str.length());
graphics.setColor(getRandomColor());
String s = String.valueOf(str.charAt(index));
code += s;
graphics.drawString(s, width / 5 * i, height / 5 * 3);
}
//画干扰线
for (int i = 0; i < 20; i++) {
int x1 = random.nextInt(width);
int y1 = random.nextInt(height);
int x2 = random.nextInt(width);
int y2 = random.nextInt(height);
graphics.setColor(getRandomColor());
graphics.drawLine(x1, y1, x2, y2);
}
//画干扰点
for (int i = 0; i < 10; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
graphics.setColor(getRandomColor());
graphics.drawString(".", x, y);
}
//将随机生成的验证码存放在全局域对象中
ServletContext context = this.getServletContext();
context.setAttribute("code", code);
//必须设置响应内容类型为图片,否则前台不识别
response.setContentType("image/png");
//获取文件输出流
OutputStream os = response.getOutputStream();
//输出图片流
ImageIO.write(image, "png", os);
os.flush();
os.close();//关闭流
}
/***
* 获取随机颜色
* @return
*/
private static Color getRandomColor() {
Random ran = new Random();
Color color = new Color(ran.nextInt(256), ran.nextInt(256), ran.nextInt(256));
return color;
}
}
登陆servlet:
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
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 javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
@WebServlet(name = "LoginServlet", value = "/login")
public class LoginServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置编码格式
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//获取前台提交的数据
String username = request.getParameter("username").trim();
String password = request.getParameter("pwd").trim();
//做出非空校验
if (username != null && password != null) {
try {
//查询数据库
Properties properties = new Properties();
//注意文件加载路径的写法
properties.load(this.getClass().getClassLoader().getResourceAsStream("druid.properties"));
//获取德鲁伊连接池对象
DataSource ds = DruidDataSourceFactory.createDataSource(properties);
//获取dbutils工具类的操作对象
QueryRunner qr = new QueryRunner(ds);
//进行查询
String sql = "select username,password from user where username=? and password=?";
//获取查询出来的用户对象
user u = qr.query(sql, new BeanHandler<user>(user.class), username, password);
if (u != null) {
//如果用户存在,让页面内部转发到新的页面,然后把用户名放在请求域对象里面,前台使用EL表达式取出来
request.setAttribute("username", u.getUsername());
//内部转发
request.getRequestDispatcher("index.jsp").forward(request, response);
} else {
//将错误信息放在请求域对象当中
request.setAttribute("msg", "用户名或密码错误");
//页面内部转发到当前页面
request.getRequestDispatcher("login.jsp").forward(request, response);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
注册servlet:
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
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 javax.sql.DataSource;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@WebServlet(name = "RegisterServlet", value = "/register")
public class RegisterServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
//设置编码格式
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//获取前台提交的数据
Map<String, String[]> map = request.getParameterMap();
//数据的校验
//把检验用户名、密码不为空,两次密码输入一致放在前台校验,你不要拿到后台
String[] username = map.get("username");
String[] pwd = map.get("pwd");
String[] repwd = map.get("repwd");
String[] tel = map.get("tel");
String[] email = map.get("email");
String[] sex = map.get("sex");
String[] birthday = map.get("birthday");
String[] hobby = map.get("hobby");
StringBuilder myHobby = new StringBuilder("==");
if (hobby.length != 0) {
for (int i = 0; i < hobby.length; i++) {
myHobby.append(hobby[i]).append("==");
}
}
//查询数据库
Properties properties = new Properties();
//注意文件加载路径的写法
properties.load(this.getClass().getClassLoader().getResourceAsStream("druid.properties"));
//获取德鲁伊连接池对象
DataSource ds = DruidDataSourceFactory.createDataSource(properties);
//获取dbutils工具类的操作对象
QueryRunner qr = new QueryRunner(ds);
//进行查询
String sql = "select username from user where username=?";
//获取查询出来的用户对象
user u = qr.query(sql, new BeanHandler<user>(user.class), username[0]);
if (u != null) {
//如果用户存在,告诉用户重新注册
request.setAttribute("hasUser", "yes");
//内部转发到提示页面
request.getRequestDispatcher("empty.jsp").forward(request, response);
} else {
//如果用户不存在
request.setAttribute("hasUser", "no");
//开始判断验证码是否正确
String code = (String) this.getServletContext().getAttribute("code");
String[] codes = map.get("code");
if (code.equalsIgnoreCase(codes[0])) {
//如果验证码输入正确
//将数据插入到数据库
String sql1 = "insert into user values(?,?,?,?,?,?,?,?)";
int update = qr.update(sql1, null, username[0], pwd[0], email[0], sex[0], birthday[0], tel[0], myHobby.toString());
if (update > 0) {
// 告知用户,你注册成功了!
request.setAttribute("username", username[0]);
//内部转发到登陆页面
request.getRequestDispatcher("login.jsp").forward(request, response);
}
} else {
request.setAttribute("codeIsRight", "no");
//页面内部转发到提示页面
request.getRequestDispatcher("empty.jsp").forward(request, response);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
实体类user
import java.util.Date;
public class user {
private Integer id;
private String username;
private String password;
private String email;
private String sex;
private Date birthday;
private String phone;
private String hobby;
public user() {
}
…………get和set方法
}
提示页面
<%--
Created by IntelliJ IDEA.
User: lenovo
Date: 2020/8/19
Time: 9:04
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>提示页面</title>
</head>
<script>
function index(){
alert("这个是提示页面,你来没!");
var codeIsRight = '<%=request.getAttribute("codeIsRight")%>';
var hasUser = '<%=request.getAttribute("hasUser")%>';
if (codeIsRight == "no") {
alert("验证码输入错误!");
window.location.href = 'register.jsp';
}
if (hasUser == "yes") {
alert("该用户已经存在,请重新注册!");
window.location.href = 'register.jsp';
}
}
index();
</script>
<body>
</body>
</html>
欢迎页面
<%--
Created by IntelliJ IDEA.
User: lenovo
Date: 2020/8/18
Time: 19:48
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>
<center>
<h1>
<%=request.getAttribute("username") == null ? "" : request.getAttribute("username")%>欢迎你的访问!
</h1>
</center>
</body>
</html>
登陆页面:
<%--
Created by IntelliJ IDEA.
User: lenovo
Date: 2020/8/18
Time: 21:32
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>用户登陆</title>
<style type="text/css">
input[type="submit"] {
background-color: #0d65d0;
color: white;
border: 0px;
width: 100px;
height: 40px;
font-size: 18px;
}
input {
height: 30px;
}
td {
font-family: Microsoft YaHei;
}
</style>
</head>
<body>
<center>
<h1>登陆页面</h1>
<form action="/LoginAndRegister_war_exploded/login" method="post">
<table border="0" cellspacing="0" cellpadding="0" width="400px" height="200px">
<tr>
<td style="text-align: right;">用户名:</td>
<td><input type="text" name="username" placeholder="请输入用户名" style="width: 90%;"/></td>
</tr>
<tr>
<td style="text-align: right;">密码: </td>
<td><input type="password" name="pwd" placeholder="请输入密码" style="width: 90%;"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="登陆" style="margin-left: 40%"/>
<a href="/LoginAndRegister_war_exploded/register.jsp" style="font-size: 13px;margin-left: 10px;">没有账号,请先注册</a>
</td>
<td></td>
</tr>
<tr>
<td colspan="2">
<div style="color: red;margin-left: 40%;">
<%=request.getAttribute("msg") == null ? "" : request.getAttribute("msg")%>
</div>
</td>
<td></td>
</tr>
</table>
</form>
</center>
</body>
</html>
注册页面:
<%--
Created by IntelliJ IDEA.
User: lenovo
Date: 2020/8/18
Time: 21:33
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>用户注册</title>
<style type="text/css">
input[type="submit"] {
background-color: #0d65d0;
color: white;
border: 0px;
width: 100px;
height: 40px;
font-size: 18px;
}
input {
height: 30px;
}
td {
font-family: Microsoft YaHei;
}
</style>
</head>
<body>
<center>
<h1>注册页面</h1>
<form action="/LoginAndRegister_war_exploded/register" method="post" onsubmit="return check()">
<table border="0" cellspacing="0" cellpadding="0" width="500px" height="400px">
<tr>
<td>用户名:</td>
<td><input type="text" name="username" placeholder="请输入用户名" style="width: 90%;" id="username"/></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="pwd" placeholder="请输入密码" style="width: 90%;" id="pwd"/></td>
</tr>
<tr>
<td>确认密码:</td>
<td><input type="password" name="repwd" placeholder="请重复输入密码" style="width: 90%;" id="repwd"/></td>
</tr>
<tr>
<td>电话:</td>
<td><input type="tel" name="tel" value="" placeholder="请输入电话号码" style="width: 90%;"/></td>
</tr>
<tr>
<td>邮箱:</td>
<td><input type="email" name="email" placeholder="请输入邮箱" style="width: 90%;"/></td>
</tr>
<tr>
<td>性别:</td>
<td>
<input type="radio" name="sex" id="0" value="男" style="vertical-align: middle;"
checked="checked"/><label
for="0">男</label>
<input type="radio" name="sex" id="1" value="女" style="vertical-align: middle;"/><label
for="1">女</label>
</td>
</tr>
<tr>
<td>出生日期:</td>
<td><input type="date" name="birthday" value="" style="width: 90%;"/></td>
</tr>
<tr>
<td>爱好:</td>
<td><input type="checkbox" name="hobby" id="song" value="唱歌" style="vertical-align: middle;"
checked="checked"/><label
for="song">唱歌</label>
<input type="checkbox" name="hobby" id="sport" value="运动" style="vertical-align: middle;"/><label
for="sport">运动</label>
<input type="checkbox" name="hobby" id="read" value="阅读" style="vertical-align: middle;"/><label
for="read">阅读</label>
</td>
</tr>
<tr>
<td>验证码:</td>
<td colspan="3">
<input type="text" name="code" placeholder="请输入验证码" id="code"/>
<img src="/LoginAndRegister_war_exploded/code"
style="width: 100px;height: 30px;border: 1px black solid;margin-bottom: -10px;" id="img"
onclick="switchCode()">
<a href="#" style="font-size: 15px;font-family: Microsoft YaHei;margin-left: 10px;"
onclick="switchCode()">看不清?换一张</a></td>
</tr>
<tr>
<td colspan="2">
<center><input type="submit" value="注册"/></center>
</td>
<td></td>
</tr>
</table>
</form>
</center>
</body>
<script>
function switchCode() {
var img = document.getElementById("img");
//欺骗服务器
img.src = "/LoginAndRegister_war_exploded/code?" + new Date().getTime();
}
function check() {
alert("你进来了吗!");
var flag = true;
var name = document.getElementById("username");
var pwd = document.getElementById("pwd");
var repwd = document.getElementById("repwd");
var code = document.getElementById("code");
if (name.value.trim().length == 0) {
alert("用户名不能为空!");
name.focus();
return false;
}
if (pwd.value.trim().length == 0) {
alert("密码不能为空!");
pwd.focus();
return false;
}
if (repwd.value.trim().length == 0 || repwd.value != pwd.value) {
alert("两次输入的密码不一致!");
repwd.focus();
return false;
}
if (code.value.trim().length == 0) {
alert("验证码不能为空!");
code.focus();
return false;
}
return flag;
}
</script>
</html>
效果演示:
8、普通项目怎么转为web工程