TinyWebserver学习(9)-HTTP

一、相关知识

1、有限状态机:

有限状态机(Finite State Machine, FSM)是一种用于描述对象在其生命周期内可能经历的不同状态及其状态转换规则的模型。它广泛应用于游戏开发、网络协议、词法解析、UI逻辑控制等领域。以下是C++中有限状态机的简介:
有限状态机的核心概念

  1. 状态(State)
    对象可能处于的某种特定情况(如游戏中的角色“空闲”、“移动”、“攻击”)。
  2. 事件(Event)
    触发状态转换的外部或内部条件(如用户输入“跳跃”、定时器超时)。
  3. 转换(Transition)
    定义在特定事件发生时,从当前状态转移到目标状态的规则(如“空闲 → 移动”)。
  4. 动作(Action)
    状态转换时执行的操作(如播放动画、发送网络包)。

2、http报文

HTTP报文分为请求报文(浏览器端向服务器发送)和响应报文(服务器处理后返回给浏览器端)两种,每种报文必须按照特有格式生成,才能被浏览器端识别。
(1)请求报文:由请求行、请求头部、空行、请求数据四部分组成

  • 请求行:用来说明请求类型(方法)、要访问的资源以及使用的http的版本
  • 请求头部:用来说明服务器要使用的附加信息,由“名/值”对组成,每对一行,中间用冒号隔开
  • 空行:请求头后面的空行是必须的,即使第四行请求数据为空行,第三行也必须是空行
  • 请求数据:也叫主体,可以添加任意类型的数据

以下是通过抓包得到的http请求报文:

GET http://jsuacm.cn/ HTTP/1.1 //Get为请求方法,URL为请求资源,1.1为http版本
Host: jsuacm.cn
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3877.400 QQBrowser/10.8.4506.400
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9//”请求数据”(GET方式的请求一般不包含)

在这里插入图片描述
常用的头部信息汇总:

头部名称类型作用说明示例值
Host请求头指定请求的目标主机名和端口号(HTTP/1.1 必须包含)Host: www.example.com
User-Agent请求头标识客户端(浏览器、操作系统等)信息Mozilla/5.0 (Windows NT 10.0)...
Accept请求头指定客户端可接受的响应内容类型(MIME 类型)text/html,application/xhtml+xml
Accept-Language请求头指定客户端可接受的语言en-US,en;q=0.9,zh-CN;q=0.8
Accept-Encoding请求头指定客户端可接受的编码方式(如压缩格式)gzip, deflate
Authorization请求头提供身份验证凭证(如 Bearer Token、Basic Auth)Bearer <token>
Referer请求头标明当前请求的来源页面 URLhttps://www.google.com/
If-Match请求头条件请求头,用于验证资源 ETag 是否匹配"67ab43"
If-None-Match请求头条件请求头,验证资源 ETag 是否不匹配(用于缓存更新)"67ab43"
If-Modified-Since请求头条件请求头,验证资源是否在指定时间后被修改Wed, 21 Oct 2023 07:28:00 GMT
Cookie请求头客户端随请求发送的 Cookie 数据sessionid=abc123

GET/ POST的区别:
GET最常见的一种请求方式,当客户端要从服务器中读取文档时,当点击网页上的链接或者通过在浏览器的地址栏输入网址来浏览网页的,使用的都是GET方式。GET方法要求服务器将URL定位的资源放在响应报文的数据部分,回送给客户端。使用GET方法时,请求参数和对应的值附加在URL后面,利用一个问号(“?”)代表URL的结尾与请求参数的开始,传递参数长度受限制。
GET方式的请求一般不包含”请求数据”部分,请求数据以地址的形式表现在请求行。显然,这种方式不适合传送私密数据。另外,由于不同的浏览器对地址的字符限制也有所不同,一般最多只能识别1024个字符,所以如果需要传送大量数据的时候,也不适合使用GET方式。
和get一样很常见,对于上面提到的不适合使用GET方式的情况,可以考虑使用POST方式,因为使用POST方法可以允许客户端给服务器提供信息较多。POST方法将请求参数封装在HTTP请求数据中,以名称/值的形式出现,可以传输大量数据,这样POST方式对传送的数据大小没有限制,而且也不会显示在URL中
(简单来讲,就是GET一般用于我们点击网页其他链接时使用,POST一般就是我们从网页上下载资源的时候使用)

