HTTP是HyperText Transfer Protocol(超文本传输协议),用于客户端与服务器之间的通信。
域名:
https://www.google.com/像这种就是域名,相信大家肯定都听过ip地址,那么为什么现在基本上都有域名标记,而不用ip地址呢,像谷歌的ip地址是:172.217.16.195由此可见域名比ip地址更直观和记忆。ip与域名之间存在映射关系(DNS)。
网络行为:
把别人的东西拿下来
把自己的东西传上去
URL编码与解码是在网络传输中保证传输准确的保障,在网络传输中,某些字符具有特殊作用,不能直接将其包括在URL会引起歧义,因此存在编码与解码,下面举个简单例子。
原始字符串: Hello World!
URL 编码后: Hello%20World%21
URL编码又名百分号编码,顾名思义是一种以%+16进制的方法。
常见字符的编码规则
! 编码为 %21
@ 编码为 %40
# 编码为 %23
$ 编码为 %24
% 编码为 %25
^ 编码为 %5E
& 编码为 %26
* 编码为 %2A
解码就是编码的逆向过程。
http请求和响应格式
httprequest
向服务器发起请求
包含请求行,请求报头,空行,请求正文
里面又记录了各种参数(如图)
请求报头和请求正文之间有个空行,这些参数是按行读取的,当不断往下读,读到空格的时候,表示请求报头读取完了。这样我们能保证读取完了报头,但有怎么保证可以读取完请求正文呢,因为在请求报头中存在很多属性,其中有一个属性就是请求正文的长度,这样就可以保证读取完请求正文
端口号:可以标识不同的服务或应用程序
telnet:可以用于登录远程设备和服务器
http协议一般绑定的固定端口是80
https协议绑定的固定端口是443
通过telnet工具连接到百度的端口
telnet ip 端口号
GET / HTTP/1.1
get和post它们都是http协议中常用的两种请求方法,它们两个方法的区别是一般获取网页就用get方法,当需要把数据交给服务器用都可以 ,
这指定了HTTP协议的版本,1.1相较于1.0是现在主流版本。
通过服务器请求获取根目录信息,接下来就会看到服务器返回的http响应,如状态行,响应头和网页内容
httpresponse
服务器返回给客户端
包含状态行,响应报头,空行,响应正文
我们要创建以下文件
HttpServer.cc
#include "HttpServer.hpp"
#include <iostream>
#include <memory>//包含了智能指针库
#include <pthread.h>
#include "Log.hpp"
using namespace std;
int main(int argc, char *argv[])
{
if(argc != 2)//检查参数数量是否正确
{
exit(1);
}
uint16_t port = std::stoi(argv[1]);//将端口号转换为uint16_t类型
// HttpServer *svr = new HttpServer();
// std::unique<HttpServer> svr(new HttpServer());
std::unique_ptr<HttpServer> svr(new HttpServer(port));
svr->Start();//启动http服务
return 0;
}
HttpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>
#include "Socket.hpp"
#include "Log.hpp"
const std::string wwwroot="./wwwroot"; // web 根目录
const std::string sep = "\r\n";
const std::string homepage = "index.html";
static const int defaultport = 8082;//表示默认端口号
class HttpServer;
class ThreadData//创建了一个线程间传递数据的类
{
public:
ThreadData(int fd, HttpServer *s) : sockfd(fd), svr(s)
{
}
public:
int sockfd;
HttpServer *svr;
};
class HttpRequest//包含请求的相关功能
{
public:
void Deserialize(std::string req)//反序列化
{
while(true)//利用分隔符切分请求头和请求体
{
std::size_t pos = req.find(sep);
if(pos == std::string::npos) break;
std::string temp = req.substr(0, pos);
if(temp.empty()) break;
req_header.push_back(temp);
req.erase(0, pos+sep.size());
}
text = req;
}
// .png:image/png
void Parse()//解析请求头第一行
{
std::stringstream ss(req_header[0]);//从第一行中提取请求方法,请求URL,HTTP版本
ss >> method >> url >> http_version;
file_path = wwwroot; // ./wwwroot
if(url == "/" || url == "/index.html") {
file_path += "/";
file_path += homepage; // ./wwwroot/index.html
}
else file_path += url; // /a/b/c/d.html->./wwwroot/a/b/c/d.html
auto pos = file_path.rfind(".");
if(pos == std::string::npos) suffix = ".html";
else suffix = file_path.substr(pos);
}
void DebugPrint()//打印http请求的详细信息
{
for(auto &line : req_header)
{
std::cout << "--------------------------------" << std::endl;
std::cout << line << "\n\n";
}
std::cout << "method: " << method << std::endl;
std::cout << "url: " << url << std::endl;
std::cout << "http_version: " << http_version << std::endl;
std::cout << "file_path: " << file_path << std::endl;
std::cout << text << std::endl;
}
public:
std::vector<std::string> req_header;//用动态数组保存请求头的各行内容
std::string text;//保存请求主体内容
// 解析之后的结果
std::string method;//储存请求的各种方法
std::string url;
std::string http_version;//用于储存http版本信息
std::string file_path; //文件路径
std::string suffix;//储存文件拓展名
};
class HttpServer
{
public:
HttpServer(uint16_t port = defaultport) : port_(port)//创建一个HttpServer对象时可指定端口号,若没有可指定端口号
{
content_type.insert({".html", "text/html"});
content_type.insert({".png", "image/png"});
}
bool Start()//启动http服务,创建监听套接字
{
listensock_.Socket();//创建套接字
listensock_.Bind(port_);//绑定套接字
listensock_.Listen();//监听
for (;;)
{
std::string clientip;//储存客户端ip和端口
uint16_t clientport;
int sockfd = listensock_.Accept(&clientip, &clientport);
if (sockfd < 0)
continue;
lg(Info, "get a new connect, sockfd: %d", sockfd);
pthread_t tid;//创建线程id
ThreadData *td = new ThreadData(sockfd, this);//将客户端套接字和HttpServer给线程,方便线程进行处理
pthread_create(&tid, nullptr, ThreadRun, td);
}
}
static std::string ReadHtmlContent(const std::string &htmlpath)//读取指定路径http请求
{
std::ifstream in(htmlpath, std::ios::binary);//以二进制读取文件
if(!in.is_open()) return "";
in.seekg(0, std::ios_base::end);
auto len = in.tellg();
in.seekg(0, std::ios_base::beg);
std::string content;
content.resize(len);
in.read((char*)content.c_str(), content.size());
//std::string content;
//std::string line;
//while(std::getline(in, line))
//{
// content += line;
//}
in.close();
return content;
}
std::string SuffixToDesc(const std::string &suffix)
{
auto iter = content_type.find(suffix);
if(iter == content_type.end()) return content_type[".html"];
else return content_type[suffix];
}
void HandlerHttp(int sockfd)//处理http请求
{
char buffer[10240];
ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // bug
if (n > 0)
{
buffer[n] = 0;
std::cout << buffer << std::endl; // 假设我们读取到的就是一个完整的,独立的http 请求
HttpRequest req;
req.Deserialize(buffer);
req.Parse();
//req.DebugPrint();
//std::string path = wwwroot;
//path += url; // wwwroot/a/a/b/index.html
// 返回响应的过程
std::string text;
bool ok = true;
text = ReadHtmlContent(req.file_path); // 失败?
if(text.empty())
{
ok = false;
std::string err_html = wwwroot;
err_html += "/";
err_html += "err.html";
text = ReadHtmlContent(err_html);
}
std::string response_line;
if(ok)
response_line = "HTTP/1.0 200 OK\r\n";
else
response_line = "HTTP/1.0 404 Not Found\r\n";
//response_line = "HTTP/1.0 302 Found\r\n";
std::string response_header = "Content-Length: ";
response_header += std::to_string(text.size()); // Content-Length: 11
response_header += "\r\n";
response_header += "Content-Type: ";
response_header += SuffixToDesc(req.suffix);
response_header += "\r\n";
response_header += "Set-Cookie: name=haha&&passwd=12345";
response_header += "\r\n";
//response_header += "Location: https://www.qq.com\r\n";
std::string blank_line = "\r\n"; // \n
std::string response = response_line;
response += response_header;
response += blank_line;
response += text;
send(sockfd, response.c_str(), response.size(), 0);
}
close(sockfd);
}
static void *ThreadRun(void *args)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData *>(args);
td->svr->HandlerHttp(td->sockfd);
delete td;
return nullptr;
}
~HttpServer()
{
}
private:
Sock listensock_;
uint16_t port_;
std::unordered_map<std::string, std::string> content_type;
};
Log.hpp
#pragma once
#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define SIZE 1024
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
#define Screen 1
#define Onefile 2
#define Classfile 3
#define LogFile "log.txt"
class Log
{
public:
Log()
{
printMethod = Screen;
path = "./log/";
}
void Enable(int method)
{
printMethod = method;
}
std::string levelToString(int level)
{
switch (level)
{
case Info:
return "Info";
case Debug:
return "Debug";
case Warning:
return "Warning";
case Error:
return "Error";
case Fatal:
return "Fatal";
default:
return "None";
}
}
// void logmessage(int level, const char *format, ...)
// {
// time_t t = time(nullptr);
// struct tm *ctime = localtime(&t);
// char leftbuffer[SIZE];
// snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
// ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
// ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
// // va_list s;
// // va_start(s, format);
// char rightbuffer[SIZE];
// vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
// // va_end(s);
// // 格式:默认部分+自定义部分
// char logtxt[SIZE * 2];
// snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
// // printf("%s", logtxt); // 暂时打印
// printLog(level, logtxt);
// }
void printLog(int level, const std::string &logtxt)
{
switch (printMethod)
{
case Screen:
std::cout << logtxt << std::endl;
break;
case Onefile:
printOneFile(LogFile, logtxt);
break;
case Classfile:
printClassFile(level, logtxt);
break;
default:
break;
}
}
void printOneFile(const std::string &logname, const std::string &logtxt)
{
std::string _logname = path + logname;
int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
if (fd < 0)
return;
write(fd, logtxt.c_str(), logtxt.size());
close(fd);
}
void printClassFile(int level, const std::string &logtxt)
{
std::string filename = LogFile;
filename += ".";
filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
printOneFile(filename, logtxt);
}
~Log()
{
}
void operator()(int level, const char *format, ...)
{
time_t t = time(nullptr);
struct tm *ctime = localtime(&t);
char leftbuffer[SIZE];
snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
va_list s;
va_start(s, format);
char rightbuffer[SIZE];
vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
va_end(s);
// 格式:默认部分+自定义部分
char logtxt[SIZE * 2];
snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);
// printf("%s", logtxt); // 暂时打印
printLog(level, logtxt);
}
private:
int printMethod;
std::string path;
};
Log lg;
// int sum(int n, ...)
// {
// va_list s; // char*
// va_start(s, n);
// int sum = 0;
// while(n)
// {
// sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
// n--;
// }
// va_end(s); //s = NULL
// return sum;
// }
打印日志信息
Makefile
HttpServer:HttpServer.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f HttpServer
Socket.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
enum//定义了不同错误码
{
SocketErr = 2,
BindErr,
ListenErr,
};
// TODO
const int backlog = 10;
class Sock
{
public:
Sock()
{
}
~Sock()
{
}
public:
void Socket()
{
sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd_ < 0)
{
lg(Fatal, "socker error, %s: %d", strerror(errno), errno);
exit(SocketErr);
}
int opt = 1;
setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
}
void Bind(uint16_t port)
{
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(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)
{
lg(Fatal, "bind error, %s: %d", strerror(errno), errno);
exit(BindErr);
}
}
void Listen()
{
if (listen(sockfd_, backlog) < 0)
{
lg(Fatal, "listen error, %s: %d", strerror(errno), errno);
exit(ListenErr);
}
}
int Accept(std::string *clientip, uint16_t *clientport)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);
if(newfd < 0)
{
lg(Warning, "accept error, %s: %d", strerror(errno), errno);
return -1;
}
char ipstr[64];
inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
*clientip = ipstr;
*clientport = ntohs(peer.sin_port);
return newfd;
}
bool Connect(const std::string &ip, const uint16_t &port)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));
int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer));
if(n == -1)
{
std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
return false;
}
return true;
}
void Close()
{
close(sockfd_);
}
int Fd()
{
return sockfd_;
}
private:
int sockfd_;
};
1.png
里面随便放个图片就可以
err.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404 Not Found</title>
<style>
body {
text-align: center;
padding: 150px;
}
h1 {
font-size: 50px;
}
body {
font-size: 20px;
}
a {
color: #008080;
text-decoration: none;
}
a:hover {
color: #005F5F;
text-decoration: underline;
}
</style>
</head>
<body>
<div>
<h1>404</h1>
<p>页面未找到<br></p>
<p>
您请求的页面可能已经被删除、更名或者您输入的网址有误。<br>
请尝试使用以下链接或者自行搜索:<br><br>
<a href="https://www.baidu.com">百度一下></a>
</p>
</div>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- <form action="/a/b/hello.html" method="post">
name: <input type="text" name="name"><br>
password: <input type="password" name="passwd"><br>
<input type="submit" value="提交">
</form> -->
<h1>这个是我们的首页</h1>
<img src="/image/1.png" alt="" width="100" height="100">
</body>
</html>
我们用postman来模拟
当然也可以直接用浏览器去访问
按照服务器ip和端口号的方式访问可以看到以下页面
我们可以看到一些访问后的信息我们这里使用postman访问的