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

http和https协议

http和https

如何同时使用http和https协议

HTTP和HTTPS的区别?

HTTP协议与HTTPS协议的区别