使用C++ REST SDK实现静态网站服务器的示例

目的

通过实现简易的静态网站服务器,来了解HTTP服务器的基本内容,以及C++ REST SDK如何使用。

静态网站

静态网站是指内容全部是静态的,不需要动态生成,当客户端请求指定资源时,将资源回复给客户端即可,不需要有进一步的交互;服务器上只是寄存了一些HTML、CSS、JavaScript以及资源文件。

对于静态网站,我们的HTTP服务器只需要实现GET功能,根据客户端请求的资源URI返回给客户端即可,HTTP 方法:GET 对比 POST

实现思路

  1. 使用http_listener实现对本地端口的http请求监听;
  2. 接收到请求后根据请求的相对URI获取所请求的文件
  3. 打开文件并发送内容及内容类型

MiniHTTPServer

http_listener需要指定监听的地址,当收到资源请求时,需要确定从哪个路径取文件,并提供启动和关闭接口:

#include <cpprest\http_listener.h>
#include <cpprest\filestream.h>
 
class MiniHTTPServer
{
public:
    explicit MiniHTTPServer(utility::string_t strUrl);//根据地址创建http_listener
    ~MiniHTTPServer(); //关闭服务器
    //设定文档根目录
    void setDocumentRoot(const utility::string_t& strWWW) { m_strWWW = strWWW;};
 
public:
    pplx::task<void> start();  //启动服务器
    pplx::task<void> stop(); //关闭服务器
    //处理错误
    static void handle_error(pplx::task<void>& t);
private:
    utility::string_t m_strWWW;//根路径
    std::unique_ptr<web::http::experimental::listener::http_listener> m_listener;
};

构造服务器:

MiniHTTPServer::MiniHTTPServer(utility::string_t strUrl)
    :m_strWWW(U(".")),m_listener(new http_listener(strUrl))
{
}

启动/停止服务器:

pplx::task<void> MiniHTTPServer::start()
{
    return m_listener->open().then([](auto t){ handle_error(t); });
}
 
pplx::task<void> MiniHTTPServer::stop()
{
    return m_listener->close().then([](auto t){ handle_error(t); });
}

使用方法如下:

#include "MiniHTTPServer.h"
 
 //使能宽字符输出
#include <io.h>
#include <fcntl.h>
int main(int argc, char** argv)
{
    _setmode(_fileno(stdout),_O_WTEXT);
 
    if (argc != 2)
    {
        std::wcout <<L"Usage:MiniHTTPServer.exe port\n";
        return -1;
    }
 
    //合成服务器地址
    utility::string_t strAddr = U("http://localhost:");
    strAddr.append(utility::conversions::to_string_t(argv[1]));
   
    //构造服务器并启用
    MiniHTTPServer oServer(strAddr);
    oServer.start().wait();
 
    std::wcout <<L"监听来自("<<strAddr<<L")的请求"<<std::endl;
 
    std::wcout << L"按回车关闭服务器.";
    std::string strVal;
    std::getline(std::cin,strVal);
 
    //关闭服务器
    oServer.stop().wait();
 
    return 0;
}

注册HTTP的GET请求处理句柄

//处理HTTP的GET请求并回复数据
void handle_get(web::http::http_request request);

//注册
MiniHTTPServer::MiniHTTPServer(utility::string_t strUrl)
    :m_strWWW(U(".")),m_listener(new http_listener(strUrl))
{
    //处理GET请求
    m_listener->support(methods::GET,[this](auto request){
        handle_get(request);
    });
}

调试用:输出请求的http_headers

现在启动服务器就可以监听客户端的GET请求了,目前可以输出请求的http_header来查看客户端请求信息:

utility::string_t MiniHTTPServer::dump_http_header(web::http::http_headers oHeader)
{
    stringstream_t ss;
    ss<<U("->http_header\n");
    for (auto const& oVal:oHeader)
    {
        ss<<oVal.first<<U(":")<<oVal.second<<U("\n");
    }
    ss<<U("<-http_header\n");
    return ss.str();
}

使用方法:

void MiniHTTPServer::handle_get(web::http::http_request request)
{
    auto path = request.relative_uri().path();
    ucout<<"HTTP GET:"<<path<<std::endl;
    ucout<<dump_http_header(request.headers());
    ......
}

根据请求URI得到资源类型

根据后缀得到资源类型:

utility::string_t MiniHTTPServer::resource_type(const utility::string_t& strSuffix)
{
    std::map<utility::string_t,utility::string_t> oVals;
    oVals[U(".html")] = U("text/html");
    oVals[U(".js")] = U("application/javascript");
    oVals[U(".css")] = U("text/css");
    oVals[U(".png")] = U("application/octet-stream");
    oVals[U(".jpg")] = U("application/octet-stream");
 
    auto pIt = oVals.find(strSuffix);
    if (pIt != oVals.end())
        return pIt->second;
    return U("application/octet-stream");
}

根据URI路径得到资源文件路径和资源类型:

std::pair<utility::string_t, utility::string_t> MiniHTTPServer::resource(const utility::string_t& strPath)
{
    //如果是ROOT,寻找index
    auto strVal = (strPath == U("/"))? U("/index.html"):strPath;
    fs::path oPath(m_strWWW);
    auto oVal = fs::absolute(oPath/strVal);
    return std::make_pair(utility::string_t(oVal),resource_type(oVal.extension()));
}

添加到GET请求处理中:

auto path = request.relative_uri().path();
ucout<<"HTTP GET:"<<path<<std::endl;
ucout<<dump_http_header(request.headers());
 
auto oVals = resource(path);
ucout<<U("映射到本地的资源为")<<oVals.first<<std::endl;
 
if (!fs::exists(oVals.first))
{
    request.reply(status_codes::NotFound,U("Path not found")).then([](auto t){ handle_error(t); });
    return;
}

向客户端发送资源文件

使用提供的异步输入流和请求回复接口向客户端发送资源文件:

//打开异步输入流
concurrency::streams::fstream::open_istream(oVals.first,std::ios::in).then([=](concurrency::streams::istream is){
    //发送异步输入流
    request.reply(status_codes::OK,is,oVals.second).then([](auto t) { handle_error(t); });
}).then([=](auto& t){
    try
    {
        t.get();
    }
    catch (...)
    {
        //打开文件失败,返回错误信息
        request.reply(status_codes::InternalError).then([](auto t) { handle_error(t); });
    }
});

复制一些静态网站内容并运行

运行效果:

服务器输出信息
浏览器访问页面

总结

以上实现了一个非常简单的静态网站服务器,只有非常基本的功能,但是为懂得HTTP库使用的人揭开了HTTP服务器的面纱,得以一窥HTTP服务器的背后。

后续将继续了解C++ REST SDK提供的功能,为构造云端连接应用做好准备。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,738评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,377评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,774评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,032评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,015评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,239评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,724评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,374评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,410评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,457评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,132评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,733评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,804评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,022评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,515评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,116评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • http协议有http0.9,http1.0,http1.1和http2三个版本,但是现在浏览器使用的是htt...
    一现_阅读 1,854评论 0 3
  • API定义规范 本规范设计基于如下使用场景: 请求频率不是非常高:如果产品的使用周期内请求频率非常高,建议使用双通...
    有涯逐无涯阅读 2,517评论 0 6
  • 一、概念(载录于:http://www.cnblogs.com/EricaMIN1987_IT/p/3837436...
    yuantao123434阅读 8,325评论 6 152
  • 一眨眼17年已经过去四分之一,剩下的时间也不太多,我总算是下定决心开始学习了。由于我天生比较愚笨,相对于那些拥有过...
    夏野阅读 587评论 0 0