Tarscpp实现Http协议

简介


要实现一个web server,需要了解基本的Http协议。

tars内置了对http协议的支持,使用还是比较简单的。

它复用了 tars rpc,因此也拥有了同步和异步以及超时的特性,并能够利用tars stat上报调用质量。

官方文档:https://github.com/TarsCloud/TarsDocs/blob/master/dev/tarscpp/tars-http1.md

本文结合笔者开发过程作一记录,并对开发中遇到的问题进行详细描述。

Http server


服务框架的生成与普通的tars协议一致。官方文档及demo已经描写清楚,不再详述。

对于只对外提供Http服务的情况,只需要在初始化时增加以下代码:

void HttpServer::initialize()
{
    addServant<HttpImp>(ServerConfig::Application + "." + ServerConfig::ServerName + ".HttpObj");
    addServantProtocol(ServerConfig::Application + "." + ServerConfig::ServerName + ".HttpObj", &TC_NetWorkBuffer::parseHttp);
}

然后在Imp中实现具体业务逻辑:

int HttpImp::doRequest(TarsCurrentPtr current, vector<char> &buffer)
{
    // parse request header
    TC_HttpRequest request; // util中tc_http.h中提供了操作HTTP的工具类
    vector<char> v = current->getRequestBuffer();
    string sBuf;
    sBuf.assign(&v[0],v.size());
    request.decode(sBuf);
    
    // 构建响应包
    TC_HttpResponse rsp; // 可以为rsp设置属性
    string s="hello";
    rsp.setResponse(s.c_str(),s.size());
    rsp.encode(buffer);
   
    return 0;
}

以上示例代码完成了srv的开发,部署的时候选择非TARS协议,使用浏览器访问该端口,就会收到hello的问候。

http client


client的使用流程同其他协议。

先获取代理,并需要设置http编码函数:

CommunicatorPtr& comm = Application::getCommunicator();
ServantPrx prx = comm->stringToProxy<ServantPrx>("test.server.HttpObj"); // 根据实际名称填写

// set protocol
prx->tars_set_protocol(ServantProxy::PROTOCOL_HTTP1, 10); // 设置协议为HTTP1,并设置串行连接个数为10

虽然http支持长短连接, 但是http无法连接复用, 即一条连接上只能并行跑一个包(请求和响应), 因此tars rpc专门支持了串行模式:

prx->tars_connection_serial(10)

相当于启动了10个http连接, 在连接上完成http请求。作用同上述代码中的参数。

同步调用:

shared_ptr<TC_HttpResponse> rsp;
shared_ptr<TC_HttpRequest> req = std::make_shared<TC_HttpRequest>();
req->setPostRequest("http://domain.com/hello", string("helloworld"), true);
//可以设置http头支持, keep-alive
req->setHeader("Connection", "keep-alive"); // 尽量使用提供的函数设置头属性,不要直接设置

prx->http_call("hello", req, rsp);  // 这里http_call第一个参数没有实际函数, 只是用来监控用, 即在web管理平台接口统计看到函数名是"hello"

异步调用:

// 编写callback
class MyHttpCb : public HttpCallback
{
public:
	virtual int onHttpResponse(const shared_ptr<TC_HttpResponse> &rsp)
	{
		return 0;
	}
	virtual int onHttpResponseException(int expCode)
	{
		return 0;
	}
};

// 发起调用
shared_ptr<TC_HttpRequest> req = std::make_shared<TC_HttpRequest>();

string buff = "helloworld";
req->setPostRequest("http://domain.com/hello", buff, true);

prx->http_call_async("hello", req, p);

可以看到,对于熟悉TARS开发的来说,这个流程还是挺简单的。

提取url


一般业务交互过程中,request会在url中表达需求,而服务端根据url处理请求。

通过TC_HttpRequestgetRequestUrl获取url即可。

关于Cookie


实际使用中,不会像示例中那么简单,经常需要使用Cookie、Token等来实现业务。

关于cookie的更多信息,请参考HTTP cookies。

在进行编码前,建议仔细阅读tc_http.h头文件,熟悉提供的各种接口。

使用getCookie()获取cookie。

服务端需要把token相关信息传输给浏览器,浏览器每次请求页面的时候都会把token带过来。这样服务器就能通过token来验证请求的合法性。

使用 TC_HttpResponsesetSetCookie设置Cookie:

string gmt = TC_Common::tm2GMTstr(time(NULL) + 8 * 60 * 60 + 1 * 60 * 60); // 设置超期时间为1小时
rsp.setSetCookie("id=0; Path=/; Domain=qq.com; Expires=" + gmt); // Path/Domain/Expires等属性直接写在后面,注意Expires需要设置为GMT时间
rsp.setSetCookie("name=foo; Path=/; Domain=qq.com; Expires=" + gmt); // 可以设置Cookie的多个域
rsp.setCacheControl("max-age=3600"); // 也可以这样设置Cookie的超时时间

关于cacheControl的更多信息,请参考Cache-Control。

关于Max-age与Expires的区别,请参考HTTP Cookies: What’s the difference between Max-age and Expires?。

简单来说:

  • Expires:设置一个过期日期,在该时间点上删除Cookie
  • Max-age:以秒为单位设置一个时间,在该时间删除Cookie

也可以直接设置:

// test_tc_http.cpp
string gmt = TC_Common::tm2GMTstr(time(NULL) + 10);

	string s = "HTTP/1.1  200  OK\r\n";//  200  Aouut Error\r\n";
	s += "Set-Cookie: n1=1; a=1; c=d; Path=/; Domain=qq.com; Expires=" + gmt + "\r\n";
	s += "Set-Cookie: n2=2; a=0; c=d; Path=/abc/def; Domain=.qq.com; Expires=" + gmt + "\r\n";
	s += "Set-Cookie: n3=3; a=5; c=d; Path=/abc/def/aaa; Domain=.qq.com; Expires=" + gmt + "\r\n";
	s += "Set-Cookie: n4=4; a=6; c=d; Path=/abc; Domain=.qq.com; Expires=" + gmt + "\r\n";
	s += "Set-Cookie: n5=5; a=2; c=d; Path=/; Domain=.qq.com; Expires=" + gmt + "\r\n";
	s += "Set-Cookie: n6=6; c=3; Path=/; Domain=y.qq.com; Expires=" + gmt + "\r\n";
	s += "Set-Cookie: n7=7; c=3; Path=/abc; Domain=.y.qq.com; Expires=" + gmt + "\r\n";
	s += "Set-Cookie: n8=6; c=3; Path=/; Domain=x.y.qq.com; Expires=" + gmt + "\r\n";
	s += "Set-Cookie: n9=7; c=4; Path=/; Domain=.x.y.qq.com; Expires=" + gmt + "\r\n";
	s += "Set-Cookie: n10=7; c=4; Path=/; Domain=qqq.com; Expires=" + gmt + "\r\n";
	s += "Set-Cookie: n11=7; c=4; Path=/; Domain=.qqq.com; Expires=" + gmt + "\r\n";
	s += "Set-Cookie: n12=8; c=4; Expires=" + gmt + "\r\n";
	s += "Accept-Ranges: bytes\r\n\r\n";

	TC_HttpResponse rsp;
	rsp.decode s);

Cookie的Path属性


上节已经描述了Cookie及其属性的使用,这里记录一下我遇到的问题。

  • 现象:

    • web请求Http服务,Http需要设置Cookie返回到web。web在以后每次请求时都会检查该Cookie,如果为空则报错。
    • srv已经把Cookie返回给了web,但web在每次检查Cookie值的时候都是空,导致业务无法进行。
  • 定位:

    • 查看了浏览器的Http头,web好像没有收到Cookie。后经仔细检查,是Cookie没有发送到正确的地方!
  • 原因:

    • srv返回Cookie时未指定Path,导致web未识别。
  • 修改:

    • 在设置Cookie时增加Path属性,问题解决。

如果对Http协议熟悉,这个问题其实很简单,可以说基本上不应该犯这样的错误。

所以磨刀不误砍柴工,在做的过程中学习,在学习中思考,在思考中总结,这样才能越做越好。

异步返回Http Rsp


作为服务端,在接收到req后,如果后台需要异步调用其他服务后,才能返回结果,就要使用异步返回rsp的方式了。

Tarscpp实现Http协议_第1张图片

如图所示,在 web srver 接收到req后,需要异步调用srv1的tars服务,由于是异步调用,所以它不会立即返回。

web srv就需要在srv1异步返回后才能返回给web。

如果两边都是TARS服务,异步嵌套调用方法如下:

// 中间模块为proxy, 它异步调用Hello后,才返回结果给请求端
//ProxyServer中的异步回调对象
class HelloCallback : public HelloPrxCallback
{

public:
    HelloCallback(TarsCurrentPtr &current) // 回调对象HelloCallback保存了上下文current
    : _current(current)
    {}

    virtual void callback_testHello(tars::Int32 ret,  const std::string& sOut)
    {
        Proxy::async_response_testProxy(_current, ret, sOut); // 回调对象收到HelloServer返回的请求以后,通过Proxy::async_response_testProxy再将请求返回client
    }
    virtual void callback_testHello_exception(tars::Int32 ret)
    { 
        TLOGERROR("HelloCallback callback_testHello_exception ret:" << ret << endl); 

        Proxy::async_response_testProxy(_current, ret, "");
    }

    TarsCurrentPtr _current;
};

//定义ProxyServer中接口
tars::Int32 ProxyImp::testProxy(const std::string& sIn, std::string &sOut, tars::TarsCurrentPtr current)
{
    try
    {
        current->setResponse(false); // 在ProxyServer的接口testProxy实现中,需要设置不自动回复current->setResponse(false)

				// 回调对象必须是new出来放在智能指针中,例如:HelloPrxCallbackPtr cb = new HelloCallback(current); 它的生命周期,业务不用管理,框架层会自动管理
        TestApp::HelloPrxCallbackPtr cb = new HelloCallback(current); // 如果回调对象中需要其他参数,可以在构造的时候传进去

        _prx->tars_set_timeout(3000)->async_testHello(cb,sIn);
    }
    catch(std::exception &ex)
    {
        current->setResponse(true);

        TLOGERROR("ProxyImp::testProxy ex:" << ex.what() << endl);
    }

    return 0; // 这里testProxy的返回值,返回参数其实都没有意义了,无论返回什么都可以
}

同TARS服务一样,web server作为代理,也具有异步返回的功能:


// 在代理模块中设置不自动回复
current->setResponse(false); // 直接使用current指针完成
// 异步调用其他服务
DemoCBPtr cb = new DemoCB(rsp, buffer, current); // 需要把rsp和buffer传给回调类
cb->async_HelloReq();

// 在异步调用的返回模块里执行异步rsp
// rsp填充
rsp.setResponse("Hello");
rsp.encode(buffer);
current_->sendResponse(buffer.data(), buffer.size());  // 返回给req

可以看到,异步嵌套调用的流程与TARS协议一致。

小结


总体而言,TARS对Http协议的支持还是很好的,使用也很简单。

对于使用方法,由于文档描述不多,demo示例较简单,建议参考test_tc_http.cpp,很详细的使用。

由于本人仍在学习中,文中错漏之处请指正。

你可能感兴趣的:(Tars)