3 HTTP、请求响应、转发、重定向(从Servlet学到JSP)

1 HTTP:

* 概念:Hyper Text Transfer Protocol 超文本传输协议
	* 传输协议:定义了,客户端和服务器端通信时,发送数据的格式
	* 特点:
		1. 基于TCP/IP的高级协议
		2. 默认端口号:80
		3. 基于请求/响应模型的:一次请求对应一次响应
		4. 无状态的:每次请求之间相互独立,不能交互数据

	* 历史版本:
		* 1.0:每一次请求响应都会建立新的连接
		* 1.1:复用连接

依次为:1 请求行、2 请求头、3 空行、4 请求体

img

POST /login.html	HTTP/1.1 # 请求行:请求方法、请求URL、HTTP协议
Host: localhost   # 请求头
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost/login.html
Connection: keep-alive代表可以复用1.1版本的
Upgrade-Insecure-Requests: 1
    # 空行
username=zhangsan	#请求体

请求消息数据格式:

  • ①请求行:包括请求方式 请求url 请求协议/版本,如GET /login.html HTTP/1.1
    • HTTP协议有7中请求方式,常见GET和POST。。。
    • Get:
      • 请求参数在请求行中,在url后。
      • 请求的url长度有限制的
      • 不太安全
    • Post:
      • 请求参数在请求体中
      • 请求的url长度没有限制的
      • 相对安全
  • ②请求头:客户端浏览器告诉服务器一些信息。是一些键值对。
    • 常见的请求头:
    • HOST:指出请求的目的地
    • User-Agent:浏览器告诉服务器,客户端使用的浏览器版本信息,以解决浏览器的兼容性问题
    • Referer:http://localhost/login.html,告诉服务器,(当前请求)从哪里来?防盗链、统计工作
  • ③空行:用于分割POST请求的请求头,和请求体的。
  • ④请求体(正文):封装POST请求消息的请求参数的
状态码:
  • 1xx:指示信息–表示请求已接收,继续处理
  • 2xx:成功–表示请求已被成功接收、理解、接受
  • 3xx:重定向–要完成请求必须进行更进一步的操作
  • 4xx:客户端错误–请求有语法错误或请求无法实现
  • 5xx:服务器端错误–服务器未能实现合法的请求

img

HTTP请求方法

img

HTTP工作原理:

HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。

1、客户端连接到Web服务器

一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如,http://www.oakcms.cn。

2、发送HTTP请求

通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。

3、服务器接受请求并返回HTTP响应

Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。

4、释放连接TCP连接

若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;

5、客户端浏览器解析HTML内容

客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。

2 请求数据组成:

get的-------

img

post的-------

img

上面的请求的组成,下面介绍请求响应的组成

请求响应

img

  • ①状态行
  • ②响应头
  • ③空行
  • ④响应正文(html)

3 Request请求处理

3.1、request对象和response对象的原理

1. request和response对象是由Tomcat服务器创建的,【每次请求都会新建】。作为实参传递给service方法里。
不管是来自get的还是来自post的,都会在request对象中。每一次发送请求,请求的详细信息会被Tomcat自动封装成一个request对象,我们以后要截取当前请求的一些信息,我们使用request对象即可。 
2. request对象是来获取请求消息,response对象是来设置响应消息

作用:request对象中封存了当前请求的所有请求信息

3.2、request对象继承体系结构:

         ServletRequest		--	接口
				|	继承
         HttpServletRequest	-- 接口(常用)
				|	实现
org.apache.catalina.connector.RequestFacade 类(tomcat)

Servlet的使用流程:

  • 设置请求编码格式
  • 设置响应编码格式
  • 获取请求信息
  • 处理请求信息
  • 响应处理结果

3.3 获取请求行数据

比如以GET方式传输来的URL为:`/day14/demo1?name=zhangsan`

在service()中可以调用下面的方法。
1. 获取请求方式 ://返回GET或Post
 	 String getMethod()  // request.getMethod();
2. (*)获取虚拟目录:/day14  以/开始,不以/结束
	 String getContextPath()
3. 获取Servlet路径: /demo1
	 String getServletPath()
4. 获取get方式请求参数:name=zhangsan
	 String getQueryString()
