HTTP
目录
1.Http的基本代码
1.1 HttpServer.hpp
1.2 简单测试一下
1.3 用telnet测试一下
1.4 用浏览器访问
1.5 返回相应的过程(网页版本)编辑
1.5.1 再次用浏览器访问
1.6 返回相应的过程(文件版本)
1.6.1网页
1.6.2 测试
1.7 反序列化截取
1.7.1 解析第一行 vector 0号下标里的字符串
1.7.1.1 stringstream分割符号
1.7.1.2 首页的设置
1.7.2 访问别的网页(这里有3张网页 )
1.8 跳转网络链接编辑
1.8.1 跳转到自己创建的网页
2.Http的细节字段
3.HTML 表单,输入账号和密码
3.1 get方法(不私密)
3.2 post方法
4. HTTP常见的Header编辑
5.长短链接
5.1 短连接(HTTP 1.0)
C/C++ 中的短连接示例:
5.2 长连接(HTTP 1.1)
C/C++ 中的长连接示例:
5.3 关键区别总结
5.4 长短连接的选择
6.图片
编辑
8.HTTP协议
8.1 认识URL(网址)
8.2 urlencode和urldecode
9.HTTP协议格式
10.HTTP的方法
11.HTTP的状态码
12.HTTP常见Header
13.最简单的HTTP服务器
抓包工具
1.Http的基本代码
1.1 HttpServer.hpp
#pragma once#include #include #include #include \"Socket.hpp\"#include \"logs/ljwlog.h\"using namespace std;static const int dafaultport = 8080;struct ThreadData{ int sockfd;};class HttpServer{public: HttpServer(uint16_t port = dafaultport):port_(port) {} bool Start() { listensock_.Socket(); listensock_.Bind(port_); listensock_.Listen(); for(;;) { string clientip; uint16_t clientport; int sockfd = listensock_.Accept(&clientip, &clientport); pthread_t tid; ThreadData *td = new ThreadData(); td->sockfd = sockfd; pthread_create(&tid, nullptr, ThreadRun, td); } } //把收到的信息打印出来 static void *ThreadRun(void *args) { pthread_detach(pthread_self()); ThreadData *td = static_cast(args); char buffer[10240]; //跟read用法很像 ssize_t n = recv(td->sockfd, buffer, sizeof(buffer), 0); if(n > 0) { buffer[n] = 0; cout<sockfd); delete td; return nullptr; } ~HttpServer() {}private: uint16_t port_; Sock listensock_;};
1.2 简单测试一下
1.3 用telnet测试一下
1.4 用浏览器访问
1.5 返回相应的过程(网页版本)
1.5.1 再次用浏览器访问
1.6 返回相应的过程(文件版本)
1.6.1网页
Document hello world
1.6.2 测试
更改一下写的网页,服务器不用关,浏览器刷新一下就可以
1.7 反序列化截取
1.7.1 解析第一行 vector 0号下标里的字符串
1.7.1.1 stringstream分割符号
stringstream流式分隔符:stringstream - C++ Reference (cplusplus.com)
1.7.1.2 首页的设置
1.7.2 访问别的网页(这里有3张网页 )
先开启服务器
1.8 跳转网络链接
Document hello world
hello world
hello world
hello world
hello world
hello world
Ljw的博客链接
1.8.1 跳转到自己创建的网页
2.Http的细节字段
3.HTML 表单,输入账号和密码
在这之间输入
HTML 标签_w3cschool
3.1 get方法(不私密)
get后会把自己输入的账号密码放进链接后面了
3.2 post方法
4. HTTP常见的Header
5.长短链接
在 HTTP协议 中,长连接和短连接的概念主要体现在连接的持久性上。具体来说,HTTP协议的长短连接关系到客户端和服务器之间的连接是否保持持久,连接是否在多个请求之间复用。
5.1 短连接(HTTP 1.0)
在HTTP/1.0中,默认情况下每个请求都建立一个新的连接,通信完成后连接就会立即关闭。这种方式被称为 短连接。每当客户端发起一个HTTP请求时,服务器会为这个请求创建一个新的TCP连接,并在响应发送完毕后立即关闭这个连接。对于每个请求和响应,都会有一次建立连接、传输数据和断开连接的过程。
短连接的特点:
- 每个请求都需要建立新的TCP连接。
- 每次请求响应后,连接都会被关闭。
- 适用于请求量不大的场景,因为每次建立和断开连接会带来一定的性能开销。
C/C++ 中的短连接示例:
假设你使用C/C++编写一个HTTP客户端发起HTTP请求并接收响应:
#include #include #include #include #include #define PORT 80int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { std::cerr << \"Socket creation failed!\" << std::endl; return -1; } sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = inet_addr(\"93.184.216.34\"); // example.com IP if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { std::cerr << \"Connection failed!\" << std::endl; return -1; } const char *request = \"GET / HTTP/1.0\\r\\nHost: example.com\\r\\n\\r\\n\"; send(sockfd, request, strlen(request), 0); char buffer[1024]; int bytes_received = recv(sockfd, buffer, sizeof(buffer) - 1, 0); buffer[bytes_received] = \'\\0\'; std::cout << \"Response:\\n\" << buffer << std::endl; close(sockfd); // Connection is closed after response return 0;}
在这个短连接示例中,我们使用HTTP/1.0协议,每次请求完毕后,TCP连接会立即关闭。
5.2 长连接(HTTP 1.1)
HTTP/1.1引入了 长连接(Keep-Alive) 的概念,在这种模式下,连接不会在每个请求后关闭,而是保持打开状态,可以复用相同的连接来处理多个请求。这意味着客户端和服务器之间的连接可以用于多个请求和响应,直到明确关闭连接。
默认情况下,HTTP/1.1会使用长连接,但可以通过设置请求头 Connection: close
来显式关闭连接。
长连接的特点:
- 连接在多个请求/响应之间保持活跃。
- 节省了每次请求建立和关闭连接的开销,适用于需要频繁通信的场景。
- 服务器会在一定时间内保持连接,如果长时间没有请求,连接会被关闭。
C/C++ 中的长连接示例:
#include #include #include #include #include #define PORT 80int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { std::cerr << \"Socket creation failed!\" << std::endl; return -1; } sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = inet_addr(\"93.184.216.34\"); // example.com IP if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { std::cerr << \"Connection failed!\" << std::endl; return -1; } // Send multiple requests over the same connection const char *request1 = \"GET / HTTP/1.1\\r\\nHost: example.com\\r\\nConnection: keep-alive\\r\\n\\r\\n\"; send(sockfd, request1, strlen(request1), 0); char buffer[1024]; int bytes_received = recv(sockfd, buffer, sizeof(buffer) - 1, 0); buffer[bytes_received] = \'\\0\'; std::cout << \"Response 1:\\n\" << buffer << std::endl; const char *request2 = \"GET /about HTTP/1.1\\r\\nHost: example.com\\r\\nConnection: keep-alive\\r\\n\\r\\n\"; send(sockfd, request2, strlen(request2), 0); bytes_received = recv(sockfd, buffer, sizeof(buffer) - 1, 0); buffer[bytes_received] = \'\\0\'; std::cout << \"Response 2:\\n\" << buffer << std::endl; close(sockfd); // Connection can be kept alive, but we close it here return 0;}
在这个长连接的例子中,客户端向服务器发送两个HTTP请求,两个请求共享同一个TCP连接,直到手动关闭连接。通过在请求头中加入 Connection: keep-alive
,客户端表明希望连接保持活跃。
5.3 关键区别总结
- 短连接(HTTP/1.0):每个HTTP请求都建立一个新的TCP连接,通信结束后立即关闭,适用于请求量不大的情况。
- 长连接(HTTP/1.1):连接在多个请求之间保持活跃,可以复用连接,直到服务器或客户端明确关闭连接,适用于需要频繁通信的场景。
5.4 长短连接的选择
- 对于HTTP/1.0,通常使用短连接,尤其是在每个请求相对独立时。
- 对于HTTP/1.1,长连接更为常见,尤其是在需要频繁请求或维持长期连接的场景(如实时应用、文件下载、网页加载等)。
长连接有助于减少连接的建立与关闭的开销,提高通信效率,尤其是在大量小请求的场景中,而短连接则适合一次性的请求和响应模式。
6.图片
文件后缀
7.cookie文件(记住密码)
文件级:可以记住一段时间
内存级:关闭就忘记
8.HTTP协议
虽然我们说, 应用层协议是我们程序猿自己定的.
但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)
就是其中之一.
8.1 认识URL(网址)
平时我们俗称的 \"网址\" 其实就是说的 URL
8.2 urlencode和urldecode
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
例如:
\"+\" 被转义成了 \"%2B\" urldecode就是urlencode的逆过程;
工具:URL 编码和解码 - 在线 (urlencoder.org)
9.HTTP协议格式
HTTP请求
- 首行: [方法] + [url] + [版本]
- Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\\n分隔;遇到空行表示Header部分结束
- Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;
HTTP响应
- 首行: [版本号] + [状态码] + [状态码解释]
- Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\\n分隔;遇到空行表示Header部分结束
- Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中.
10.HTTP的方法
其中最常用的就是GET方法和POST方法.
11.HTTP的状态码
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
12.HTTP常见Header
- Content-Type: 数据类型(text/html等)
- Content-Length: Body的长度
- Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
- User-Agent: 声明用户的操作系统和浏览器版本信息;
- referer: 当前页面是从哪个页面跳转过来的;
- location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
- Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
13.最简单的HTTP服务器
实现一个最简单的HTTP服务器, 只在网页上输出 \"hello world\"; 只要我们按照HTTP协议的要求构造数据, 就很容易 能做到;
HttpServer.cc
#include \"HttpServer.hpp\"#include#includeusing namespace std;void Usage(const string& proc){ cout<<\"\\nUsage\" << proc << \"port\\n\\n\" <<endl;}int main(int argc, char* argv[]){ if(argc != 2) { Usage(argv[0]); exit(0); } uint16_t port = stoi(argv[1]); unique_ptr svr(new HttpServer(port)); svr->Start(); return 0;}
HttpServer.hpp
#pragma once#include #include #include #include #include #include #include \"Socket.hpp\"#include \"logs/ljwlog.h\"using namespace std;static const int dafaultport = 8080;const string wwwroot = \"./wwwroot\";//web根目录const string sep = \"\\r\\n\";//首页const string homepage = \"index.html\";class ThreadData{public: ThreadData(int fd): sockfd(fd) {}public: int sockfd;};class HttpRequest{public: //反序列化 这里把一个字符串变成多个字符串 void Deserialize(string req) { while(true) { //找一行移动一行 // 分隔符 size_t pos = req.find(sep); if(pos == string::npos) { break; } string temp = req.substr(0, pos); //读到空行就直接跳出 if(temp.empty()) break; req_header.push_back(temp); req.erase(0, pos + sep.size()); } text = req; } void DebugPrint() { cout<<\"-----------------------\"<<endl; for(auto& line:req_header) { cout<< line << \"\\n\\n\"; } cout<< \"method: \" << method << endl; cout<< \"url\" << url <<endl; cout<< \"http_version\" << http_version <<endl; cout <> method >> url >> http_version; file_path = wwwroot; // if(url == \"/\" || url == \"/index.html\") { file_path += \"/\"; file_path += homepage;// ./wwwroot/index.html } else { file_path += url;// /a/b/c/d.html->./wwwroot/a/b/c/d.html } }public: vector req_header;//把每一行push到req_header string text;//正文 //解析后的结果 vector 0号下标里的字符串 string method; string url; string http_version; string file_path;};class HttpServer{public: HttpServer(uint16_t port = dafaultport):port_(port) {} bool Start() { listensock_.Socket(); listensock_.Bind(port_); listensock_.Listen(); for(;;) { string clientip; uint16_t clientport; int sockfd = listensock_.Accept(&clientip, &clientport); if(sockfd 0) { buffer[n] = 0; cout<< buffer; //假设我们读到的是一个完整的请求,独立的Http请求 HttpRequest req; req.Deserialize(buffer); req.Parse();//解析 //req.DebugPrint(); // //返回相应的过程 string text = ReadHtmlContent(req.file_path); string response_line = \"HTTP/1.0 200 OK\\r\\n \"; string response_header = \"Content-Length \"; response_header = to_string(text.size()); response_header += \"\\r\\n\"; string blank_line = \"\\r\\n\"; string response = response_line; response += response_header; response += blank_line; response += text; //和write类似 send(sockfd, response.c_str(), response.size(), 0); } close(sockfd); } //把收到的信息打印出来 static void *ThreadRun(void *args) { pthread_detach(pthread_self()); ThreadData *td = static_cast(args); HandlerHttp(td->sockfd); delete td; return nullptr; } ~HttpServer() {}private: uint16_t port_; Sock listensock_;};
makefile
httpServer:HttpServer.ccg++ -g -o $@ $^ -std=c++11 -lpthread.PHONT:cleanclean:rm -f httpServer
Socket.hpp
#pragma once#include #include #include #include #include #include #include #include #include #include #include #include #include \"./logs/ljwlog.h\"using namespace std;enum{ SocketErr = 2, BindErr, ListenErr};const int backlog = 10;class Sock{public: Sock() { } ~Sock() { }public: void Socket() // 创建套接字的接口 { sockfd_ = socket(AF_INET, SOCK_STREAM, 0); // 流式套接字 第二个参数是协议类型 if (sockfd_ < 0) { FATAL(\"Socket errno,error:%d,errstring:%s\", errno, strerror(errno)); exit(SocketErr); } } void Bind(uint16_t port) // 绑定的接口 { struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(port); // 主机转网络 local.sin_addr.s_addr = INADDR_ANY; // ip默认0.0.0.0 if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0) { FATAL(\"Bind errno,error:%d,errstring:%s\", errno, strerror(errno)); exit(BindErr); } } void Listen() // 监听状态的接口 { if (listen(sockfd_, backlog) < 0) { FATAL(\"Listen errno,error:%d,errstring:%s\", errno, strerror(errno)); exit(ListenErr); } } // 知道谁链接的我 int Accept(string *clientip, uint16_t *clientport) // 获取连接的接口 { struct sockaddr_in peer; // 远端的意思 socklen_t len = sizeof(peer); int newfd = accept(sockfd_, (struct sockaddr *)&peer, &len); if (newfd < 0) { WARN(\"accept error, %s: %d\", strerror(errno), errno); return -1; } // 网络转主机 // 拿出客户端的ip和端口号 char ipstr[64]; inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr)); // 网络转主机 *clientip = ipstr; // 网络转主机 *clientport = ntohs(peer.sin_port); // 网络转主机 return newfd; } void Close() { close(sockfd_); } int Connect(const string &ip, const uint16_t &port) // 方便两个客户端和服务器都能使用这个Sock的这个公共方法 { struct sockaddr_in peer; memset(&peer, 0, sizeof(peer)); peer.sin_family = AF_INET; peer.sin_port = htons(port); inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr)); int n = connect(sockfd_, (struct sockaddr *)&peer, sizeof(peer)); if (n == -1) { cerr << \"connect to\" << ip << \"::\" << port <<\"error\"<< endl; return false; } return true; } int Fd() { return sockfd_; }private: int sockfd_;};
wwwroot/a/b/hello.html
Document 第二个网页
第二个网页
第二个网页
第二个网页
第二个网页
第二个网页
wwwroot/image
wwwroot/index.html
Document <!-- name:
password:
--> 这是我们的首页
<!-- Document hello world
hello world
hello world
hello world
hello world
hello world
Ljw的博客链接 第二个网页 -->
wwwroot/x/y/world.html
Document 第三个网页
第三个网页
第三个网页
第三个网页
第三个网页
第三个网页