【网络】http协议

news/2024/11/7 11:37:23/

🥁作者华丞臧.
📕​​​​专栏:【网络】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站


文章目录

  • 前言
  • 一、http协议
    • 1.1 认识URL
      • http是做什么的
      • urlencode和urldecode
    • 1.2 http格式
      • http请求
      • http响应
    • 1.3 http的方法
    • 1.4 http的状态码
    • 1.5 http常见Header
  • 二、http服务器
    • 2.1 最简单的http服务器
    • 2.2 简单的http服务器
    • 2.3 html的http服务器
    • 2.4 表单
      • get
      • post
      • get和post特点
    • 2.5 永久和临时重定向
    • 2.6 Cookie


前言

在上一篇文章中我们见识了序列化和反序列化,其是处于应用层的,是进行网络通信双方进行一种约定;在网络发展至今,已经有了一些非常成熟的场景,并且有大佬自定义的一些协议,写的很好因此成为了应用层特定协议的标准,

一、http协议

虽然我们说,应用层协议是我们程序猿自己定的。但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,供我们直接参考使用,http(超文本传输协议)就是其中之一,底层主流采用的是tcp协议,http是无连接的。

1.1 认识URL

平时我们所说的网址就是URL。
在这里插入图片描述

服务器地址是域名,域名就是字符串类型的字段,域名在我们访问网网站的时候必须被转换称为IP地址,访问网络服务必须具有网络号即端口号,网络通信的本质就是socket,IP+port。
一般在使用确定协议的时候,再url上面会缺省端口号,在进行网络通信时端口号会被(浏览器)自动添加上,所以浏览器访问指定url时,浏览器必须给我们自动添加port,浏览器就必须知道这些端口,所以特定的众所周知的服务端口号都是并且必须是确定的。因此,在前面学习端口号时,我们说用户自己使用的套接字不能在0~1023。

http --> 80
https --> 443
sshd --> 22

http是做什么的

http是以网页的形式呈现的,比如说查阅文档、查看视频;所以http是获取网页资源的,视频、音频等也都是文件,而网页也是一个.html文件。

http是向特定服务器申请特定资源的,获取到本地进行展示或者某种使用的。

网页上的资源在该网页的网络服务器(软件)所在的服务器(硬件)上,而服务器都是Linux,资源文件存放在Linux服务器上;用户在网页上申请资源时,服务器需要根据路径找到该文件,所以URL中需要添加资源文件的路径。/是Linux下的路径分隔符,但是需要注意这个/不一定为根目录,因为软件层面可以做一些处理,比如在这个目录前添加一个路径。

urlencode和urldecode

像 / ? :等这样的字符,已经被url当做特殊意义理解了,因此这些字符不能随意出现。
比如, 某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义。在进行网络传输进行编码时,尤其是http中有些字段也会进行编码,服务端进行解码。编码是浏览器做的,解码是服务器做的。
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。
在这里插入图片描述

“+” 被转义成了 “%2B”
urldecode就是urlencode的逆过程:
urlencode工具

网页的内容在开发者工具中可以看到,如下图:
在这里插入图片描述

1.2 http格式

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中。

1.3 http的方法

方法说明支持的HTTP协议版本
GET获取资源1.0、1.1
POST传输实体主体1.0、1.1
PUT传输文件1.0、1.1
HEAD获取报文首部1.0、1.1
DELETE删除文件1.0、1.1
OPTIONS询问支持的方法1.1
TRACE追踪路径1.1
CONNECT要求用隧道协议链接代理1.1
LINK建立和资源之间的联系1.0
UNLINE断开连接关系1.0

其中最常用的就是GET方法和POST方法.

1.4 http的状态码