5. (*)获取请求URI:/day14/demo1
	 String getRequestURI():	//虚拟项目和别名// /day14/demo1
	 StringBuffer getRequestURL() // :http://localhost/day14/demo1

	 URL:统一资源定位符 : http://localhost/day14/demo1	中华人民共和国
     URI:统一资源标识符 : /day14/demo1					共和国

6. 获取协议及版本:HTTP/1.1
         req.getScheme();//获取协议//一般应用返回的是http、SSL返回的是https;
	     String getProtocol();
		request.getServerName();//返回当前页面所在的服务器的名字,IP
       request.getServerPort();//返回当前页面所在的服务器使用的端口,80

String path = request.getContextPath();//项目名
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";//http://localhost:8080/项目名/


7. 获取客户机的IP地址:
 	String getRemoteAddr()
 			获取请求行数据
 					req.getHeader("键名");//返回指定的请求头信息
 					req.getHeaderNames();//返回请求头的键名的枚举集合
 					

ServletContext application = req.getServletContext();

3.4 获取请求头数据

(不论get还是post请求方式都可以使用下列方法来获取请求参数)

1. 通过请求头的名称获取请求头的值
    String req.getHeader(String name键名)//找不到返回null,不报错
    
2. 获取所有的请求头名称
    Enumeration e =  request.getHeaderNames()//获取所有的请求头名称
	while(e.hasMoreElements()){
        String name = (String)e.nextElenment();//获取键
        String value = req.getHeader(name);//获取值
        System.out.println( name+":"+value);
	}

3.5 获取请求体数据

请求体:只有POST请求方式,才有请求体,在请求体中封装了POST请求的请求参数
* 步骤:
1. 获取流对象
*  BufferedReader getReader():获取字符输入流,只能操作字符数据
*  ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据
* 在文件上传知识点后讲解

2. 再从流对象中拿数据。
获取用户数据
- String req.getParameter("键名");//返回指定的用户数据,一对多时可能漏值。好像get和post都能使用
- String[] req.getParameterValues("键名");//返回同键不同值的请求数据(多选),返回的数组。
- Enumeration<String> req.getParameterNames()//返回所有用户请求数据的枚举集合
- Map<String,String[]> getParameterMap()//获取所有参数的map集合
		
	注意:	如果要获取的请求数据不存在,不会报错,返回null。
 
	注意:
		request对象由tomcat服务器创建,并作为实参传递给处理请求的servlet的service方法。

3.6 获取ServletContext:

ServletContext getServletContext()

4 获取数据时中文乱码问题

乱码问题分为:

  • 请求乱码:浏览器发给服务器的数据,服务器收到解析出现乱码。我们应该告诉服务器我们使用的请求编码格式
  • 响应乱码:服务器发给浏览器的数据,浏览器收到解析出现乱码
4.1 请求编码:POST方式
乱码原因:
  • 浏览器默认的数据编码格式是GBK,浏览器发的是GBK,(不一定,拿Fiddler软件看吧)
  • tomcat服务器解析是用给定utf-8,会乱码 。

说明:对于POST方式,它采用的编码是由页面来决定的,如ContentType(“text/html; charset=GBK”)。当通过点击页面的submit按钮来提交表单时,浏览器首先会根据ContentType的charset编码格式来对POST表单的参数进行编码然后提交给服务器,在服务器端同样也是用ContentType中设置的字符集来进行解码,这就是通过POST表单提交的参数一般而言都不会出现乱码问题

当然这个字符集编码我们是可以自己设定的,通过:

解决方案:

方法1:在 service 方法中使用:

request.setCharacterEncoding(“utf-8);//设置编码
request.getParameter("");//获取数据

方法2:(实际更常用)

springMVC已经提供了轮子给我们使用,在web.xml添加post乱码filter:
在web.xml中加入:

<!-- 字符编码过滤器 -->
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!-- 设置请求编码 -->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <!-- 设置响应编码格式为也是encoding -->
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern><!-- 拦截所有用户请求 -->
</filter-mapping>
4.2 请求编码:GET方式
乱码原因
  • get请求方式的参数是放到了URL后面。这时设置content-type是没用的。
  • tomcat服务器收到这个请求就会根据设置的调用默认的URIEncoding来进行解码,默认为ISO8859-1,并封装成request对象。
    Tomcat8及以上版本不存在乱码问题,请求URL默认的编码是UTF-8,但是请求体还是使用的ISO-8859-1
    Tomcat7及之前的服务器使用的是ISO8859-1编码,不支持中文
