HTTP
HTTP协议(Hypertext Transfer Protocol,超文本传输协议)是一种应用层协议,主要用于在Web浏览器和Web服务器之间传输数据。
一、认识URL
http请求样例
看起来是一行一行的,但是本质还是一个大字符串,中间以换行符分割,跟我们在自定义协议时操作一样,通过换行对字符串进行解析
如何理解web根目录?
web根目录实际就是服务器上的一个指定的目录,里面存放了所有http的资源,如果请求url= / ,我们请求的是默认首页,我们会对请求的路径进行拼接,从而找到服务器中的某个资源,其他情况也是同理,都需要服务器进行相关的路径处理。
std::string GetFileContent(std::string path) // 读相应目录下的文件资源
{
std::ifstream in(path,std::ifstream::binary);
std::string content;
if (!in.is_open())
return "";
in.seekg(0,in.end);
size_t n = in.tellg();
in.seekg(0,in.beg);
std::vector<char> v(n);
in.read(v.data(),v.size());
in.close();
return std::string(v.begin(),v.end());
}
std::string Handler(std::string &request)
{
HttpRequest req;
req.Deserialize(request);// 反序列化http请求
req.Parse();// 解析 http 请求
req.Debug();
//创建响应报文
std::string httpstatusline = "http/1.1 200 ok"; //响应行
std::string content = GetFileContent(req.Path()); // 响应正文
std::string httpheader = "Content-Length: " + std::to_string(content.size()) + "\r\n";// 相应报头
httpheader += "\r\n"; // 空行
std::string httpresponse = httpstatusline + httpheader + content;
return httpresponse;
}
二、Get/post
我们上网不仅仅可以获取资源,也可以上传数据,可以结合html进行传参,如登录、注册、搜索等都需要我们向服务器传递数据。
Get:可以用来获取资源,也能用来传递参数
post:传递参数,通过正文进行传输
GET vs POST
- GET用url进行传参,POST请求正文传参
- url传参,字节个数有限制,POST方法,参数没有限制
- GET私密性更差一些,因为输入的参数会回显出来
但是GET和POST都不安全,GET能被看到,POST是明文传输,可以被捕捉到,安全和加密有关
一些其他方法的介绍
方法 | 说明 | 支持的http版本 |
PUT | 传输文件 | 1.0、1.1 |
HEAD | 获取报文首部 | 1.0、1.1 |
DELETE | 删除文件 | 1.1、1.1 |
OPTIONS | 询问支持的方法 | 1.1 |
TRACE | 追踪路径 | 1.1 |
CONNECT | 要求用隧道协议连接代理 | 1.1 |
三、http的状态码
类别 | 原因短语 | |
1XX | Information (信息性状态码) | 接收的请求正在处理 |
2XX | Success (成功状态码) | 请求正常处理完毕 |
3XX | Redirection (重定向状态码) | 需要进行附加操作以完成请求 |
4XX | Client Error (客户端错误码) | 服务器无法处理请求 |
5XX | Server Error (服务器错误状态码) | 服务器处理请求出错 |
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
简单说明一下:
- 1xx(信息性状态码):表示请求已经接收,继续处理。例如:
- 100 Continue:客户端应继续其请求。
- 101 Switching Protocols:服务器根据客户端的请求切换协议。
- 2xx(成功状态码):表示请求已经被成功接收、理解、并处理。例如:
- 200 OK:请求成功,成功被服务器接收、理解、并接受。
- 201 Created:请求已经被服务器接收并创建了新的资源。
- 204 No Content:服务器成功处理了部分GET请求,但没有返回任何内容。
- 3xx(重定向状态码):表示请求需要进一步操作,以完成请求。例如:
- 301 Moved Permanently:被请求的资源已永久移动到新位置,将来任何对此资源的引用都应该使用本响应返回的URI。
- 302 Found(或302 Moved Temporarily):请求的资源现在临时从不同的URI响应请求。
- 4xx(客户端错误状态码):表示客户端的请求有问题。例如:
- 400 Bad Request:请求错误,通常是访问的域名未绑定引起。
- 403 Forbidden:禁止访问。
- 5xx(服务器错误状态码):表示服务器处理请求时出现了错误
这里挑几个重要的说一下
1、404 Not Found
当我们访问的资源不存在时,就会返回,具体的实现就是将正文部分的内容变成下面的网页内容
2、307 Temporary Redirect 临时重定向
需要配合报头"Location: 网页链接"使用
3、301 Moved Permanently 永久移动
用法和307相同,但是两者的应用场景不一样
307:应用于页面的跳转,比如跳转到登录或注册页面
301:应用于当网络域名改变/网站过期,可以通过它来重定向之前的链接,让它指向新的域名网址
四、http的报头字段
- Content-Type: 数据类型(text/html等)
- Content-Length: Body的长度
- Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上
- User-Agent: 声明用户的操作系统和浏览器版本信息
- referer: 当前页面是从哪个页面跳转过来的
- location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问
- Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能
注意:请求的所有资源都有后缀,后缀决定了文件的类型。浏览器可以通过正文部分的文件后缀知道文件类型,但是有些浏览器无法自动识别文件类型,需要通过Content-Type来获取文件类型,所以http有专门的文件后缀和Content-Type的映射表,下面是网上截取的一部分
五、Cookie和session
HTTP协议中的无连接和无状态是其两个重要特性。
- 首先,无连接的含义是限制每次连接只处理一个请求。当服务器处理完客户的请求并收到客户的应答后,连接就会断开。这种设计方式的主要目的是节省传输时间,使得请求时建立连接,请求完成后释放连接,以便尽快将资源释放出来服务其他客户端。尽管无连接可以提高效率,但在某些情况下也可能导致额外的开销,因为每次请求都需要重新建立连接。然而,HTTP/1.1版本引入了Keep-Alive功能,使得客户端到服务器端的连接可以持续有效,当出现对服务器的后继请求时,可以避免重新建立连接。
- 其次,无状态指的是协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即我们给服务器发送HTTP请求之后,服务器根据请求会给我们发送数据过来,但是发送完不会记录任何信息。这意味着每个HTTP请求都是独立的,服务器不会记住前一个请求的状态或信息。这种设计方式简化了服务器的实现,并减轻了其负担,但也带来了一些问题。例如,由于每个请求都是独立的,单个请求所需要的所有信息都必须包含在请求中一次发送到服务器,这可能导致单个消息的结构变得复杂,需要支持大量的元数据。
为了解决HTTP无状态的问题,人们提出了多种方案,如使用cookie进行身份验证和状态管理。这样,服务器可以通过cookie来识别不同的客户端和它们的会话状态,从而在一定程度上模拟有状态的行为。
实际在使用浏览器访问网页时,尤其是那种需要vip身份才能访问特定资源的网站,都会要求我们去登录注册,用来标识每一个用户,但http是无状态的,即服务器不会记录用户的信息,所以每次我们在访问时,都需要发送身份信息,如果每次都需要用户手动输入,用户肯定会有砸掉电脑的冲动,所以浏览器可以用cookie对用户信息作记录,每次访问网页就可以自动帮我们在请求报文中加上我们的用户信息。
cookie分为内存级和文件级:
- 内存级:将信息存放在浏览器进程的上下文中,只在进程运行时有效,一旦关闭再打开就失效了
- 文件级:将信息存放在浏览器软件的特定目录的文件中,一直有效,除非手动删除/修改文件内容
通过把"Set-Cookie: username=zxws"这样的语句添加到http报头字段实现Cookie
这种基于Cookie实现的就是会话管理
但是这种会话管理并不安全,因为我们是用账户密码直接进行的http请求,很容易被别人拿到,为了加强安全性,我们一般将用户的账户和密码哈希成一个标识符sessionid,用它进行身份的验证
附录
// HttpProtocol.hpp
// 仅仅是模拟实现最简单的http的请求和响应的基本功能
// 代码并不全面和严谨,仅仅是为了方便我们直观的认识和了解http底层到底是如何做的
// 可以用网络套接字通信模拟实现一个简单的http网页服务,有兴趣可以尝试一下
#pragma once
#include "TcpServer.hpp"
#include <string>
#include <vector>
#include <sstream>
#include <fstream>
const std::string HttpSeq = "\r\n";
const std::string wwwroot = "./wwwroot";
const std::string homepage = "index.html";
class HttpRequest
{
public:
HttpRequest() : _path(wwwroot)
{
}
bool GetLine(std::string &request, std::string *line)
{
auto pos = request.find(HttpSeq);
if (pos == std::string::npos)
return false;
*line = request.substr(0, pos);
request.erase(0, pos + HttpSeq.size());
return true;
}
void Deserialize(std::string request)
{
std::string line;
bool ok = GetLine(request, &line);
if (!ok)
return;
_req_line = line;
while (1)
{
ok = GetLine(request, &line);
if (ok)
{
if (line.empty())
{
_req_content = request;
break;
}
else
{
_req_header.emplace_back(line);
}
}
else
{
break;
}
}
}
void Parse()
{
ParseLine();
if (_url == "/")
{
_path += _url;
_path += homepage;
}
else
{
_path += _url; // ps:需要处理一些只有路径没有文件资源的问题
}
}
std::string Suffix()
{
size_t pos = _path.rfind('.');
if (pos == std::string::npos)
return "";
return _path.substr(pos + 1);
}
std::string GetFileContentHelper(const std::string &path)
{
std::ifstream in(path, std::ifstream::binary);
std::string content;
if (!in.is_open())
return "";
in.seekg(0, in.end);
size_t n = in.tellg();
in.seekg(0, in.beg);
std::vector<char> v(n);
in.read(v.data(), v.size());
in.close();
return std::string(v.begin(), v.end());
}
std::string GetFileContent()
{
return GetFileContentHelper(_path);
}
void ParseLine()
{
std::stringstream ss(_req_line);
ss >> _method >> _url >> _version;
}
std::string Path()
{
return _path;
}
void Debug()
{
std::cout << _req_line << std::endl;
for (auto line : _req_header)
{
std::cout << line << std::endl;
}
std::cout << _req_blank << std::endl;
std::cout << _req_content << std::endl;
// std::cout << _method << std::endl;
// std::cout << _url << std::endl;
// std::cout << _version << std::endl;
}
std::string Get_404()
{
return GetFileContentHelper("./wwwroot/404.html"); // 该404.html网页大家可以自己写
}
private:
std::string _req_line;
std::vector<std::string> _req_header;
std::string _req_blank = "";
std::string _req_content;
// method url 版本号
std::string _method;
std::string _url;
std::string _version;
std::string _path;
std::string _suffix;
};
class HttpResponce
{
public:
HttpResponce() : _version("http/1.1"), _status_code(200), _status_code_desc("ok"), _resp_blank("\r\n")
{
}
void MakeStatusLine()
{
_status_line = _version + " " + std::to_string(_status_code) + " " + _status_code_desc;
}
void SetCode(int code)
{
_status_code = code;
}
void SetDesc(const std::string &desc)
{
_status_code_desc = desc;
}
void AddHeader(const std::string &header)
{
_resp_header.emplace_back(header);
}
void AddContent(const std::string &content)
{
_resp_content = content;
}
std::string Serialize()
{
std::string res = _status_line + HttpSeq;
for (auto &header : _resp_header)
res += header;
res += _resp_blank;
res += _resp_content;
// std::cout << res << std::endl;
return res;
}
private:
std::string _status_line;
std::vector<std::string> _resp_header;
std::string _resp_blank;
std::string _resp_content;
std::string _version; // 版本号
int _status_code; // 状态码
std::string _status_code_desc; // 状态信息
};