HTTP协议
虽然我们说, 应用层协议是我们程序猿自己定的.
但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其中之一.
URL
平时我们俗称的 “网址” 其实就是说的 URL
urlencode和urldecode:
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现. 比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
urldecode就是urlencode的逆过程
HTTP协议格式
HTTP基本特征:
- 无连接
TCP建立连接和http无关,http直接向对方发送http request即可,服务器构建一个response,然后解释 - 无状态
http本身无状态,不会记录任何用户信息,request<->response,记录你的基本信息的技术cookie+session - 简单快速
短连接进行文本传输(html,img,css,js) http/1.1:长连接
http构成
http构成:request、response
请求:
- 首行: [方法] + [url] + [版本]
- Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束.
- 空行
- Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;
响应:
- 首行: [版本号] + [状态码] + [状态码解释]
- Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
- 空行
- Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中.
怎么知道请求读完了?
读到空行(\r\n)说明报头读完了,空行之后是请求正文(有效载荷)
怎么保证读取正文时不读取到第二个请求的报头?
请求报头中有Content-Length,就是有效载荷的长度。直接读取这个长度大小
HTTP常见Header:
Content-Type: 数据类型(text/html等)
Content-Length: Body的长度
Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
User-Agent: 声明用户的操作系统和浏览器版本信息;
referer: 当前页面是从哪个页面跳转过来的;
location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能
fiddler抓包工具
它承担的是代理的工作keep-alive:长连接
Linux下的抓包
telnet www.baidu.com 80
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.0 |
LINK | 建立和资源之间的联系 | 1.0 |
UNLINE | 断开连接关系 | 1.0 |
我们说通过http获取的资源就是:文本(html/css/js),图片,音频,视频
GET和POST
- GET:方法的含义是请求从服务器获取资源,这个资源可以是静态的文本、页面、图片视频等。比如,你打开我的文章,浏览器就会发送 GET 请求给服务器,服务器就会返回文章的所有文字及资源。
- POST:而POST 方法则是相反操作,它向 URI 指定的资源提交数据,数据就放在报文的 body 里。
比如,你在我文章底部,敲入了留言后点击「提交」,浏览器就会执行一次 POST 请求,把你的留言文字放进了报文 body 里,然后拼接好 POST 请求头,通过 TCP 协议发送给服务器。
在都传参的情况下:
GET方法:通过url传参 参数长度受限
POST方法:通过正文传参 理论上可以传无限长参
POST方法比GET方法更私密,POST方法传参并不会回显到浏览器的url中,但是它也是要以正文的方式传上去。除非加密,否则没有安全可言
HTTP状态码
1XX:信息性状态码。接收的请求正在处理,不要着急。
2XX:成功状态码。请求正常处理完毕
3XX:重定向状态码。需要进行附加操作以完成请求
4XX:客户端错误状态码。服务器无法处理请求
5XX:服务器错误状态码。服务器处理请求出错
301:永久重定向
302:临时重定向
307:临时重定向
cookie和session
你登录完看完第一部电影,如果无状态,看第二部又要登录一次。
http是无状态的,但是http是给用户用的,如果无状态的,给用户会带来很差的体验。
cookie:本质是浏览器中的一个文件
客户端向服务器上传了用户名和密码,和服务器上注册的用户名和密码对比,一样则登录成功。
服务器会在自己的response里包含set-cookie,把cookie写入浏览器,浏览器以文件形式保存set-cookie里的内容。
下一次浏览器发起请求时,会自动带上cookie在自己的request里,然后服务器收到这个http请求当中会提取cookie信息,对其进行自动认证,从此以后不用再重复登录。
文件:内存级、硬盘级
cookie是内存级的:只保存在内存里不往硬盘上写。只要浏览器不关闭,认证还在,但浏览器关闭就没了。
硬盘级:关闭浏览器也不需要重新登录。(如登录qq时选择记住我)
session
client发起请求,server通过认证,返回set-cookie,下次客户端再发起请求时就会自动携带cookie
。。。server通过认证,在服务器上给该用户形成一个新的sid,是在全服务器内唯一的序列号,服务器端把这个sid及登陆情况等敏感信息统一写到sid->session文件中,返回时直接返回set-cookie:sid=xxx,其中只包含了session id;客户端再把sid保存到cookie文件中。
下次请求时,cookie中只包含sid,上传给服务器,服务器通过sid找到session文件,通过session获得登录状态。
敏感信息:单单cookie,敏感信息放在本地 session:敏感信息保存在server中。 cookie中只包含sid 。
有了session,只会泄漏sid,即使被拿到了要访问的网站,对方也只能和我们在服务器访问同样的资源,而获取不到账号密码等敏感信息,因为这些信息保存在server中。
session与cookie相比,是相对安全的。
最简单的HTTP服务器
httpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <signal.h>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#define BACKLOG 5
using namespace std;
class HttpServer{
private:
int port;
int lsock;
public:
HttpServer(int _p):port(_p), lsock(-1)
{
}
void InitServer()
{
signal(SIGCHLD, SIG_IGN);
lsock = socket(AF_INET, SOCK_STREAM, 0);
if(lsock < 0){
cerr << "socket error" << endl;
exit(2);
}
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
int opt = 1;
setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if(bind(lsock, (struct sockaddr*)&local, sizeof(local)) < 0){
cerr << "socket bind error" << endl;
exit(3);
}
if(listen(lsock, BACKLOG) < 0){
cerr << " listen error" << endl;
exit(4);
}
}
void EchoHttp(int sock)
{
char request[2048];
size_t s = recv(sock, request, sizeof(request), 0); //bug!
if(s > 0){
request[s] = 0;
cout << request << endl;
string response = "HTTP/1.0 302 Found\r\n";
response += "Content-type: text/html\r\n";
response += "location: https://www.baidu.com";
response += "\r\n";
//response += "\ <!DOCTYPE html>\ <html>\ <head>\ <title>hello</title>\ </head>\ <body>\ <h1>Welcome</h1>\ <p>helloworld</p>\ </body>\ </html>\ ";
send(sock, response.c_str(), response.size(), 0);
}
while(1){
sleep(1);
}
//close(sock);
}
void Start()
{
struct sockaddr_in peer;
for(;;){
socklen_t len = sizeof(peer);
int sock = accept(lsock, (struct sockaddr*)&peer, &len);
if(sock < 0){
cerr << "accept error "<<endl;
continue;
}
cout << "get a new connect ... done" << endl;
if(fork() == 0){
//child
close(lsock);
EchoHttp(sock);
exit(0);
}
close(sock);
}
}
~HttpServer()
{
if(lsock != -1){
close(lsock);
}
}
};
httpServe.cc
#include "httpServer.hpp"
static void Usage(string proc)
{
cout << "Usage:\n\t";
cout << proc << " port" << endl;
}
int main(int argc ,char *argv[])
{
if(argc != 2){
Usage(argv[0]);
exit(1);
}
HttpServer *hp = new HttpServer(atoi(argv[1]));
hp->InitServer();
hp->Start();
return 0;
}
HTTPS
http:超文本传输,对应的端口号是80
https:也是超文本传输,但在和传输层之间新增了一层安全层。对应的端口号是443
SSL:非标准的
TLS:标准的
其实是同一个东西。最重要的作用:加密解密
https往下交付时先交付给SSL/TLS,对数据加密,再交付给传输层。
加密
对称加密:只有一个秘钥,用这个秘钥进行加密和解密。
client加密,发送给server解密,中途可能会被劫持信息。所以不能直接对称加密。
https:秘钥协商过程
非对称加密:公钥、私钥。
通常:公钥是用来加密的,私钥是用来解密的。
client端向server端发起请求,server端给了公钥,该公钥只有加密功能。client端用拿到的公钥对对称加密的私钥进行加密,然后发送给server。server收到后用私钥对对称私钥进行解密,拿到对称的秘钥。此时对称秘钥就是被加密的。
为什么要用对称加密?
非对称加密算法往往比较复杂,效率比较低。
对称加密较快。
该秘钥协商过程就是SSL/TLS做的。
此时如果有一个黑客(中间服务器),它也有公钥、私钥,可以把client的公钥替换成他的公钥,clent端把私钥用公钥进行加密后发给server端,会被中间服务器劫持,然后对其用私钥进行解密,拿到对称秘钥。然后代替client访问server。
中间信息被篡改问题?
数据摘要+数据签名(指纹)
文本通过hash得到定长的字符序列:数据摘要
被篡改后的新闻本也通过哈希得到定长的字符序列(数据摘要)与原来的数据摘要不一样了。
将原本的内容形成的数据摘要与接收到的数据摘要进行对比,如果不一样就是信息被篡改该了。
当然黑客也可以在修改原文本的同时,把你的数据摘要也修改了。
实际server端在进行数据发送时,还要对数据摘要进行秘钥加密,此时得到的加密的摘要叫做数据签名(指纹)
即便黑客获取了内容并篡改,也破解不了被秘钥加密的数据签名。
我们对数据签名进行解密,拿到摘要,根据内容形成新的摘要,一对比就知道是否被篡改。
远端服务器进程身份认证问题?
在这里,公钥也可以进行解密,私钥也可以用来进行加密。
client在通信前,先向服务器要公钥和权威信息(可以认为是一种摘要信息,ca私钥加密变为指纹),将其和内置证书信息对比,如果能吻合就可以。
client能拿到公正处的ca公钥,然后将指纹用公钥进行解密,拿到摘要,将摘要与自己的内置证书信息对比。
如果中间人拿到公正的公钥,进行解密也只能拿到摘要,即使篡改也会被发现。
公钥加密,私钥解密:通常是用来进行数据通信。
私钥加密,公钥认证:用来进行认证。
注:win+r输入cerimgr.msc可以看到内置的公正信息。
总结:
https相比http做了很多安全性考量,不过相对也会慢一些。
安全只能有相对的安全。
文章评论