解决方案:

方法一(String编码,不推荐):

new String(request.getParameter("name").getBytes("iso-8859-1"),"utf-8");
//原来以为的解析方式是8859的,所以我们先用8859解出来字节,再用utf8重新编码字符

方法二(tomcat/conf/server.xml设置):

解决方案是通知设置tomcat编码URL的格式与请求体一致,或单独设置URL编码格式
在 tomcat 服务器目录下的 conf 文件下找到 server.xml 文件,打开进行如下配置:
<Connector port="8080" protocol="HTTP/1.1"  maxThreads="150" 
           connectionTimeout="20000"
           redirectPort="8443"
           URIEncoding="utf-8"/> (单独设置了URL编码格式)
---------------或 -------------------------
<Connector port="8080" protocol="HTTO/1.1"
    connectionTimeout="20000"
    redirection="8443" 
    useBodyEncodingForURI="true">(设置URL的编码格式与请求体一致)
然后再使用req.setCharacterEncoding("");//设置请求体编码格式
----------------------------------------
(默认是iso-8859-1)。
4.3 响应编码
设置响应编码格式:告诉浏览器接收的格式(response默认直接使用ISO-8859-1编码然后交给浏览器)

方式1:在使用response对象之前设置编码格式(不用)
    response.setCharacterEncoding("UTF-8");//通知服务器用什么编码集将字符转为字节//只能让字符串不乱吗,浏览器还是不知道如何解析页面字符串

方式2:在使用response对象之前设置一个响应头,通知浏览器以什么编码打开,告诉response对象如何读取字符串
response.setContentType("text/html;charset=Utf-8");
//或resp.setHeader("content-type","text/html;charset=utf-8");//等价
//只要设置过ContentType,服务器自动识别编码会自动设置response.setCharacterEncoding("")

方式3:在web.xml中配置过滤器:稍后介绍

https://blog.csdn.net/qq_38409944/article/details/80637980

4.4 SpringMVC中的编码Filter
springMVC已经提供了轮子给我们使用,在web.xml添加post乱码filter:
在web.xml中加入:

<!-- 字符编码过滤器 -->
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!-- 设置请求编码 -->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <!-- 设置响应编码格式为也是encoding -->
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern><!-- 拦截所有用户请求 -->
</filter-mapping>

服务器启动的时候就会创建Filter,将init-param中的参数加载,注入到CharacterEncodingFilter 类中,浏览器每次发送请求都会经过这个过滤器,然后调用doFilterInternal

在这里插入图片描述

可以看到CharacterEncodingFilter继承OncePerRequestFilter,OncePerRequestFilter是个抽象类,其中的抽象方法doFilterInternal被CharacterEncodingFilter实现,同时OncePerRequestFilter实现了doFilter方法,调用了doFilterInternal方法。

