20.添加HTTP模块

news2024/11/21 2:39:46

添加一个简单的静态HTTP。

这里默认读者是熟悉http协议的。

来看看http请求Request的例子

客户端发送一个HTTP请求到服务器的请求消息,其包括:请求行、请求头部、空行、请求数据。

HTTP之响应消息Response 

服务器接收并处理客户端发过来的请求后会返回一个HTTP的响应消息,其包括:状态行、消息报头、空行和响应正文。

 前面所说的就是http的请求和响应答复。那我们可以封装出两个类。

HttpRequest:http请求类封装

HttpResponse:http响应类封装

注意:这里会使用到我们之前写的Buffer类。因为服务器是把读到的数据存储在Buffer中的,所以大家要熟悉Buffer类的一些用法

1、HttpRequest 类

该类的主要作用是客户端发送请求,服务端收到的数据存放于Buffer中,之后解析成HttpRequest请求对象,调用成员函数设置请求头、请求体等。

首先会有请求方式method_,http版本version_,请求头headers_(用map管理)。请求的路径path_(即是url),还有请求体query_

请求体有可能是在url中的"?"后面,也可能是在请求头后面的。

class HttpRequest
{
public:
	enum class Method{
		kInvalid, kGet, kPost, kHead, kPut, kDelete
	};
	enum class Version{
		kUnknown, kHttp10, kHttp11
	};

	HttpRequest()
		:method_(Method::kInvalid),
		version_(Version::kUnknown)
	{
	}

	void setVersion(Version v) { version_ = v; }
	Version getVersion()const { return version_; }

	bool setMethod(const char* start, const char* end)
	{
		string m(start, end);
		if (m == "GET") {
			method_ = Method::kGet;
		}
		else if (m == "POST") {
			method_ = Method::kPost;
		}
        //省略"HEAD","DELETE"等等方式......
		return method_ != Method::kInvalid;
	}
	Method getMothod()const { return method_; }

	const char* methodString()const {
		const char* result = "UNKNOWN";
		switch (method_) {
		case Method::kGet:
			result = "GET";
			break;
		case Method::kPost:
			result = "POST";
			break;
         //省略"HEAD","DELETE"等等方式......
		}
		return result;
	}

	void setPath(const char* start, const char* end) {
		path_.assign(start, end);
	}
	const string& path()const { return path_; }

	void setQuery(const char* start, const char* end) {
		query_.assign(start, end);
	}
	const string& query()const { return query_; }

	void addHeader(const char* start, const char* colon, const char* end)
	{
		//isspace(int c)函数判断字符c是否为空白符
		//说明:当c为空白符时,返回非零值,否则返回零。(空白符指空格、水平制表、垂直制表、换页、回车和换行符。
		
		// 要求冒号前无空格
		string field(start, colon);
		++colon;
		while (colon < end && isspace(*colon))// 过滤冒号后的空格
			++colon;

		string value(colon, end);
		while (!value.empty() && isspace(value[value.size() - 1]))//过滤value中的空格
			value.resize(value.size() - 1);

		headers_[field] = value;
	}

	string getHeader(const string& field)const
	{
		string result;
		auto it = headers_.find(field);
		if (it != headers_.end()) {
			return it->second;
		}
		return result;
	}

	const std::unordered_map<string, string>& headers()const { return headers_; }

private:
	Method method_;
	Version version_;
	string path_;	//请求路径
	string query_;	//请求体
	
	std::unordered_map<string, string> headers_;
};

注意:添加请求头时,函数addHeader需要删除键值对的字符串左侧和右侧的空字符,保证解析正常。因为解析请求头时,对一行字符串用冒号“:”进行分割解析。

2、HttpResponse 类

服务器端得到的客户的请求信息后,再创建一个HttpResponse响应对象,也是会调用成员函数设置响应头部、响应体,并格式化到Buffer中,回复给客户端。

