文章目錄
- http的方法
- http狀態碼
- http重定向
- http常見Header
- 實現簡單業務邏輯
- Protocol.hpp
- Util.hpp
- Server.hpp
- Server.cc
- 效果
http的方法
方法 | 說明 | 支持的HTTP版本 |
---|---|---|
GET | 獲取資源 | 1.0/1.1 |
POST | 傳輸實體主體 | 1.0/1.1 |
PUT | 傳輸文件 | 1.0/1.1 |
HEAD | 獲得報文首部 | 1.0/1.1 |
DELETE | 刪除文件 | 1.0/1.1 |
OPTIONS | 詢問支持方法 | 1.1 |
TRACE | 追蹤路徑 | 1.1 |
CONNECT | 要求用隧道協議連接代理 | 1.1 |
LINK | 建立和資源之間的聯系 | 1.0 |
UNLINE | 斷開連接關系 | 1.0 |
其中最為常見的請求方法為:GET POST
事實上,瀏覽器向服務器進行數據提交時,本質是前端通過form表單提交的,瀏覽器會自動將form表單中的內容轉換為GET/POST的方法請求
例如在QQ的網址上會有登陸框,查看登陸框的源代碼就會發現有form表單
如果輸入了賬號密碼之后點擊了登陸按鈕,瀏覽器就會將賬號和密碼根據指定的GET或者POST方法發送給服務器。
其中兩者的區別有:
- GET方法會將獲取到的數據作為參數直接通過url 傳遞,也就是說GET方法會在url 上直接顯示出數據,格式為:http://ip:port/XXX/YY?name=value&name2=value2。會直接暴露出數據
- POST方法不是通過url 傳遞數據,而是直接向請求的正文里提交數據。也就是說參數會存在在正文里,服務器再從正文里提取參數
- 因為GET方法是再url中直接傳遞參數,所以參數不能太大
- POST在正文傳遞參數,所以可以參數很大
需要注意的是:
雖然POST方法不會暴露數據,但是并不意味著就是安全的。私密 != 安全。
如果要談到安全,那就必須要加密,加密內容屬于https協議
http狀態碼
類別 | 原因 | |
---|---|---|
1XX | informational - 信息性狀態碼 | 接受的請求正在處理 |
2XX | success - 成功狀態碼 | 請求正常處理完畢 |
3XX | redirection - 重定向狀態碼 | 需要進行附加操作以完成請求 |
4XX | client error - 客戶端錯誤狀態碼 | 服務器無法處理請求 |
5XX | server error - 服務端錯誤狀態碼 | 服務器處理請求出錯 |
其中最常見的就是 404 網頁不存在
200 代表OK,404 Not Found,403 Forbiden,302 Redirect 重定向,504 Bad Gateway
http重定向
要實現重定向其實很簡單,將狀態碼修改為307代表重定向,然后在正文里加入重定向的網址即可,這樣向服務器請求后,服務器就會將處理的請求重定向到指定的網址
// 服務端處理的回調函數
bool func(const HttpRequest &req, HttpResponse &res)
{// 打印方便調試查看接收到的數據是否正確cout << "---------------http--------------" << endl;cout << req._inbuffer;cout << "_method: " << req._method << endl;cout << " _url: " << req._url << endl;cout << " _httpversion: " << req._httpversion << endl;cout << " _path: " << req._path << endl;cout << " _suffix: " << req._suffix << endl;cout << " _size: " << req._size << endl;cout << "---------------end---------------" << endl;// 狀態行// string resline = "HTTP/1.1 200 OK\r\n";string resline = "HTTP/1.1 307 Temporary Redirect\r\n";// 響應報頭// 需要注意正確的給客戶端返回資源,圖片是圖片,網頁是網頁string rescontet = Util::suffixToDesc(req._suffix);// 添加資源長度到報頭rescontet += "Content-Length: ";rescontet += to_string(req._size);rescontet += "\r\n";// 添加重定向rescontet += "Location: https://www.qq.com/\r\n";// 空行string resblank = "\r\n";// 響應正文string body;// 判斷資源是否存在,不存在就返回錯誤狀態碼 - 404if (!Util::FileIsNo(req._path, &body)){Util::FileIsNo(errorhtml, &body);}// 寫回響應的數據,后續要發送回客戶端res._outbuffer += resline;res._outbuffer += rescontet;res._outbuffer += resblank;res._outbuffer += body;return true;
}
http常見Header
名稱 | 意義 |
---|---|
Content-Type | 數據類型(text/html等) |
Content-Length | Body的長度 |
Host | 客戶端告知服務器, 所請求的資源是在哪個主機的哪個端口上 |
User-Agent | 聲明用戶的操作系統和瀏覽器版本信息 |
referer | 當前頁面是從哪個頁面跳轉過來的 |
location | 搭配3xx狀態碼使用, 告訴客戶端接下來要去哪里訪問 – 重定向 |
Cookie | 用于在客戶端存儲少量信息. 通常用于實現會話(session)的功能 |
實現簡單業務邏輯
代碼里涉及html代碼,不詳細講解
由于服務器較弱,所以圖片獲取直接從網址獲取,不從服務器讀取
需要注意,一個網頁看到的結果,可能是有多個資源組合而成,例如有網頁,圖片,視頻等。所以要獲取一張完整的網頁效果需要瀏覽器發送多次請求,那么服務器就要根據請求的類型不同對響應正文處理的方式要指明Content-Type的類型。例如網頁為“text/html”,jpg格式的圖片為“image/jpeg”,不同的格式可自行搜索
判斷格式的方法可以根據 url 中資源的后綴進行判斷
以下代碼均有注釋:
Protocol.hpp
請求響應類
因為瀏覽器會自動處理收到的響應報文,所以不需要編寫處理方法只需要將響應報文發送回瀏覽器即可
#pragma once#include <iostream>
#include <string>
#include <sstream>
#include "Util.hpp"using namespace std;// 定義分隔符
#define sep "\r\n"
#define default_root "./wwwroot"
#define home_page "index.html"
#define errorhtml "./wwwroot/404.html"// 請求
class HttpRequest
{
public:string _inbuffer; // 接收請求數據string _method; // 處理數據方法的名稱string _url; // urlstring _httpversion; // http協議版本string _path; // 查找資源的路徑string _suffix;// 資源后綴int _size;//資源長度HttpRequest(){}// 處理收到的數據// 添加默認路徑void parse(){// 拿到第一行string line = Util::GetOneLine(_inbuffer, sep);if(line.empty())return;cout << "line: " << line << endl;// 拿到第一行中的三個字段stringstream ss(line);ss >> _method >> _url >> _httpversion;// 添加默認路徑_path = default_root;_path += _url;// 如果url為/ 則添加默認路徑if(_path[_path.size() - 1] == '/')_path += home_page;// 獲取資源的后綴auto pos = _path.rfind(".");if(pos == string::npos)_suffix = ".html";else_suffix = _path.substr(pos);// 獲取到長度_size = Util::GetLen(_path);}};// 響應
class HttpResponse
{
public:string _outbuffer;
};
Util.hpp
工具類,將共有的方法定義同個類,方便調用
#pragma once#include <iostream>
#include <string>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>using namespace std;class Util
{
public:// 提取并刪除首行// 讀到的首行并不需要處理static string GetOneLine(string &inbuffer, const string &sep){auto pos = inbuffer.find(sep);if (pos == string::npos)return "";string sub = inbuffer.substr(0, pos);inbuffer.erase(0, sub.size() + sep.size());return sub;}// 判斷請求的資源是否存在static bool FileIsNo(const string resource, string *out){ifstream in(resource);// 打開文件失敗說明資源不存在if (!in.is_open())return false;string line;while (getline(in, line))*out += line;in.close();return true;}// 根據后綴指明響應報頭類型static string suffixToDesc(const string &suffix){string st = "Content-Type: ";if (suffix == ".html")st += "text/html";else if (suffix == ".jpg")st += "image/jpeg";st += "\r\n";return st;}// 獲取資源的長度static int GetLen(const string &path){struct stat s;int n = stat(path.c_str(), &s);if(n == 0)return s.st_size;return -1;}
};
Server.hpp
#pragma once#include "Protocol.hpp"
#include <sys/types.h>
#include <sys/socket.h>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <sys/wait.h>
#include <unistd.h>using func_t = function<bool(const HttpRequest &, HttpResponse &)>;class Server
{
public:Server(func_t func, uint16_t &port): _port(port), _func(func){}void Init(){// 創建負責監聽的套接字 面向字節流_listenSock = socket(AF_INET, SOCK_STREAM, 0);if (_listenSock < 0)exit(1);// 綁定網絡信息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(3);// 設置socket為監聽狀態if (listen(_listenSock, 5) < 0)exit(4);}// 服務端讀取處理請求方法void HttpHandler(int sock){// 確保讀到完整的http請求char buffer[4096];size_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);HttpRequest req;HttpResponse res;if (n > 0){buffer[n] = 0;req._inbuffer = buffer;// 處理讀到的數據req.parse();// 調用回調方法反序列化請求并得到響應結果和序列化響應結果_func(req, res);// 發回客戶端send(sock, res._outbuffer.c_str(), res._outbuffer.size(), 0);}}void start(){while (1){// server獲取建立新連接struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));socklen_t len = sizeof(peer);// 創建通信的套接字// accept的返回值才是真正用于通信的套接字_sock = accept(_listenSock, (struct sockaddr *)&peer, &len);if (_sock < 0)continue;cout << "sock: " << _sock << endl;// 利用多進程實現pid_t id = fork();if (id == 0) // child{close(_listenSock);// 調用方法包括讀取、反序列化、計算、序列化、發送HttpHandler(_sock);close(_sock);exit(0);}close(_sock);// fatherpid_t ret = waitpid(id, nullptr, 0);}}private:int _listenSock; // 負責監聽的套接字int _sock; // 通信的套接字uint16_t _port; // 端口號func_t _func;
};
Server.cc
#include "Server.hpp"
#include <memory>// 輸出命令錯誤函數
void Usage(string proc)
{cout << "Usage:\n\t" << proc << " local_ip local_port\n\n";
}// 服務端處理的回調函數
bool func(const HttpRequest &req, HttpResponse &res)
{// 打印方便調試查看接收到的數據是否正確cout << "---------------http--------------" << endl;cout << req._inbuffer;cout << "_method: " << req._method << endl;cout << " _url: " << req._url << endl;cout << " _httpversion: " << req._httpversion << endl;cout << " _path: " << req._path << endl;cout << " _suffix: " << req._suffix << endl;cout << " _size: " << req._size << endl;cout << "---------------end---------------" << endl;// 狀態行string resline = "HTTP/1.1 200 OK\r\n";// string resline = "HTTP/1.1 307 Temporary Redirect\r\n";// 響應報頭// 需要注意正確的給客戶端返回資源,圖片是圖片,網頁是網頁string rescontet = Util::suffixToDesc(req._suffix);// 添加資源長度到報頭rescontet += "Content-Length: ";rescontet += to_string(req._size);rescontet += "\r\n";// // 添加重定向// rescontet += "Location: https://www.qq.com/\r\n";// 空行string resblank = "\r\n";// 響應正文string body;// 判斷資源是否存在,不存在就返回錯誤狀態碼 - 404if (!Util::FileIsNo(req._path, &body)){Util::FileIsNo(errorhtml, &body);}// 寫回響應的數據,后續要發送回客戶端res._outbuffer += resline;res._outbuffer += rescontet;res._outbuffer += resblank;res._outbuffer += body;return true;
}int main(int argc, char *argv[])
{// 啟動服務端不需要指定IPif (argc != 2){Usage(argv[0]);exit(1);}uint16_t port = atoi(argv[1]);unique_ptr<Server> server(new Server(func, port));// 服務端初始化server->Init();// 服務端啟動server->start();return 0;
}
效果
在此就不寫出html的文件了,看著效果能夠實現即可
可以看到瀏覽器向服務器發送請求,服務器返回響應,響應里就包括了自己編寫的html文件,所以瀏覽器處理后就顯示出了自己的網頁