> 技术文档 > HTTP

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.图片

​编辑

7.cookie文件(记住密码)

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:
-->

这是我们的首页

HTTP<!-- Document

hello world

hello world

hello world

hello world

hello world

hello world

Ljw的博客链接 第二个网页 -->

wwwroot/x/y/world.html

   Document 

第三个网页

第三个网页

第三个网页

第三个网页

第三个网页

第三个网页