按照上面的响应例子,那应该有响应头headers_,响应的状态码statusCode_,状态码的文字描述statusMessage_,响应体body_等等。

成员函数就是一些设置状态码,设置响应头等操作。

class HttpResponse
{
public:
	enum class HttpStatusCode
	{
        kUnknown,
        k200Ok = 200,
        k301MovedPermanently = 301,
        k400BadRequest = 400,
        k404NotFound = 404,
	};

    explicit HttpResponse(bool close)
        :statusCode_(HttpStatusCode::kUnknown),
        closeConnection_(close)
    {
    }

    void setStatusCode(HttpStatusCode code) { statusCode_ = code; }
    void setstatusMessage(const string& message) { statusMessage_ = message; }
    void setCloseConnection(bool on) { closeConnection_ = on; }
    bool closeConnection()const { return closeConnection_; }

    void setContentType(const string& contentType) { addHeader("Content-Type", contentType); }

    void addHeader(const string& key, const string& value) {
        headers_[key] = value;
    }

    void setBody(const string& body) { body_ = body; }

    void appendToBuffer(Buffer* output)const;


private:
    std::unordered_map<string, string> headers_;
    HttpStatusCode  statusCode_;    //状态码
    string statusMessage_;    //响应行中的状态码文字描述
    bool closeConnection_;    //是否关闭连接
    string body_;        //响应体
};

这里特别值得一说的是如何把响应消息格式化的操作格式化appendToBuffer(Buffer* output)

该函数默认使用HTTP1.1版本,按照HTTP协议对HttpResponse对象进行格式化输出到Buffer中。

按照要求添加响应行,响应头,空行,响应体。

void HttpResponse::appendToBuffer(Buffer* output) const
{
	//响应行
	string buf = "HTTP/1.1 " + std::to_string(static_cast<int>(statusCode_));
	output->append(buf);
	output->append(statusMessage_);
	output->append("\r\n");

	//响应头部
	if (closeConnection_) {
		output->append("Connection: close\r\n");
	}
	else {
		output->append("Connection: Keep-Alive\r\n");
		buf = "Content-Length:" + std::to_string(body_.size()) + "\r\n";
		output->append(buf);
	}

	for (const auto& header : headers_) {
		buf = header.first + ": " + header.second + "\r\n";
		output->append(buf);
	}

	output->append("\r\n");	//空行
	output->append(body_);	//响应体
}

3、HttpContext 类

服务端接收客户请求,存在Buffer中,那怎么从Buffer中解析得到我们想要的信息呢这时,需要一个解析类HttpContext,解析后数据封装到回复HttpRequest中。

其成员有处理的状态state_,响应request_。

class HttpContext
{
public:
	enum class HttpRequestPaseState{
		kExpectRequestLine,	//请求行
		kExpectHeaders,    // 请求头
		kExpectBody,        // 请求体
		kGotAll,            //表示都处理完全
	};

	HttpContext()
		:state_(HttpRequestPaseState::kExpectRequestLine)//默认从请求行开始解析
	{
	}

	bool parseRequest(Buffer* buf);// 解析请求Buffer

	bool gotAll()const { return state_ == HttpRequestPaseState::kGotAll; }

	void reset()// 为了复用HttpContext
	{
		state_ = HttpRequestPaseState::kExpectRequestLine;
		HttpRequest dumy;
		request_.swap(dumy);
	}

	const HttpRequest& request() const{ return request_; }

	HttpRequest& request(){ return request_; }

private:
	bool processRequestLine(const char* begin, const char* end);

	HttpRequestPaseState state_;	//需要处理的状态,状态机
	HttpRequest request_;
};

一个正常的请求,一般至少是有请求行的,默认解析状态为kExpectRequestLine。

这里就主要关注是如何解析Buffer的。

3.1、请求解析 parseRequest(Buffer* buf)

这里为了方便找到buf中的"\r\n",添加了Buffer::findCRLF()函数。

const char Buffer::kCRLF[] = "\r\n";

	//为了方便解析http "\r\n"位置
