This action will force synchronization from 搜狗开源/workflow, which will overwrite any changes that you have made since you forked the repository, and can not be recovered!!!
Synchronous operation will process in the background and will refresh the page when finishing processing. Please be patient.
这是一个http代理服务器,可以配置在浏览器里使用。支持所有的http method。
因为https代理的原理不同,这个示例并不支持https代理,你只能浏览http网站。
这个proxy在实现上需要抓取下来完整的http页面再转发,下载/上传大文件会有延迟。
之前的示例我们使用了默认的http server参数。但这个例子里,我们做一点修改,限制请求的大小,防止被恶意攻击。
int main(int argc, char *argv[])
{
...
struct WFServerParams params = HTTP_SERVER_PARAMS_DEFAULT;
params.request_size_limit = 8 * 1024 * 1024;
WFHttpServer server(¶ms, process);
if (server.start(port) == 0)
{
pause();
server.stop();
}
else
{
perror("cannot start server");
exit(1);
}
return 0;
}
与上一个示例不同,我们在server构造,多传入一个参数结构。我们可以看看http server有哪些配置。
在WFHttpServer.h里,http server的默认参数如下:
static constexpr struct WFServerParams HTTP_SERVER_PARAMS_DEFAULT =
{
.max_connections = 2000,
.peer_response_timeout = 10 * 1000,
.receive_timeout = -1,
.keep_alive_timeout = 60 * 1000,
.request_size_limit = (size_t)-1,
.ssl_accept_timeout = 10 * 1000,
};
max_connections:最大连接数2000,达到上限之后会关闭最久未使用的keep-alive连接。没找到keep-alive连接,则拒绝新连接。
peer_response_timeout:每读取到一块数据或发送出一块数据的超时时间为10秒。
receive_timeout:接收一条完整的请求超时时间为-1,无限。
keep_alive_timeout:连接保持1分钟。
request_size_limit:请求包最大大小,无限制。
ssl_accept_timeout:完成ssl握手超时,10秒。
参数里没有send_timeout,即完整的回复超时。这个参数需要每次请求根据自己回复包的大小来确定。
这个代理服务器本质上是将用户请求原封不动转发到对应的web server,再将web server的回复原封不动转发给用户。
浏览器发给proxy的请求里,request uri包含了scheme和host,port,转发时需要去除。
例如,访问http://www.sogou.com/
, 浏览器发送给proxy请求首行是:
GET
http://www.sogou.com/
HTTP/1.1
需要改写为:
GET
/
HTTP/1.1
void process(WFHttpTask *proxy_task)
{
auto *req = proxy_task->get_req();
SeriesWork *series = series_of(proxy_task);
WFHttpTask *http_task; /* for requesting remote webserver. */
tutorial_series_context *context = new tutorial_series_context;
context->url = req->get_request_uri();
context->proxy_task = proxy_task;
series->set_context(context);
series->set_callback([](const SeriesWork *series) {
delete (tutorial_series_context *)series->get_context();
});
http_task = WFTaskFactory::create_http_task(req->get_request_uri(), 0, 0,
http_callback);
const void *body;
size_t len;
/* Copy user's request to the new task's reuqest using std::move() */
req->set_request_uri(http_task->get_req()->get_request_uri());
req->get_parsed_body(&body, &len);
req->append_output_body_nocopy(body, len);
*http_task->get_req() = std::move(*req);
/* also, limit the remote webserver response size. */
http_task->get_resp()->set_size_limit(200 * 1024 * 1024);
*series << http_task;
}
以上是process的全部内容。先解析向web server发送的http请求的构造。
req->get_request_uri()调用得到浏览器请求的完整URL,通过这个URL构建发往server的http任务。
这个http任务重试与重定向次数都是0,因为重定向是由浏览器处理,遇到302等会重新发请求。
req->set_request_uri(http_task->get_req()->get_request_uri());
req->get_parsed_body(&body, &len);
req->append_output_body_nocopy(body, len);
*http_task->get_req() = std::move(*req);
上面4个语句,其实是在生成发往web server的http请求。req是我们收到的http请求,我们最终要通过std::move()把它直接移动到新请求上。
第一行实际上就是将request_uri里的http://host:port
部分去掉,只保留path之后的部分。
第二第三行把解析下来的http body指定为向外输出的http body。需要做这个操作的原因是,我们的HttpMessage实现里,
解析得到的body和发送请求的body是两个域,所以这里需要简单的设置一下,无需复制内存。
第四行,一次性把请求内容转移给向web server发送的请求。
构造好http请求后,将这个请求放到当前series末尾,process函数结束。
显然process函数并不是proxy逻辑的全部,我们还需要处理从web server返回的http response,填写返回给浏览器的response。
在echo server的示例里,我们并不需要进行网络通信,直接填写返回页面就好。但proxy我们需要等待web server的结果。
我们当然可以占用这个process函数的线程,等待结果返回,但这种同步等待的方式明显不是我们想要的。
那么,我们就需要在异步得到请求结果之后,再去回复用户请求,在等待结果期间,不能占用任何的线程。
所以,在process的头部,我们给当前series设置了一个context,context里包含了proxy_task本身,以便我们异步填写结果。
struct tutorial_series_context
{
std::string url;
WFHttpTask *proxy_task;
bool is_keep_alive;
};
void process(WFHttpTask *proxy_task)
{
SeriesWork *series = series_of(proxy_task);
...
tutorial_series_context *context = new tutorial_series_context;
context->url = req->get_request_uri();
context->proxy_task = proxy_task;
series->set_context(context);
series->set_callback([](const SeriesWork *series) {
delete (tutorial_series_context *)series->get_context();
});
...
}
之前client的示例中我们说过,任何一个运行中的任务,都处在一个series里,server任务也不例外。
所以,我们可以得到当前series,并设置context。其中url主要是后续打日志之用,proxy_task是主要内容,后续需要填写resp。
接下来我们就可以看看处理web server响应的部分了。
void http_callback(WFHttpTask *task)
{
int state = task->get_state();
auto *resp = task->get_resp();
SeriesWork *series = series_of(task);
tutorial_series_context *context =
(tutorial_series_context *)series->get_context();
auto *proxy_resp = context->proxy_task->get_resp();
...
if (state == WFT_STATE_SUCCESS)
{
const void *body;
size_t len;
/* set a callback for getting reply status. */
context->proxy_task->set_callback(reply_callback);
/* Copy the remote webserver's response, to proxy response. */
resp->get_parsed_body(&body, &len);
resp->append_output_body_nocopy(body, len);
*proxy_resp = std::move(*resp);
...
}
else
{
// return a "404 Not found" page
...
}
}
我们只关注成功的情况。一切可以从web server得到一个完整http页面,不管什么返回码,都是成功。所有失败的情况,简单返回一个404页面。
因为返回给用户的数据可能很大,在我们这个示例里,设置为200MB上限。所以,和之前的示例不同,我们需要查看reply成功/失败状态。
http server任务和我们自行创建的http client任务的类型是完全相同的,都是WFHttpTask。不同的是server任务是框架创建的,它的callback初始为空。
server任务的callback和client一样,是在http交互完成之后被调用。所以,对server任务来讲,就是reply完成之后被调用。
后面三行代码我们应该很熟悉了,无拷贝地将web server响应包转移到proxy响应包。
在这个http_callback函数结束之后,对浏览器的回复被发送出,一切都是在异步的过程中进行。
剩下的一个函数是reply_callback(),在这里只为了打印一些log。在这个callback执行结束后,proxy task会被自动delete。
最后,series的callback里销毁context。
这里需要说明一下,回复消息的时机是在series里所有其它任务被执行完后,自动回复,所以并没有task->reply()接口。
但是,有task->noreply()调用,如果对server任务执行了这个调用,在原本回复的时刻,直接关闭连接。但callback依然会被调用(状态为NOREPLY)。
在server任务的callback里,同样可以通过series_of()操作获得任务的series。那么,我们依然可以往这个series里追加新任务,虽然回复已经完成。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。