一.四种常用的请求
| 特征 | GET | POST | PUT | DELETE |
|---|---|---|---|---|
| 核心用途 | 从服务器获取数据(查) | 向服务器提交数据(增 / 改) | 向服务器更新数据(改) | 从服务器删除数据(删) |
| 参数位置 | URL 后缀(?key=value)→ 属于请求头 | 请求体(Body)→ 独立数据区 | 请求体(Body)→ 独立数据区 | URL 路径 / 请求体(多为路径) |
| 数据可见性 | 参数暴露在 URL 中,肉眼可见 | 参数隐藏在请求体,不可见 | 参数隐藏在请求体,不可见 | 参数多在 URL,可见 |
| 数据大小限制 | 受浏览器 / 服务器限制(通常几 KB) | 无明确限制(可传大文件) | 无明确限制 | 同 GET(路径参数有限) |
| 安全性 | 低(参数易被劫持、缓存) | 高(参数加密传输) | 高 | 中(需权限校验) |
| 幂等性 | 是(多次请求结果一致) | 否(多次提交可能重复创建) | 是(多次更新结果一致) | 是(多次删除结果一致) |
| 缓存 | 可缓存(浏览器记录 URL) | 不可缓存 | 不可缓存 | 不可缓存 |
| 前端示例 | <a href="/user?id=1"> | <form method="post"> | Ajax/Promise 请求 | Ajax/Promise 请求 |
| Spring MVC 示例 | @GetMapping("/user") | @PostMapping("/user") | @PutMapping("/user/{id}") | @DeleteMapping("/user/{id}") |
二.HTTP


2.1浏览器中的书写格式

2.2HTTP特点
- 支持客户/服务器模式。
- 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有 GET、POST。每种方法规定了客户与服务器联系的类型不同。由于 HTTP 协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
- 灵活:HTTP 允许传输任意类型的数据对象。传输的类型由Content-Type加以标记。
- 无连接:无连接是表示每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。 HTTP1.1 版本后支持可持续连接。通过这种连接,就有可能在建立一个 TCP 连接后,发送请求并得到回应,然后发送更多的请求并得到更多的回应。通过把建立和释放 TCP 连接的开销分摊到多个请求上,则对于每个请求而言,由于 TCP 而造成的相对开销被大大地降低了。而且,还可以发送流水线请求,也就是说在发送请求 1 之后的回应到来之前就可以发送请求 2。也可以认为,一次连接发送多个请求,由客户机确认是否关闭连接,而服务器会认为这些请求分别来自不同的客户端。
- 无状态:HTTP 协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
三.HTTP请求头
3.1GET请求的请求头

3.1.1GET请求头中包含请求行和请求头,没有请求体因为参数都在URL中。

3.1.2请求行的内容

3.2POST请求的请求头

四.HTTP响应头
4.1格式