(2) 响应报文: 由状态行+消息报头+空行+响应正文四个部分组成

  • 状态行:由HTTP协议版本号,状态码,状态消息 三部分组成;
  • 消息报头:用来说明客户端要使用的一些附加信息;
  • 空行
  • 响应正文:主要就是服务端向客户端发送的数据,比如一个页面、照片、视频等

以下是抓取的一段响应报文

HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Wed, 20 Oct 2021 06:46:15 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 737265<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content=""><meta name="author" content=""><link rel="icon" href="../../favicon.ico"><title>吉首大学	</title>

在这里插入图片描述
HTTP状态码与请求方法

HTTP有5种类型的状态码,具体的:

  • 1xx:指示信息—表示请求已接收,继续处理。

  • 2xx:成功—表示请求正常处理完毕。
    200 OK:客户端请求被正常处理。
    206 Partial content:客户端进行了范围请求。

  • 3xx:重定向—要完成请求必须进行更进一步的操作。
    301 Moved Permanently:永久重定向,该资源已被永久移动到新位置,将来对该资源访问都要使用本响应返回的若干个URI之一。
    302 Found:临时重定向,请求的资源临时从不同的URI中获得。

  • 4xx:客户端错误—请求有语法错误,服务器无法处理请求。
    400 Bad Request:请求报文存在语法错误。
    403 Forbidden:请求被服务器拒绝。
    404 Not Found:请求不存在,服务器上找不到请求的资源。

  • 5xx:服务器端错误—服务器处理请求出错。
    500 Internal Server Error:服务器在执行请求时出现错误。

执行逻辑:

  1. 首先主线程接收到一个客户端发来的事件,如读/写等,并将该事件按照事件类型标识,并加入任务队列

eventLoop()

			......//处理客户连接上接收到的数据else if (events[i].events & EPOLLIN){dealwithread(sockfd);}else if (events[i].events & EPOLLOUT){dealwithwrite(sockfd);......}
