Linux网络:HTTP协议
- 零、前言
- HTTP协议
- 1、认识URL
- 2、urlencode和urldecode
- 3、HTTP协议格式
- 1)HTTP请求
- 2)HTTP响应
- 4、HTTP的方法
- 5、HTTP的状态码
- 6、HTTP常见的Header
- 7、Cookie和Session
零、前言
在此之前我们对网络套接字编程有了一定的基础和了解,接下来我们将自顶向下学习Linux网络分层协议栈,透过对协议栈的深入学习从而加深我们对网络的理解
HTTP协议
- 概念及介绍:
- HTTP(Hyper Text Transfer Protocol)协议又叫做超文本传输协议,是一个简单的请求-响应协议,HTTP通常运行在TCP之上
- 在编写网络通信代码时,我们可以自己进行协议的定制,但实际有很多优秀的工程师早就已经写出了许多非常成熟的应用层协议,其中最典型的就是HTTP协议
1、认识URL
URL(Uniform Resource Lacator)叫做统一资源定位符,也就是我们通常所说的网址,是因特网的万维网服务程序上用于指定信息位置的表示方法
- 示图:
- 一个URL大致由如下几部分构成:
- 协议方案名称
- 协议名称表示请求时需要使用的协议,通常使用的是HTTP协议或安全协议HTTPS
- HTTPS是以安全为目标的HTTP通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性
- 登录信息
- 登录认证信息包括登录用户的用户名和密码,登录认证信息可以在URL中体现出来
- 绝大多数URL的这个字段都是被省略的,因为登录信息可以通过其他方案交付给服务器
- 服务器地址
- 服务器地址也叫做域名,比如
www.alibaba.com
,www.qq.com
,www.baidu.com
- 在计算机的世界中用IP地址标识公网内的一台主机,但IP地址是一串数字并不适合用户使用,为了方便用户从而有了具有更好的自描述性的域名
- 实际上域名和IP地址是等价的,在计算机当中使用的时候既可以使用域名,也可以使用IP地址
ping
命令获取域名解析后的IP地址:
- 服务器端口号
- HTTP协议和套接字编程一样都是位于应用层的,进行网络数据传输时需要主动确定服务端的ip和port
- 常用的服务与端口号之间的对应关系都是明确的,所以使用时不要指明该协议对应的端口号的,而URL中也通常省略服务器的端口号
- 带层次的文件路径
要获取(访问)的应用资源的路径,即资源的存储位置,一般会使用“/”来分级描述
- 注意:
- 比如我们打开浏览器输入百度的域名后,此时浏览器就帮我们获取到了百度的首页,我们可以将这种资源称为网页资源,此外我们还会向服务器请求视频、音频、网页、图片等资源
- HTTP之所以叫做超文本传输协议,而不叫做文本传输协议,就是因为有很多资源实际并不是普通的文本资源
- 从这里的路径分隔符,我们可以分辨服务器的平台:Linux的路径分隔符是
/
,Windows的路径分隔符是 - 查询字符串
用于获取资源时,向服务器端传递参数,可以一个或多个,多个则以”&”连接,通常以“?”作为开始符号,例如例子“?q=java”表示传递的搜索参数java,即该应用url表示搜索java方面的内容
- 片段标识符
也叫做哈希值,通常以#开始,表示定位到页面某个位置(或者说定位到页面的锚点,熟悉前端的人应该知道锚点是指页面某个部分的id),这部分内容不传到服务器端,而是用于前端页面定位显示
2、urlencode和urldecode
- 概念及介绍:
- 像 / ? : 等这样的字符, 已经被url当做特殊意义理解了,因此这些字符不能随意出现
- 如某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义
- 示例:
- 转义规则:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
3、HTTP协议格式
1)HTTP请求
- 请求格式示图:
- 请求格式组成:
- 首行: [方法] [url] [版本]
- Header: 请求的属性, 冒号分割的键值对;每组属性之间使用n分隔;遇到空行表示Header部分结束
- Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个
- Content-Length属性来标识Body的长度 注:前面三部分是一般是HTTP协议自带的,是由HTTP协议自行设置的,而请求正文一般是用户的相关信息或数据;如果用户在请求时没有信息要上传给服务器,此时请求正文就为空字符串
- 示例获取HTTP请求:
- 用套接字编写一个TCP服务器,使用浏览器访问服务器的ip和port,也就是使用浏览器发起http请求
- 服务端不对这个HTTP请求进行过任何解析,直接将http请求进行打印输出
- http服务器代码:
http_server.hpp:
#pragma once
#include<iostream>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<strings.h>
namespace ns_Http
{
class HttpServer
{
private:
uint16_t port;
int listen_sock;
public:
HttpServer(uint16_t _port):port(_port),listen_sock(-1){}
~HttpServer()
{
if(listen_sock>=0) close(listen_sock);
}
void InitHttpServer()
{
listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0)
exit(2);
struct sockaddr_in local;
socklen_t len=sizeof(local);
bzero(&local,len);
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(listen_sock,(struct sockaddr*)&local,len)<0)
exit(3);
if(listen(listen_sock,5)<0)
exit(4);
}
static void* Routine(void* args)
{
int sock=*((int*)args);
delete (int*)args;
pthread_detach(pthread_self());
//逻辑处理
std::cout<<">-------------------------------begin-------------------------------<sock:"<<sock<<std::endl;
char buffer[1024]={0};
ssize_t s=recv(sock,buffer,sizeof(buffer)-1,0);
if(s>0)
{
std::cout<<buffer<<std::endl;
}
std::cout<<">--------------------------------end--------------------------------<sock:"<<sock<<std::endl;
close(sock);
return nullptr;
}
void Loop()
{
while(true)
{
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
bzero(&peer,len);
int sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
if(sock<0)
continue;
pthread_t tid;
int* p=new int(sock);
pthread_create(&tid,nullptr,Routine,p);
}
}
};
}
http_server.cc:
#include "http_server.hpp"
int main(int argc,char* argv[])
{
if(argc!=2)
{
std::cout<<"Usage:nt./http_server port"<<std::endl;
exit(1);
}
ns_Http::HttpServer hs(atoi(argv[1]));
hs.InitHttpServer();
hs.Loop();
return 0;
}
- 效果:
- 示图:
- 请求行格式:
[请求方法] [url] [版本] (空格分开)
- 请求报头格式:
Name:[空格]内容(一行就是一个属性,这里的“行”是以换行符作为标准)
- 请求报头内容组成:
- Host :请求的资源在哪个主机的端口上
- Connection:该请求支持长连接(heep_alive)
- Content-Length:正文内容长度
- Content-Type:数据类型
- User-Agent:声明用户的操作系统和浏览器版本信息
- Accent:发起了请求
- Referer:当前页面是从哪个页面跳转过来的
- Accept-Encoding:接受的编码
- Accept-Language:接受的语言类型
- Cookie:用于在客户端存储少量信息,通常用于实现会话(session)功能
- HTTP如何进行解包:
- 请求行和请求报头是HTTP的报头信息,而这里的请求正文实际就是HTTP的有效载荷,而请求当中的空行起到分离报头和有效载荷的作用
- 读取一个请求时,通过报头中的Content-Length(正文的长度)来精准控制读取该请求正文的长度,从而将连续的几个请求进行分开
- HTTP如何进行分用:
理论上 HTTP 不需要向上交付,HTTP已经是最上一层的协议,但是上一层还有用户,需要将正文、请求方法和属性等交给用户
2)HTTP响应
- 响应格式示图:
- 响应格式组成:
- 首行:[版本号] [状态码] [状态码解释]
- Header:请求的属性,冒号分割的键值对;每组属性之间使用n分隔;遇到空行表示Header部分结束
- Body:空行后面的内容都是Body,Body允许为空字符串,如果Body存在,则在Header中会有一个 Content-Length属性来标识Body的长度;如果服务器返回了一个html页面,那么html页面内容就是在body中
- 获取响应示例:http服务器代码构建响应
std::string response;
std::ifstream in(HOME_PAGE,std::ios::in|std::ios::binary);
std::string body;
std::string line;
if(!in.is_open())//打开失败则发送404页面
{
std::ifstream _in(PAGE_404,std::ios::in|std::ios::binary);
while(getline(_in,line))
body =line;
response = "HTTP/1.0 404 Not Foundn";
response = "Content-Type: text/htmln";
response = ("Content-Length: " std::to_string(body.size()) "n");
response = "n";
response = body;
send(sock, response.c_str(), response.size(), 0);
}
else//打开成功则发送首页页面
{
while(getline(in,line))
body =line;
response = "HTTP/1.0 200 OKn";
response = "Content-Type: text/htmln";
response = ("Content-Length: " std::to_string(body.size()) "n");
response = "n";
response = body;
send(sock, response.c_str(), response.size(), 0);
}
- 适用网页获取响应:
- 使用postman进行GET方法获取响应:
- 使用telnet命令获取响应:
注:客户端在发起HTTP请求是会告诉服务器自己所使用的http版本,此时服务器就可以根据客户端使用的http版本,为客户端提供对应的服务,而不至于因为双方使用的http版本不同而导致无法正常通信
4、HTTP的方法
- 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 |
UNLINK | 断开连接关系 | 1.0 |
注:其中最常用的就是GET方法和POST方法
- GET方法和POST方法对比:
- GET方法一般用于获取某种资源信息,而POST方法一般用于将数据上传给服务器,上传数据时也有可能使用GET方法,比如搜索提交数据时
- GET方法和POST方法都可以带参:GET方法是通过url传参的;POST方法是通过正文传参的
- POST方法通过正文传参能传递更多的参数,而url的长度是有限,所以GET方式传参有限
- POST方法传参更加私密,因为GET方法会将参数回显到url当中,POST方法在正文中不会被别人轻易看到。但是实际两种方法都不安全,POST方法传参可以被截取,要做到安全只能通过加密来完成
- 参数提交GET和post方式演示:
注:表单当中的method属性指定参数提交的方法,action属性表示将表单中的参数提交给服务器上的哪个资源位置
- GET方式示图:
- post方式示图:
5、HTTP的状态码
在开发好了网站后,用户通过URL对资源进行操作,服务器端要告诉用户交互的结果,比如新增资源是成功还是失败了。一个较好的办法就是遵循HTTP协议,使用请求响应的HTTP状态码(Status Code)来进行判断
- HTTP的状态码:
状态码 | 类别 | 原因短语 |
---|---|---|
1XX | Informational(信息性状态码) | 接收的请求正在处理 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
注:最常见的状态码如200(OK),404(Not Found),403(Forbidden请求权限不够),302(Redirect),504(Bad Gateway)
- 常见的状态码有:
- 200 OK:客户端请求成功
- 301 Permanent Redirect:永久重定向,表示资源已经永久移动到另一个位置
- 307/302 Temporary Redirect:临时重定向,表示资源临时移动到了另一个位置
- 403 Forbidden:指的是服务器端有能力处理该请求,但是拒绝授权访问
- 404 Not Found:请求资源不存在,比如资源被删除了,或用户输入了错误的URL
- 500 Internal Server Error:服务器发生不可预期的错误,一般是代码的BUG所导致的
- 502 Bad Gateway:表示作为网关或代理角色的服务器,从上游服务器(如tomcat、php-fpm)中接收到的响应是无效的
- 重定向状态码:
- 重定向就是通过各种方法将各种网络请求重新定个方向转到其它位置,此时这个服务器相当于提供了一个引路的服务
- 重定向又可分为临时重定向和永久重定向,其中状态码301表示的就是永久重定向,而状态码302和307表示的是临时重定向
- 永久重定向第一次访问浏览器进行重定向,并且更新客户端的标签,后续再访问直接就是重定向后的网站;临时重定向,每次访问该网站时都需要浏览器来帮我们完成重定向跳转到目标网站
- 临时重定向演示:
进行临时重定向时需要用到Location字段,Location字段是HTTP报头当中的一个属性信息,该字段表明了你所要重定向到的目标网站
- 构建临时重定向http响应代码:
//构建HTTP响应
std::string response = "HTTP/1.0 307 Temporary Redirectn"; //状态行
response = "Location: https://coca1cole.blog.csdn.net/n"; //跳转页面
response = "n"; //空行
send(sock, response.c_str(), response.size(), 0);
close(sock);
- 效果:
6、HTTP常见的Header
- HTTP常见的Header:
- Content-Type:数据类型(text/html等)
- Content-Length:正文的长度
- Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上 注:Host字段表明了客户端要访问的服务的IP和端口,有些服务器实际提供的是一种代理服务,也就是代替客户端向其他服务器发起请求,然后将请求得到的结果再返回给客户端,在这种情况下客户端就必须告诉代理服务器它要访问的服务对应的IP和端口
- User-Agent:声明用户的操作系统和浏览器的版本信息 注:User-Agent代表的是客户端对应的操作系统和浏览器的版本信息,访问一些网站是就会根据主机的版本和系统推送相匹配的服务
- Referer:当前页面是哪个页面跳转过来的 注:Referer记录上一个页面的好处一方面是方便回退,另一方面可以知道我们当前页面与上一个页面之间的相关性
- Location:搭配3XX状态码使用,告诉客户端接下来要去哪里访问
- Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能
- Keep-Alive(长连接):
- HTTP/1.0是通过request&response的方式来进行请求和响应的,HTTP/1.0常见的工作方式就是客户端和服务器先建立链接,然后客户端发起请求给服务器,服务器再对该请求进行响应,然后立马端口连接
- 现在主流的HTTP/1.1是支持长连接的,所谓的长连接就是建立连接后,客户端可以不断的向服务器一次写入多个HTTP请求,而服务器在上层依次读取这些请求就行了,此时一条连接就可以传送大量的请求和响应
7、Cookie和Session
- 概念及介绍:
- HTTP实际上是一种无状态协议,HTTP的每次请求/响应之间是没有任何关系的,但你在使用浏览器的时候发现并不是这样的
- 当你登录一次能某网站账号后,再将网站关了甚至是重启电脑,再次网站时并没有要求你再次输入账号和密码(账号还是登录好的状态),这实际上是通过cookie技术实现的
- cookie技术原理:
- 因为HTTP是一种无状态协议,每次进行http请求时都不会保存之前的一种页面转态(比如用户登录),所以每当都要需要重新输入账号和密码进行认证(客户端提交账号和密码参数进行认证)
- 而cookie是内置到HTTP协议当中的一种保存状态技术,当认证通过后服务端响应给客户端进行Set-Cookie,客户端收到响应后会自动将Set-Cookie的值保存在cookie文件当中,接下来每次进行http请求的同时都会将之前页面的cookie参数一同进行提交,从而达到了之前状态的保存的效果
- cookie的弊端:
cookie虽然在持久保存客户端数据提供了方便,但是如果cookie被人拦截了,那人就可以取得期中的参数信息。如果是账号和密码,那么就存在账号被盗以及账号被利用做坏事
- session技术及原理:
- 单纯的使用cookie是非常不安全的,因为此时cookie文件当中就保存的是你的私密信息,一旦cookie泄漏你的隐私信息也就泄漏
- 当我们第一次登录某个网站输入账号和密码后,服务器认证成功后会生成一个哈希出来的SessionID,这个SessionID与用户信息是不相关的,系统会将所有登录用户的账号和SessionID值维护起来
- 当认证通过后服务端会将这个生成的SessionID值响应给客户端,客户端收到响应后会自动提取出SessionID的值并保存在浏览器的cookie文件当中,后续访问该服务器时,对应的HTTP请求当中就会自动携带上这个SessionID进行身份验证
- 而服务器识别到HTTP当中的SessionID,再到对应的数据库当中进行对比,对比成功就说明这个用户是曾经登录过的,即认证成功
注:引入session技术后,浏览器当中的cookie文件保存的是SessionID,同样的这个cookie文件可能被盗取,但是账号和密码并不会被泄漏,而是对应的SessionID是会泄漏的,此时非法用户仍然可以盗取我的SessionID去访问我曾经访问过的服务器,相当于依旧存在利用SessionId进行账号登录并利用账号做坏事
- cookie 和session的区别:
- cookie将数据存放在客户的浏览器上;session将数据放在服务器中,将sessionid存在客户端中
- cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当加入session技术
- session会在一定时间内保存在服务器上,当访问增多会占用服务器的性能,考虑到减轻服务器性能方面应当使用cookie
- 建议将登陆信息等重要信息存放为session,其他信息如果需要保留可以放在cookie中
- cookie技术的演示:
在服务器给客户端的HTTP响应当中设置Set-Cookie字段,即使用cookie技术
- 构建响应代码:
response = "HTTP/1.0 200 OKn";
response = "Content-Type: text/htmln";
response = ("Content-Length: " std::to_string(body.size()) "n");
response = "Set-Cookie: sessionid=123456n"; //添加Set-Cookie字段
response = "n";
response = body;
send(sock, response.c_str(), response.size(), 0);
- 运行效果: