软件架构与网络通信

软件架构:

  • B/S:浏览器端-服务器端(JavaEE体系) 客户端不用再去开发,开发更加快速
  • C/S:客户端-服务器端

资源分类:

  • 静态资源:所有用户访问后得到的结果是一样的,称为静态资源
    • 如html css js
  • 动态资源:每个用户访问相同资源后,得到的结构可能不一样,称为动态资源。动态资源被访问后,需要先转换为静态资源,再返回给浏览器
    • 如 servlet php jsp

网络通信三要素:

  • IP:电子设备在网络中的唯一标识
  • 端口:应用程序在计算机中的唯一标识
  • 传输协议:规定了通信的规则
    • 基础协议:tcp udp
image-20250413200701460
image-20250413200701460

http概述

http:超文本传输协议

传输协议:定义了客户端和服务器端通信的时候发送数据的格式

特点: - 基于TCP/IP的高级协议 - 默认端口号是80 - 基于请求响应模型,一次请求对应一次响应 - 无状态的,每次请求之间相互独立

http的请求消息和请求行

请求消息数据格式:

  1. 请求行

    明确请求的基本信息(操作类型、目标资源、协议版本)

    请求方式 请求url 请求协议/版本

    请求方法:

    常见方法(Servlet 中常用)

    方法 含义 特点 是否有请求体
    GET 请求获取资源 请求参数在请求行,数据通过 URL 传递,明文可见,长度有限制(浏览器通常限制 8KB)
    POST 提交数据(如表单) 数据放在请求体中,适合传输大量数据,安全性较高
    PUT 更新资源(全量更新) 需指定资源完整路径,幂等性(多次操作结果一致)
    DELETE 删除资源 指定 URL 对应的资源,幂等性
    HEAD 获取资源头部信息 仅返回响应头,不返回响应体,用于快速检查资源是否存在

    请求url:协议://主机:端口/路径?查询参数#锚点

    • 协议:固定为httphttps(如http://

    • 主机:服务器域名(如www.example.com)或 IP 地址(如192.168.1.1

    • 端口:可选,默认80(HTTP)或443(HTTPS),如8080

    • 路径:资源在服务器上的路径(如/user/login

    • 查询参数:可选,格式为key=value&key2=value2,通过?与路径分隔

    • 锚点:客户端浏览器使用,用于定位页面内元素,服务器不处理

      Servlet服务器接收的 URL 部分:不包含锚点,仅到查询参数为止

  2. 请求头

    携带请求的附加信息(客户端环境、请求参数、安全信息等)

    请求头名称:请求头值

    通用头字段:

    字段名 含义 示例 Servlet 获取方法
    Host 目标服务器的主机和端口(必传) localhost:8080 request.getHeader("Host")
    User-Agent 客户端信息(浏览器 / 操作系统) Mozilla/5.0 (Windows NT 10.0) Chrome/110.0.0.0 request.getHeader("User-Agent")
    Referer 来源页面 URL(防盗链,统计) http://example.com/login.html request.getHeader("Referer")
    Accept 客户端接受的响应内容类型 text/html,application/json request.getHeader("Accept")

    可以在服务器端获取User-Agent该头的信息,解决浏览器端兼容性问题

    与请求体相关的头字段:

    字段名 含义 示例 Servlet 关联
    Content-Type 请求体的数据格式和编码 application/x-www-form-urlencoded; charset=UTF-8(表单数据) multipart/form-data; boundary=xxx(文件上传) 通过request.getContentType()获取
    Content-Length 请求体的字节长度 1024 通过request.getContentLength()获取
  3. 请求空行

    作为请求头和请求体的分隔符,必须存在且仅包含一个换行符(\r\n

  4. 请求体

    封装POST请求消息的请求体的,GET请求没有请求体

    空行之后(可选,非必须),存放请求的具体数据(如表单提交的参数、上传文件内容等)

    1. 数据格式
    • application/x-www-form-urlencoded:表单默认格式,键值对编码为key=value&key2=value2,不支持文件上传
    • multipart/form-data:文件上传专用格式,通过boundary分隔不同字段,支持二进制数据
    • application/json:JSON 格式数据,需在Content-Type头中声明
    1. Servlet 中获取请求体数据
    • 普通表单数据:通过request.getParameter("参数名")直接获取(自动解码)
    • JSON 数据:需通过request.getReader()request.getInputStream()手动解析
    • 文件上传:需使用MultipartHttpServletRequest(需配置MultipartResolver

响应消息数据格式

Request请求原理

image-20250413202921595
image-20250413202921595

request对象和response对象的原理:

由服务器创建,我们仅使用
request对象获取请求消息,response对象来设置响应消息

request对象继承体系的结构

Servlet中的HttpServletRequest对象是处理HTTP请求的核心接口

基本继承层次:

1
2
3
4
5
javax.servlet.ServletRequest (接口)

javax.servlet.http.HttpServletRequest (接口)

org.apache.catalina.connector.RequestFacade (Tomcat实现类)

ServletRequest接口

这是最基础的请求接口,定义了通用的请求方法,不限于HTTP协议:

  • 核心方法
    • getParameter(String name) - 获取请求参数
    • getAttribute(String name) - 获取请求属性
    • setAttribute(String name, Object o) - 设置请求属性
    • getInputStream() - 获取输入流
    • getReader() - 获取字符读取器
    • getContentType() - 获取内容类型
    • getLocalAddr() - 获取服务器IP地址

HttpServletRequest接口

继承自ServletResponse接口,专门用来封装HTTP响应消息。 由于HTTP请求消息分为状态行,响应消息头,响应消息体三部分,因此,在HttpServletResponse接口中定义了向客户端发送响应状态码,响应消息头,响应消息体的方法。:

  • HTTP相关方法
    • getHeader(String name) - 获取请求头
    • getMethod() - 获取HTTP方法(GET/POST等)
    • getCookies() - 返回一个cookie对象数组
    • getSession() - 返回与这个请求相关的会话对象
    • getRequestURI() - 获取请求URI
    • getQueryString() - 返回请求URL中的查询字符串
    • getContextPath() - 返回请求上下文的请求URI部分
    • getServletPath() - 获取Servlet路径

实现类 (以Tomcat为例)

Tomcat中的具体实现类结构:

1
2
3
Request (org.apache.catalina.connector.Request)

RequestFacade (org.apache.catalina.connector.RequestFacade)
  • Request:Tomcat内部的实际实现类,包含大量内部方法
  • RequestFacade:提供给Servlet开发者的门面类,遵循门面模式,隐藏了内部复杂实现

javax.servlet.http包内容

HttpServlet是由GenericServlet抽象类扩展而来的,HttpServlet抽象类的声明如下所示:

1
public abstract class HttpServlet extends GenericServlet implements Serializable 

HttpServlet之所以运用广泛的另一个原因是现在大部分的应用程序都要与HTTP结合起来使用。这意味着我们可以利用HTTP的特性完成更多更强大的任务。Javax.servlet.http中的许多类型都覆盖了Javax.servlet中的类型。

img
img

HttpServlet抽象类

HttpServlet 抽象类是继承于 GenericServlet 抽象类而来的。

使用 HttpServlet 抽象类时,还需要借助分别代表 Servlet 请求和 Servlet 响应的 HttpServletRequest 和 HttpServletResponse 对象。

HttpServletRequest 接口扩展于 javax.servlet.ServletRequest 接口,HttpServletResponse 接口扩展于javax.servlet.servletResponse 接口。

1
public interface HttpServletRequest extends ServletRequest
1
public interface HttpServletResponse extends ServletResponse

其中,HttpServlet 抽象类覆盖了 GenericServlet 抽象类中的Service( )方法,并且添加了一个自己独有的Service(HttpServletRequest request,HttpServletResponse方法。

这是 GenericServlet 抽象类中定义的service方法:

1
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

HttpServlet 实现的这个 service 方法

1
2
3
4
5
6
7
8
9
10
11
12
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException("non-HTTP request or response");
}

this.service(request, response);
}

HttpServlet 中的 service 方法把接收到的 ServletRequsest 类型的对象转换成了 HttpServletRequest 类型的对象,把ServletResponse 类型的对象转换成了 HttpServletResponse 类型的对象。

之所以能够这样强制的转换,是因为在调用Servlet的Service方法时,Servlet容器总会传入一个 HttpServletRequest 对象和 HttpServletResponse 对象,预备使用HTTP。因此,转换类型当然不会出错了。

转换之后,service方法把两个转换后的对象传入了另一个service方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 参数是HttpServletRequest对象和HttpServletResponse对象,刚好接收了上一个service方法传过来的两个对象
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
// 解析HttpServletRequest中的方法参数,并调用以下方法之一,每一种方法都表示一个Http方法。doGet和doPost是最常用的
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
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);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}

通过request获取内容

通过request获得请求行

请求行包含HTTP方法、URI和协议版本,例如:GET /test?name=value HTTP/1.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 获取请求方法(GET/POST/PUT/DELETE等)
String method = request.getMethod();

// 获取请求URL(不包含协议、域名和端口)
String requestURL = request.getRequestURL().toString();

// 获取请求URI(相对于Context Path的部分)
String requestURI = request.getRequestURI();

// 获取查询字符串(问号后面的部分)
String queryString = request.getQueryString();

// 获取协议和版本
String protocol = request.getProtocol(); // HTTP/1.1

// 获取上下文路径(web应用的根路径)
String contextPath = request.getContextPath();

// 获取Servlet路径
String servletPath = request.getServletPath();

获取请求头信息

请求头包含了客户端环境和请求的附加信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 获取指定请求头的值
String userAgent = request.getHeader("User-Agent");
String accept = request.getHeader("Accept");

// 获取所有请求头名称的枚举
Enumeration<String> headerNames = request.getHeaderNames();

// 遍历所有请求头
while(headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = request.getHeader(name);
System.out.println(name + ": " + value);
}

// 获取Int类型的请求头值
int contentLength = request.getIntHeader("Content-Length");

// 获取日期类型的请求头值
long date = request.getDateHeader("If-Modified-Since");

// 获取多值请求头
Enumeration<String> languages = request.getHeaders("Accept-Language");

获取请求体

请求体主要出现在POST、PUT等请求中,包含客户端发送的数据

获取表单数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 获取单个表单参数值
String username = request.getParameter("username");

// 获取多值参数(如复选框)
String[] hobbies = request.getParameterValues("hobby");

// 获取所有参数名的枚举
Enumeration<String> paramNames = request.getParameterNames();

// 获取所有参数的Map
Map<String, String[]> paramMap = request.getParameterMap();

// 遍历所有参数
paramMap.forEach((key, values) -> {
System.out.print(key + ": ");
for(String value : values) {
System.out.print(value + " ");
}
System.out.println();
});

获取原始请求体

1
2
3
4
5
6
7
8
9
10
11
// 获取输入流读取原始数据
ServletInputStream inputStream = request.getInputStream();
BufferedReader reader = request.getReader();

// 示例:读取JSON请求体
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String jsonBody = sb.toString();

获取其他请求信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 获取客户端IP地址
String clientIP = request.getRemoteAddr();

// 获取客户端主机名
String clientHost = request.getRemoteHost();

// 获取客户端端口
int clientPort = request.getRemotePort();

// 获取服务器信息
String serverName = request.getServerName();
int serverPort = request.getServerPort();

// 获取请求的会话
HttpSession session = request.getSession();

// 获取Cookie数组
Cookie[] cookies = request.getCookies();

// 获取内容类型
String contentType = request.getContentType();

// 获取字符编码
String encoding = request.getCharacterEncoding();

// 设置字符编码(处理中文乱码)
request.setCharacterEncoding("UTF-8");

示例

处理JSON请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected void doPost(HttpServletRequest request, HttpServletResponse response) 
throws ServletException, IOException {

// 设置字符编码
request.setCharacterEncoding("UTF-8");

// 读取JSON请求体
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = request.getReader()) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
}

// 解析JSON
JSONObject json = new JSONObject(sb.toString());
String username = json.getString("username");
// 处理业务逻辑...
}

Request乱码问题的解决方法

Servlet默认编码是ISO-8559-1,需要手动修改编码方式为UTF-8编码

1
2
request.setCharacterEncoding("UTF-8");  // 解决post提交方式的乱码
parameter = newString(parameter.getbytes("iso8859-1"),"utf-8"); // 解决get提交的方式的乱码

通过Response设置响应

HttpServletResponse对象是Servlet中用于构建HTTP响应的核心接口,它提供了丰富的方法来设置响应状态、响应头和响应体

设置响应状态

设置状态码

1
2
3
4
5
6
7
8
9
10
11
// 设置成功状态码(200)
response.setStatus(HttpServletResponse.SC_OK);

// 设置404未找到
response.setStatus(HttpServletResponse.SC_NOT_FOUND);

// 设置500服务器错误
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

// 设置302重定向(已废弃,推荐使用sendRedirect)
response.setStatus(HttpServletResponse.SC_FOUND);

设置状态码和错误消息

1
2
3
4
5
// 设置状态码和自定义错误消息
response.sendError(HttpServletResponse.SC_NOT_FOUND, "资源不存在");

// 仅设置状态码(使用默认错误消息)
response.sendError(HttpServletResponse.SC_FORBIDDEN);

设置响应头

基本响应头设置

1
2
3
4
5
6
7
8
9
10
11
// 设置单个响应头
response.setHeader("Content-Type", "text/html;charset=UTF-8");

// 添加响应头(可设置多个同名头)
response.addHeader("Set-Cookie", "name=value");

// 设置Int类型响应头
response.setIntHeader("Content-Length", 1024);

// 设置日期类型响应头
response.setDateHeader("Expires", System.currentTimeMillis() + 3600000);

常用响应头快捷方法

1
2
3
4
5
6
7
8
// 设置内容类型和字符编码(等同于setHeader("Content-Type", ...))
response.setContentType("text/html;charset=UTF-8");

// 设置内容长度(等同于setIntHeader("Content-Length", ...))
response.setContentLength(1024);

// 设置字符编码(单独设置)
response.setCharacterEncoding("UTF-8");

设置响应体

获取输出流

1
2
3
4
5
6
7
// 获取字节输出流(用于二进制数据)
// 获得字节流,通过该字节流的write(byte[] bytes)可以向response缓冲区中写入字节,再由Tomcat服务器将字节内容组成Http响应返回给浏览器。
ServletOutputStream outputStream = response.getOutputStream();

// 获取字符输出流(用于文本数据)
// 获得字符流,通过字符流的write(String s)方法可以将字符串设置到response缓冲区中,随后Tomcat会将response缓冲区中的内容组装成Http响应返回给浏览器端。
PrintWriter writer = response.getWriter();

