关注

HTTP协议

一.四种常用的请求

特征GETPOSTPUTDELETE
核心用途从服务器获取数据(查)向服务器提交数据(增 / 改)向服务器更新数据(改)从服务器删除数据(删)
参数位置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 + 防重表(最通用,推荐)

核心思路

  1. 前端 / 调用方生成唯一请求 ID(如 UUID、雪花算法 ID),每次请求携带这个 ID;
  2. 后端接收请求后,先往「防重表」(仅含 request_id 唯一键 + 业务状态)插入这条 ID;
  3. 插入成功 → 执行核心业务;插入失败(唯一键冲突)→ 说明是重复请求,直接返回成功(或提示 “请求已处理”);
  4. 结合数据库事务,确保 “插入防重表 + 执行业务” 原子性。
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:基于数据库乐观锁(适合更新类操作)

核心思路

  1. 针对 “更新数据” 场景(如扣减库存、修改订单状态),在数据表中加 version 版本号字段(或 status 状态字段);
  2. 更新时带上版本号 / 状态条件,只有满足条件才执行更新;
  3. 重复请求时,版本号 / 状态已变更,更新行数为 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(高性能,适合高并发)

核心思路

  1. 利用 Redis 的 SETNX 命令(SET if Not Exists):只有当 key 不存在时才设置成功;
  2. 以 “唯一请求 ID” 或 “业务唯一标识(如 userId+productId)” 作为 Redis key,设置过期时间(避免死锁);
  3. 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

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--