@Override
protected void doFilterInternal(
    HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {

    if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {//条件为设置的encoding不为空 且 (传入了forceEncoding或CharacterEncoding为空)
        request.setCharacterEncoding(this.encoding);//关键语句
        if (this.forceEncoding) {// 关键语句
            response.setCharacterEncoding(this.encoding);
        }
    }
    filterChain.doFilter(request, response);
}

5 Response

在使用Request对象获取了请求数据并进行处理后,处理的结果如何显示到浏览器中呢?答案是:使用Response对象。

解释:服务器在调用指定的Servlet进行请求处理的时候,会给Servlet的方法传递两个实参request和response。其中resquest中封存了请求相关的请求数据,而response则是用来进行响应的一个对象。

使用:

  • 设置响应头
  • 设置响应编码格式
  • 设置响应实体
响应设置
Respone对象:
作用: 用来响应数据到浏览器的一个对象
搜httpServletResponse

使用: 设置响应头
- setHeader(String name,String value);//在响应头中添加响应信息,但是同键会覆盖
- addHeader(String name,String value);//在响应头中添加响应信息,但是不会覆盖。

设置响应状态
- sendError(int num,String msg);//自定义响应状态码(响应错误信息)。响应码说明
例:response.sendError(404,"sorry返回的信息");

设置响应实体
- resp.getWriter().write(String str);//响应具体的数据给浏览器
//什么叫响应实体:即str的内容是我们html可以识别的内容,相当于把这个实体写入到我们的浏览器中,而这个实体是浏览器可以直接识别的如<h1>等内容
例:resp.getWriter().write("<br>");

6 请求转发

问题:

服务器在接收到浏览器的请求后 ,仅仅使用一个Servlet进行请求处理,会造成不同的 Servlet 逻辑代码冗余,Servlet 的职责不明确。

解决:
使用请求转发。

特点:

  • 一次请求,浏览器地址栏信息不改变。多次转发
  • 只能转发到当前服务器内部资源中。
  • 转发是一次请求

共享数据:

  • 域对象:一个有作用范围的对象,可以在范围内共享数据

  • request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据

  • void setAttribute(String name,Object obj):存储数据
    Object getAttitude(String name):通过键获取值
    void removeAttribute(String name):通过键移除键值对
        
    获取ServletContext:
        ServletContext getServletContext()
    

请求转发使用方法:

步骤1. 通过request对象获取【请求转发器对象】:RequestDispatcher getRequestDispatcher(String path)
例:RequestDispatcher rd = request.getRequestDispatcher("URL地址")
    
步骤2. 使用RequestDispatcher对象来进行转发:forward(ServletRequest request, ServletResponse response) 
   例:request.getRequestDispatcher("URL地址").forward(request, response)

有三种方式得到RequestDispatcher对象:

1.ServletRequest接口中的getRequestDispatcher
2.ServletContext接口中的getRequestDispatcher()和getNamedDispatcher
区别:
ServletContext中的参数必须以斜杆/开始,context root上下文根的绝对路径
ServletRequest中的参数可以写绝对路径,也可以写相对路径。
Request 对象作用域

问题:使用请求转发后,不同的 Servlet 之间怎么进行数据的共享呢?或者说数据怎么从一个 servlet 流转给另外一个 Servlet 呢?
解决:使用 request 对象的作用域
使用:

  • 设置:request.setAttribute(object name,Object value);(session同理)
  • 获取:request.getAttribute(Object obj)

作用:解决了一次请求内的不同 Servlet 的数据(请求数据+其他数据)共享问题。

作用域:基于请求转发,一次请求中的所有 Servlet 共享。

注意:使用 Request 对象进行数据流转,数据只在一次请求内有效。

特点:服务器创建,每次请求都会创建,生命周期一次请求

localhost:8080/login/page

localhost:8080/login/login?uname=张三&pwd=123

比如登录失败应该返回登录界面让重新登录,而不是只输出登录失败
-------------------
login Servlet中:
//使用request对象实现不同Servlet的数据流转//增加到request中
req.setAttribute("str", "用户名或密码错误");//处理意见
//使用请求转发
req.getRequestDispatcher("转到的servlet--page登录页面").forward(req, resp);
return;
----------------------
在pageServlet中添加
String str=(String) req.getAttribute("str");//返回的是Object,所以得转换
//获取request作用域数据
String str=(String) req.getAttribute("str")==null?"":(String) req.getAttribute("str");
resp.getWriter().write("<font color='red' size='20px'>"+str+"</font>");//str错误信息展示


---------------------------
注意转发了request和response,相当于获取了相同的请求数据。
response用于转发处理建议,相当于add response。

7 重定向

问题:

  • 如果当前的请求,Servlet 无法进行处理怎么办?
  • 如果使用请求转发,造成表单数据重复提交怎么办?

自己处理不了,告诉谁可以处理

解决:使用重定向

语法:

response.sendRedirect(/url路径”) //重定向的uri,第一个/代表项目根目录
  • 本地路径为:uri

  • 网络路径为:定向资源的 URL 信息

特点:

  • 地址栏改变
  • 发生了两次请求,第二次会覆盖第一次的请求数据。
  • 数据不共享:sendRedirect告诉服务器重新发一个新的请求去访问mainServlet,里面没有了账号密码。所以频繁刷新还是刷新的main。请求与请求间没有关系。浏览器地址栏信息改变,避免表单重复提交

时机:如果请求中有表单数据,而数据又比较重要,不能重复提交,建议使用重定向。如果请求被Servlet接收后,无法进行处理,建议使用重定向定位到可以处理的资源。

原来getRequestDispatcher方法地址栏还是localhost:8080/login/login?uname=张三&pwd=123,暴露了登录数据,F5刷新会频繁地登录

但是此时丢失了数据

重定向是response的另外一个用处,与forward不同的是,重定向会丢失所有的请求参数和request范围的属性,因为重定向将生成第二次请求,与前一次请求不在同一个request范围内,所以发送一次请求的请求参数和request范围的属性全部丢失。

一、表单的重复提交:
①. 在表单提交到一个Servlet ,而Servlet 又通过请求转发的方式响应一个JSP(HTML) 页面, 这个时候地址栏显示的是Servlet的路径, 再刷新页面,这个时候会造成表单的重复提交。

②. 在响应页面没有到达时候重复点击“提交按钮”,会造成表单的重复提交。

③. 你在响应页面时候点击返回,在点击提交按钮的时候,会造成表单的重复提交。

二、如何避免表单的重复提交呢?
要防止表单重复提交,就要标识用户的每一次访问请求,使得每一次访问对服务端来说都是唯一确定的。为了标识用户的每次访问请求,可以在用户请求一个表单域时增加一个隐藏表单项,这个表单项的值每次都是唯一的token,如:

①. 在原表单生成一个随机值token 。

②. 在原表单把随机值token 放入session 和隐藏域(type="hidden")。

③. 在serlvet 中,获取session 跟隐藏域的值:

> 若一致,把session 中的Attribute 清除。
> 若不一致,直接重定向到提示页面,并且return 程序。

表单页面

<%@page import="org.apache.catalina.Session"%>
<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
 
   <%
          String token=new Date().getTime()+"";
          HttpSession  session= request.getSession(true);
          session.setAttribute("token", token); 
   %>
 
   <h1>Step-1:选择要购买的书籍</h1>
   <form action="<%=request.getContextPath() %>/procesStep1" method="post">
        <input type="hidden"  name="token"  value="<%=token%>">
        <table cellpadding="10" cellspacing="0" border="1px">
            <tr>
             <td>书名</td>
             <td>购买</td>
            </tr>
            <tr>
             <td>java</td>
             <td><input type="checkbox" name="book" value="java"></td>
            </tr>
            <tr>
             <td>C++</td>
             <td><input type="checkbox" name="book" value="c++"></td>
            </tr>
            <tr>
             <td>Python</td>
             <td><input type="checkbox" name="book" value="python"></td>
            </tr>
            <tr>
             <td>web</td>
             <td><input type="checkbox" name="book" value="web"></td>
            </tr>
            <tr>
            <td  colspan="2"> <input type="submit" value="submit"></td>
            </tr>
        </table>
        
   </form>
 
</body>
</html>
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		//1550936037932
		 String sessionToken = (String)request.getSession().getAttribute("token");
		 String requestToken = request.getParameter("token");
		 if(sessionToken!=null&&sessionToken.equals(requestToken)) {
			 request.getSession().removeAttribute("token");
		 }else {
			 //request.getRequestDispatcher("/shoppingCart/relogin.jsp");
			 response.sendRedirect(request.getContextPath()+"/shoppingCart/relogin.jsp");
			 return ;
		 }
		 
		//request.setCharacterEncoding("UTF-8");
	    ServletContext servletContext = request.getServletContext();
	    String servletContextName = servletContext.getServletContextName();
	      ServletConfig servletConfig = getServletConfig();
	     String[] parameterValues = request.getParameterValues("book");
	     request.getSession().setAttribute("book", parameterValues);
	     String contextPath = request.getContextPath();
	     System.out.println(contextPath);
	     //下面是绝对路径
	     //   response.sendRedirect(contextPath+"/shoppingCart/step_2.jsp");
	     //相对路径
	     response.sendRedirect("shoppingCart/step_2.jsp");
		
	}