void WebServer::dealwithread(int sockfd)
{util_timer *timer = users_timer[sockfd].timer;//reactor(反应堆),就是IO多路复用,收到事件后,根据事件类型分配给某个线程if (1 == m_actormodel){if (timer){adjust_timer(timer);}//若监测到读事件,将该事件放入请求队列m_pool->append(users + sockfd, 0); //users是一个数组指针,sockfd是索引,因此这个表示的就是当前处理的客户端的对象//stat:0表示read事件,1表示write事件while (true){if (1 == users[sockfd].improv){if (1 == users[sockfd].timer_flag){deal_timer(timer, sockfd);users[sockfd].timer_flag = 0;}users[sockfd].improv = 0;break;}}}
  1. 如果有事件添加进入任务队列,则会通知线程池,有空闲线程则会取出任务来进行执行,执行线程执行函数run()(内核是run函数,但是run不是静态函数,所以不能作为线程执行函数,而是在外层套了一个壳的worker()函数)
void threadpool<T>::run()
{while (true){m_queuestat.wait();//等待信号m_queuelocker.lock();if (m_workqueue.empty()){m_queuelocker.unlock();continue;}T *request = m_workqueue.front();m_workqueue.pop_front();m_queuelocker.unlock();if (!request)continue;if (1 == m_actor_model) //reactor{if (0 == request->m_state){if (request->read_once()){request->improv = 1;connectionRAII mysqlcon(&request->mysql, m_connPool);request->process();}else{request->improv = 1;request->timer_flag = 1;}}else{if (request->write()){request->improv = 1;}else{request->improv = 1;request->timer_flag = 1;}}}else{connectionRAII mysqlcon(&request->mysql, m_connPool);request->process();}}
}
  1. 然后根据事件的类型(request->state),来选择执行相应的函数,这里我们以读事件为例。首先,客户端发来请求响应,服务端需要先将客户端发来的请求响应的内容保存下来然后再进行解析,保存请求响应的函数即是read_once()函数
    该函数的逻辑也比较简单,主要就是将套接字发送的内容储存到m_read_buf这个缓存区中
//循环读取客户数据,直到无数据可读或对方关闭连接
//非阻塞ET工作模式下,需要一次性将数据读完
bool http_conn::read_once()
{if (m_read_idx >= READ_BUFFER_SIZE){return false;}int bytes_read = 0;//LT读取数据if (0 == m_TRIGMode){bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);m_read_idx += bytes_read;if (bytes_read <= 0){return false;}return true;}//ET读数据else{while (true){bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);if (bytes_read == -1){if (errno == EAGAIN || errno == EWOULDBLOCK)break;return false;}else if (bytes_read == 0){return false;}m_read_idx += bytes_read;}return true;}
}
  1. 我们得到缓冲区中的请求响应后,就需要对其进行解析,解析函数为process()函数,这个函数会先使用process_read()函数对请求响应进行解析,然后使用process_write()输出回应报文
void http_conn::process()
{HTTP_CODE read_ret = process_read(); // 请求报文处理,限定结果在枚举范围之内if (read_ret == NO_REQUEST) //如果请求不完整,需要继续接收请求数据{modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);return;}bool write_ret = process_write(read_ret);//相应报文处理if (!write_ret){close_conn();}modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode);
}
  1. process_read()函数是请求报文解析函数,是http的核心函数之一,我们可以结合下面的图来看
/* 该函数为请求报文处理函数,通过while循环来实现对主从状态机的封装,其中主状态机为process_read()函数,从状态机为parse_line()函数*/
http_conn::HTTP_CODE http_conn::process_read() 
{LINE_STATUS line_status = LINE_OK;HTTP_CODE ret = NO_REQUEST;char *text = 0;//m_checked_state 主状态机状态为CHECK_STATE_REQUESTLINE时,该条件涉及解析消息体//line_status 从状态机状态转移为LINE_OK时,该条件涉及解析请求行while ((m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK) || ((line_status = parse_line()) == LINE_OK)){text = get_line(); //得到改行的具体内容m_start_line = m_checked_idx;LOG_INFO("%s", text);switch (m_check_state){case CHECK_STATE_REQUESTLINE: //请求行,初始化的时候定义了{ret = parse_request_line(text);if (ret == BAD_REQUEST)return BAD_REQUEST;break;}case CHECK_STATE_HEADER: //头信息{ret = parse_headers(text);if (ret == BAD_REQUEST)return BAD_REQUEST;else if (ret == GET_REQUEST){return do_request();}break;}case CHECK_STATE_CONTENT: //消息体{ret = parse_content(text);if (ret == GET_REQUEST)return do_request();line_status = LINE_OPEN;break;}default:return INTERNAL_ERROR;}}return NO_REQUEST;
}

在这里插入图片描述
6. 其中,主状态机则为process_read()函数,从状态机为parse_line()函数,parse_line主要作用就是从读缓冲区中读取一行内容,并将每一行结尾的\r\n改为\0\0。

//从状态机,用于分析出一行内容
//返回值为行的读取状态,有LINE_OK,LINE_BAD,LINE_OPEN
http_conn::LINE_STATUS http_conn::parse_line()
{char temp;for (; m_checked_idx < m_read_idx; ++m_checked_idx){temp = m_read_buf[m_checked_idx];if (temp == '\r'){if ((m_checked_idx + 1) == m_read_idx)return LINE_OPEN;else if (m_read_buf[m_checked_idx + 1] == '\n'){m_read_buf[m_checked_idx++] = '\0';m_read_buf[m_checked_idx++] = '\0';return LINE_OK;}return LINE_BAD;}else if (temp == '\n'){if (m_checked_idx > 1 && m_read_buf[m_checked_idx - 1] == '\r'){m_read_buf[m_checked_idx - 1] = '\0';m_read_buf[m_checked_idx++] = '\0';return LINE_OK;}return LINE_BAD;}}return LINE_OPEN;
}
  1. 如果返回LINE_OK,表示改行已经读完,并且该行的地址m_check_idx也已经更新,接着通过get_line()这个函数就返回改行具体的内容了。
    然后进入switch循环,首先my_check_state的初始状态为CHECK_STATE_REQUESTLINE,即解析请求行,然后执行parse_request_line函数来解析,函数如下:
    主要作用就是解析出m_method、m_url这两个属性,然后将my_check_state的状态修改为CHECK_STATE_HEADER。
//解析http请求行,获得请求方法,目标url及http版本号
http_conn::HTTP_CODE http_conn::parse_request_line(char *text)
{m_url = strpbrk(text, " \t");//用于查找第一个出现指定字符串的位置,如果找到,则返回指向该字符的指针,否则返回NULLif (!m_url){return BAD_REQUEST;}*m_url++ = '\0';char *method = text;if (strcasecmp(method, "GET") == 0)m_method = GET;else if (strcasecmp(method, "POST") == 0){m_method = POST;cgi = 1; //是否启用POST}elsereturn BAD_REQUEST;m_url += strspn(m_url, " \t"); //从m_url开始,跳过空白字符,返回第一个非空白字符的指针,该作用是确保指针指向有效的字符m_version = strpbrk(m_url, " \t");if (!m_version)return BAD_REQUEST;*m_version++ = '\0';m_version += strspn(m_version, " \t");if (strcasecmp(m_version, "HTTP/1.1") != 0) //检查版本是否为1.1return BAD_REQUEST;if (strncasecmp(m_url, "http://", 7) == 0) //如果前缀包括http://,则去掉{m_url += 7;m_url = strchr(m_url, '/');}if (strncasecmp(m_url, "https://", 8) == 0) //同上{m_url += 8;m_url = strchr(m_url, '/');}if (!m_url || m_url[0] != '/')return BAD_REQUEST;//当url为/时,显示判断界面if (strlen(m_url) == 1)strcat(m_url, "judge.html");m_check_state = CHECK_STATE_HEADER;return NO_REQUEST;
}
  1. 接下来是解析头部信息HEADER,函数如下:
    其逻辑为如果检测到该行头部为“Connection:”、“keep-alive”等,则赋予相应的属性值,但是m_check_state不变;
    如果检测到“Content-length”,这代表接下来是客户端发送过来的内容了,这样的话就转变m_check_state的属性为CHECK_STATE_CONTENT,代表下一次循环就要解析内容了
//解析http请求的一个头部信息
http_conn::HTTP_CODE http_conn::parse_headers(char *text)
{if (text[0] == '\0'){if (m_content_length != 0){m_check_state = CHECK_STATE_CONTENT;return NO_REQUEST;}return GET_REQUEST;}else if (strncasecmp(text, "Connection:", 11) == 0){text += 11;text += strspn(text, " \t");if (strcasecmp(text, "keep-alive") == 0){m_linger = true;}}else if (strncasecmp(text, "Content-length:", 15) == 0){text += 15;text += strspn(text, " \t");m_content_length = atol(text);}else if (strncasecmp(text, "Host:", 5) == 0){text += 5;text += strspn(text, " \t");m_host = text;}else{LOG_INFO("oop!unknow header: %s", text);}return NO_REQUEST;
}
  1. 以下是解析内容体的函数,因为在该项目中,客户端主要传输的对象很简单,就只有输入的用户名和密码。
    如果,你希望在这个项目的基础上继续改进,一个主要的改进方向就是这个,你可以上传文件、图片等等。
    那么既然客户端有上次内容,那么服务器肯定需要对内容做一个回应或者处理,那么就引出了接下来的do_request() 函数
//判断http请求是否被完整读入
http_conn::HTTP_CODE http_conn::parse_content(char *text)
{if (m_read_idx >= (m_content_length + m_checked_idx)){text[m_content_length] = '\0';//POST请求中最后为输入的用户名和密码m_string = text;return GET_REQUEST;}return NO_REQUEST;
}
  1. do_request() 函数因为比较长,所以在这里就不直接粘贴出来了,大家对照着源码来学习吧。

  2. 有请求报文就会有响应报文,响应报文主要根据请求报文返回的状态来定义,在process()函数中,响应报文函数为process_write()函数
    该函数的作用主要返回几种状态码,如404、200等,这些状态码的含义之前也已经叙述了,大家可以翻到上面去看。

bool http_conn::process_write(HTTP_CODE ret)
{switch (ret){case INTERNAL_ERROR:{add_status_line(500, error_500_title);add_headers(strlen(error_500_form));if (!add_content(error_500_form))return false;break;}case BAD_REQUEST:{add_status_line(404, error_404_title);add_headers(strlen(error_404_form));if (!add_content(error_404_form))return false;break;}case FORBIDDEN_REQUEST:{add_status_line(403, error_403_title);add_headers(strlen(error_403_form));if (!add_content(error_403_form))return false;break;}case FILE_REQUEST:{add_status_line(200, ok_200_title);if (m_file_stat.st_size != 0){add_headers(m_file_stat.st_size);m_iv[0].iov_base = m_write_buf;m_iv[0].iov_len = m_write_idx;m_iv[1].iov_base = m_file_address;m_iv[1].iov_len = m_file_stat.st_size;m_iv_count = 2;bytes_to_send = m_write_idx + m_file_stat.st_size;return true;}else{const char *ok_string = "<html><body></body></html>";add_headers(strlen(ok_string));if (!add_content(ok_string))return false;}}default:return false;}m_iv[0].iov_base = m_write_buf;m_iv[0].iov_len = m_write_idx;m_iv_count = 1;bytes_to_send = m_write_idx;return true;
}

在这里插入图片描述

这些基本上就是http的整个运行的逻辑框架了,大家有什么不懂的,评论区见~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/913225.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/913225.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025最新软件测试面试八股文

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 Part1 1、你的测试职业发展是什么&#xff1f; 测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师…

React 强大的表单验证库formik之集成Yup、React Hook Form库

简介 Formik 是为 React 开发的开源表单库&#xff0c;提供状态管理、验证和提交处理功能&#xff0c;可简化复杂表单的开发。 核心优势 ‌- 状态管理 ‌&#xff1a;自动跟踪输入值、验证状态和提交进度&#xff0c;无需手动编写状态逻辑。 ‌ ‌- 验证功能 ‌&#xff1a;…

破解风电运维“百模大战”困局,机械版ChatGPT诞生?

面对风机87%的非计划停机&#xff0c;30多个专用模型为何束手无策&#xff1f;一套通用大模型如何实现轴承、齿轮、转子“一站式”健康管理&#xff1f;一、行业痛点&#xff1a;风机运维深陷“碎片化泥潭”1.1 87%停机故障由多部件引发齿轮断裂、轴承磨损、电机短路……风电故…

Spring Bean 控制销毁顺序的方法总结

控制Spring Bean销毁顺序的方法 在Spring框架中&#xff0c;有几种方法可以控制Bean的销毁顺序。以下是主要的几种方式及其示例代码&#xff1a; 1. 实现DisposableBean接口 实现DisposableBean接口并重写destroy()方法&#xff0c;Spring会按照依赖关系的相反顺序调用这些方…

Gemini CLI MCP 教程:设置、命令与实际使用

如果你想要为任何 MCP 服务器设置 Gemini CLI,这个快速教程将为你提供指导。 Gemini CLI 结合 MCP(模型上下文协议)服务器,改变了你通过自然语言命令进行编码的方式。 你可以要求 Gemini 分析你的代码库、管理文件、查询数据库,或通过对话提示与 API 交互。 MCP 服务器连…

LangChain 构建向量数据库和检索器

LangChain 构建向量数据库和检索器实战 随着大语言模型&#xff08;LLM&#xff09;在各类 AI 应用中不断普及&#xff0c;RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索增强生成&#xff09;逐渐成为提升回答准确率与上下文关联性的重要技术路径。 一、什…

基于 LangChain 实现通义千问 + Tavily 搜索 Agent 的简单实践

文章目录 一、相关背景1.1 LangChain 简介1.2 通义千问&#xff08;Tongyi Qianwen&#xff09;1.3 Tavily 实时搜索引擎1.4 架构总览 二、环境配置设置 API 密钥 三、 Tavily 搜索四、智能 Agent 示例&#xff1a;自动判断是否调用 Tavily4.1 初始化 Agent4.2 提问两个问题&am…

SegGPT: 在上下文中分割一切

摘要 我们提出了 SegGPT&#xff0c;一个在上下文中执行“分割一切”的通用模型。我们将各种分割任务统一为一个通用的上下文学习框架&#xff0c;通过将不同类型的分割数据转换为相同的图像格式来兼容这些差异。SegGPT 的训练被建模为一个带有随机颜色映射的上下文着色问题。…

【网络】Linux 内核优化实战 - net.core.busy_read

目录 核心功能工作原理与优势配置方式1. 临时配置&#xff08;重启失效&#xff09;2. 永久配置&#xff08;重启生效&#xff09; 与 net.core.busy_poll 的协同作用适用场景与注意事项适用场景&#xff1a;注意事项&#xff1a; 总结 net.core.busy_read 是 Linux 内核中与网…

alpine安装及配置nodejs开发测试环境

在Alpine Linux上安装和使用Node.js&#xff0c;打造开发和测试的环境。 apk仓库打开社区的源。 先在命令行中使用命令apk search nodejs npm yarn对仓库源进行搜索&#xff0c;&#xff0c;看看nodejs、yarn、npm的版本情况。 localhost:~# apk search nodejs npm yarn nod…

Apache Commons Pool中的GenericObjectPool详解

GenericObjectPool 是 Apache Commons Pool 库中的核心类&#xff0c;用于实现对象的池化管理&#xff0c;适用于数据库连接、HTTP 客户端、线程等昂贵资源的复用。以下从核心概念、工作原理、参数配置、使用场景及最佳实践等方面详细解析&#xff1a; ⚙️ 一、核心概念与组成…

攻防世界CTF题目解析系列————(1)

题目来源:攻防世界wife_wife 打开题目之后&#xff0c;发现登录界面&#xff0c;然后尝试弱口令&#xff0c;sql二次注入&#xff0c;xss发现都没有&#xff0c;然后看见下面go register&#xff08;去注册&#xff09;按钮 成功注册&#xff08;username和password随便搞&…

楚存科技SD NAND贴片式T卡—高性能存储解决方案、赋能AI智能硬件

楚存科技SD NAND贴片式T卡—高性能存储解决方案、赋能AI智能硬件应用 在 AIoT 技术重构产业生态的时代浪潮中&#xff0c;智能硬件正从单一功能终端向数据枢纽演进 —— 智能家居设备日均产生 TB 级交互数据&#xff0c;工业物联网传感器需实时存储生产参数&#xff0c;车载智…

Python[数据结构及算法 --- 查找]

一.顺序查找&#xff08;无序表&#xff09;&#xff1a; def sequentialSearch(alist, item):pos 0found Falsewhile pos < len(alist) and not found:if alist[pos] item:found Trueelse:pos pos 1return foundtestlist [1, 2, 32, 8, 17, 19, 42, 13, 0] print(s…

Seata Saga模式实战:Java微服务中的分布式事务管理

在分布式系统中&#xff0c;Saga模式是一种用于管理跨多个服务的事务的柔性事务解决方案。它通过将长事务拆分为多个本地事务&#xff08;每个事务对应一个服务的操作&#xff09;&#xff0c;并通过补偿机制保证最终一致性。以下是Java中Saga模式的详细介绍&#xff0c;包括实…

若依学习笔记1-validated

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 保证 Spring AOP 相关的依赖包 --><dependency><groupId>org.springframework.boot<…

Excel 如何处理更复杂的嵌套逻辑判断?

处理复杂的嵌套逻辑判断&#xff0c;是Excel进阶路上必然会遇到的一道坎。当简单的IF函数“套娃”变得冗长、难以阅读和维护时&#xff0c;我们就需要更高级、更清晰的工具。 这里介绍三种从基础到高级的处理方法&#xff1a; 传统的 IF 函数嵌套 (经典&#xff0c;但容易混乱)…

使用Claude和MCP增强Selenium

1.配置MCP服务器打开Claude Desktop—>Settings—>Developer—>Edit Config{"mcpServers": {"selenium": {"command": "npx","args": ["-y", "angiejones/mcp-selenium"]}} }配置完成后重启Cl…

数据仓库锚点建模方法的前世今生

数据仓库锚点建模方法&#xff08;Anchor Modeling&#xff09;作为一种面向复杂数据环境的创新方法论&#xff0c;其发展历程与技术演进深刻反映了数据管理从结构化到动态化的转型需求。以下从起源、发展、核心思想、技术演进及未来趋势五个维度&#xff0c;系统梳理锚点建模的…

<三>Sping-AI alibaba 文生图

环境和配置请看&#xff1c;二&#xff1e;Sping-AI alibaba 入门-记忆聊天及持久化 源代码&#xff1a;https://github.com/springaialibaba/spring-ai-alibaba-examples/blob/main/spring-ai-alibaba-image-example/dashscope-image/src/main/java/com/alibaba/cloud/ai/exam…