const char* findCRLF()const {
	const char* crlf = std::search(peek(), beginWirte(), kCRLF, kCRLF + 2);
	return crlf == beginWirte() ? nullptr : crlf;
}

传入需要解析的Buffer对象,根据期望解析的部分(即是状态state_)进行处理。

处理就三种情况:请求行,请求头,请求体。具体的流程可以看代码

bool HttpContext::parseRequest(Buffer* buf)
{
	bool ok = true;
	bool hasMore = true;
	while (hasMore) {
		if (state_ == HttpRequestPaseState::kExpectRequestLine) {	//解析请求行
			//查找出buf中第一次出现"\r\n"位置
			const char* crlf = buf->findCRLF();
			if (crlf) {
				//若是找到"\r\n",说明至少有一行数据,可以进行解析
				//buf->peek()为数据开始部分
				ok = processRequestLine(buf->peek(), crlf);
				if (ok) {//解析成功
					buf->retrieveUntil(crlf + 2);//buf->peek()向后移动2字节,到下一行
					state_ = HttpRequestPaseState::kExpectHeaders;
				}
				else {
					hasMore = false;
				}
			}
			else {
				hasMore = false;
			}
		}
		else if (state_ == HttpRequestPaseState::kExpectHeaders) {
			const char* crlf = buf->findCRLF();	//找到"\r\n"位置
			if (crlf) {
				const char* colon = std::find(buf->peek(), crlf, ':');//定位分隔符
				if (colon != crlf) {
					request_.addHeader(buf->peek(), colon, crlf);	//添加键值对
				}
				else {
					/*state_ = HttpRequestPaseState::kGotAll;
					hasMore = false;*/

					state_ = HttpRequestPaseState::kExpectBody;//这样就可以解析body
				}
				buf->retrieveUntil(crlf + 2);	//后移动2字节
			}
			else {
				hasMore = false;
			}
		}
		else if (state_ == HttpRequestPaseState::kExpectBody) {//解析请求体
			if (buf->readableBytes()) {//表明还有数据,那就是请求体
				request_.setQuery(buf->peek(), buf->beginWirte());
			}
			state_ = HttpRequestPaseState::kGotAll;
			hasMore = false;
		}
	}
	return ok;
}

3.1、请求行的解析 processRequestLine()

请求行有固定格式Method URL Version \r\n,URL中可能带有请求参数。根据空格符进行分割成三段字符。URL可能带有请求参数,使用"?”分割解析。

bool HttpContext::processRequestLine(const char* begin, const char* end)
{
	bool succeed = true;

	const char* start = begin;
	const char* space = std::find(start, end, ' ');
	//第一个空格前的字符串是请求方法 例如:post
	if (space != end && request_.setMethod(start, space)) {
		start = space + 1;
		space = std::find(start, end, ' ');//寻找第二个空格 url
		if (space != end) {
			const char* question = std::find(start, space, '?');
			if (question != space) {// 有"?",分割成path和请求参数
				request_.setPath(start, question);
				request_.setQuery(question, space);
			}
			else {
				request_.setPath(start, space);//只有path
			}
	
			//最后一部分,解析http协议版本
			string version(space + 1, end);
			if (version == "HTTP/1.0")
				request_.setVersion(HttpRequest::Version::kHttp10);
			else if (version == "HTTP/1.1")
				request_.setVersion(HttpRequest::Version::kHttp11);
			else
				succeed = false;
		}
	}
	return succeed;
}

这样解析就完成了。

4、HttpServer类

为了可以方便使用,封装个HttpServer类。

该类内部会有Server类型成员,并提供了一个回调函数的接口,当服务器收到http请求时,调用客户端的处理函数进行处理。

HttpServer支持多线程,也可以使用单线程。

class HttpServer
{
public:
	using HttpCallback = std::function<void(const HttpRequest&, HttpResponse*)>;

	HttpServer(EventLoop* loop, const InetAddr& listenAddr);