类别原因短语
1XXInformational(信息状态码)接收的请求正在处理
2XXSuccess(成功状态码)请求正常处理完毕
3XXRedirection(重定向状态码)需要进行附加操作以完成请求
4XXClient Error(客户端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理请求出错

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

1.5 http常见Header

  • Content-Type: 数据类型(text/html等)
  • Content-Length: Body的长度
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
  • User-Agent: 声明用户的操作系统和浏览器版本信息;
  • referer: 当前页面是从哪个页面跳转过来的;
  • location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
  • Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;

二、http服务器

2.1 最简单的http服务器

使用tcp协议的服务器端,在浏览器中可以连接该服务器,浏览器上默认使用http协议进行连接。在前几篇文章中已经编写了tcp套接字,这里只需要编写提供服务的代码即可。

#include <iostream>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<sys/types.h>
#include<sys/wait.h>
#include "Log.hpp"using namespace std;
volatile bool quitSer = false;void Usage(void *vgs)
{std::cout << "Usage:./tcpserver port ip" << std::endl;
}
//提供服务
void Serverhttp(int sock)
{char buffer[10240];ssize_t s = read(sock, buffer, sizeof buffer);cout << s << endl;if(s > 0){logMessage(DEBUG, "read success..");std::cout << buffer;}
}class server
{
public:server(int port, std::string ip = ""): sockfd_(-1), ip_(ip), port_(port){}~server(){}public:void init(){// 1. 创建套接字sockfd_ = socket(AF_INET, SOCK_STREAM, 0);if (sockfd_ < 0){logMessage(FATAL, "socket:%s[%d]", strerror(errno), sockfd_);exit(SOCK_ERR);}logMessage(DEBUG, "socket success..");// 2.填充域struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port_);ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));// 3. 绑定网络信息if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0){logMessage(FATAL, "bind:%s[%d]", strerror(errno), sockfd_);exit(BIND_ERR);}logMessage(DEBUG, "bind success...");// 4. 监听套接字if (listen(sockfd_, 5) < 0){logMessage(FATAL, "listen:%s[%d]", strerror(errno), sockfd_);exit(LISTEN_ERR);}logMessage(DEBUG, "listen success...");// 完成}void start(){char inbuffer_[1024]; // 用来接收客户端发来的消息// 提供服务while (true){quitSer = false;struct sockaddr_in peer;socklen_t len = sizeof(peer);// 5. 获取连接, accept 的返回值是一个新的socket fdlogMessage(DEBUG,"start1");int serviceSock = accept(sockfd_, (struct sockaddr *)&peer, &len);logMessage(DEBUG,"start2");if (serviceSock < 0){// 获取链接失败logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);continue;}logMessage(DEBUG, "accept success");pid_t id = fork();if(id == 0){pid_t tid = fork();if(tid == 0){uint16_t peerPort = htons(peer.sin_port);std::string peerIp = inet_ntoa(peer.sin_addr);Serverhttp(serviceSock);exit(0);}exit(0);}close(serviceSock);int ret = waitpid(id, nullptr, 0);if(ret < 0){logMessage(WARINING, "wait child error:%d", errno);}logMessage(DEBUG, "wait success...");}}private:int sockfd_;uint16_t port_;std::string ip_;
};// ./tcpserver port ip
int main(int argc, char *argv[])
{if (argc < 2 || argc > 3){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]);std::string ip;if (argc == 3)ip = argv[2];std::cout << port << ":" << ip << std::endl; server tcpSer(port, ip);tcpSer.init();tcpSer.start();return 0;
}

服务端并没有提供服务,所以访问服务器时网页显示如下图:
在这里插入图片描述

在Linux服务端可以看到请求的相关属性,如下图:
在这里插入图片描述

2.2 简单的http服务器

服务器收到请求,提供服务向对应的套接字文件中写入一个“hello world”,注意响应的格式为首行+header+body,其中header和body之间需要空一行。

void Serverhttp(int sock)
{char buffer[10240];ssize_t s = read(sock, buffer, sizeof buffer);cout << s << endl;if(s > 0){logMessage(DEBUG, "read success..");std::cout << buffer;}//开始响应string response;response = "HTTP/1.0 200 OK\r\n"; //响应首行response += "\r\n";   //空行response += "hello world!!";  //正文send(sock, response.c_str(), response.size(), 0);
}

服务器收到的请求报文如下图:
在这里插入图片描述
浏览器收到的响应如下图:
在这里插入图片描述
使用telnet可以查看服务器给用户的响应。

//安装telnet
sudo yum install -y telnet//使用格式
telnet ip port
//connected后
//出现escape character时使用ctrl+}
//输入请求

在这里插入图片描述

2.3 html的http服务器

作为服务端,它并不关心html的信息也不需要管html的格式是什么,服务器只负责网络请求并且把网页信息给用户推过去就行了,所以实际上在进行网站开发时,网页的资源都是通过html文件推送给用户的,请求的文件在请求行中,其第二个字段就是用户需要访问的文件,如下图:
在这里插入图片描述
其中/并不是根目录,而是web根目录,可以设置成为根目录。

在这里插入图片描述

//获取资源路径
string getPath(string in)
{size_t pos = in.find(CRLF);string request = in.substr(0, pos);cout << "request:" << request << endl;// get path http/1.0size_t first = request.find(SPACE); //第一个空格cout << "first:" << first << endl;if(first == string:: npos) return nullptr;size_t second = request.rfind(SPACE); //第二个空格cout << "second:" << second << endl;if(second == string::npos) return nullptr;string path = request.substr(first + SPACE_LEN, second- (first + SPACE_LEN));if(path.size() == 1 && path[0] == '/') path += HOME_PAGE; //用户访问根目录,就返回主页cout << "getPath:" << path << endl;return path;
}
//获取资源
string readFile(string& recource)
{ifstream in(recource);if(!in.is_open()) return "404";string content;string line;while(getline(in, line)) content += line;return content;
}//提供服务
void Serverhttp(int sock)
{//std::cout << peerIp << ":" << peerPort << std::endl;char buffer[10240];ssize_t s = read(sock, buffer, sizeof buffer);cout << s << endl;if(s > 0){logMessage(DEBUG, "read success..");std::cout << buffer;}// 1. 获取路径string path = getPath(buffer);string recource = ROOT_PATH;recource += path;cout << recource << endl;// 获取对应文件内容string html = readFile(recource);// 开始响应string response;response = "HTTP/1.0 200 OK\r\n";response += "Content-Type:text/html\r\n";response += ("Content-Length:" + std::to_string(html.size()) + "\r\n");response += "\r\n";//response += "<html><h1>hello world!!</h1><html>\r\n";response += html;send(sock, response.c_str(), response.size(), 0);
}

关于html的编写方法可以参考👉菜鸟教程

//index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"> //
<title>华丞臧(runoob.com)</title> //网页名称
</head>
<body> //正文<h1>我的第一个标题</h1>   //标题<p>我的第一个段落。</p>
</body>
</html>

在浏览器中访问服务器可以看到网页显示如下图所示:

在这里插入图片描述
Liunx上收到的请求如下图,左为服务器收到的请求,右为客户端收到的响应。
在这里插入图片描述

2.4 表单

用户的网络行为无非有两种

  1. 用户将自己的远端资源拿到本地:get /index.html http/1.1;
  2. 用户想将自己的属性字段提交到远端。

在浏览器上,网页通常会让用户注册一个账号,用来标识该用户,用户的存放在远端的资源也可以使用该账号标识,在html中这种端口称为表单。

get

在http中get会以明文方式将用户对应的参数信息拼接到url中。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>华丞臧(runoob.com)</title>
</head>
<body><h1>hello world</h1><p>我的第一个段落。</p><form action="html_form_action.php" method="get">Username: <input type="text" name="user"><br>Password: <input type="password" name="passwd"><br><input type="submit" value="Submit"></form>
</body>
</html>

表单形式如下图所示:
在这里插入图片描述

在这里插入图片描述

post

在http中post会以明文方式将用户对应的参数信息拼接到正文中。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>华丞臧(runoob.com)</title>
</head>
<body><h1>hello world</h1> <p>我的第一个段落。</p><form action="html_form_action.php" method="post">Username: <input type="text" name="user"><br>  //用户名,<br>标识换行Password: <input type="password" name="passwd"><br>  //密码<input type="submit" value="Submit"></form>
</body>
</html>

表单形式如下图所示:
在这里插入图片描述
在表单中填入用户名和密码后,可以看到url中并没有拼接用户参数,如下图:
在这里插入图片描述
使用progress telerik fiddler可以查看用户发送的请求报文(Raw),可以看到用户参数被拼接到正文当中,如下图:
在这里插入图片描述
在服务端也可以收到用户发来的请求,如下图:
在这里插入图片描述

get和post特点

不难发现,不管是get方法还是post方法都是不安全的,都是以明文的方式传输;如果有第三方来攻击用户,无疑能非常简单的获取用户的数据;与get方法相比,post方法只是更为私密但还是明文传输,不过post方法会将参数添加到请求的正文中,因此当用户需要传输大资源的时候无疑使用post方法更加合适。

2.5 永久和临时重定向

301:永久重定向。

void Serverhttp(int sock)
{char buffer[10240];ssize_t s = read(sock, buffer, sizeof buffer);cout << s << endl;if(s > 0){logMessage(DEBUG, "read success..");std::cout << buffer;}string response = "HTTP/1.1 301 Permanent redirect\r\n";   //永久重定向response +="Location: https://blog.csdn.net/qq_59456417?spm=1011.2415.3001.5343r\n";response += "\r\n";send(sock, response.c_str(), response.size(), 0);
}

访问服务器结果如下:
在这里插入图片描述

302:临时重定向。

void Serverhttp(int sock)
{char buffer[10240];ssize_t s = read(sock, buffer, sizeof buffer);cout << s << endl;if(s > 0){logMessage(DEBUG, "read success..");std::cout << buffer;}string response = "HTTP/1.0 302 Temporary redirect\r\n";   //临时重定向response +="Location: https://www.qq.com/r\n";response += "\r\n";send(sock, response.c_str(), response.size(), 0);
}

访问服务器结果如下:
在这里插入图片描述

  • 临时重定向:当客户端发起请求时,服务端对请求不进行处理,但是服务端会给可会端返回一个302状态码,并且在属性字段还会返回一个Location字段,客户端会根据这个字段进行自动跳转;如果服务器只是做一个局部性的临时的升级或者短时间不能使用,这时我们就可以进行临时重定向,如果有用户访问,就将临时重定向到对应的服务器上。。
  • 永久重定向:当客户端发起请求时,服务端对请求不进行处理,但是服务端会给可会端返回一个301状态码,并且在属性字段还会返回一个Location字段,客户端会根据这个字段进行自动跳转;如果服务器永久不再使用,但是该服务器积累了一定的用户,这时我们就可以搞一个同样的服务器进行永久重定向,如果有用户访问,就重定向到对应的服务器上。
  • 临时和永久重定向的区别在于客户端浏览器的处理,对于临时重定向浏览器不会记录重定向的目标位置,对于永久重定向浏览器会记录重定向的目标位置,所以永久重定向访问一次后再次访问时浏览器就会直接访问记录的服务器,注意并不是所有浏览器都会去做将永久重定向的网址更新。
  • 服务器的资源是否愿意被临时访问还是愿意永久访问,决定是临时重定向还是永久重定向。

2.6 Cookie

http状态特点之一:无状态,用户各种资源的请求http协议不会记录,也就是说http不知道用户访问服务器次数;虽然http不会记录,但http可以借助其他的手段来记录用户的访问。

为什么需要记录呢?

因为用户需要会话保持,网站中某些资源是需要进行用户认证的,如果访问不被记录,就会导致每次访问资源都需要进行用户认证,比如在腾讯视频上每次访问vip影视都需要用户进行一次登录这显然是非常麻烦的的,所以服务器需要保持用户会话。

我们可以登录一下bilibili如下所示,点击网址左边的锁,可以看到有一个Cookie,Cookie是一种浏览器会话保持的策略。
在这里插入图片描述

在Cookie类表中可以找到以bilibili开头的:
在这里插入图片描述
当你把Cookie中关于bilibili的全部删除,下一次再进入bilibili时就需要重新登陆了。

Cookie内容:

  • 用户名
  • 密码
  • 到期时间
  • 地址
  • 等等
void Serverhttp(int sock)
{char buffer[10240];ssize_t s = read(sock, buffer, sizeof buffer);cout << s << endl;if(s > 0){logMessage(DEBUG, "read success..");std::cout << buffer;}// 1. 获取路径string path = getPath(buffer);string recource = ROOT_PATH;recource += path;cout << recource << endl;string html = readFile(recource);// 开始响应string response;response = "HTTP/1.0 200 OK\r\n";response += "Content-Type:text/html\r\n";response += ("Content-Length:" + std::to_string(html.size()) + "\r\n");response += "Set-Cookie: this is my Cookie content\r\n";response += "\r\n";response += html;send(sock, response.c_str(), response.size(), 0);
}

在这里插入图片描述
Cookie就是浏览器给用户维护的一个文件,Cookie可以在磁盘上也可以在内存中,内存中的Cookie将浏览器关闭后Cookie就会释放,再次启动浏览器访问服务器时就需要重新登录。Cookie对用户的安全极大的影响,第三方如果拿到用户的Cookie,第三方就可以通过Cookie使用用户的身份登录服务器,更严重的第三方还是以通过Cookie拿到用户的用户名和密码。

Cookie策略存在极大的安全隐患,所以现在主流的方案不是Cookie,而是Cookie + session的方式。服务器将用户名和密码形成一个session文件,这个文件的文件名session_id在服务器上具有唯一性,服务器将session_id返回客户端,客户端将session_id写入本地的Cookie,后续访问都会通过session_id去访问服务器,此时用户的用户名和密码就是存放在服务器上,软件厂商来维护session和cookie,大公司有充足的动力和能力来保护自己不受攻击,并且还会有各种措施来防护。
在这里插入图片描述

补充:

  • http-pipeline:怎么请求的,就按顺序来响应。

http://www.ppmy.cn/news/31692.html

相关文章

Linux:文件流指针 与 文件描述符

目录一、文件描述符二、文件流指针三、缓冲区之前讲解过了IO库函数和IO接口&#xff0c;库函数是对系统调用接口的封装&#xff0c;也就是说实际上在库函数内部是通过调用系统调用接口来完成最终功能的。 库函数通过文件流指针操作文件&#xff0c;系统调用接口通过文件描述符操…

编程语言分类

目录 ❤ 机器语言 机器语言的编程 ❤ 汇编语言 ❤ 高级语言(编程语言) 编译型 解释型 ❤ 动态语言和静态语言 ❤ 强类型定义语言和弱类型定义语言 ❤ 主流语言介绍 C语言 C java python JavaScript SQL PHP python从小白到总裁完整教程目录:https://blog…

动态规划入门经典问题讲解

最近开始接触动态规划问题&#xff0c;以下浅谈&#xff08;或回顾&#xff09;一下这些问题的求解过程。解题思路对于动态规划问题&#xff0c;由于最终问题的求解需要以同类子问题作为基础&#xff0c;故需要定义一个dp数组&#xff08;一维或二维&#xff09;来记录问题求解…

汽车车机芯片Linux系统内核编译问题总结

谈到车机,很多人会想到华为问界上装的大屏车机,号称车机的天花板,基于鸿蒙OS的,而今天谈到的车机芯片用的是linux内核Kernel,对于它的编译,很多人一时会觉得头大,的确如果工具不是很齐全,就会遇到这样那样的问题,但是过程都会有错误提示,按照错误提示基本可以解决,而…

JS_wangEditor富文本编辑器

官网&#xff1a;https://www.wangeditor.com/ 引入 CSS 定义样式 <link href"https://unpkg.com/wangeditor/editorlatest/dist/css/style.css" rel"stylesheet"> <style>#editor—wrapper {border: 1px solid #ccc;z-index: 100; /* 按需定…

字符串内存优化

【前言】 字符串占用的内存往往很多&#xff0c;优化的空间很大&#xff0c;在做内存优化的时候&#xff0c;优化字符串内存是必不可少的一步。 字符串内存优化的核心原则有三个&#xff1a; 1.复用字符串&#xff0c;减少字符串数量 2.降低不可复用字符串的占用的内存 3.…

ESP32设备驱动-红外寻迹传感器驱动

红外寻迹传感器驱动 1、红外寻迹传感器介绍 红外寻迹传感器具有一对红外线发射管与接收管,发射管发射出一定频率的红外线,当检测方向遇到障碍物(反射面)时,红外线反射回来被接收管接收,经过比较器电路处理之后,输出接口会输出一个数字信号(低电平或高电平,取决于电路…

若依框架部署从零开始2023版(前后端分离)

前言电脑最近重装了一次系统&#xff0c;目前什么都没有安装&#xff0c;记录一下从零开始部署前后端分离版本的若依框架系统先去官网把若依源码拉下来代码克隆若依目前已经有很多的版本了&#xff0c;因为现在开发比较流行前后端分离&#xff0c;因此这里演示前后端分离版本点…