最后跟响应正文
4.2为什么有空行
注意: 为什么 HTTP 报文中要存在空行呢?
- 因为 HTTP 协议并没有规定报头部分的键值对有多少个,使用空行就相当于是报文的结束标记或报文和正文之间的分隔符
- HTTP 在传输层依赖 TCP 协议,TCP 是面向字节流的。如果没有这个空行,就会出现”粘包问题“
五.GET和POST的区别(包含幂等)
5.1区别
原文链接:https://blog.csdn.net/weixin_51367845/article/details/123313047
其实 GET 和 POST 的区别是一个经典的面试题,以下为大家介绍如何在面试中回答上述问题
答题模板:
GET 和 POST 其实没有本质区别,使用 GET 的场景完全可以使用 POST 代替,使用 POST 的场景一样可以使用 GET 代替。但是在具体的使用上,还是存在一些细节的区别
GET 习惯上会把客户端的数据通过 query string 来传输(body 部分是空的);POST 习惯上会把客户端的数据通过 body 来传输(query string 部分是空的)
我的理解:GET请求把参数显式地放在URL中,但是POST请求不会这么显示地提交数据,会把数据放在请求体中。
GET 习惯上用于从服务器获取数据;POST 习惯上是客户端给服务器提交数据
一般情况,程序员会把 GET 请求的处理,实现成“幂等”的;对于 POST 请求的处理,不要求实现成“幂等”
GET 请求可以被缓存,可以被浏览器保存到收藏夹中;POST 请求不能被缓存
5.2补充: 幂等是什么?
一个 HTTP 方法是幂等的,指的是同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说,幂等的方法不应该具有副作用。
比如我们去抢购一件物品,如果我们已经抢到了要进行下单,由于很多人都在抢购,所以下单后,我们发现好像没有什么反应,因此我们又不断的点机下单。如果最终我们只需要付一件产品的钱,就是幂等的,如果要支付N件产品的钱,就不是幂等的
在正确的条件下,GET、HEAD、PUT 和 DELETE 等方法是幂等的;POST 方法不是幂等的
幂等性只与后端服务器的实际状态有关,而每一次请求接收到的状态码不一定相同
5.3怎么实现事务的幂等性
方案 1:基于唯一请求 ID + 防重表(最通用,推荐)
核心思路:
- 前端 / 调用方生成唯一请求 ID(如 UUID、雪花算法 ID),每次请求携带这个 ID;
- 后端接收请求后,先往「防重表」(仅含
request_id唯一键 + 业务状态)插入这条 ID; - 插入成功 → 执行核心业务;插入失败(唯一键冲突)→ 说明是重复请求,直接返回成功(或提示 “请求已处理”);
- 结合数据库事务,确保 “插入防重表 + 执行业务” 原子性。
CREATE TABLE request_duplicate (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
request_id VARCHAR(64) NOT NULL COMMENT '唯一请求ID',
business_type VARCHAR(32) NOT NULL COMMENT '业务类型(如创建订单、扣减库存)',
create_time DATETIME NOT NULL COMMENT '创建时间',
UNIQUE KEY uk_request_id (request_id) -- 核心:唯一索引保证幂等
) COMMENT '请求防重表';
@Service
public class OrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 核心业务方法:创建订单(保证幂等)
@Transactional(rollbackFor = Exception.class)
public String createOrder(String requestId, Long userId, Long productId) {
// 1. 先插入防重表(request_id 是唯一索引)
try {
int insertCount = jdbcTemplate.update(
"INSERT INTO request_duplicate (request_id, business_type, create_time) VALUES (?, ?, NOW())",
requestId, "CREATE_ORDER"
);
// 插入失败(唯一键冲突)→ 重复请求
if (insertCount == 0) {
return "订单已创建(重复请求)";
}
} catch (SQLIntegrityConstraintViolationException e) {
// 捕获唯一键冲突异常 → 重复请求
return "订单已创建(重复请求)";
}
// 2. 执行核心业务:创建订单(此处仅示例,实际需根据业务编写)
jdbcTemplate.update(
"INSERT INTO `order` (user_id, product_id, create_time) VALUES (?, ?, NOW())",
userId, productId
);
return "订单创建成功";
}
}
方案 2:基于数据库乐观锁(适合更新类操作)
核心思路:
- 针对 “更新数据” 场景(如扣减库存、修改订单状态),在数据表中加
version版本号字段(或status状态字段); - 更新时带上版本号 / 状态条件,只有满足条件才执行更新;
- 重复请求时,版本号 / 状态已变更,更新行数为 0 → 判定为重复请求。
@Service
public class StockService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional(rollbackFor = Exception.class)
public String deductStock(Long productId, Integer deductNum) {
// 1. 查询库存(带版本号)
Map<String, Object> stockMap = jdbcTemplate.queryForMap(
"SELECT stock, version FROM product_stock WHERE product_id = ?",
productId
);
Integer currentStock = (Integer) stockMap.get("stock");
Integer version = (Integer) stockMap.get("version");
// 2. 检查库存是否充足
if (currentStock < deductNum) {
return "库存不足";
}
// 3. 乐观锁更新:仅当版本号匹配时才扣减(避免重复扣减)
int updateCount = jdbcTemplate.update(
"UPDATE product_stock SET stock = stock - ?, version = version + 1 WHERE product_id = ? AND version = ?",
deductNum, productId, version
);
// 更新行数为0 → 重复请求/并发更新,版本号已变
if (updateCount == 0) {
return "库存扣减失败(重复请求/并发冲突)";
}
return "库存扣减成功";
}
}
方案 3:基于 Redis 分布式锁 / SETNX(高性能,适合高并发)
核心思路:
- 利用 Redis 的
SETNX命令(SET if Not Exists):只有当 key 不存在时才设置成功; - 以 “唯一请求 ID” 或 “业务唯一标识(如 userId+productId)” 作为 Redis key,设置过期时间(避免死锁);
- SETNX 成功 → 执行核心业务;失败 → 重复请求,直接返回。
代码示例:
@Service
public class OrderService {
@Autowired
private StringRedisTemplate redisTemplate;
@Transactional(rollbackFor = Exception.class)
public String createOrder(String requestId, Long userId, Long productId) {
// 1. Redis key:防重标识(request_id)
String redisKey = "order:duplicate:" + requestId;
// 2. SETNX + 过期时间(原子操作,避免死锁)
Boolean success = redisTemplate.opsForValue().setIfAbsent(redisKey, "1", 5, TimeUnit.MINUTES);
// 3. SETNX 失败 → 重复请求
if (Boolean.FALSE.equals(success)) {
return "订单已创建(重复请求)";
}
// 4. 执行核心业务(创建订单)
// ... 业务代码省略 ...
return "订单创建成功";
}
}
六.HTTPServlet及其衍生
6.1source code
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";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE = "jakarta.servlet.http.LocalStrings";
private static final ResourceBundle lStrings = ResourceBundle.getBundle("jakarta.servlet.http.LocalStrings");
private static final Set<String> SENSITIVE_HTTP_HEADERS = new HashSet();
public static final String LEGACY_DO_HEAD = "jakarta.servlet.http.legacyDoHead";
private final transient Object cachedAllowHeaderValueLock = new Object();
private volatile String cachedAllowHeaderValue = null;
private volatile boolean cachedUseLegacyDoHead;
public void init(ServletConfig config) throws ServletException {
super.init(config);
this.cachedUseLegacyDoHead = Boolean.parseBoolean(config.getInitParameter("jakarta.servlet.http.legacyDoHead"));
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_get_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_post_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_put_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_delete_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}
/*省略下面方法*/
}
6.2HTTPServletRequest
6.2.1code
package jakarta.servlet.http;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpUpgradeHandler;
import jakarta.servlet.http.Part;
import java.util.*;
import java.security.Principal;
import java.io.IOException;
public interface HttpServletRequest extends ServletRequest {
// ====================== 1. 认证相关 ======================
// 获取当前请求的认证方式(如BASIC/FORM/CLIENT_CERT/DIGEST),未认证返回null
String getAuthType();
// 获取客户端发送的所有Cookie,无Cookie返回null
Cookie[] getCookies();
// ====================== 2. 请求头相关 ======================
// 获取指定名称的日期类型请求头(返回毫秒数),无则返回-1(如If-Modified-Since)
long getDateHeader(String var1);
// 获取指定名称的请求头值,无则返回null(如User-Agent/Token)
String getHeader(String var1);
// 获取指定名称的所有请求头值(一个头可能有多个值),无则返回空枚举
Enumeration<String> getHeaders(String var1);
// 获取所有请求头的名称,无则返回空枚举
Enumeration<String> getHeaderNames();
// 获取指定名称的整数类型请求头值,无/非整数返回-1(如Content-Length)
int getIntHeader(String var1);
// ====================== 3. 请求映射相关(默认实现) ======================
// 获取HTTP请求映射信息(匹配值/路径模式/Servlet名/匹配类型),默认返回空实现
default HttpServletMapping getHttpServletMapping() {
return new HttpServletMapping() {
// 获取请求匹配的具体值(如/user/123中的123)
public String getMatchValue() { return ""; }
// 获取匹配的路径模式(如/user/*)
public String getPattern() { return ""; }
// 获取映射的Servlet名称
public String getServletName() { return ""; }
// 获取映射匹配类型(如精确匹配/路径匹配)
public MappingMatch getMappingMatch() { return null; }
};
}
// ====================== 4. 请求方法/路径相关 ======================
// 获取HTTP请求方法(GET/POST/PUT/DELETE等)
String getMethod();
// 获取请求路径中的额外路径信息(如/ServletName/pathInfo中的pathInfo),无则返回null
String getPathInfo();
// 获取PathInfo对应的磁盘真实路径,无则返回null
String getPathTranslated();
// 创建推送构建器(用于HTTP/2服务器推送),默认返回null
default PushBuilder newPushBuilder() { return null; }
// 获取当前应用的上下文路径(如/webapp),根应用返回空字符串
String getContextPath();
// 获取URL中的查询字符串(?name=张三&age=20),无则返回null
String getQueryString();
// ====================== 5. 用户身份相关 ======================
// 获取已认证的用户名,未登录返回null
String getRemoteUser();
// 判断当前登录用户是否属于指定角色(如ADMIN/USER)
boolean isUserInRole(String var1);
// 获取当前用户的身份凭证对象(包含用户名等信息),未登录返回null
Principal getUserPrincipal();
// ====================== 6. 会话(Session)相关 ======================
// 获取客户端请求中的SessionID,无则返回null
String getRequestedSessionId();
// 获取请求的URI(如/user/info)
String getRequestURI();
// 获取完整的请求URL(如http://localhost:8080/webapp/user/info)
StringBuffer getRequestURL();
// 获取Servlet的路径(如/ServletName)
String getServletPath();
// 获取Session:var1=true则不存在时创建,false则不存在返回null
HttpSession getSession(boolean var1);
// 获取Session(不存在则创建),等价于getSession(true)
HttpSession getSession();
// 更换当前Session的ID(防止Session固定攻击)
String changeSessionId();
// 判断请求中的SessionID是否有效
boolean isRequestedSessionIdValid();
// 判断SessionID是否来自Cookie
boolean isRequestedSessionIdFromCookie();
// 判断SessionID是否来自URL重写(如url?jsessionid=xxx)
boolean isRequestedSessionIdFromURL();
// ====================== 7. 手动认证相关 ======================
// 触发客户端认证(如弹出BASIC登录框),认证成功返回true
boolean authenticate(HttpServletResponse var1) throws IOException, ServletException;
// 手动登录用户(编程式认证,无需表单/HTTP认证)
void login(String var1, String var2) throws ServletException;
// 手动退出登录(销毁当前用户认证状态)
void logout() throws ServletException;
// ====================== 8. 文件上传相关 ======================
// 获取请求中的所有文件上传部件(Part),无则返回空集合
Collection<Part> getParts() throws IOException, ServletException;
// 获取指定名称的文件上传部件(如<input type="file" name="file1">)
Part getPart(String var1) throws IOException, ServletException;
// ====================== 9. 协议升级相关 ======================
// 将HTTP连接升级为其他协议(如WebSocket),返回升级处理器
<T extends HttpUpgradeHandler> T upgrade(Class<T> var1) throws IOException, ServletException;
// ====================== 10. 响应拖尾字段相关(HTTP/1.1) ======================
// 获取响应拖尾字段(Trailer),默认返回空Map
default Map<String, String> getTrailerFields() { return Collections.emptyMap(); }
// 判断拖尾字段是否已准备好,默认返回true
default boolean isTrailerFieldsReady() { return true; }
}
6.2.2成员属性意义
| 常量名 | 对应值 | 认证方式 | 核心含义 & 应用场景 |
|---|---|---|---|
BASIC_AUTH | "BASIC" | HTTP 基本认证 | 最简单的认证方式,客户端将用户名 + 密码用 Base64 编码后放在 Authorization 请求头中传输(明文本质,仅适合 HTTPS) |
FORM_AUTH | "FORM" | 表单认证 | 基于 HTML 表单的认证(如登录页),是 Web 应用最常用的方式(Servlet 容器 / SSO 框架的核心认证方式) |
CLIENT_CERT_AUTH | "CLIENT_CERT" | 客户端证书认证 | 基于 SSL/TLS 客户端证书的强认证,服务端验证客户端的数字证书(适合金融、政务等高安全场景) |
DIGEST_AUTH | "DIGEST" | HTTP 摘要认证 | 比 BASIC 安全的认证方式,客户端传输 “密码摘要” 而非明文(避免 Base64 解码泄露,已逐渐被 OAuth 替代) |
6.3HTTPServletResponse
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package jakarta.servlet.http;
import jakarta.servlet.ServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.function.Supplier;
public interface HttpServletResponse extends ServletResponse {
int SC_CONTINUE = 100;
int SC_SWITCHING_PROTOCOLS = 101;
int SC_OK = 200;
int SC_CREATED = 201;
int SC_ACCEPTED = 202;
int SC_NON_AUTHORITATIVE_INFORMATION = 203;
int SC_NO_CONTENT = 204;
int SC_RESET_CONTENT = 205;
int SC_PARTIAL_CONTENT = 206;
int SC_MULTIPLE_CHOICES = 300;
int SC_MOVED_PERMANENTLY = 301;
int SC_MOVED_TEMPORARILY = 302;
int SC_FOUND = 302;
int SC_SEE_OTHER = 303;
int SC_NOT_MODIFIED = 304;
int SC_USE_PROXY = 305;
int SC_TEMPORARY_REDIRECT = 307;
int SC_BAD_REQUEST = 400;
int SC_UNAUTHORIZED = 401;
int SC_PAYMENT_REQUIRED = 402;
int SC_FORBIDDEN = 403;
int SC_NOT_FOUND = 404;
int SC_METHOD_NOT_ALLOWED = 405;
int SC_NOT_ACCEPTABLE = 406;
int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
int SC_REQUEST_TIMEOUT = 408;
int SC_CONFLICT = 409;
int SC_GONE = 410;
int SC_LENGTH_REQUIRED = 411;
int SC_PRECONDITION_FAILED = 412;
int SC_REQUEST_ENTITY_TOO_LARGE = 413;
int SC_REQUEST_URI_TOO_LONG = 414;
int SC_UNSUPPORTED_MEDIA_TYPE = 415;
int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
int SC_EXPECTATION_FAILED = 417;
int SC_INTERNAL_SERVER_ERROR = 500;
int SC_NOT_IMPLEMENTED = 501;
int SC_BAD_GATEWAY = 502;
int SC_SERVICE_UNAVAILABLE = 503;
int SC_GATEWAY_TIMEOUT = 504;
int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
void addCookie(Cookie var1);
boolean containsHeader(String var1);
String encodeURL(String var1);
String encodeRedirectURL(String var1);
void sendError(int var1, String var2) throws IOException;
void sendError(int var1) throws IOException;
void sendRedirect(String var1) throws IOException;
void setDateHeader(String var1, long var2);
void addDateHeader(String var1, long var2);
void setHeader(String var1, String var2);
void addHeader(String var1, String var2);
void setIntHeader(String var1, int var2);
void addIntHeader(String var1, int var2);
void setStatus(int var1);
int getStatus();
String getHeader(String var1);
Collection<String> getHeaders(String var1);
Collection<String> getHeaderNames();
default void setTrailerFields(Supplier<Map<String, String>> supplier) {
}
default Supplier<Map<String, String>> getTrailerFields() {
return null;
}
}
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/qq_74776071/article/details/158541289