	void setHttpCallback(const HttpCallback& cb) { httpCallback_ = cb; }

	void start(int numThreads);

private:
	void onConnetion(const ConnectionPtr& conn);	//连接到来的回调函数
	void onMessage(const ConnectionPtr& conn, Buffer* buf);	//消息到来的回调函数
	void onRequest(const ConnectionPtr& conn, const HttpRequest&);

	Server server_;
	HttpCallback httpCallback_;

};

函数setHttpCallback就是设置用户的业务处理回调函数的。

4.1HttpServer构造函数

//默认的回调函数
void defaultHttpCallback(const HttpRequest& req, HttpResponse* resp)
{
	resp->setStatusCode(HttpResponse::HttpStatusCode::k404NotFound);
	resp->setstatusMessage("Not Found");
	resp->setCloseConnection(true);
}
//构造函数
HttpServer::HttpServer(EventLoop* loop, const InetAddr& listenAddr)
	:server_(listenAddr,loop)
	, httpCallback_(defaultHttpCallback)
{
    //新连接到来回调该函数
	server_.setConnectionCallback([this](const ConnectionPtr& conn) {onConnetion(conn); });
    //消息到来回调该函数	
    server_.setMessageCallback([this](const ConnectionPtr& conn, Buffer* buf) {onMessage(conn, buf); });
}

这里就是初始化Server,并将HttpServer的回调函数传给Server。主要有两个函数。

前面的HttpResponse类和HttpRequest类已经在HttpServer使用了,但是解析类HttpContext还没有使用。

很容易想到是在回调函数中使用。在有消息到来的时候,就会进行解析数据,这时就会使用到HttpContext。可以在每次调用函数onMessage中创建HttpContext对象。这在短连接中使用是合适的。但是在长连接的情况下,这样可能效率不高

那么就可以在有新连接到来的时刻,就设置好HttpContext。

那就说到onConnetion函数

4.2 连接到来的回调函数onConnetion

//这里绑定一个HttpContext主要是为了长连接中仅分配一次对象,提高效率。
void HttpServer::onConnetion(const ConnectionPtr& conn)
{
	if (conn->connected()) {
		//conn->setContext(std::make_shared<HttpContext>()); //c++11的std::shared_ptr<void>
        conn->setContext(HttpContext());    //c++17的std::any
	}
}

该函数为一个新的Connection绑定一个HttpContext对象,绑定之后,HttpContext就相当于Connection的成员,TcpConection在MessageCallback中就可以随意的使用该HttpContext对象了。
这里绑定一个HttpContext主要是为了长连接中仅分配一次对象,提高效率

这里绑定使用的是c++17的std::any。std::any表示可以接受任意类型的变量。

来看看Conntection类中需要添加的变量

#include<any>
class Connection:public std::enable_shared_from_this<Connection>
{
public:
    //省略之前的变量和函数
	//void setContext(std::shared_ptr<void> context) { context_ = context; }
	//std::shared_ptr<void> getConntext()const { return context_; }

	void setContext(const std::any& context) { context_ = context; }
	std::any* getMutableContext() { return &context_; }
private:
	//std::shared_ptr<void> context_;	//c++11做法
	std::any context_;	//用来解析http或者websocket或者其他协议的
};

首先我们要明确为什么要的是接收任意类型的变量这总做法,为什么不是直接就是用HttpContext类替代std::any。

因为我们后续可能还需要解析其他协议的,例如websockte协议(下一节会讲解)。要是直接写HttpContext的话,那要解析websocket协议的时候,Connection类中还需要添加websocketContext类成员变量,这就很麻烦的。所以用std::any来就可以绑定所有的解析类。

那又有疑惑,为什么不直接用void*?简单点说是,它类型不安全,还需要用户手动去delete。

std::shared_ptr和void*一样不能解决类型安全的问题。详细的了解可以查看该文章https://www.cnblogs.com/gnivor/p/12793239.html

