1.HTTP概念
应用层 典型的 协议 HTTP(超文本传输协议), 它是应用最广泛的协议 作用为:将任意内容拉取到本地浏览器,让浏览器进行解释
客户端client 把自己的"东西" 给别人 同时也想把 别人的"东西" 拿到自己本地 一般称为 CS 模式
http中的网页文本 、图片 、视频、音频 统一称为资源 东西实际上就是资源
2. URL
要访问服务器,就必须知道服务器的IP地址和端口号
需要有一个 域名解析服务 如: baidu.com (域名) 解析成 110.242.68.4(IP地址)
如:QQ官网
https 作为协议 www.qq.com 作为服务器地址
server的端口号不能随意指定,必须是众所周知且不能随便更改的 端口号和成熟的应用层协议是 一 一对应的
https 常用的端口号为443 http 常用的端口号为80
协议名称 和端口号之间是一对一 ,强相关 如:附近着火了,第一时间想起的就是打119 进行救火
由于http是超文本传输协议,就需要告诉别人要访问什么资源
第一个 / 表示 web根目录 第二个 / 表示 路径分隔符 / / 表示 URL想访问服务器的 什么资源
? 表示区分 URL 左侧 和右侧的分隔符 ? 后面跟的都是参数
参数是KV的 ,=左边的 uid 可看作是K ,=右边的 1 可看作V
URL 被称为 统一资源定位符
urlencode 和 urldecode
- 只搜索 ?/#: 这些特殊符号 发现特殊符号被转化为16进制格式数字 因为URL本身用一些字符作为特殊字符,所以在使用特殊字符时,所以特殊符号被转化为16进制格式数字,用来和URL本身的特殊字符进行区分 转化的过程 被称之为 URL的 encode编码,用于解决 URL中特殊符号的问题 这个工作是由浏览器或者客户端 自动做的
- 服务器收到的就为 16进制格式,而不想要16进制格式,想要特殊符号 ,就需要进行 decode编码
转义规则
将需要转码的字符转为 16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
点击查看:自动编码工具
在该网站上可以进行 urlencode 与urlencode 解码
3. HTTP的宏观理解
HTTP的请求
按照完整的说法,HTTP分为四部分
第一部分——请求行 HTTP的请求行,以行为单位,分为三部分 请求方法 URL 协议版本 请求方法: GET/ POST URL:请求资源 协议版本:http/1.0 http/1.1 http/2.0 三部分之间用空格作为分隔符,把这三部分 分离开
第二部分——请求报头 由 Key:Value 所构成的多行结构
第三部分——空行 rn
第四部分——有效载荷 一般是用户可能提交的参数 (可以没有)
HTTP的响应
状态行 分为 协议版本 状态码 状态码描述 三部分之间用空格作为分隔符,把这三部分 分离开 协议版本:http/1.0 http/1.1 http/2.0 状态码: 如404 状态码描述 : 404所对应的含义 如:Not Found
响应报头 也是 由Key:Value 所构成的多行结构
有效载荷 可能是 html /css的文件资源,也可能是请求对应的图片等
4. 见一见HTTP请求和响应
请求报头
当从浏览器输入 主机IP 端口号 ,Linux上显示如下数据
GET / HTTP/1.1 第一行作为 请求行
由 Key Value 构成的 多行结构 作为 请求报头 并没有包含 有效载荷 Host 表示 这次请求给哪台主机,一般为目标服务器的IP地址和端口号 Connection 表示 这次请求的链接模式 长/短链接 Cache-control 表示 双方在通信时 要建立缓存,最大缓存的生存时间默认为0(不缓存) User_Agent 表示 HTTP请求的客户端信息 Accept_Encodong 表示 作为客户端,能接受的编码和压缩类型 Accept_Language 表示 作为客户端,能接受编码符号
1. 模拟一个简单的响应response
创建一个Main.cc,通过调用 回调函数HandlerHttp的方式来实现整个过程
对于回调函数 HandlerHttp,在是一个完整的http请求报文的前提下,分别将状态行 分隔符 有效载荷 添加到 response响应中,并将 响应返回 有效载荷部分以网页部分呈现的
响应报头
进行文本分析时,按行进行分割读取,直到找到一行是空行,则认为把报头读完了
报头中key 为 Content-Length ,Value 为 Body的长度(有效载荷的长度)
当在Linux上运行程序,并输入端口号时 浏览器上 输入 主机IP 端口号 ,就会使主函数 调用回调函数 打印 this a test 同时Linux会出现如下数据 响应的 状态行 响应报头 空行 有效载荷
由于有效载荷内部分为 图片、视频、音频 资源 为了便于区分 使用 Content_Type :Body的种类
图片、视频、音频 资源 这些资源本质都是文件
图片的后缀为.png 网页的后缀为.html 视频的后缀为.mp3 Linux资源都要有自己的后缀,需要告诉别人 ,就需要 Content-Type 对照表
若后缀为.html,则 Content-Type 对照表 为 text/html 若后缀为.png,则 Content-Type 对照表 为 image/png
在响应后 添加 网页的Content-Type 对照表 text/html,以及 SEP分隔符
2. 从路径中获取内容
给http维护一个自己的目录,即 wwroot 创建 index.html 里面放入这个网页中的所有资源
创建 Until.hpp 在Until这个类中,创建一个接口 ReadFile 用于读取整个文件内容
第一个参数 path 为指定的路径 第二个参数file_content 表示输出 即文件对应的内容
path表示路径,在wwwroot目录下的index.html中获取文件 将获取到的文件交给 字符串body
ReadFile函数的实现
1. 获取文件本身的大小 输入 man 2 stat
对指定的文件路径,获取它的struct stat 属性 成功返回0,失败返回-1
st_size 表示这个文件 按字节为单位的大小 st_mode: 匹配很多的宏
2. 调整string的空间 保证能够把文件全部放下
开辟size大小个空间
3. 读取
O_RDONLY 读取
path作为路径,可以找到对应 index.html的内容,再将内容传给body字符串中,作为有效载荷
3.不同资源进行区分
只有是请求,无脑响应的都是这些资源
若请求到不同的资源,应该加以区分 用户想要什么就给他什么,没有就返回404
把request 进行处理,进行反序列化,由字符串信息变成结构化字段 创建一个 HttpRequest 结构体 里面包含 状态行的请求方法、URL、请求版本以及请求报头
URL作为请求资源,所以将 path替换成 req.url_ 即可
反序列化的实现
在主函数Main.cc中 创建ReadOneLine函数,将message中的第一行的请求行取出 创建 ParseRequestLine函数,将 请求行解析成 请求方法、URL、协议版本
两个函数都在Util.hpp中实现
ReadOneLine函数的实现
加上static修饰,是为了防止有隐藏的this指针存在 使用find函数寻找sep分隔符,若找到则返回pos位置的下标 使用substr函数 取出[0,pos]区间的子串 作为返回值 使用 erase函数 将下标从0开始 删除 pos sep.size()个字符
ParseRequestLine函数的实现
sstream 流 按照空格作为分隔符,打印到三个string中
路径path的最终表示
路径path是需要加上 web根目录的
所以定义一个web根目录 webRoot
在使用请求时,先在路径path中 加入web根目录 ,再添加对应的 URL(请求资源)
4. 同时显示 文字 和 图片
点击查看:石榴花图片
在wwwroot中 创建 image文件,并进入inmage中
wget :远程获取资源的命令
使用 wget 图片地址,获取图片
使用 mv 指令 ,将 原图片名字改为 1.jpg
此时在vscode中的 image 文件中,就可以显示图片了
一张网页包含很多要素资源,如:图片 文字 视频 每一个资源都要发起一次http请求
在浏览器中搜索 w3cschool
在HTML教程中,找到HTML图像,其中寻找到 替换文本属性
第一个/表示 web根目录 即wwwroot 在wwroot目录下找到image文件中的 1.jpg
若获取图片失败,则会显示文字 这是一张石榴花图片
由于这次资源既包含文字 又包含图片,所以类型不同,需要处理 Content-Type (body的种类)
添加成员变量,判断 要访问的是什么资源(如:图片 文字)
在反序列化函数中 使用 rfind 函数 ,从后往前 查找 字符 . ,再使用substr 函数 从下标 pos开始取len个字符 若没有给len,则一直取到path_字符串结束
在HandlerHttp函数的 使用请求中 将 Content-Type (body的种类) 进行封装成 一个GetContentType的接口
GetContentType函数的实现
若后缀为.html,则 Content-Type 对照表 为 text/html 若后缀为.css,则 Content-Type 对照表 为 test/css 若后缀为.js,则 Content-Type 对照表 为 application/x-javascript 若后缀为.png,则 Content-Type 对照表 为 image/png 若后缀为.jpg,则 Content-Type 对照表 为 image/jpeg
在浏览器 输入 主机IP 端口号 ,发现图片并没有显示,而且出现了乱码
网页必须指明编码格式,否则就会出现乱码 所以修改index.html的内容
再次输入 主机IP 和端口号 就可以同时显示 文字 和图片了
5.模拟的完整代码
wwwroot
index.html(图片)
代码语言:javascript复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charest="UTF-8">
<meta name="viewport" content="width=device-width" ,initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>this is a test </h1>
<h1>this is a test </h1>
<h1>this is a test </h1>
<h1>this is a test </h1>
<img src="/image/1.jpg" alt="这是一张石榴花图片">
</body>
</html>
Err.hpp(错误)
代码语言:javascript复制#pragma once
enum
{
USAGE_ERR=1,
SOCKET_ERR,//2
BIND_ERR,//3
LISTEN_ERR,//4
SETSID_ERR,//5
OPEN_ERR//6
};
HttpServer.hpp(初始化和启动)
代码语言:javascript复制#include<iostream>
#include<string>
#include<pthread.h>
#include<functional>
#include"Sock.hpp"
static const uint16_t defaultport=8888;//默认端口号
class HttpServer;
//定义 func_t 类型 为 返回值为string 参数为string的包装器
using func_t =std::function<std::string( std::string&)>;
class ThreadData
{
public:
ThreadData(int sock,std::string ip,const uint16_t& port,HttpServer*tsvrp)//构造
:_sock(sock),_ip(ip),_port(port),_tsvrp(tsvrp)
{}
~ThreadData()
{}
public:
int _sock;//套接字
HttpServer *_tsvrp;//指针指向Tcp服务器
std::string _ip;
uint16_t _port;
};
class HttpServer
{
public:
HttpServer(func_t f,int port= defaultport)
:func(f),port_(port)
{}
void InitServer()//初始化
{
listensock_.Socket();//创建套接字
listensock_.Bind(port_);//绑定
listensock_.Listen();//监听
}
void HandlerHttpRequest(int sock)//
{
char buffer[4096];
std::string request;
//将套接字的数据读取到buffer中
ssize_t s=recv(sock,buffer,sizeof(buffer)-1,0);
if(s>0)//读取成功
{
buffer[s]=0;//将'