请求转发VS重定向

一、请求转发和请求重定向的区别

转发forward重定向redirect
请求执行forward后依然是上一次请求执行redirect生成第二次请求
请求参数forward的目标页面可以访问原请求的请求参数,因为依然是同一次请求,所有原请求的请求参数、request范围的属性全部存在redirect的目标页面不能访问原请求的请求参数,因为第二次请求了,所有原请求的请求参数、request范围的属性全部丢失
地址栏地址栏里的请求URL不会改变地址栏改为重定向的目标URL。相当于在浏览器地址栏里输入新的URL后按回车键

请求转发和请求重定向又分别称为服务器跳转和客户端跳转

1.1、组件之间可否共享信息不同(本质区别)

当客户端向服务器发送请求时,服务器收到请求后,会将请求封装成一个HttpServletRequest对象request,并且所有的请求参数都封装在request对象中,这个对象时Jsp的内置对象可以直接在Jsp中使用。服务器收到请求后,还需要请求别的页面,这是就有两种方式:请求转发和请求重定向。

  • 请求转发:客户端和浏览器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,保存的对象对于一个每个信息资源是共享的。请求由服务器转发给另外一个页面处理,如何转发,何时转发,转发几次,客户端是不知道的。请求转发时,从发送第一次到最后一次请求的过程中,web容器只创建一次request和response对象,新的页面继续处理同一个请求。也可以理解为服务器将request对象在页面之间传递。

  • 请求重定向:实际是两次HTTP请求,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。是客户端的行为,每次请求重定向都是由客户端发起的,也就是说重定向一次,就刷新request对象的属性,之前的request对象的属性值就失效了。