那说完std::any和回调函数onConnetion,那就到函数onMessage。

4.3 新消息到来的回调函数onMessage

void HttpServer::onMessage(const ConnectionPtr& conn, Buffer* buf)
{
	//HttpContext* context = reinterpret_cast<HttpContext*>(conn->getConntext().get());	//c++11做法
	auto context = std::any_cast<HttpContext>(conn->getMutableContext());	//c++117
	if (!context) {
		LOG_ERROR<<"context is bad\n";
		return;
	}

	if (!context->parseRequest(buf)) {
		conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");
		conn->shutdown();
	}

	if (context->gotAll()) {
		onRequest(conn, context->request());
		context->reset();//一旦请求处理完毕,重置context,因为HttpContext和Connection绑定了,我们需要解绑重复使用
	}
}

当Connection中所拥有的连接有新消息到来时,会调用它的messageCallback_函数,其实就是调用HttpServer的onMessage()函数。而之前在函数onConnection()中把HttpContext利用std::any绑定给了Connection,那在该函数中就可以对Connection使用HttpContext类来解析数据包了。

onMessage()函数首先调用HttpContext的parserRequset()函数解析请求,判断请求是否合法,进而选择关闭连接,或者处理请求(函数onRequest)。

4.4处理请求的函数onRequest

void HttpServer::onRequest(const ConnectionPtr& conn, const HttpRequest& req)
{
	const std::string& connetion = req.getHeader("Connection");
	bool close = connetion == "close" || (req.getVersion() == HttpRequest::Version::kHttp10 && connetion != "Keep-Alive");

	HttpResponse response(close);
	//执行用户注册的回调函数
	httpCallback_(req, &response);

	Buffer buf;
	response.appendToBuffer(&buf);
	conn->send(&buf);//发送数据
	if (response.closeConnection()) {
		conn->shutdown();
	}
}

先判断是长连接还是短连接。接着使用close构造一个HttpResponse对象。之后很重要的是执行用户注册的回调函数,这个就是用户的业务函数。

5.HtttpServer的用法

#include"src/Server.h"
//省略一些其他头文件

//用户的业务处理的函数
void onRequest(const HttpRequest& req, HttpResponse* resp)
{
	if (req.path() == "/") {// 根目录请求
		resp->setStatusCode(HttpResponse::HttpStatusCode::k200Ok);
		resp->setstatusMessage("OK");
		resp->setContentType("text/html");
		resp->addHeader("Server", "li");
		resp->setBody("<html><head><title>This is title</title></head>"
			"<body><h1>Hello</h1>Now is hello" 
			"</body></html>");
	}
	else if (req.path() == "/hello") {
		resp->setStatusCode(HttpResponse::HttpStatusCode::k200Ok);
		resp->setstatusMessage("OK");
		resp->setContentType("text/plain");
		resp->setBody("hello, world!\n");
	}
	else {
		resp->setStatusCode(HttpResponse::HttpStatusCode::k404NotFound);
		resp->setstatusMessage("Not Found");
		resp->setCloseConnection(true);
	}
}

int main(int argc, char* argv[])
{
	EventLoop loop;
	HttpServer server(&loop, InetAddr(9999));
	server.setHttpCallback(onRequest);        //比普通的server添加了这行
	server.start(0);    //副io线程数量为0,单线程运行
	loop.loop();

	return 0;
}

主要就是用户自写的一个业务处理函数,之后调用HttpServer类的函数setHttpCallback来进行注册即可。

这里例子是创建了端口是9999的HTTPServer,提供访问的是/,/hello。

在浏览器输入 http://localhost:9999或者http://localhost:9999/hello即可访问成功。(localhost可以改成是自己linux的ip)

HTTP调用的流程图

HTTP服务器基本就是结束了,这里的是简单静态web服务器,我们没有解析客户发送过来的body。需要其他功能可以在这基础上进行完善或添加,比如支持fcgi。

