HTTP和HTTPS协议
Posted 影中人lx
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HTTP和HTTPS协议相关的知识,希望对你有一定的参考价值。
HTTP协议
HTTP协议是一种应用层的协议,全称为超文本传输协议。
URL
URL值统一资源定位标志,也就是俗称的网址。
协议方案名
http://表示的就是协议方案名,常用的协议有HTTP协议、HTTPS协议、FTP协议等。HTTPS协议是以HTTP协议为基础,通过传输加密和身份认证保证了传输过程的安全性。
登录信息
user:pass表示登录认证信息。绝大多数情况下,该字段是被省略。一般通过登录窗口的方式让用户输入。比如gitee的登录窗口:
服务器地址
服务器地址也叫做域名。在进行网络访问时,网络地址通过DNS域名解析转换为标识唯一主机的IP地址。比如使用ping命令访问百度和京东的官网,最后会被转换为ip地址:
实际上,可以认为域名和IP地址是等价的;在计算机世界中使用的时候既可以使用域名,也可以使用IP地址。但URL呈现出来是可以让用户看到的,因此URL当中是以域名的形式表示服务器地址的。
服务器端口
一般0-1023号端口已经被一些特定的服务占有。比如HTTP协议默认的端口是80,HTTPS默认的端口是443。
带层次的文件路径
/dir/index.htm
表示的是要访问的资源所在的路径。访问服务器的目的是获取服务器上的某种资源,通过前面的域名和端口已经能够找到对应的服务器进程了,此时要做的就是指明该资源所在的路径。
这里的’/
'并不是指根目录,而是指web更目录。具体的信息将在后面解释。
查询字符串
uid=1
表示的是请求时提供的额外的参数,这些参数是以键值对的形式,通过&
符号分隔开的。
比如我们查询晓歌的灯如昼新皮肤的信息:
在上面的URL中,存在wd这个字段。这个字段也就是我们想要查询的关键字。
片段标识符
片段标识符是对资源的补充
urlencode和urldecode
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现。
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义 。
比如我们搜索C++关键字:
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。
在线编码工具
市面上存在很多免费的在线编码工具:https://tool.chinaz.com/Tools/urlencode.aspx
比如输入C++并进行编码,就可以得到C++对应的编码为C%2B%2B
比如输入C%2B%2B进行解密,就可以得到对应的解码为C++
HTTP协议的格式
HTTP协议能够干什么?
HTTP协议是向特定的服务器申请特定的资源,并获取到本地的协议。
通过wget命令申请百度首页的资源。并得到一个html文件到本地。我们将html中的内容在浏览器中打开:
因此成功获取百度首页的静态资源。
HTTP协议的请求格式
HTTP请求格式可以分为四个部分,格式如下:
请求格式包含以下四个部分:
- 请求行:请求方法+url(文件路径格式)+http版本
- 请求报头:请求的属性,这些属性都是以
key: value
的形式按行陈列的。 - 空行:作为请求报头和请求正文的的分割线
- 请求正文:请求正文允许为空字符串,如果请求正文存在,则在请求报头中会有一个Content-Length属性来标识请求正文的长度。
HTTP如何保证自己的报头和有效载荷全部被读取?
- 读取完整的报头:逐行读取,直到读取到空行。
- 读取完整的正文:在报头中一定存在一个关于key:value保存正文长度的属性。
获取HTTP请求
HTTP协议的底层通常使用的传输层协议是TCP协议,因此可以通过一个TCP服务器获取HTTP请求。
int main()
//创建套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0)
cerr << "socket error!" << endl;
return 1;
//绑定
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(8888);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
cerr << "bind error!" << endl;
return 2;
//监听
if (listen(listen_sock, 5) < 0)
cerr << "listen error!" << endl;
return 3;
//启动服务器
struct sockaddr peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
for (;;)
int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
if (sock < 0)
cerr << "accept error!" << endl;
continue;
if (fork() == 0) //爸爸进程
close(listen_sock);
if (fork() > 0) //爸爸进程
exit(0);
//孙子进程
char buffer[1024];
recv(sock, buffer, sizeof(buffer), 0); //读取HTTP请求
cout << "--------------------------request begin--------------------------" << endl;
cout << buffer << endl;
cout << "---------------------------request end---------------------------" << endl;
close(sock);
exit(0);
//爷爷进程
close(sock);
waitpid(-1, nullptr, 0); //等待爸爸进程
return 0;
上面的服务器通过监听8888端口获取HTTP请求信息。
说明:
- 浏览器向服务器发起HTTP请求后,由于服务器没有对其进行响应,此时浏览器就会认为服务器没有收到请求,然后再不断发起新的HTTP请求。因此虽然我们只用浏览器访问了一次,但会受到多次HTTP请求。
- 由于浏览器发起请求时默认用的就是HTTP协议,因此我们在浏览器的url框当中输入网址时可以不用指明HTTP协议。
- 这里URL中的
/
并不是指云服务器的根目录,而是web根目录。web根目录可以由自己指定。
下面访问其他路径下的资源:
当访问的资源发生变化时,请求头中的URL也跟着改变。
HTTP的响应格式
HTTP响应格式可以分为四个部分,格式如下:
响应格式包含以下四个部分:
- 状态行:HTTP版本+状态码+状态码描述符
- 响应报头:响应的属性,以
key: value
键值对的形式按行陈列的。 - 空行:响应报头和响应正文的分割线
- 响应正文:响应正文允许为空字符串,如果响应正文存在,则响应报头中会有一个Content-Length属性来标识响应正文的长度。
比如我们访问百度搜索晓歌的信息:
模拟HTTP的响应
下面我们在服务器中构建HTTP响应:当浏览器发送请求时,在网页上显示accept your request
。
int main()
//创建套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0)
cerr << "socket error!" << endl;
return 1;
//绑定
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(8888);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
cerr << "bind error!" << endl;
return 2;
//监听
if (listen(listen_sock, 5) < 0)
cerr << "listen error!" << endl;
return 3;
//启动服务器
struct sockaddr peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
for (;;)
int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
if (sock < 0)
cerr << "accept error!" << endl;
continue;
if (fork() == 0) //爸爸进程
close(listen_sock);
if (fork() > 0) //爸爸进程
exit(0);
//孙子进程
char buffer[1024];
recv(sock, buffer, sizeof(buffer), 0); //读取HTTP请求
cout << "--------------------------request begin--------------------------" << endl;
cout << buffer << endl;
cout << "---------------------------request end---------------------------" << endl;
//构建HTTP响应
string response="http/1.1 200 ok\\r\\n";
string world="accept your request";
response+=("Content-Length: "+ to_string(world.size()) + "\\r\\n");
response+="\\r\\n";
response+=world;
send(sock,response.c_str(),response.size(),0);
close(sock);
exit(0);
//爷爷进程
close(sock);
waitpid(-1, nullptr, 0); //等待爸爸进程
return 0;
在实际的使用中,难道每一个请求都需要程序员去构造响应正文?实际上,HTTP请求的请求行中存在URL,URL就是请求需要访问资源的存放地址。
响应正文存放在哪?
在URL中,存在一个带层次的文件路径:(比如) /dir/index.html。所以可以从请求行的第二个字段中获取资源路径。
GET /dir/index.html http/1.0
下面的函数实现了从请求行提取读取URL路径:
#define CRLF "\\r\\n"
#define SPACE " "
#define SPACELEN strlen(SPACE)
#define ROOT_PATH "wwwpath"
#define HOME_PAGE "index.html"
string readURL(string buffer)
//读取第一行
size_t pos=buffer.find(CRLF);
if(pos==std::string::npos) return "";
string firstline=buffer.substr(0,pos);
size_t first=firstline.find(SPACE);
if(first==std::string::npos) return "";
size_t second=firstline.rfind(SPACE);
if(second==std::string::npos) return "";
string URL=buffer.substr(first+SPACELEN,second-SPACELEN-first);
if(URL.size()==1&&URL[0]=='/')
URL+=HOME_PAGE;
return URL;
注意:
对于访问web根目录/
,需要进行特殊处理。一般默认为web根目录下的index.html文件。
实验
当我们访问对应URL路径在资源时,便打开对应文件夹,并添加到HTTP响应正文并返回。
string readFile(const string& filepath)
std::ifstream in(filepath,std::ifstream::binary);
if(!in.is_open()) return "404";
std::string content;
std::string line;
while (getline(in,line))
content+=line;
cout<<content<<endl;
in.close();
return content;
假设我们将当前目录设置为根目录,并创建一个index.html文件。
<html>
<head></head>
<body>
<h1>Hello webroot</h1>
</body>
</html>
并创建文件夹a/b/c,在该文件夹下创建一个d.html文件
<html>
<head></head>
<body>
<h1>Hello d.html</h1>
</body>
</html>
访问web根目录结果:
访问/a/b/c/d.html资源结果:
完整的主程序
int main()
//创建套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0)
cerr << "socket error!" << endl;
return 1;
//绑定
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(8081);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
cerr << "bind error!" << endl;
return 2;
//监听
if (listen(listen_sock, 5) < 0)
cerr << "listen error!" << endl;
return 3;
//启动服务器
struct sockaddr peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
for (;;)
int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
if (sock < 0)
cerr << "accept error!" << endl;
continue;
if (fork() == 0) //爸爸进程
close(listen_sock);
if (fork() > 0) //爸爸进程
exit(0);
//孙子进程
char buffer[1024];
recv(sock, buffer, sizeof(buffer), 0); //读取HTTP请求
//读取URL
string URL=readURL(buffer);
cout<<"URL:"<<URL<<endl;
//拼接路径
string filepath=ROOT_PATH+URL;
cout<<filepath<<endl;
//构建HTTP响应
string response="http/1.1 200 ok\\r\\n";
string world=readFile(filepath);
response+=("Content-Length: "+ to_string(world.size()) + "\\r\\n");
response+="\\r\\n";
response+=world;
send(sock,response.c_str(),response.size(),0);
close(sock);
exit(0);
close(sock);
exit(0);
//爷爷进程
close(sock);
waitpid(-1, nullptr, 0); //等待爸爸进程
return 0;
POST和GET方法
GET方法
网络行文无非有两种:
- 把远端的资源拿到本地:GET
- 将自己的属性提交到远端:POST或者GET方法。
我们在web根目录下的index.html文件中添加一个表单用于比较两者的区别:
<html>
<head></head>
<body>
<h1>Hello webroot</h1>
<form action="/a/b/c/d.html" method="get">
Username: <input type="text" name="user"><br>
Password: <input type="password" name="passwd"><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
得到结果为:
观察URL的变化。提交的user和passwd以明文的方式出现在URL中。
这也是GET方法的特点:把参数以明文的方式按照Key:value格式拼接到URL后面。
POST方法
将表单的方法修改为POST。
<html>
<head></head>
<body>
http和https