Java web 的核心HttpServletRequest 和 HttpServletResponse

前言

Java从诞生之初到现在,仅仅20多年的时间,我有幸在它发展的初级阶段结实了这门语言。因为我相信,随着Java生态的越来越活跃,以及Java的设计思想越来越被时代所认可,Java的开发者越来越多,它就会像一家公司一样不断壮大,不断汲取新鲜血液以及不断的创新发展,直至30年、50年甚至100年。
Java的诞生是为了在不同的硬件设备上做应用,比如电视、冰箱、洗衣机等等,因此才设计了JVM用来屏蔽不同的底层系统,然而它并没有发展成想象中那样,而是误打误撞的和互联网结下了深厚的渊源,因此才有了Java web这一称号,因为Java早起并不是写web的,Java 是门派,web只是它门下的发展的一个弟子,但就是这个web弟子,将java门派推向了盛世繁华。
Java的生长出现在2000年到2010年间,随着世界互联网科技的不断发展,Servlet的诞生是Java迎合互联网发展的产物,也是将Java推向互联网开发技术的划时代变革。
所以,什么是Servlet呢?

网络

在了解Servlet之前,需要先了解网络。上面说了,Servlet是迎合互联网诞生的产物,因此,先有互联网,后才有了Servlet。
网络的概念诞生于上个世纪,随着世界工业的不断壮大,计算机各个组成硬件不断升级,比如CPU、内存等等,致使了计算机的功能越来越强大,计算效率和速度越来越高。人们提出了将计算机连接起来的想法,那么怎么连接呢?
从一台电脑到另一台电脑的通信,人们将他们从上到下分为了ISO七层模型,以致后来发展出来的TCP/IP网络模型。但是这个模型只是一个网络通信骨架,只有骨架没有血肉,不能称为一个完整的形体。
于是每一层之间,诞生了很多协议,也就是约定。传文件用什么格式来表达,传邮件用什么格式来表达等等。其中有一个现在大名鼎鼎的HTTP协议,就是用来网页交互的。

HTTP

Http是一种超文本传输协议,因为它不但可以传文本,还可以传图片,传视频等等媒体。具体概念百度上有,这里就不再赘述。
这里要讨论的是,HTTP与Servlet的联系。
先看计算机与计算机如何基于HTTP进行通信的。
在这里插入图片描述
上面图片中密密麻麻的英文很熟悉,那个就是打开网页按F12弹出的信息,就是HTTP信息,包含了请求头,请求体,响应体等等。
而Serlet呢?
Java是一门编程语言,Servlet自然是一种编写信息的工具,HttpServlet自然就是一种编写Http信息的工具。
这就是上文提到了,Servlet为什么是一种迎合互联网而诞生的产物。

本文要讨论的核心是HttpServletRequest和HttpServletResponse。
作为一枚Java程序员,要想玩转Java Web,就必须弄懂web核心Servlet。而HttpServlet是Servlet的核心,因此,

HttpServlet是当下这个互联网时代Java发展成长过程中核心中的核心。

上文提过,HttpServlet是一种编写Http信息的工具。只要知道了Http的组成部分,自然就明白HttpServlet的设计原理。

Http组成

Http是一种无状态、无连接的请求响应式协议,所以可以分为两部分:请求和响应
请求
请求包含了请求头和请求体
请求头中包含了 请求行、请求属性和结束行
请求行中又包含了请求方法,URL和协议版本
响应
响应包含了响应头和响应体等
响应头中包含响应行,响应属性和结束行
响应行中包含了响应状态码,协议版本等等

具体关系结构图如下:
在这里插入图片描述

先看第一部分:

(一)、 HTTP请求

(1) 请求方法
请求头中包含了请求行,请求行中包含了请求方法。
常见的请求方法有GET/POST/PUT/DELETE等等。最常用的就是GET和POST方法。
对于GET方法可以没有请求体,因为其请求参数拼接在URL上,因此长度会受到限制,暴露更明显。
对于POST方法必须有请求体,因为其参数存于请求体中,因此长度没有限制,暴露相对安全一点。
(2)请求属性
常见的请求属性有:

Connection: keep-alive                                  # 维护客户端和服务端的连接关系 
Content-Length: 68                                      # 描述HTTP消息实体的传输长度
Accept: application/json, text/javascript, */*; q=0.01          #发送端(客户端)希望接受的数据类型、q 是权重系数,范围 0 =< q <= 1,q 值越大,请求越倾向于获得其“;”之前的类型表示的内容
Origin: http://apptest.zhidianlife.com:8007            # 浏览器在referrer字段中只显示源网站的源地址(即协议、域名、端口),而不包括完整的路径  
Authorization: c81e7286507f4aa4b6179f4c381b4c64          # 请求所需的认证信息
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64)      AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36          # 客户端版本号的名字
Content-Type: application/json                     # 请求实体,文档类型
Referer: http://apptest.zhidianlife.com:8007/procurement/order?_t=756512&_winid=w9290         #  从来于哪里
Accept-Encoding: gzip, deflate                        # 客户端接收编码类型,一些网络压缩格式: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9                      # 客户端接收的语言类型 、中文

(3)结束标志符
上图中,最容易忽略的就是尾部的CRLF——结束标志。一般在用Java模拟Http协议时,尾部必须要加\r\n标识符,意味着这一单位信息的结束。

(二)、 HTTP响应

(一)响应状态码
响应头包含了HTTP协议版本,响应状态码等等。
响应状态码共有五种分类,以1开头、2开头、3开头、4开头和5开头的状态码。
1XX…
2XX…
3XX…
4XX…
5XX…
(二)响应正文
响应正文就是网页源码,客户端浏览器会将网页源码解析渲染为动态操作页面。
(三)结束标志符
结束标志符合上文的结束标识符一致。

HttpServlet

HttpServlet是针对Http开发出来的,了解了Http组成后,就能大概明白其机制。具体情况接下来深入源码中讨论看看HttpServlet做了什么。

public abstract class HttpServlet extends GenericServlet {
# 这一部分定义了各种请求方法的标识符
private static final long serialVersionUID = 1L;
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
...

# 这一部分定义了各种请求方法的具体实现 	
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	....省略
}
protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	....省略
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	....省略
}
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	....省略
}
...

#最重要的service方法
 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    long lastModified;
    
    # 如果是get请求,则转到get方法中..下同
    if (method.equals("GET")) {
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
            this.doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader("If-Modified-Since");
            } catch (IllegalArgumentException var9) {
                ifModifiedSince = -1L;
            }

            if (ifModifiedSince < lastModified / 1000L * 1000L) {
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
    } 
    ....
}
}

最开始的java web就是通过自定义Servlet并继承HttpServlet,以实现service方法。在上层调用时会调用传进去的自定义Servlet的service方法。
这里有两个疑问:
(1) 自定义servlet是否必须继承HttpServlet后,其Service方法才能被调用成功。
(2)自定义Servlet能否直接实现Servlet接口,达到请求访问的目的。
猜想:
上层调用的是HttpServlet的service方法,只要传入HttpServlet或其子类,都可以成功调用传入对象的service方法。

上层调用方法猜想:
?   ?   ?method(HttpServlet  httpServlet){
	httpServlet.service(httpServletRequest , httpServletResponse);
}

通过实验和验证发现:
自定义的MyServlet只要实现了Servlet,其自定义的Myservlet的service方法都可以被成功调用。
这说明上层调用的是接口Servlet的service方法,而非HttpServlet的service方法。
那Servlet 与 HttpServlet有什么差异呢?
在这里插入图片描述
HttpServlet是Servlet的子类,除了Servlet,它还实现了更多的功能,并自定义实现了HTTP协议的各种请求方法及处理方法,是一个继承了Servlet功能并且拓展了HTTP相关处理方法的一个类。是一个抽象类但没有抽象方法,这说明了并不强行需要子类实现它的任何方法。
在上述HttpServlet的service源码中可以看到,service做了一个路由转发,根据不同的请求方式转发到不同的具体实现方法中去。而上层在调用时向service方法中,注入了两个对象:
HttpServletRequest 和 HttpServletResponse。
(1) 它们是一个什么对象,它们来自哪里。
(2) 它们内部方法有哪些,它们可以做什么。

上层是哪里?

Servlet由上层来控制,service方法也是上层在调用。那么上层在哪里,是一个什么东西?
这里要引入Web容器,Web容器是什么?字面理解就是一个盛装web程序的容器,web程序明确的可以理解为Servlet。所以web容器也可以理解为一个盛装Serlvet的容器。
在这里插入图片描述
Web容器是一种应用统称,其具体有Tomcat、JBoss等等中间件。

HttpServletRequest

web容器和这个对象有什么关系呢?
HttpServletRequest是一个请求对象,由web容器负责将接收到的HTTP信息解析出来生成一个HttpServletRequest对象, 所以这个对象其实就是对HTTP协议请求信息的封装。
包含了请求头各个参数、请求体参数、协议版本,请求地址、时间戳、cookie、session等等。
具体可以跟踪调用链,HttpServletRequest对象信息来自于顶层接口ServletRequest,该接口可调用的信息就是被封装进来的信息:

public interface ServletRequest {

Object getAttribute(String var1);  // 某个属性

Enumeration<String> getAttributeNames(); // 属性集合

String getCharacterEncoding(); // 编码方式

void setCharacterEncoding(String var1) throws UnsupportedEncodingException;

int getContentLength();  // 请求体长度

long getContentLengthLong();

String getContentType(); // 请求体内容形式

ServletInputStream getInputStream() throws IOException;

String getParameter(String var1);  // 请求体参数

Enumeration<String> getParameterNames(); // 请求体参数集合

String[] getParameterValues(String var1); // 请求体中的value值

Map<String, String[]> getParameterMap();  // 请求体中的value值集合

String getProtocol(); // 协议

String getScheme();

String getServerName(); 

int getServerPort(); // 端口

BufferedReader getReader() throws IOException;

String getRemoteAddr(); 

String getRemoteHost(); // 请求主机

void setAttribute(String var1, Object var2);

void removeAttribute(String var1);

Locale getLocale();

Enumeration<Locale> getLocales();

boolean isSecure();

RequestDispatcher getRequestDispatcher(String var1);

/** @deprecated */
@Deprecated
String getRealPath(String var1);

int getRemotePort();

String getLocalName();

String getLocalAddr();

int getLocalPort();

ServletContext getServletContext();

AsyncContext startAsync() throws IllegalStateException;

AsyncContext startAsync(ServletRequest var1, ServletResponse var2) throws IllegalStateException;

boolean isAsyncStarted();

boolean isAsyncSupported();

AsyncContext getAsyncContext();

DispatcherType getDispatcherType();
}

HttpServletResponse

这个对象同理,是对HTTP响应信息的一个封装。
具体可追踪到ServletResponse对象:

public interface ServletResponse {
String getCharacterEncoding();

String getContentType();

ServletOutputStream getOutputStream() throws IOException;

PrintWriter getWriter() throws IOException;

void setCharacterEncoding(String var1);  // 设置编码方式

void setContentLength(int var1);

void setContentLengthLong(long var1);

void setContentType(String var1);

void setBufferSize(int var1);

int getBufferSize();

void flushBuffer() throws IOException;

void resetBuffer();

boolean isCommitted();

void reset();

void setLocale(Locale var1);

Locale getLocale();
}

整个交互流程

从上可以看到,一个web网页的请求到响应过程。
在这里插入图片描述

客户端一般是使用浏览器访问,当用户使用浏览器向服务器发送消息时,

  1. 浏览器会将客户的请求以HTTP协议的形式发送到服务器端
  2. web容器将HTTP协议封装到HttpServletRequest和HttpServletResponse对象中, web找到对应的Servlet服务,将 HttpServletRequest和HttpServletResponse这两个对象传递进去。
  3. 服务程序对这两个对象进行处理计算和数据库交互后,将这两个对象返还给Web容器。
  4. Web容器将这两个对象数据解析,以Http协议形式发送给客户端浏览器。

最后,浏览器收到信息后,按照HTTP协议进行解析整个流程就完成了。

程序员可以做什么

对于这一套流程,程序员通常是编写Servlet服务,比如编写Java web处理HttpServletRequest和HttpServletResponse对象。java web从早期的 servlet + JSP 以及发展到了如今的 SSM,从单体式架构发展到了微服务,如Springboot + Spring cloud。一个复杂的系统可能还包含消息中间件(如RabbitMQ)、缓存中间件(如Redis)、负载均衡中间件(如Nginx)等等。
这整套系统都属于Servlet服务,系统要处理的对象也就是HttpServletRequest和HttpServletResponse。这两个对象就是消息传递对象,程序要解决的就是消息的处理正确,处理请求时出现的高并发、保证系统异常情况下的高可用等等。

以上仅为个人理解,如有不当的地方,请路过的大神帮忙斧正

猜你喜欢

转载自blog.csdn.net/weixin_43901067/article/details/106295855
今日推荐