完整源代码:https://github.com/liwook/CPPServer/tree/main/code/server_v20

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

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

相关文章

尚硅谷SpringMVC (9-13)

九、HttpMessageConverter HttpMessageConverter &#xff0c;报文信息转换器&#xff0c;将请求报文转换为 Java 对象&#xff0c;或将 Java 对象转换为响应报文 HttpMessageConverter提供了两个注解和两个类型&#xff1a; RequestBody &#xff0c; ResponseBody &#xff…

如何创建美观的邮件模板并通过qq邮箱的SMTP服务向用户发送

最近在写注册功能的自动发送邮箱告知验证码的功能&#xff0c;无奈根本没有学过前端&#xff0c;只有写Qt的qss基础&#xff0c;只好借助网页设计自己想要的邮箱格式&#xff0c;最终效果如下: 也推销一下自己的项目ShaderLab&#xff0c;可运行ShaderToy上的大部分着色器代码&…

Weblogic漏洞(二)之 Weblogic漏洞环境安装

Weblogic漏洞环境安装 这里我们使用Kali虚拟机安装docker并搭建vulhub靶场来进行Weblogic漏洞环境的安装 安装 docker #更新软件 apt-get update #安装CA证书 apt-get install -y apt-transport-https ca-certificates #安装docker apt install docker.io #查看docker是否安…

【商业案例应用】C端产品设计流程——团购产品案例

文章目录 1、项目背景介绍2、产品前期分析3、产品规划4、总结 1、项目背景介绍 2、产品前期分析 3、产品规划 4、总结

SSH连接安卓手机Termux —— Android手机

文章目录 前言1.安装ssh2.安装cpolar内网穿透3.远程ssh连接配置4.公网远程连接5.固定远程连接地址 前言 使用安卓机跑东西的时候&#xff0c;屏幕太小&#xff0c;有时候操作不习惯。不过我们可以开启ssh&#xff0c;使用电脑PC端SSH远程连接手机termux。 本次教程主要实现在…

C语言入门 Day_13 二维数组

目录 前言&#xff1a; 1.字符串 2.创建二维数组 3.使用二维数组 4.易错点 5.思维导图 前言&#xff1a; 我们学习了字符类型char&#xff0c;我们可以用char来表示一个大写或者小写的字母&#xff0c;但真实应用中我们往往使用的是多个字符组成的一个单词或者句子。 …

Linux gdb单步调试的原理

文章目录 一、demo演示二、原理分析参考资料 一、demo演示 .section .data message:.string "Hello, World!\n" len . - message.section .text .globl _start _start:# 调用 write() 函数输出 "Hello, World!"mov $1, %rax # 系统调用号为 1…

海域可视化监管:浅析海域动态远程视频智能监管平台的构建方案

一、方案背景 随着科技的不断进步&#xff0c;智慧海域管理平台已经成为海洋领域监管的一种重要工具。相比传统的视频监控方式&#xff0c;智慧海域管理平台通过建设近岸海域视频监控网、海洋环境监测网和海上目标探测网络等&#xff0c;可实现海洋管理的数字化转型。 传统的…

『好书推荐』|《Effective软件测试》

作者简介 《Effective软件测试》 是一本由清华大学出版社出版的图书&#xff0c;作者是[荷]毛里西奥阿尼什&#xff08;Maurcio Aniche&#xff09;&#xff0c;译者是朱少民、李洁、张元。是2023年6月新推出的一本书籍。 Maurcio Aniche博士是荷兰代尔夫特理工大学软件工程系的…

页面页脚部分CSS分享

先看效果&#xff1a; CSS部分&#xff1a;&#xff08;查看更多&#xff09; <style>body {display: grid;grid-template-rows: 1fr 10rem auto;grid-template-areas: "main" "." "footer";overflow-x: hidden;background: #F5F7FA;min…

使用LightPicture开源搭建私人图床:详细教程及远程访问配置方法

