記事ディレクトリ
1.HTTPの概念
アプリケーション層の典型的なプロトコルは HTTP (Hypertext Transfer Protocol) です。これは最も広く使用されているプロトコルであり、その
機能はあらゆるコンテンツをローカル ブラウザにプルし、ブラウザに解釈させることです。
クライアントは、自分の「モノ」を他人に与える
と同時に、他人の「モノ」を自分のローカルエリアに届けたいと考えており、これを
一般にCSモードと呼びます。
http 内の Web ページのテキスト、画像、ビデオ、音声を総称してリソースと呼びます
。
2. URL
サーバーにアクセスするには、サーバーの IP アドレスとポート番号を知っている必要があります
ドメイン名解決サービスが必要です。
例: baidu.com (ドメイン名) は 110.242.68.4 (IP アドレス) に解決されます。
例:QQ公式サイト
https プロトコルとして
www.qq.com サーバーアドレスとして
サーバーのポート番号は任意に指定することはできず、よく知られている必要があり、簡単に変更することはできませんが、
ポート番号は成熟したアプリケーション層プロトコルに 1 対 1 で対応します。
https で一般的に使用されるポート番号は 443、
http で一般的に使用されるポート番号は 80 です。
プロトコル名とポート番号は 1 対 1 の関係にあり、
例えば近くで火事があった場合、真っ先に思い浮かぶのは 119 番して火を消すことです。
http はハイパーテキスト転送プロトコルであるため、他の人にアクセスしたいリソースを伝える必要があります。
最初の / は Web ルート ディレクトリを表し
、2 番目の / はパス区切り文字を表します。
// は、URL がサーバー上でアクセスしたいリソースを表します。
? URL の左側と右側を区別する区切り文字を示します
? 以下はパラメータです
パラメータは KV、= 左の uid を K、右の = 1 を V とみなすことができます
URLはUniform Resource Locatorと呼ばれます
URLコードとURLデコード
- ?/#のみ検索:これらの特殊記号は16進数に変換されることがわかります。URL
自体が一部の文字を特殊文字として使用しているため、特殊文字を使用する場合、特殊記号は16進数に変換されます。 URL 自体の特殊文字からの
変換プロセスはURL エンコーディングと呼ばれ、URL 内の特殊記号の問題を解決するために使用されます。この作業はブラウザまたはクライアントによって自動的に行われます。 - サーバーが受信するものは 16 進形式ですが、16 進形式が不要な場合や特殊な記号が必要な場合は、それをデコードする必要があります。
エスケープルール
トランスコードする必要がある文字を 16 進数に変換し、右から左に 4 桁を取得し (4 桁未満は直接処理されます)、2 桁ごとに 1 つずつ作成し、前に % を追加して、%XY 形式にエンコードします。
クリックして表示:自動コーディング ツール
urlencodeとurlencodeのデコードはこのWebサイトで実行できます
3. HTTP のマクロの理解
HTTPリクエスト
完全な声明によると、HTTP は 4 つの部分に分かれています
最初の部分 - リクエスト行
HTTP リクエスト行は行単位で 3 つの部分に分かれています リクエストメソッド URL プロトコルバージョン リクエスト
メソッド: GET/POST
URL: リクエストリソース
プロトコルバージョン: http/1.0 http/1.1 http/
2.0 3 つの部分 これら 3 つの部分を区切るには、区切り文字としてスペースを使用します。
パート 2 - リクエスト ヘッダー
Key:Value で構成される複数行の構造
パート 3 - 空白行
\r\n
パート 4 - ペイロードは
通常、ユーザーが送信できるパラメーターです (オプション)
HTTPレスポンス
ステータス行は、プロトコル バージョンのステータス コードとステータス コードの説明に分かれています。3
つの部分を区切るには、スペースを区切り文字として使用します。
プロトコル バージョン: http/1.0 http/1.1 http/2.0ステータス コード: 404ステータス コードの説明
など
: 404 対応する意味: Not Found
応答ヘッダーも Key:Value で構成される複数行の構造です。
ペイロードは html/css ファイル リソースである場合もあれば、要求された対応する画像などである場合もあります。
4. HTTP リクエストとレスポンスに対応する
リクエストヘッダー
LinuxではブラウザからホストIP+ポート番号を入力すると以下のデータが表示されます
リクエスト行としてのGET/HTTP/1.1の最初の行
Key Value で構成される複数行の構造はリクエスト ヘッダーとして使用され
、ペイロードは含まれません。Host
はリクエストがどのホストに送信されるかを示します。通常はターゲット サーバーの IP アドレスとポート番号です。Connection はリンク モードを示し
ます。ロング/ショート リンク
キャッシュ制御は、通信時に双方がキャッシュを確立する必要があることを示します。最大キャッシュ生存時間のデフォルトは 0 (キャッシュなし)です。User_Agent は
HTTPリクエストのクライアント情報を示します。Accept_Encodong
はエンコーディングとクライアントとして受け入れられる圧縮タイプ
。Accept_Language は、クライアントとしてエンコード記号を受け入れられることを示します。
1. 単純な応答をシミュレートする
Main.cc を作成し、コールバック関数 HandlerHttp を呼び出してプロセス全体を実装します。
コールバック関数 HandlerHttp は、完全な http リクエストメッセージであることを前提として、レスポンスにステータス行区切りペイロードを付加してレスポンスを返しますが、ペイロード部分は Web ページ部分に表示されます
。
応答ヘッダー
テキスト分析を実行する場合、空白行が見つかるまで読み取りが行ごとに分割され、その後ヘッダーが読み取られたとみなされます。
ヘッダーのキーは Content-Length で、値は本文の長さ (ペイロードの長さ) です。
Linux 上でプログラムを実行してポート番号を入力する場合、
ブラウザ上でホスト IP + ポート番号を入力すると、main 関数がコールバック関数を呼び出してこれをテストとして出力し、同時に
Linux は次のようなデータ応答を返します。ステータス行応答ヘッダー空行ペイロード
ペイロードは内部的に画像、動画、音声リソースに分かれているため、
区別しやすくするためにBodyの種類であるContent_Typeを使用します。
写真、ビデオ、オーディオ リソースは本質的にファイルです。
写真のサフィックスは .png、
Web ページのサフィックスは .html、
ビデオのサフィックスは .mp3 です。Linux
リソースにはすべて独自のサフィックスがあります。他の人に伝える必要がある場合は、Content-Type 比較表が必要です。
サフィックスが .html の場合、Content-Type 比較テーブルは text/html となり、
サフィックスが .png の場合、Content-Type 比較テーブルは image/png になります。
Web ページの Content-Type 比較表 text/html と SEP 区切り文字を応答の後に追加します。
2. パスからコンテンツを取得する
http 用の独自のディレクトリを維持します。つまり、wwroot は、index.html
を作成し、すべてのリソースをこの Web ページに配置します。
until.hppを作成します。Untilクラスで、ファイルの内容全体を読み取るための
インターフェイスReadFileを作成します。
最初のパラメータ path は指定されたパスで、
2 番目のパラメータ file_content は出力がファイルに対応するコンテンツであることを示します。
Pathはパスを表し、wwwrootディレクトリのindex.htmlにあるファイルを取得し
、取得したファイルを文字列ボディに渡します。
ReadFile関数の実装
1. ファイル自体のサイズを取得するには
、「man 2 stat」と入力します。
指定されたファイル パスについて、その struct stat 属性を取得します。
成功した場合は 0 を返し、失敗した場合は -1 を返します。
st_size は、このファイルのサイズをバイト単位で示します。
st_mode:多くのマクロに一致します。
2. すべてのファイルを配置できるように文字列のスペースを調整します。
サイズスペースを作成する
3. 読む
O_RDONLY 読み取り
パスとして、index.html に対応するコンテンツを見つけて、そのコンテンツをペイロードとして本体文字列に渡すことができます。
3. 異なるリソースを区別する
これらのリソースにはリクエストのみがあり、無知な応答はありません。
異なるリソースが要求された場合は、それらを区別する必要があります。
ユーザーが望むものは何でも提供してください。そうでない場合は、404 を返します。
リクエストを処理し、逆シリアル化して、文字列情報を構造化フィールドに変換し、リクエスト メソッド、URL、リクエスト バージョン、ステータス行のリクエスト ヘッダーを含む
HttpRequest 構造体を作成します。
URL はリクエストされたリソースとして使用されるため、パスを req.url_ に置き換えます。
逆シリアル化の実装
メイン関数 Main.cc で、メッセージの最初の行のリクエスト行を取り出すReadOneLine 関数
を作成し、リクエスト行をリクエスト メソッド、URL、プロトコル バージョンに解析するParseRequestLine 関数を作成します。
どちらの関数も Util.hpp に実装されています
ReadOneLine関数の実装
静的変更は、非表示の this ポインターの存在を防ぐために追加されます。find
関数を使用して sep セパレーターを検索します。見つかった場合は、pos 位置の添字を返します。substr 関数を使用して、
[0, pos] 内の部分文字列を取り出します。 ] 間隔を戻り値とする
Erase関数を使用する pos+sep.size()の0から始まる文字を削除する
ParseRequestLine関数の実装
sstream ストリームは、区切り文字としてスペースを使用し、それらを 3 つの文字列に出力します。
パス path の最終表現
パス path には Web ルート ディレクトリを追加する必要があります
そこで、Web ルート ディレクトリ webRoot を定義します。
リクエストを使用する場合は、まず Web ルート ディレクトリをパスに追加し、次に対応する URL (リクエスト リソース) を追加します。
4. 文字と写真を同時に表示
クリックして表示:ザクロの花の写真
wwwroot にイメージ ファイルを作成し、「inimage」と入力します。
wget:リソースをリモートから取得するコマンド
wget + 画像アドレスを使用して画像を取得します
mv コマンドを使用して、元の画像名を 1.jpg に変更します。
この時、vscode内の画像ファイルで画像を表示することができます
Web ページには、画像、テキスト、ビデオなどの多くの要素リソースが含まれており、
各リソースは http リクエストを開始する必要があります。
ブラウザでw3cschool を検索します
HTML チュートリアルで、replace text 属性を探す HTML 画像を見つけます。
最初の / は Web ルート ディレクトリ、つまり wwwroot を表します
。wwroot ディレクトリ内の画像ファイルで 1.jpg を見つけます。
画像の取得に失敗した場合は、「ザクロの花の写真です」というテキストが表示されます。
このリソースにはテキストと画像の両方が含まれるため、タイプが異なり、Content-Type (ボディ タイプ) を処理する必要があります。
メンバー変数を追加して、アクセスするリソース (画像やテキストなど) を決定します。
逆シリアル化関数の rfind 関数を使用して、後ろから前に文字を検索します。次に、substr 関数を使用して、添え字 pos から始まる len 文字を取得します。len が指定されていない場合は、path_string の終わりまで取得されます
。
HandlerHttp 関数を使用するリクエストでは、
Content-Type (ボディ タイプ) がGetContentTypeインターフェイスにカプセル化されます。
GetContentType関数の実装
サフィックスが .html の場合、Content-Type 比較表は text/html、
サフィックスが .css の場合、Content-Type 比較表は test/css、
サフィックスが .js の場合、 Content-Type 比較表は application/x-javascript です。
接尾辞が .png の場合、Content-Type 比較表は image/png です。
接尾辞が .jpg の場合、Content-Type 比較表は image/ jpeg。
ブラウザにホストIP+ポート番号を入力すると画像が表示されず文字化けしてしまいます。
Webページでエンコード形式を指定しないと文字化けが発生するため、
index.htmlの内容を修正してください。
ホストIPとポート番号を再度入力すると、テキストと画像が同時に表示されます。
5. シミュレーションの完全なコード
wwwroot
インデックス.html(画像)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charest="UTF-8">
<meta name="viewport" content="width=device-width" ,initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>this is a test </h1>
<h1>this is a test </h1>
<h1>this is a test </h1>
<h1>this is a test </h1>
<img src="/image/1.jpg" alt="这是一张石榴花图片">
</body>
</html>
Err.hpp (エラー)
#pragma once
enum
{
USAGE_ERR=1,
SOCKET_ERR,//2
BIND_ERR,//3
LISTEN_ERR,//4
SETSID_ERR,//5
OPEN_ERR//6
};
HttpServer.hpp (初期化と起動)
#include<iostream>
#include<string>
#include<pthread.h>
#include<functional>
#include"Sock.hpp"
static const uint16_t defaultport=8888;//默认端口号
class HttpServer;
//定义 func_t 类型 为 返回值为string 参数为string的包装器
using func_t =std::function<std::string( std::string&)>;
class ThreadData
{
public:
ThreadData(int sock,std::string ip,const uint16_t& port,HttpServer*tsvrp)//构造
:_sock(sock),_ip(ip),_port(port),_tsvrp(tsvrp)
{
}
~ThreadData()
{
}
public:
int _sock;//套接字
HttpServer *_tsvrp;//指针指向Tcp服务器
std::string _ip;
uint16_t _port;
};
class HttpServer
{
public:
HttpServer(func_t f,int port= defaultport)
:func(f),port_(port)
{
}
void InitServer()//初始化
{
listensock_.Socket();//创建套接字
listensock_.Bind(port_);//绑定
listensock_.Listen();//监听
}
void HandlerHttpRequest(int sock)//
{
char buffer[4096];
std::string request;
//将套接字的数据读取到buffer中
ssize_t s=recv(sock,buffer,sizeof(buffer)-1,0);
if(s>0)//读取成功
{
buffer[s]=0;//将'\0'赋值给buffer中
request=buffer;
std::string response =func(request);//回调函数 将request变为response
send(sock,response.c_str(),response.size(),0);//发送 将respnse中的内容 发送到sock套接字中
}
else
{
//读取失败
logMessage(Info,"client quit ...");//打印日志
}
}
static void* threadRoutine(void *args)
{
//线程分离 若不关心线程返回值 则提前告诉它 要进行分离
pthread_detach(pthread_self());
ThreadData* td=(ThreadData*)args;
td->_tsvrp->HandlerHttpRequest(td->_sock);
close(td->_sock);
delete td;
return nullptr;
}
void Start()//启动
{
for(;;)
{
std::string clientip;
uint16_t clientport;
int sock=listensock_.Accept(&clientip,&clientport);//获取客户端IP和端口号
if(sock<0)
{
continue;
}
pthread_t tid;
ThreadData *td =new ThreadData(sock,clientip,clientport,this);
pthread_create(&tid,nullptr,threadRoutine,td);
}
}
~HttpServer()
{
}
private:
int port_; //端口号
Sock listensock_;//套接字
func_t func; //包装器类型的回调函数
};
Log.hpp (ログ)
#pragma once
#include<iostream>
#include<string.h>
#include<cstdio>
#include<cstring>
#include<cstdarg>
#include<unistd.h>
#include<sys/types.h>
#include<time.h>
const std::string filename="tecpserver.log";
//日志等级
enum{
Debug=0, // 用于调试
Info , //1 常规
Warning, //2 告警
Error , //3 一般错误
Tatal , //4 致命错误
Uknown//未知错误
};
static std::string tolevelstring(int level)//将数字转化为字符串
{
switch(level)
{
case Debug : return "Debug";
case Info : return "Info";
case Warning : return "Warning";
case Error : return "Error";
case Tatal : return "Tatal";
default: return "Uknown";
}
}
std::string gettime()//获取时间
{
time_t curr=time(nullptr);//获取time_t
struct tm *tmp=localtime(&curr);//将time_t 转换为 struct tm结构体
char buffer[128];
snprintf(buffer,sizeof(buffer),"%d-%d-%d %d:%d:%d",tmp->tm_year+1900,tmp->tm_mon+1,tmp->tm_mday,
tmp->tm_hour,tmp->tm_min,tmp->tm_sec);
return buffer;
}
void logMessage(int level, const char*format,...)
{
//日志左边部分的实现
char logLeft[1024];
std::string level_string=tolevelstring(level);
std::string curr_time=gettime();
snprintf(logLeft,sizeof(logLeft),"%s %s %d",level_string.c_str(),curr_time.c_str());
//日志右边部分的实现
char logRight[1024];
va_list p;//p可以看作是1字节的指针
va_start(p,format);//将p指向最开始
vsnprintf(logRight,sizeof(logRight),format,p);
va_end(p);//将指针置空
//打印日志
printf("%s%s\n",logLeft,logRight);
//保存到文件中
FILE*fp=fopen( filename.c_str(),"a");//以追加的方式 将filename文件打开
//fopen打开失败 返回空指针
if(fp==nullptr)
{
return;
}
fprintf(fp,"%s%s\n",logLeft,logRight);//将对应的信息格式化到流中
fflush(fp);//刷新缓冲区
fclose(fp);
}
Main.cc (コールバック関数呼び出し)
#include<vector>
#include<memory>
#include"HttpServer.hpp"
#include"Util.hpp"
using namespace std;
const std::string SEP="\r\n";
const std::string defaultHomePage ="index.html";//默认首页
const std::string webRoot="./wwwroot";//web根目录
class HttpRequest
{
public:
HttpRequest()
:path_(webRoot)
{
}
~HttpRequest()
{
}
void Print()
{
logMessage(Debug,"method:%s,url:%s,version:%s",method_.c_str(),url_.c_str(),httpVersion_.c_str());
/*for(const auto&line:body_)
{
logMessage(Debug,"-%s",line.c_str());
}
*/
logMessage(Debug,"path:%s",path_.c_str());
logMessage(Debug,"suffix:%s",suffix_.c_str());
}
public:
std::string method_;//请求方法
std::string url_; //URL
std::string httpVersion_;//请求版本
std::vector<std::string> body_;//请求报头
std::string path_; //想要访问的资源
std::string suffix_;//后缀 用于判断访问是什么资源
};
//反序列化 将字符串转化为 HttpRequest结构体
HttpRequest Deserialize(std::string &message)
{
HttpRequest req;
std::string line=Util::ReadOneLine(message,SEP);//在message中根据分隔符读走状态行
//将请求行分为 请求方法 URL 协议版本
Util::ParseRequestLine(line,&req.method_,&req.url_,&req.httpVersion_);//解析请求行
logMessage(Info,"method:%s,url:%s,version:%s",req.method_.c_str(),req.url_.c_str(),req.httpVersion_.c_str());
//将状态行处理后,剩余请求报头,每一次取一行 将其放入body中
while(!message.empty())
{
line=Util::ReadOneLine(message,SEP);
req.body_.push_back(line);
}
req.path_ += req.url_; //path_在构造时,已经默认为web根目录了,所以只需加上资源即可
//只有一个'/',需加上默认首页
if(req.path_[req.path_.size()-1]=='/')
{
req.path_+= defaultHomePage;
}
auto pos=req.path_.rfind(".");
if( pos==std::string::npos)//没找到
{
req.suffix_=".html";//默认为html
}
else
{
req.suffix_=req.path_.substr(pos);
}
return req;
}
std::string GetContentType(std::string &suffix)//判断是哪一种资源的后缀
{
std::string content_type =" Content-Type: ";
if(suffix==".html"|| suffix==".htm")
{
content_type+="text/html";
}
else if(suffix==".css ")
{
content_type+="text/css";
}
else if(suffix==".js")
{
content_type+="application/x-javascript";
}
else if(suffix==".png")
{
content_type+="image/png";
}
else if(suffix==".jpg")
{
content_type+="image/jpeg";
}
else
{
}
return content_type+SEP;
}
std::string HandlerHttp( std::string &message)//回调函数的实现
{
//1.读取请求
//request 一定是一个完整的http请求报文
//给别人返回的 http response
cout<<"---------------------------"<<endl;
//2.反序列化和分析请求
HttpRequest req = Deserialize(message);
req.Print();
//3.使用请求
std::string body;//有效载荷
Util::ReadFile(req.path_,&body);//将path路径中的内容交给body字符串中
//做一次响应
//状态行 : 协议版本 状态码 状态码描述
//200表示请求是正确的
std::string response="HTTP/1.0 200 OK"+SEP;//状态码
//Content-Length获取有效载荷长度
response+="Content-Length: "+std::to_string(body.size())+SEP;//响应报头
response+=GetContentType(req.suffix_);
response += SEP; //分隔符
response += body; //有效载荷
return response;
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
exit(USAGE_ERR);
}
uint16_t port=atoi(argv[1]);
std::unique_ptr<HttpServer> tsvr(new HttpServer(HandlerHttp,port));
tsvr->InitServer();
tsvr->Start();
return 0;
}
メイクファイル
httserver:Main.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f httserver
Sock.hpp (TCP ソケット)
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
#include"Log.hpp"
#include"Err.hpp"
static const int gbacklog=32;
static const int defaultfd=-1;
class Sock
{
public:
Sock() //构造
:_sock(defaultfd)
{
}
void Socket()//创建套接字
{
_sock=socket(AF_INET,SOCK_STREAM,0);
if(_sock<0)//套接字创建失败
{
logMessage( Tatal,"socket error,code:%s,errstring:%s",errno,strerror(errno));
exit(SOCKET_ERR);
}
}
void Bind(uint16_t port)//绑定
{
struct sockaddr_in local;
memset(&local,0,sizeof(local));//清空
local.sin_family=AF_INET;//16位地址类型
local.sin_port= htons(port); //端口号
local.sin_addr.s_addr= INADDR_ANY;//IP地址
//若小于0,则绑定失败
if(bind(_sock,(struct sockaddr*)&local,sizeof(local))<0)
{
logMessage( Tatal,"bind error,code:%s,errstring:%s",errno,strerror(errno));
exit(BIND_ERR);
}
}
void Listen()//将套接字设置为监听状态
{
//小于0则监听失败
if(listen(_sock,gbacklog)<0)
{
logMessage( Tatal,"listen error,code:%s,errstring:%s",errno,strerror(errno));
exit(LISTEN_ERR);
}
}
int Accept(std::string *clientip,uint16_t * clientport)//获取连接
{
struct sockaddr_in temp;
socklen_t len=sizeof(temp);
int sock=accept(_sock,(struct sockaddr*)&temp,&len);
if(sock<0)
{
logMessage(Warning,"accept error,code:%s,errstring:%s",errno,strerror(errno));
}
else
{
//inet_ntoa 4字节风格IP转化为字符串风格IP
*clientip = inet_ntoa(temp.sin_addr) ; //客户端IP地址
//ntohs 网络序列转主机序列
*clientport= ntohs(temp.sin_port);//客户端的端口号
}
return sock;//返回新获取的套接字
}
int Connect(const std::string&serverip,const uint16_t &serverport )//发起链接
{
struct sockaddr_in server;
memset(&server,0,sizeof(server));//清空
server.sin_family=AF_INET;//16位地址类型
server.sin_port=htons(serverport);//端口号
//inet_addr 字符串风格IP转化为4字节风格IP
server.sin_addr.s_addr=inet_addr(serverip.c_str());//IP地址
//成功返回0,失败返回-1
return connect(_sock, (struct sockaddr*)&server,sizeof(server));
}
int Fd()
{
return _sock;
}
void Close()
{
if(_sock!=defaultfd)
{
close(_sock);
}
}
~Sock()//析构
{
}
private:
int _sock;
};
.hppまで
#pragma once
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<sstream>
#include"Log.hpp"
class Util
{
public:
static bool ReadFile(const std::string &path,std::string *fileContent )//读取整个文件内容
{
//1.获取文件本身的大小
struct stat st;//定义一个struct stat 类型的结构体
int n=stat(path.c_str(),&st);
if(n<0)//读取失败
{
return false;
}
int size = st.st_size;
//2.调整string的空间
fileContent->resize(size);
//3.读取
int fd=open(path.c_str(),O_RDONLY);
if(fd<0)//读取失败
{
return false;
}
read(fd,(char*)fileContent->c_str(),size);//从文件fd中读取,放到fileContent
close(fd);
logMessage( Info,"read file %s done ",path.c_str());
return true;
}
//在message中根据分隔符取出状态行
static std::string ReadOneLine( std:: string &message,const std::string &sep)
{
auto pos=message.find(sep);//查找sep分隔符,找到则返回pos位置的下标
while(pos==std::string::npos)//没找到
{
return "";
}
std::string s=message.substr(0,pos);//取[0,pos]区间作为子串
message.erase(0,pos+sep.size());从下标为0处开始 删除pos+sep.size()个字符
return s;
}
//将请求行分=解析为 请求方法 URL 协议版本
static bool ParseRequestLine(const std::string &line,std::string * method,std::string *url,std::string *httpVersion)
{
//以空格为单位,对内容做提取
std::stringstream ss(line);
ss >> *method >> *url >> *httpVersion;
return true;
}
};