1.2、浏览器地址栏显示不同(表面区别)

  • 请求转发之后地址栏的信息并不会有任何的改变。
  • 请求重定向之后地址栏是会改变的,变为跳转之后的页面地址。

二、java实现

2.1 java中的请求转发(服务器跳转)

执行到跳转语句时就会立刻进行跳转

request.getRequestDispatcher("success.jsp").forward(request,response);

在JSP中,可以使用
ServletContext context = getServletContext();
context.getRequestDispatcher("success.jsp").forward(request,response);

在jsp页面中你也会看到通过下面的方式实现转发:

<jsp:forward page="success.jsp" />

2.2 java中的请求重定向(客户端跳转)

  • response.sendRedirect(“apage.jsp”); 不能传递request 范围的属性 ,但是可以通过地址重写的方式向跳转页传递参数,因为该方法执行完之后就相当于一次http request 的结束,这是服务器会向客户端发送302 状态码和新的url ,告诉客户端重新发送request 请求到新的url ,然后客户端照此执行,执行即新的请求响应流程开始,服务器再重新创建HttpServletRequest 对象和HttpServletResponse 对象,此时两个请求已经不在一个线程了,所以request 和response 对象都不是开始的那个了;

  • response.setHeader(“refresh”,“2;URL=index.jsp”) 2 秒后跳转到其他页面;

  • <a href="http://www.baidu.com"> 百度</a> 超链接

  • 表单提交

三、路径问题

转发和重定向的URLString前有加 / 为绝对路径 ,反之为相对路径

  • 服务器解析的路径:请求转发

  • 浏览器解析的路径:主要写在html中的路径都是浏览器解析,除此之外都是服务器解析。

3.1绝对路径

1、重定向的 / 表示:http://服务器ip:端口/

response.sendRedirect("/Manager/index.jsp")

生成的地址:web服务器本身地址+参数生成完整的URL 即:http://localhost:8080/Manager/index.jsp

2、请求转发的 / 表示:http://服务器ip:端口/项目名。是在项目内转发,所以无需项目名

request.getRequestDispatcher("/index.jsp").forward(request, response); 

生成的地址:http://localhost:8080/项目名/index.jsp

请求转发地址的写法和servlet配置的urlPatterns的语法规则一样,/代表的是当前项目

重定向是浏览器发来的,只知道发到某个服务器,但是不知道发到服务器的哪个project,所以需要自己用代码声明; 而请求转发是服务器某个project内部的转发,转来转去都是在某个project内部,所以不需要手动声明项目名。

3.2 相对路径

假设通过表单请求指定的Url资源 action=“LoginServlet”
则表单生成的请求地址为:http://localhost:8080/项目名/LoginServlet

1、请求重定向:response.sendRedirect(“Manager/index.jsp”)
生成相对路径:http://localhost:8080/项目名/Manager/index.jsp

