🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12891150.html
目录
前言
💬 hello! 各位铁子们大家好哇。
今日更新了Linux网络http的内容
🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝
HTTP 协议
虽然我们说, 应用层协议是我们程序猿自己定的. 但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其中之一。
在互联网世界中,HTTP(HyperText Transfer Protocol,超文本传输协议)是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如 HTML 文档)。
HTTP 协议是客户端与服务器之间通信的基础。客户端通过 HTTP 协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP 协议是一个无连接、无状态的协议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息。
认识 URL
平时我们俗称的 "网址" 其实就是说的 URL
域名会被我们客户端的应用自动转换成ip地址,叫做地址转化,也称DNS。
上面的url中,没有端口号,这里的端口号默认被忽略了,他们会默认绑定某个知名端口,所以显示上会被忽略。当浏览器发起请求时,会自动拼接端口号。
域名后面是路径,可以表示文件的唯一性。
域名表示唯一一台主机,路径表示主机上唯一的文件资源,二者结合就可以表示互联网中唯一的一个文件资源!
所以,URL也称为统一资源定位符。
urlencode 和 urldecode
像 / ? : 等这样的字符, 已经被 url 当做特殊意义理解了. 因此这些字符不能随意出现. 比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为 16 进制,然后从右到左,取 4 位(不足4 位直接处理),每2位做一位,前面加上%,编码成%XY 格式
这种对特殊字符进行编码处理的操作叫做urlencode。
urldecode 就是 urlencode 的逆过程;
HTTP 协议请求与响应格式
Http请求
- 首行: [方法] + [url] + [版本]
- Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\r\n 分隔;遇到空行表示 Header 部分结束
- Body: 空行后面的内容都是 Body. Body 允许为空字符串. 如果Body 存在, 则在Header 中会有一个 Content-Length 属性来标识 Body 的长度;
HTTP 响应
- 首行: [版本号] + [状态码] + [状态码解释]
- Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\r\n 分隔;遇到空行表示 Header 部分结束
- Body: 空行后面的内容都是 Body. Body 允许为空字符串. 如果Body 存在, 则在Header 中会有一个 Content-Length 属性来标识 Body 的长度; 如果服务器返回了一个 html 页面, 那么 html 页面内容就是在 body 中.
HTTP 常见 Header
- Content-Type: 数据类型(text/html 等)
- Content-Length: Body 的长度
- Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
- User-Agent: 声明用户的操作系统和浏览器版本信息;
- referer: 当前页面是从哪个页面跳转过来的;
- Location: 搭配 3xx 状态码使用, 告诉客户端接下来要去哪里访问;
- Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能
关于 connection 报头
HTTP 中的 Connection 字段是 HTTP 报文头的一部分,它主要用于控制和管理客户端与服务器之间的连接状态
核心作用:
- 管理持久连接:Connection 字段还用于管理持久连接(也称为长连接)。持久连接允许客户端和服务器在请求/响应完成后不立即关闭 TCP 连接,以便在同一个连接上发送多个请求和接收多个响应。
持久连接(长连接):
- HTTP/1.1:在 HTTP/1.1 协议中,默认使用持久连接。当客户端和服务器都不明确指定关闭连接时,连接将保持打开状态,以便后续的请求和响应可以复用同一个连接。
- HTTP/1.0:在 HTTP/1.0 协议中,默认连接是非持久的。如果希望在HTTP/1.0上实现持久连接,需要在请求头中显式设置 Connection: keep-alive
语法格式:
- Connection: keep-alive:表示希望保持连接以复用 TCP 连接。
- Connection: close:表示请求/响应完成后,应该关闭TCP 连接
HTTP 的状态码
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
以下是仅包含重定向相关状态码的表格:
HTTP 状态码 301(永久重定向)和 302(临时重定向)都依赖Location 选项。以下是关于两者依赖 Location 选项的详细说明:
HTTP 状态码 301(永久重定向):
- 当服务器返回 HTTP 301 状态码时,表示请求的资源已经被永久移动到新的位置。
- 在这种情况下,服务器会在响应中添加一个 Location 头部,用于指定资源的新位置。这个 Location 头部包含了新的 URL 地址,浏览器会自动重定向到该地址
例如,在 HTTP 响应中,可能会看到类似于以下的头部信息:
HTTP/1.1 301 Moved Permanently\r\n
Location: https://www.new-url.com\r\n
HTTP 状态码 302(临时重定向):
- 当服务器返回 HTTP 302 状态码时,表示请求的资源临时被移动到新的位置。
- 同样地,服务器也会在响应中添加一个 Location 头部来指定资源的新位置。浏览器会暂时使用新的 URL 进行后续的请求,但不会缓存这个重定向。
总结:无论是 HTTP 301 还是 HTTP 302 重定向,都需要依赖Location 选项来指定资源的新位置。这个 Location 选项是一个标准的 HTTP 响应头部,用于告诉浏览器应该将请求重定向到哪个新的 URL 地址。
HTTP 的方法
其中最常用的就是 GET 方法和 POST 方法
HTTP 常见方法
GET 方法
- 用途:用于请求 URL 指定的资源。
- 示例:GET /index.html HTTP/1.1
- 特性:指定资源经服务器端解析后返回响应内容
POST 方法
- 用途:用于传输实体的主体,通常用于提交表单数据。
- 示例:POST /submit.cgi HTTP/1.1
- 特性:可以发送大量的数据给服务器,并且数据包含在请求体中
总结:
GET一般用来获取静态资源,也可以通过url向服务器传递参数。
POST可以通过 http request 的正文来进行参数传递
url传递参数,参数的体量一定不大,正文可以很大。
POST方法,比GET方法传参更私密,但都不安全。
HEAD 方法
- 用途:与 GET 方法类似,但不返回报文主体部分,仅返回响应头。
- 示例:HEAD /index.html HTTP/1.1
- 特性:用于确认 URL 的有效性及资源更新的日期时间等。
DELETE 方法
- 用途:用于删除文件,是 PUT 的相反方法。
- 示例:DELETE /example.html HTTP/1.1
- 特性:按请求 URL 删除指定的资源
OPTIONS 方法
- 用途:用于查询针对请求 URL 指定的资源支持的方法。
- 示例:OPTIONS * HTTP/1.1
- 特性:返回允许的方法,如 GET、POST 等。
部分http代码
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include<functional>
#include <fstream>
#include <unordered_map>
const static std::string base_sep = "\r\n";
const static std::string line_sep = ": ";
const static std::string prefixpath = "wwwroot"; // web根目录
const static std::string homepage = "index.html";
const static std::string httpversion = "HTTP/1.0";
const static std::string spacesep = " ";
const static std::string suffixsep = ".";
const static std::string html_404 = "404.html";
const static std::string arg_sep = "?";
class HttpRequest
{
private:
std::string GetLine(std::string &reqstr)
{
auto pos = reqstr.find(base_sep);
if (pos == std::string::npos)
return std::string();
std::string line = reqstr.substr(0, pos);
reqstr.erase(0, line.size() + base_sep.size());
return line.empty() ? base_sep : line;
}
void ParseReqLine()
{
std::stringstream ss(_req_line); // cin >>
ss >> _method >> _url >> _version;//a/b/c.html or /login?user=XXX&passwd=1234 /register
if(strcasecmp(_method.c_str(),"GET")==0)
{
auto pos=_url.find(arg_sep);
if(pos!=std::string::npos)
{
_body_text=_url.substr(pos+arg_sep.size());
_url.resize(pos);
}
}
_path += _url;
if (_path[_path.size() - 1] == '/')
{
_path += homepage;
}
// wwwroot/index.html
// wwwroot/image/1.png
auto pos = _path.rfind(suffixsep);
if (pos != std::string::npos)
{
_suffix = _path.substr(pos);
}
else
{
_suffix = ".default";
}
}
void ParseReqHeader()
{
for (auto &header : _req_headers)
{
auto pos = header.find(line_sep);
if (pos == std::string::npos)
continue;
std::string k = header.substr(0, pos);
std::string v = header.substr(pos + line_sep.size());
if (k.empty() || v.empty())
continue;
_headers_kv.insert(std::make_pair(k, v));
}
}
public:
HttpRequest() : _blank_line(base_sep), _path(prefixpath)
{
}
void Deserialize(std::string &reqstr)
{
// 基本的反序列化
_req_line = GetLine(reqstr);
std::string header;
do
{
header = GetLine(reqstr);
if (header.empty())
break;
else if (header == base_sep)
break;
_req_headers.push_back(header);
} while (true);
if (!reqstr.empty())
{
_body_text = reqstr;
}
// 再进一步反序列化
ParseReqLine();
ParseReqHeader();
}
std::string Url()
{
LOG(DEBUG, "Client Want url %s\n", _url.c_str());
return _url;
}
std::string Path()
{
LOG(DEBUG, "Client Want path %s\n", _path.c_str());
return _path;
}
std::string Suffix()
{
return _suffix;
}
std::string Method()
{
LOG(DEBUG, "Client request method is %s\n", _method.c_str());
return _method;
}
std::string GetRequestBody()
{
LOG(DEBUG, "Client request method is %s, args: %s, request path: %s\n", _method.c_str(),_body_text.c_str(),_path.c_str());
return _body_text;
}
void Print()
{
std::cout << "---------------------------" << std::endl;
std::cout << "###" << _req_line << std::endl;
for (auto &header : _req_headers)
{
std::cout << "@@@" << header << std::endl;
}
std::cout << "***" << _blank_line;
std::cout << ">>>" << _body_text << std::endl;
std::cout << "Method:" << _method << std::endl;
std::cout << "Url:" << _url << std::endl;
std::cout << "Version:" << _version << std::endl;
for (auto &header_kv : _headers_kv)
{
std::cout << ")))" << header_kv.first << "->" << header_kv.second << std::endl;
}
}
~HttpRequest()
{
}
private:
// 基本的httprequest的格式
std::string _req_line;
std::vector<std::string> _req_headers;
std::string _blank_line;
std::string _body_text;
// 更具体的属性字段,需要进一步反序列化
std::string _method;
std::string _url;
std::string _path;
std::string _suffix; // 资源后缀
std::string _version;
std::unordered_map<std::string, std::string> _headers_kv;
};
class HttpResponse
{
public:
HttpResponse() : _version(httpversion), _blank_line(base_sep)
{
}
void AddCode(int code, const std::string &desc)
{
_status_code = code;
_desc = desc;
}
void AddHeader(const std::string &k, const std::string &v)
{
_headers_kv[k] = v;
}
void AddBodyText(const std::string body_text)
{
_resp_body_text = body_text;
}
std::string Serialize()
{
// 1.构建状态行
_status_line = _version + spacesep + std::to_string(_status_code) + spacesep + _desc + base_sep;
// 2.构建应答报头
for (auto &header : _headers_kv)
{
std::string header_line = header.first + line_sep + header.second + base_sep;
_resp_headers.push_back(header_line);
}
// 3.空行和正文
// 4.正式序列化
std::string responsestr = _status_line;
for (auto &line : _resp_headers)
{
responsestr += line;
}
responsestr += _blank_line;
responsestr += _resp_body_text;
return responsestr;
}
~HttpResponse()
{
}
private:
// httpresponse base 属性
std::string _version;
int _status_code;
std::string _desc;
std::unordered_map<std::string, std::string> _headers_kv;
// 基本的httprequest的格式
std::string _status_line;
std::vector<std::string> _resp_headers;
std::string _blank_line;
std::string _resp_body_text;
};
using func_t=std::function<HttpResponse(HttpRequest&)>;
class HttpServer
{
public:
std::string GetFileContent(const std::string &path)
{
std::ifstream in(path, std::ios::binary);
if (!in.is_open())
return std::string();
in.seekg(0, in.end);
int filesize = in.tellg(); // 告诉我你的rw偏移量是多少
in.seekg(0, in.beg);
std::string content;
content.resize(filesize);
in.read((char *)content.c_str(), filesize);
in.close();
return content;
}
public:
HttpServer()
{
_mime_type.insert(std::make_pair(".html", "text/html"));
_mime_type.insert(std::make_pair(".jpg", "image/jpeg"));
_mime_type.insert(std::make_pair(".png", "image/png"));
_mime_type.insert(std::make_pair(".default", "text/html"));
_code_to_desc.insert(std::make_pair(100, "Continue"));
_code_to_desc.insert(std::make_pair(200, "OK"));
_code_to_desc.insert(std::make_pair(201, "Created"));
_code_to_desc.insert(std::make_pair(301, "Moved Permanently"));
_code_to_desc.insert(std::make_pair(302, "Found"));
_code_to_desc.insert(std::make_pair(404, "Not Found"));
}
// #define TEST
std::string HandlerHttpRequest(std::string &reqstr) // req曾经被客户端序列化过
{
#ifdef TEST
std::cout << "--------------------------------" << std::endl;
std::cout << reqstr;
std::string responsestr = "HTTP/1.1 200 OK\r\n";
responsestr += "Content-Type: text/html\r\n";
responsestr += "\r\n";
responsestr += "<html><h1>hello Linux, hello bite!</h1></html>";
return responsestr;
#else
std::cout << "--------------------------------" << std::endl;
std::cout << reqstr;
std::cout << "--------------------------------" << std::endl;
HttpRequest req;
HttpResponse resp;
req.Deserialize(reqstr);
// req.Method();
if (req.Path() == "wwwroot/redir")
{
//处理重定向
std::string redir_path="https://www.qq.com";
resp.AddCode(301,_code_to_desc[301]);
resp.AddHeader("Location",redir_path);
}
else
{
// 最基本的上层处理,处理静态资源
std::string content = GetFileContent(req.Path());
if (content.empty())
{
std::string content = GetFileContent("wwwroot/404.html");
resp.AddCode(404, _code_to_desc[404]);
resp.AddHeader("Content-Length", std::to_string(content.size()));
resp.AddHeader("Content-Type", _mime_type[".html"]);
resp.AddBodyText(content);
}
else if(!req.GetRequestBody().empty())
{
if(IsServiceExists(req.Path()))
{
resp=_service_list[req.Path()](req);
}
}
else
{
resp.AddCode(200, _code_to_desc[200]);
resp.AddHeader("Content-Length", std::to_string(content.size()));
resp.AddHeader("Content-Type", _mime_type[req.Suffix()]);
resp.AddHeader("Set-Cookie","username=zhangsan");
// resp.AddHeader("Set-Cookie","passwd=12345");
resp.AddBodyText(content);
}
}
return resp.Serialize();
#endif
}
void InsertService(const std::string &servicename,func_t f)
{
std::string s=prefixpath+servicename;
_service_list[servicename]=f;
}
bool IsServiceExists(const std::string &servicename)
{
auto iter=_service_list.find(servicename);
if(iter==_service_list.end()) return false;
else return true;
}
~HttpServer()
{
}
private:
std::unordered_map<std::string, std::string> _mime_type;
std::unordered_map<int, std::string> _code_to_desc;
std::unordered_map<std::string, func_t> _service_list;
};