注意,其中response对象的getOutSream()和getWriter()方法都可以发送响应消息体,但是他们之间相互排斥,不可以同时使用,否则会发生异常。

重定向相关

302重定向

1
2
3
4
5
6
7
8
// 简单重定向(相对路径)
response.sendRedirect("newPage.html");

// 重定向到绝对URL
response.sendRedirect("http://example.com/newPath");

// 重定向到上下文路径下的资源
response.sendRedirect(request.getContextPath() + "/secured/page.jsp");

请求转发(服务器内部)

1
2
3
4
5
// 获取转发器
RequestDispatcher dispatcher = request.getRequestDispatcher("/target.jsp");

// 转发请求和响应
dispatcher.forward(request, response);

示例

RESTful API响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void doGet(HttpServletRequest request, HttpServletResponse response) 
throws ServletException, IOException {

response.setContentType("application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");

try (PrintWriter out = response.getWriter()) {
JSONObject json = new JSONObject();
json.put("status", "success");
json.put("data", new JSONObject()
.put("id", 123)
.put("name", "张三")
.put("email", "zhangsan@example.com"));

out.print(json.toString());
}
}

文件下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected void doGet(HttpServletRequest request, HttpServletResponse response) 
throws ServletException, IOException {

String fileName = "example.pdf";
String filePath = "/path/to/files/" + fileName;

response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");

try (InputStream in = new FileInputStream(filePath);
OutputStream out = response.getOutputStream()) {

byte[] buffer = new byte[4096];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
}
}

乱码问题

response缓冲区的默认编码也是iso8859-1

1
response.setCharacterEncoding("utf-8");  // 更改response的编码方式为UTF-8

更改response的编码方式为UTF-8,只是其中一步,因为发送端服务端虽然改变了编码方式为UTF-8,但是接收端浏览器端仍然使用GB2312编码方式解码,还是无法还原正常的中文,因此还需要告知浏览器端使用UTF-8编码去解码。

1
2
// 通知浏览器使用utf8
response.setHeader("Content-Type", "text/html;charset=utf-8");

response.setContentType(“text/html;charset=UTF-8”)这个方法包含了上面的两个方法的调用,因此在实际的开发中,只需要调用一个response.setContentType(“text/html;charset=UTF-8”)方法即可。

imgimg

Servlet的工作流程

img
img

关于Web基础部分

JavaWeb-http

引用文章

JavaWeb——HTTP详解

JavaWeb——Servlet

菜鸟教程Servlet部分