文章目录
URL
我们所说的网址就是URL

如果在参数中出现?/特殊字符,会自动转义,/ ?字符已经被url特殊处理了

'+'被转义为%2b
🚩HTTP协议格式
请求格式

请求行:方法+URL+版本号+\r\n
请求报头:每行一对键值对+\r\n
\r\n空行,(没有空行的话无法区分报头和正文)
正文,(怎么确定正文的大小?报头中content-length确定)
例如

HTTP常用方法

常用的就GET,POST
响应格式

与请求类似,

常见状态码

常用状态码:200(OK),404 (Not Found),403 (Forbidden),302(Redirect,重定向(跳转网站)),504(Bad Gateway)
HTTP常见Header
Content-Type: 数据类型(text/html等)
Content-Length: Body的长度
Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
User-Agent: 声明用户的操作系统和浏览器版本信息;
referer: 当前页面是从哪个页面跳转过来的;
location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
🚩模拟实现HTTP服务器
目录结构

c
//socket.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "log.hpp"
#include <string.h>
#include <unistd.h>
using namespace std;
Log lg;
const int backlog=10;
enum{
SocketErr=2,
BindErr,
ListenErr,
};
class Sock{
public:
Sock()
{}
~Sock()
{}
void Socket()
{
sockfd_=socket(AF_INET,SOCK_STREAM,0);
if(sockfd_<0)
{
lg(Fatal,"socket error, strerror: %s, errno: %d",strerror(errno),errno);
exit(SocketErr);
}
}
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, strerror: %s, errno: %d",strerror(errno),errno);
exit(BindErr);
}
}
void Listen()
{
if(listen(sockfd_,backlog)<0)
{
lg(Fatal,"Listen Error, strerror: %s, errno: %d",strerror(errno),errno);
exit(ListenErr);
}
}
int Accept(std::string* ip,uint16_t *port)
{
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
int newfd=accept(sockfd_,(struct sockaddr*)&peer,&len);
if(newfd<0)
{
lg(Warning,"accept error, strerror: %s, errno: %d",strerror(errno),errno);
return -1;
}
char ipstr[64];
inet_ntop(AF_INET,&peer.sin_addr,ipstr,sizeof(ipstr));
*ip=ipstr;
*port=ntohs(peer.sin_port);
return newfd;
}
bool Connect(const 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<0)
{
cerr<<"Connect to"<<ip<<":"<<port<<endl;
return false;
}
return true;
}
void Close()
{
close(sockfd_);
}
int Fd()
{
return sockfd_;
}
private:
int sockfd_;
};
//server.hpp
#pragma once
#include <iostream>
#include "Socket.hpp"
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>
std::string blank_line="\r\n";
std::string wwwroot="./wwwroot";
std::string homepage="index.html";
class HttpServer;
class ThreadData
{
public:
ThreadData(int sockfd,HttpServer* hs)
:_sockfd(sockfd),_hs(hs)
{
}
public:
int _sockfd;
HttpServer* _hs;
};
class HttpResponse
{
public:
void Derialize(std::string req)
{
while(true)
{
std::size_t pos=req.find(blank_line);
if(pos==string::npos)break;
std::string temp=req.substr(0,pos);
if(temp.empty())//空行
break;
vc.push_back(temp);
req.erase(0,pos+blank_line.size());
}
text+=req;
}
void Parse()
{
std::stringstream ss(vc[0]);
ss>>method>>url>>version;
file_path=wwwroot;
if(url=="/"||url=="index.html")
{
file_path+="/";
file_path+=homepage; //wwwroot/index.html
}
else{
file_path+=url; //wwwroot/a/b/t/t
}
}
void Dubugprint()
{
for(auto line:vc)
{
std::cout<<line<<std::endl;
std::cout<<"------------"<<std::endl;
}
std::cout<<"method: "<<method<<std::endl;
std::cout<<"url: "<<url<<std::endl;
std::cout<<"version: "<<version<<std::endl;
std::cout<<"file_path: "<<file_path<<std::endl;
std::cout<<"text: "<<text<<std::endl;
}
public:
vector<string> vc;
string text;
std::string method;
std::string url;
std::string version;
std::string file_path;
};
class HttpServer
{
public:
HttpServer(uint16_t port)
:_port(port)
{
}
void start()
{
_listen.Socket();
_listen.Bind(_port);
_listen.Listen();
lg(Info,"server create success");
for(;;)
{
std::string clientip;
uint16_t port;
int sockfd=_listen.Accept(&clientip,&port);
lg(Info,"get a newfd:%d",sockfd);
pthread_t tid;
ThreadData* td=new ThreadData(sockfd,this);
pthread_create(&tid,nullptr,ThreadRun,td);
}
}
static std::string ReadHtmlContent(const std::string& htmlpath)
{
std::ifstream in(htmlpath);
if(!in.is_open())
{
return "404";
}
std::string content;
std::string line;
while(getline(in,line))
{
content+=line;
}
in.close();
return content;
}
static void HandlerHttp(int sockfd)
{
char buffer[10240];
ssize_t n=recv(sockfd,buffer,sizeof(buffer)-1,0);
if(n>0)
{
buffer[n]=0;
//std::cout<<buffer<<endl;
//返回响应过程
HttpResponse rep;
rep.Derialize(buffer);
rep.Parse();
rep.Dubugprint();
std::string text=ReadHtmlContent(rep.file_path);
std::string response_line="HTTP/1.0 299 OK\r\n";
std::string response_header ="Content-Length: ";
response_header+=std::to_string(text.size()); //"Content-length: 11"
std::string response=response_line;
response+=response_header;
response+="\r\n";
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);
HandlerHttp(td->_sockfd);
close(td->_sockfd);
delete td;
return nullptr;
}
Sock _listen;
uint16_t _port;
//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>
<h1>这个是我们的首页</h1>
<a href="http://115.175.6.15:8080/a/b/hello.html">到第二张网页</a>
<a href="http://115.175.6.15:8080/c/d/hello2.html">到第三张网页</a>
</body>
</html>
};
//hello.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>
<h1>第二张图片</h1>
<h1>第二张图片</h1>
<h1>第二张图片</h1>
<a href="http://115.175.6.15:8080">回到首页</a>
<a href="http://115.175.6.15:8080/c/d/hello2.html">到第三张网页</a>
</body>
</html>
//hello2.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>
<h1>第三张图片</h1>
<a href="http://115.175.6.15:8080">回到首页</a>
<a href="http://115.175.6.15:8080/a/b/hello.html">到第三张网页</a>
</body>
</html>
//Log.hpp
#pragma once
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
#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/";
mkdir(path.c_str(), 0777);
}
void Enable(int method)
{
printmethod=method;
}
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";
}
}
/*
void logmassage(int level,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_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\n",logtxt);
}*/
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\n",logtxt);
printlog(level,logtxt);
}
void printlog(int level,const string& logtxt)
{
switch(printmethod)
{
case Screen:
cout<<logtxt<<endl;
break;
case Onefile:
printOnefile(Logfile,logtxt);
break;
case Classfile:
printClassfile(level,logtxt);
break;
}
}
void printOnefile(const string& failname,const string& logtxt)
{
string _failname = path + failname;
int fd=open(_failname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666);
if(fd<0)
{
return;
}
write(fd,logtxt.c_str(),logtxt.size());
close(fd);
}
void printClassfile(int level,const string& logtxt)
{
string _failname=Logfile;
_failname += ".";
_failname += levelToString(level);
printOnefile(_failname,logtxt);
}
~Log()
{}
private:
int printmethod;
string path;
};
//server.cc
#include <iostream>
#include "HttpServer.hpp"
using namespace std;
int main(int argc,char* argv[])
{
if(argc!=2)
{
exit(1);
}
HttpServer* svr=new HttpServer(stoi(argv[1]));
svr->start();
return 0;
}
启动服务端

打开浏览器输入 ip:端口号

实现成功,点击第二张网页

xhell查看浏览器发送的请求

我们可以看到域名也发生了变化,其实就是跳转到特定的文件

⭐Cookie
为什么我们第一次访问b站要登陆账户,但是第二次就不需要再登陆了?
:因为浏览器帮我们记住账户了,账户信息存储在Cookie中,此外,服务器为我们分配了ID=,我们每次访问b站视频==,浏览器都会带着Cookie和ID让服务器确认账号信息
Cookie信息在浏览器左上角,如果我们删除Cookie就会让我们再次输入账户信息

客户端第一次向服务端发送账号和密码,服务器为客户端建立Cookie并分配ID,之后客户端每次访问服务端都带着Cookie和ID,服务端在数据库查找确认
在我们服务器代码加入Cookie报头
c
response_header+="Set-Cookie: name=haha&&passward=123456";
response_header+="\r\n";
左上角打开我们的浏览器Cookie信息