2、请求转发:相对路径情况下生成的完整URL与重定向方法相同。

代码

-------------response.java---------
package com.bjsxt.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ResponseServlet extends HttpServlet {
	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
			//获取请求信息...
				//获取请求头...
				//获取请求行...
				//获取用户数据...
        
			//处理请求...
				
			//响应处理结果
				//设置响应头
				resp.setHeader("mouse", "two fly birds");
				resp.setHeader("mouse", "bjsxt");
				resp.addHeader("key", "thinkpad");
				resp.addHeader("key", "wollo");
				//设置响应编码格式
					//resp.setHeader("content-type", "text/html;charset=utf-8");
					//resp.setContentType("text/plain;charset=utf-8");//普通文本
					//resp.setContentType("text/xml;charset=utf-8");
					resp.setContentType("text/html;charset=utf-8");
				//设置响应状态吗
					//resp.sendError(404, "this Method is not supported");
				//设置响应实体
					resp.getWriter().write("<b>今天天气真好,适合学习</b>");		
	}
}

案例 :用户登录

* 用户登录案例需求:
	1.编写login.html登录页面
		use rname & password 两个输入框
		点击登录完成登录操作
	2.使用Druid数据库连接池技术,操作mysql,day14数据库中user表
	3.使用JdbcTemplate技术封装JDBC
	4.登录成功跳转到SuccessServlet展示:登录成功!用户名,欢迎您
	5.登录失败跳转到FailServlet展示:登录失败,用户名或密码错误

数据库–登录页面page,

转到Servlet:login进行密码验证,New ServiceImple层对象,调用密码验证方法

转到Service(接口+imple),New DaoImple

转到Dao,进行JDBC

* 分析

* 开发步骤
	1. 创建项目,导入html页面,配置文件,jar包
	2. 创建数据库环境
		CREATE DATABASE day14;
		USE day14;
		CREATE TABLE USER(
			id INT PRIMARY KEY AUTO_INCREMENT,
			username VARCHAR(32) UNIQUE NOT NULL,
			PASSWORD VARCHAR(32) NOT NULL
		);
INSERT INTO 
3. 创建包cn.itcast.domain,创建类User
    package cn.itcast.domain;
/**
		 * 用户的实体类
		 */
public class User {

    private int id;
    private String username;
    private String password;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
            "id=" + id +
            ", username='" + username + '\'' +
            ", password='" + password + '\'' +
            '}';
    }
}

4 创建包cn.itcast.util,编写工具类JDBCUtils

package cn.itcast.util;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;


//JDBC工具类 使用Durid连接池
public class JDBCUtils {

    private static DataSource ds ;

    static {

        try {
            //1.加载配置文件
            Properties pro = new Properties();
            //使用ClassLoader加载配置文件,获取字节输入流
            InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
            pro.load(is);
            //2.初始化连接池对象
            ds = DruidDataSourceFactory.createDataSource(pro);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //获取连接池对象
    public static DataSource getDataSource(){
        return ds;
    }

    //获取连接Connection对象
    public static Connection getConnection() throws SQLException {
        return  ds.getConnection();
    }
}

5 创建包cn.itcast.dao,创建类UserDao,提供login方法

package cn.itcast.dao;

import cn.itcast.domain.User;
import cn.itcast.util.JDBCUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

/**
		 * 操作数据库中User表的类
		 */
public class UserDao {

    //声明JDBCTemplate对象共用
    private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());

    /**
		     * 登录方法
		     * @param loginUser 只有用户名和密码
		     * @return user包含用户全部数据,没有查询到,返回null
		     */
    public User login(User loginUser){
        try {
            //1.编写sql
            String sql = "select * from user where username = ? and password = ?";
            //2.调用query方法
            User user = template.queryForObject(sql,
                                                new BeanPropertyRowMapper<User>(User.class),
                                                loginUser.getUsername(), loginUser.getPassword());
            return user;
        } catch (DataAccessException e) {
            e.printStackTrace();//记录日志
            return null;
        }
    }
}

6 编写cn.itcast.web.servlet.LoginServlet类

package cn.itcast.web.servlet;

import cn.itcast.dao.UserDao;
import cn.itcast.domain.User;

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("/loginServlet")
public class LoginServlet extends HttpServlet {


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.设置编码
        req.setCharacterEncoding("utf-8");
        //2.获取请求参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        //3.封装user对象
        User loginUser = new User();
        loginUser.setUsername(username);   			loginUser.setPassword(password);
        //4.调用UserDao的login方法
        UserDao dao = new UserDao();
        User user = dao.login(loginUser);