文章目录 1.前言2. Lightpicture网站搭建2.1. Lightpicture下载和安装2.2. Lightpicture网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 现在的手机越来越先进&#xff0c;功能也越来越多&#xff0c;而手机…

urllib库

目录 1、简介 2、请求模块 3、解析模块 1、简介 urllib是python内置的标准库&#xff0c;无需下载&#xff0c;导入即可使用。 2、请求模块 urllib包里有一个request模块 from urllib import request# 1.request模块# 1.1发送网络请求 # urlopen() : 打开url地址 resp re…

进程的概念、组成、特征

1.概念 进程是操作系统进行资源分配的最小的单位。 2.组成 进程由PCB、程序段、数据段组成。PCB是操作系统需要的&#xff0c;而程序段和数据段是用户所需要的。 PCB是一种数据结构&#xff0c;操作系统所需的进程资源都存储在PCB中&#xff0c;PCB也是进程存在的唯一标识。…

【快应用】后台运行的快应用如何自动前台打开

【关键词】 Onhide、router、后台 【问题背景】 快应用退到后台运行后&#xff0c;隔几秒钟后&#xff0c;会自动打开跳转到某个页面&#xff0c;这种情形应该如何去定位处理&#xff1f; 【问题分析】 退到后台运行&#xff0c;再自动拉起看似很诡异&#xff0c;以为是快应…

Autofac使用(3)---AOP支持

1、Nuget引入程序集 2、扩展IInterceptor public class CusotmInterceptor : IInterceptor{/// <summary>/// 切入者逻辑/// /// 使用了Intercept 方法把 要调用的Call方法给包裹起来了/// </summary>/// <param name"invocation"></param>p…

架构设计基础设施保障IaaS弹性伸缩和无服务器计算

目录 1 高可用弹性伸缩实践2 无服务器计算&#xff08;FaaS&#xff09; 1 高可用弹性伸缩实践 背景 弹性伸缩是云服务架构的重要优势&#xff0c;能够很好的解决高并发场景下的性能瓶颈&#xff0c; 同时节省运营成本。 在 IaaS 端&#xff0c;能够弹性伸缩的最实用的产品形…

AIoT+5G改变智慧城市:揭秘智慧公厕的奇妙魅力

AIoT5G的新型智慧城市应用带来了智慧公厕的全新体验。通过智能监测、高速网络、智能调控、智慧管理等技术应用&#xff0c;公厕的舒适性、便捷性和智慧化程度得到了极大提升。可以看到的是&#xff0c;智慧公厕正逐渐激活智慧城市的生活场景&#xff0c;为城市居民带来更好的生…

无需租用云服务器:使用Linux本地搭建web服务并实现内网穿透发布公网访问的详细教程

文章目录 前言1. 本地搭建web站点2. 测试局域网访问3. 公开本地web网站3.1 安装cpolar内网穿透3.2 创建http隧道&#xff0c;指向本地80端口3.3 配置后台服务 4. 配置固定二级子域名5. 测试使用固定二级子域名访问本地web站点 前言 在web项目中,部署的web站点需要被外部访问,则…

Mac电脑其他文件占用超过一大半的内存如何清理?

mac的存储空间时不时会提示内存已满&#xff0c;查看内存占用比例最大的居然是「其他文件」&#xff0c;「其他文件」是Mac无法识别的格式文件或应用插件扩展等等...如果你想要给Mac做一次彻底的磁盘空间清理&#xff0c;首当其冲可先对「其他文件」下手&#xff0c;那么我们该…

Mactracker for mac,让您轻松掌握Mac电脑硬件信息的利器

Mactracker for mac是一款运行在MacOS平台上的Mac硬件信息查询工具。它能够方便地显示您电脑所有硬件的信息&#xff0c;包括处理器速度、内存、光盘驱动器、图形卡、支持的macOS版本和扩展选项等。此外&#xff0c;它还提供了有关Apple鼠标、键盘、显示器、打印机、扫描仪、数…