文章目录
- URL
- 🚩HTTP协议格式
-
- 请求格式
-
- HTTP常用方法
- 响应格式
-
- 常见状态码
- HTTP常见Header
- 常见状态码
- 🚩模拟实现HTTP服务器
-
- ⭐Cookie
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服务器
目录结构

1//socket.hpp 2#pragma once 3 4#include <iostream> 5#include <sys/types.h> 6#include <sys/socket.h> 7#include <arpa/inet.h> 8#include "log.hpp" 9#include <string.h> 10#include <unistd.h> 11using namespace std; 12 13Log lg; 14const int backlog=10; 15 16enum{ 17 SocketErr=2, 18 BindErr, 19 ListenErr, 20}; 21 22class Sock{ 23public: 24 Sock() 25 {} 26 ~Sock() 27 {} 28 29 void Socket() 30 { 31 sockfd_=socket(AF_INET,SOCK_STREAM,0); 32 if(sockfd_<0) 33 { 34 lg(Fatal,"socket error, strerror: %s, errno: %d",strerror(errno),errno); 35 exit(SocketErr); 36 } 37 } 38 void Bind(uint16_t port) 39 { 40 struct sockaddr_in local; 41 memset(&local,0,sizeof(local)); 42 local.sin_family=AF_INET; 43 local.sin_port=htons(port); 44 local.sin_addr.s_addr=INADDR_ANY; 45 46 if(bind(sockfd_,(struct sockaddr*)&local,sizeof(local))<0) 47 { 48 lg(Fatal,"Bind error, strerror: %s, errno: %d",strerror(errno),errno); 49 exit(BindErr); 50 } 51 52 } 53 void Listen() 54 { 55 if(listen(sockfd_,backlog)<0) 56 { 57 lg(Fatal,"Listen Error, strerror: %s, errno: %d",strerror(errno),errno); 58 exit(ListenErr); 59 } 60 } 61 int Accept(std::string* ip,uint16_t *port) 62 { 63 struct sockaddr_in peer; 64 socklen_t len=sizeof(peer); 65 int newfd=accept(sockfd_,(struct sockaddr*)&peer,&len); 66 if(newfd<0) 67 { 68 lg(Warning,"accept error, strerror: %s, errno: %d",strerror(errno),errno); 69 return -1; 70 } 71 char ipstr[64]; 72 inet_ntop(AF_INET,&peer.sin_addr,ipstr,sizeof(ipstr)); 73 *ip=ipstr; 74 *port=ntohs(peer.sin_port); 75 76 return newfd; 77 } 78 bool Connect(const string& ip,const uint16_t& port) 79 { 80 struct sockaddr_in peer; 81 memset(&peer,0,sizeof(peer)); 82 peer.sin_family=AF_INET; 83 peer.sin_port=htons(port); 84 inet_pton(AF_INET,ip.c_str(),&(peer.sin_addr)); 85 86 int n=connect(sockfd_,(struct sockaddr*)&peer,sizeof(peer)); 87 if(n<0) 88 { 89 cerr<<"Connect to"<<ip<<":"<<port<<endl; 90 return false; 91 } 92 return true; 93 } 94 void Close() 95 { 96 close(sockfd_); 97 } 98 int Fd() 99 { 100 return sockfd_; 101 } 102private: 103 int sockfd_; 104}; 105 106//server.hpp 107#pragma once 108#include <iostream> 109#include "Socket.hpp" 110#include <string> 111#include <pthread.h> 112#include <fstream> 113#include <vector> 114#include <sstream> 115#include <sys/types.h> 116#include <sys/socket.h> 117#include <unordered_map> 118 119std::string blank_line="\r\n"; 120std::string wwwroot="./wwwroot"; 121std::string homepage="index.html"; 122 123class HttpServer; 124 125class ThreadData 126{ 127public: 128 ThreadData(int sockfd,HttpServer* hs) 129 :_sockfd(sockfd),_hs(hs) 130 { 131 132 } 133public: 134 int _sockfd; 135 HttpServer* _hs; 136}; 137 138class HttpResponse 139{ 140public: 141 void Derialize(std::string req) 142 { 143 while(true) 144 { 145 std::size_t pos=req.find(blank_line); 146 if(pos==string::npos)break; 147 std::string temp=req.substr(0,pos); 148 if(temp.empty())//空行 149 break; 150 vc.push_back(temp); 151 req.erase(0,pos+blank_line.size()); 152 } 153 text+=req; 154 155 } 156 void Parse() 157 { 158 std::stringstream ss(vc[0]); 159 ss>>method>>url>>version; 160 161 file_path=wwwroot; 162 if(url=="/"||url=="index.html") 163 { 164 file_path+="/"; 165 file_path+=homepage; //wwwroot/index.html 166 } 167 else{ 168 file_path+=url; //wwwroot/a/b/t/t 169 } 170 } 171 void Dubugprint() 172 { 173 for(auto line:vc) 174 { 175 std::cout<<line<<std::endl; 176 std::cout<<"------------"<<std::endl; 177 } 178 std::cout<<"method: "<<method<<std::endl; 179 std::cout<<"url: "<<url<<std::endl; 180 std::cout<<"version: "<<version<<std::endl; 181 std::cout<<"file_path: "<<file_path<<std::endl; 182 std::cout<<"text: "<<text<<std::endl; 183 } 184public: 185 vector<string> vc; 186 string text; 187 188 std::string method; 189 std::string url; 190 std::string version; 191 192 std::string file_path; 193}; 194 195class HttpServer 196{ 197public: 198 HttpServer(uint16_t port) 199 :_port(port) 200 { 201 202 } 203 void start() 204 { 205 _listen.Socket(); 206 _listen.Bind(_port); 207 _listen.Listen(); 208 lg(Info,"server create success"); 209 for(;;) 210 { 211 std::string clientip; 212 uint16_t port; 213 int sockfd=_listen.Accept(&clientip,&port); 214 lg(Info,"get a newfd:%d",sockfd); 215 pthread_t tid; 216 ThreadData* td=new ThreadData(sockfd,this); 217 pthread_create(&tid,nullptr,ThreadRun,td); 218 } 219 } 220 static std::string ReadHtmlContent(const std::string& htmlpath) 221 { 222 std::ifstream in(htmlpath); 223 if(!in.is_open()) 224 { 225 return "404"; 226 } 227 std::string content; 228 std::string line; 229 while(getline(in,line)) 230 { 231 content+=line; 232 } 233 in.close(); 234 return content; 235 } 236 static void HandlerHttp(int sockfd) 237 { 238 char buffer[10240]; 239 ssize_t n=recv(sockfd,buffer,sizeof(buffer)-1,0); 240 if(n>0) 241 { 242 buffer[n]=0; 243 //std::cout<<buffer<<endl; 244 //返回响应过程 245 246 247 HttpResponse rep; 248 rep.Derialize(buffer); 249 rep.Parse(); 250 rep.Dubugprint(); 251 252 std::string text=ReadHtmlContent(rep.file_path); 253 254 std::string response_line="HTTP/1.0 299 OK\r\n"; 255 std::string response_header ="Content-Length: "; 256 response_header+=std::to_string(text.size()); //"Content-length: 11" 257 258 std::string response=response_line; 259 response+=response_header; 260 response+="\r\n"; 261 response += blank_line; 262 263 response+=text; 264 send(sockfd,response.c_str(),response.size(),0); 265 } 266 close(sockfd); 267 } 268 static void *ThreadRun(void* args) 269 { 270 pthread_detach(pthread_self()); 271 ThreadData* td=static_cast<ThreadData*>(args); 272 273 HandlerHttp(td->_sockfd); 274 275 close(td->_sockfd); 276 delete td; 277 return nullptr; 278 } 279Sock _listen; 280uint16_t _port; 281 282//index.html 283<!DOCTYPE html> 284<html lang="en"> 285<head> 286 <meta charset="UTF-8"> 287 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 288 <title>Document</title> 289</head> 290<body> 291 <h1>这个是我们的首页</h1> 292 293 294 <a href="http://115.175.6.15:8080/a/b/hello.html">到第二张网页</a> 295 296 <a href="http://115.175.6.15:8080/c/d/hello2.html">到第三张网页</a> 297</body> 298</html> 299}; 300 301//hello.html 302<!DOCTYPE html> 303<html lang="en"> 304<head> 305 <meta charset="UTF-8"> 306 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 307 <title>Document</title> 308</head> 309<body> 310 <h1>第二张图片</h1> 311 <h1>第二张图片</h1> 312 <h1>第二张图片</h1> 313 314 <a href="http://115.175.6.15:8080">回到首页</a> 315 316 <a href="http://115.175.6.15:8080/c/d/hello2.html">到第三张网页</a> 317</body> 318</html> 319//hello2.html 320<!DOCTYPE html> 321<html lang="en"> 322<head> 323 <meta charset="UTF-8"> 324 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 325 <title>Document</title> 326</head> 327<body> 328 <h1>第三张图片</h1> 329 330 <a href="http://115.175.6.15:8080">回到首页</a> 331 332 <a href="http://115.175.6.15:8080/a/b/hello.html">到第三张网页</a> 333</body> 334</html> 335 336 337//Log.hpp 338#pragma once 339#include <iostream> 340#include <stdlib.h> 341#include <time.h> 342#include <stdarg.h> 343#include <string.h> 344#include <sys/types.h> 345#include <sys/stat.h> 346#include <fcntl.h> 347#include <unistd.h> 348 349using namespace std; 350#define SIZE 1024 351 352#define Info 0 353#define Debug 1 354#define Warning 2 355#define Error 3 356#define Fatal 4 357 358#define Screen 1 359#define Onefile 2 360#define Classfile 3 361 362#define Logfile "log.txt" 363class Log{ 364public: 365 Log() 366 { 367 printmethod=Screen; 368 path="./log/"; 369 mkdir(path.c_str(), 0777); 370 } 371 void Enable(int method) 372 { 373 printmethod=method; 374 } 375 string levelToString(int level) 376 { 377 switch(level) 378 { 379 case Info: 380 return "Info"; 381 case Debug: 382 return "Debug"; 383 case Warning: 384 return "Warning"; 385 case Error: 386 return "Error"; 387 case Fatal: 388 return "Fatal"; 389 } 390 } 391/* 392void logmassage(int level,char* format, ...) 393{ 394 time_t t=time(nullptr); 395 struct tm *ctime = localtime(&t); 396 char leftbuffer[SIZE]; 397 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); 398 399 va_list(s); 400 va_start(s,format); 401 402 char rightbuffer[SIZE]; 403 vsnprintf(rightbuffer,sizeof(rightbuffer),format,s); 404 va_end(s); 405 406 char logtxt[SIZE*2]; 407 snprintf(logtxt,sizeof(logtxt),"%s %s\n",leftbuffer,rightbuffer); 408 409 printf("%s\n",logtxt); 410 411}*/ 412 void operator()(int level,const char* format, ...) 413 { 414 time_t t=time(nullptr); 415 struct tm *ctime = localtime(&t); 416 char leftbuffer[SIZE]; 417 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); 418 419 va_list(s); 420 va_start(s,format); 421 422 char rightbuffer[SIZE]; 423 vsnprintf(rightbuffer,sizeof(rightbuffer),format,s); 424 va_end(s); 425 426 char logtxt[SIZE*2]; 427 snprintf(logtxt,sizeof(logtxt),"%s %s",leftbuffer,rightbuffer); 428 429 //printf("%s\n",logtxt); 430 printlog(level,logtxt); 431 } 432 void printlog(int level,const string& logtxt) 433 { 434 switch(printmethod) 435 { 436 case Screen: 437 cout<<logtxt<<endl; 438 break; 439 case Onefile: 440 printOnefile(Logfile,logtxt); 441 break; 442 case Classfile: 443 printClassfile(level,logtxt); 444 break; 445 } 446 } 447 void printOnefile(const string& failname,const string& logtxt) 448 { 449 string _failname = path + failname; 450 int fd=open(_failname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666); 451 if(fd<0) 452 { 453 return; 454 } 455 write(fd,logtxt.c_str(),logtxt.size()); 456 close(fd); 457 } 458 void printClassfile(int level,const string& logtxt) 459 { 460 string _failname=Logfile; 461 _failname += "."; 462 _failname += levelToString(level); 463 printOnefile(_failname,logtxt); 464 } 465 ~Log() 466 {} 467private: 468 int printmethod; 469 string path; 470 471}; 472 473//server.cc 474#include <iostream> 475#include "HttpServer.hpp" 476using namespace std; 477int main(int argc,char* argv[]) 478{ 479 if(argc!=2) 480 { 481 exit(1); 482 } 483 HttpServer* svr=new HttpServer(stoi(argv[1])); 484 svr->start(); 485 return 0; 486} 487
启动服务端

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

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

xhell查看浏览器发送的请求

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

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

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

《【Linux】网络之http协议》 是转载文章,点击查看原文。