        //5.判断user
        if(user == null){
            //登录失败
            req.getRequestDispatcher("/failServlet").forward(req,resp);
        }else{
            //登录成功
            //存储数据
            req.setAttribute("user",user);
            //转发
            req.getRequestDispatcher("/successServlet").forward(req,resp);
        }

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req,resp);
    }
}

7 编写FailServlet和SuccessServlet类

@WebServlet("/successServlet")
public class SuccessServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取request域中共享的user对象
        User user = (User) request.getAttribute("user");

        if(user != null){
            //给页面写一句话

            //设置编码
            response.setContentType("text/html;charset=utf-8");
            //输出
            response.getWriter().write("登录成功!"+user.getUsername()+",欢迎您");
        }
    }	
}


@WebServlet("/failServlet")
public class FailServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //给页面写一句话

        //设置编码
        response.setContentType("text/html;charset=utf-8");
        //输出
        response.getWriter().write("登录失败,用户名或密码错误");

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request,response);
    }
}

	8 login.html中form表单的action路径的写法
		* 虚拟目录+Servlet的资源路径

	9 BeanUtils工具类,简化数据封装
		* 用于封装javaBean的
		1. javaBean:标准的java类
			1. 要求:
				1. 类必须被public修饰
				2. 必须提供空参的构造器
				3. 成员变量必须使用private修饰
				4. 提供公共setter和getter方法
			2. 功能:封装数据
       2. 概念:
			成员变量:
			属性:setter和getter方法截取后的产物
				例如:getUsername() --> Username--> username
      3. 方法:
			1. setProperty()
			2. getProperty()
			3. populate(Object obj , Map map):将map集合的键值对信息,封装到对应的javaBean对象中
package com.bjsxt.servlet;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * request对象学习:
 * 		作用:request对象中封存了当前请求的所有请求信息
 * 		使用:
 * 			获取请求头数据
 * 					req.getMethod();//获取请求方式
 * 					req.getRequestURL();//获取请求URL信息
 * 					req.getRequestURI();//获取请求URI信息
 * 					req.getScheme();//获取协议
 * 			获取请求行数据
 * 					req.getHeader("键名");//返回指定的请求头信息
 * 					req.getHeaderNames();//返回请求头的键名的枚举集合
 * 			获取用户数据
 * 					req.getParameter("键名");//返回指定的用户数据
 * 					req.getParameterValues("键名");//返回同键不同值的请求数据(多选),返回的数组。
 * 					req.getParameterNames()//返回所有用户请求数据的枚举集合
 * 			注意:
 * 				如果要获取的请求数据不存在,不会报错,返回null。
 * 
 * 		注意:
 * 			request对象由tomcat服务器创建,并作为实参传递给处理请求的servlet的service方法。
 *
 */
public class RequestServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        //获取请求头数据
        //获取请求方式
        String method = req.getMethod();
        System.out.println(method);
        //获取请求URL
        StringBuffer url=req.getRequestURL();
        System.out.println(url);
        //获取URI
        String uri=req.getRequestURI();
        System.out.println(uri);
        //获取协议
        String h=req.getScheme();
        System.out.println(h);
        //获取请求行数据
        //获取指定的请求行信息
        String value=req.getHeader("aaa");
        System.out.println(value);
        //获取所有的请求行的键的枚举
        Enumeration e = req.getHeaderNames();
        while(e.hasMoreElements()){
            String name=(String) e.nextElement();
            String value2=req.getHeader(name);
            System.out.println(name+":"+value2);
        }
        //获取用户数据
        String name=req.getParameter("uname");
        String pwd=req.getParameter("pwd");
        System.out.println(name+":"+pwd);
        //String fav=req.getParameter("fav");
        String[] favs=req.getParameterValues("fav");
        if(favs!=null){
            for(String fav:favs){
                System.out.println(fav);
            }
        }
        //获取所有的用户请求数据的键的枚举集合---req.getParameterNames()	
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值