认识URL
平时我们俗称的 "网址" 其实就是说的 URL
举例说明
各个字段解析
https : 协议方案名
blog.csdn.net :服务器地址/域名
/m0_57249790:带层次的文件路径
spm=1000.2115.3001.5343: 查询字符串
http的本质
通过http协议从服务器拿下来对应的资源。
什么是资源呢?
一切你在网络上看到的都是文件(视频、音乐、文章等等)。
这些文件存放在你的服务器上 -->因此需要Linux系统的路径结构来从
服务器上拿到这些资源。
因为文件资源种类特别多,http都能搞定,所以:http叫做超文本传输协议!
urlencode和urldecode
客户端的url中会对我们需要搜索的内容中特殊符号和汉字进行encode过程
服务器(软件)收到url请求就是自己对特殊符号和汉字进行decode的过程。
特殊字符的转义
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
urlencode工具
UrlEncode编码/UrlDecode解码 - 站长工具
http请求的格式
状态码
例如:200/400/302/307/500/404
状态码描述
404 -> Not Found 200 -> OK
1.请求和响应怎么保证应用层完整读取完毕了?
a. 我可以读取完整的一行
b. while(读取完整的一行) - 所有的请求行+请求报头全部读完 - 直到空行!
c. 我们能保证把报头读完,报头有一个属性:Content-Length:XXX正文长度
2.请求和响应是怎么做到序列化和反序列化的?
a. http自己实现的,第一行+请求/响应报头,只要按照\r\n将字符串1->n即可!
b. 正文不用做
实现简单的http服务器代码
服务端
#pragma once
#include <iostream>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <functional>
#include <unordered_map>
#include "Protocol.hpp"
#include "Util.hpp"
using namespace std;
namespace server
{
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR
};
static const uint16_t gport = 8080;
static const int gbacklog = 5;
using func_t = function<bool(const HttpRequest &, HttpResponse &)>;
class HttpServer
{
public:
HttpServer(func_t func, const uint16_t &port = gport)
: _func(func), _listensock(-1), _port(port)
{
}
void initServer()
{
// 1. 创建socket文件套接字对象
_listensock = socket(AF_INET, SOCK_STREAM, 0);
if (_listensock < 0)
{
exit(SOCKET_ERR);
}
// 2.bind绑定自己的网络信息
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port); // 主机转网络
local.sin_addr.s_addr = INADDR_ANY;
if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
exit(BIND_ERR);
}
// 3. 设置socket 为监听状态
if (listen(_listensock, gbacklog) < 0) // TODO
{
exit(LISTEN_ERR);
}
}
// void registerCb(std::string servicename, func_t cb)
// {
// funcs.insert(std::make_pair(servicename,cb));
// }
void HandlerHttp(int sock)
{
// 1. 读到完整的http请求
// 2. 反序列化
// 3. httprequest,httprespons,_func(req,resp)
// 4. resp序列化
// 5. send
char buffer[4096];
// 构建对象
HttpRequest req;
HttpResponse resp;
// 大概率我们直接就能读取到完整的http请求
ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
// n>0说明读取成功
if (n > 0)
{
buffer[n] = 0;
req.inbuffer = buffer;
// 对进行收到的字符串进行处理
req.parse();
// funcs[req.path](req,resp);
_func(req, resp);
send(sock, resp.outbuffer.c_str(), resp.outbuffer.size(), 0);
}
}
void start()
{
for (;;)
{
// 4. server 获取新链接
// sock 和客户端进行通信的fd
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
if (sock < 0)
{
cout << "sock: " << sock << endl;
continue;
}
// version 2
pid_t id = fork();
if (id == 0) // child
{
close(_listensock); // 子进程中不需要监听因此关闭监听的文件描述符
if (fork() > 0)
exit(0);
HandlerHttp(sock);
close(sock);
exit(0);
}
close(sock);
// father
waitpid(id, nullptr, 0);
}
}
~HttpServer()
{
}
private:
int _listensock; // 不是用来进行数据通信的,它是用来监听链接到来,获取新链接的!
uint16_t _port;
func_t _func;
// std::unordered_map<std::string, func_t> funcs;
};
}
客户端
#include "HttpServer.hpp"
#include <memory>
using namespace std;
using namespace server;
void Usage(string proc)
{
cerr << "Usage:\n\t" << proc << " proc\r\n\r\n";
}
bool Get(const HttpRequest &req, HttpResponse &resp)
{
cout << "------------------http start ------------------" << endl;
cout << req.inbuffer << endl;
cout << "------------------http end ------------------" << endl;
return true;
}
// ./httpServer 8080
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(0);
}
// atoi 字符串转整型
uint16_t port = atoi(argv[1]);
unique_ptr<HttpServer> httpsvr(new HttpServer(Get, port));
// httpsvr->registerCb("/",GET); // 功能路由
// httpsvr->registerCb("/search",Search);
// httpsvr->registerCb("/test.py",Other);
httpsvr->initServer();
httpsvr->start();
return 0;
}
http协议的特点
由于http(超文本传输协议) 已经是一个非常成熟的协议了,因此我们直接使用网站(手机和电脑都可以)请求就可以让我们写的对应http服务端收到请求的消息。
硬编码网页回显
我们也可以硬编码一个网页,将该html代码发送给服务器,让服务器回显出来
std::string body = "<html lang=\"en\"><head><meta charset=\"UTF-8\"><title>for test</title><h1>hello world</h1></head><body><p>山河为碑,历史作证;硝烟已散,精神永存。如今,伟大抗战精神早已成为中华儿女的精神底色,薪火相传、历久弥新。</p></body></html>";
分隔http请求
static std::string getOneLine(std::string &buffer, const std::string &sep)
{
auto pos = buffer.find(sep);
// 没找到标识符
if (pos == std::string::npos)
return "";
// 截取到标识符之前的内容
std::string sub = buffer.substr(0, pos);
// buffer.erase(0, sub.size() + sep.size());
// 返回截取到的标识符
return sub;
}
POST和GET
我们进行数据提交的时候,本质前端要通过from表单提交,浏览器会自动将from表单中的内容转换成GET/POST方法。
区别
GET
GET通过url传递参数,具体:http://ip:port/XXX/YY?name=value&name2=value2
POST
POST提交参数通过http请求的正文提交参数!
私密性
POST方法通过正文提交参数,所以一般用户看不到,私密性更好, 私密性 != 安全性;
GET方法不私密;
无论是GET方法和POST方法,都不安全!要谈安全,必须加密!
GET通过URL传参,注定不能太大。
但是POST,通过正文,正文可以很大,甚至可以是其他的东西。
HTTP状态码
HTTP状态码是指在HTTP协议中,服务器向客户端返回的3位数字代码,用于表示请求的处理结果。常见的HTTP状态码有以下几种:
- 1xx:信息类,表示请求已被接收,继续处理。
- 2xx:成功类,表示请求已成功被服务器接收、理解和处理。
- 3xx:重定向类,表示需要进一步操作以完成请求。
- 4xx:客户端错误类,表示服务器无法处理请求。
- 5xx:服务器错误类,表示服务器处理请求时发生了错误。
其中,一些常见的HTTP状态码包括:
- 200 OK:请求成功。
- 301 Moved Permanently:永久重定向。
- 404 Not Found:资源未找到。
- 500 Internal Server Error:服务器内部错误。
需要注意的是,HTTP状态码是由服务器返回给客户端的。
长连接
其实一张我们看到的网页,实际上可能由多种元素构成 ——一张完整的网页需要多次http请求;
http网页中可能包含多个元素,如果频繁发起http请求,http是基于tcp的,tcp面向链接的;
频繁创建链接的问题;需要client和server都要支持,长连接,建立好一条链接,获取一大份资源的时候,通过一条链接完成。
长连接 —— Connection: keep-alive
短连接 —— Connection: close
会话保持
定位
会话保持严格意义不是http天然具备的,是后面使用发现需要的
什么叫做会话保持呢?
http协议是无状态的! 但是用户需要,因为用户查看新的网页是常规操作,如果发生网页跳转,那么新的页面也就无法识别是哪一个用户了,为了让用户一经登录,可以在整个网站,按照自己的身份进行随意访问,这就是会话保持。
cookie技术
把我们用户的输入信息: 用户名&&密码保存起来
往后只要访问同一个网站,浏览器会自动推送历史保留信息
这种技术叫做cookie技术。
内容
1.浏览器会自动记录客户信息
2.记录客户信息这个文件叫做cookie文件,当用户首次登录通过验证以后,用户的
账号密码信息可以直接被保存在cookie文件里面,在站内做跳转就不需要在填写用户账号密码了,
cookie分为文件级别和内存级别。
设置cookie
respheader += "Set-Cookie: name=1234567abcdefg; Max-Age=120\r\n";
name后面是我们需要保存的内容,Max-Age后面是我们所设置的时间(单位是秒)