我正在开发一个web服务,将json对象服务于季叶玉异步树。我的HTML有以下内容:
<ul id="tt" method="POST" class="easyui-tree" url="http://w.x.y.z:1024/testrest">
</ul>假设w.x.y.z是我服务器的IP地址。根据他们的PHP服务的jeasyui文档,我需要返回一个包含键id、text和state的字典对象数组。好吧,到目前为止还不错。我试图使用微软的cpprest在c++中开发json服务。我在RHEL7.2上编译并安装了这个库,并且能够使用它编写一些基本服务。问题在于(我认为)将被发送回客户端的json的编码。
下面是一个用cpprest编写的功能齐全的json服务器,它处理POST请求和响应,其中包含一个单填充的符合jeasyui所期望的协议的字典对象数组:
#include <cpprest/http_listener.h>
#include <cpprest/json.h>
#pragma comment(lib, "cpprestlib" )
using namespace web;
using namespace web::http;
using namespace web::http::experimental::listener;
#include <iostream>
#include <map>
#include <set>
#include <string>
using namespace std;
#define TRACE(msg) wcout << msg
void handle_request(http_request request, function<void(const json::value &, json::value &, bool)> action)
{
json::value answer;
TRACE("\nHandle_request\n");
// Spit out the HTTP header to the console...
const auto HeaderString = request.to_string();
wcout << HeaderString.c_str() << endl;
request
.extract_json()
.then([&answer, &action](pplx::task<json::value> task) {
try
{
const auto & jvalue = task.get();
if (!jvalue.is_null())
{
action(jvalue, answer, false);
}
else
{
action(jvalue, answer, true);
}
}
catch (http_exception const & e)
{
wcout << "HTTP exception in handle_request: " << e.what() << endl;
}
})
.wait();
request.reply(status_codes::OK, answer);
}
void handle_post(http_request request)
{
TRACE("\nHandle POST\n");
handle_request(
request,
[](const json::value & jvalue, json::value & answer, bool bNull)
{
const utility::string_t sID("id");
const utility::string_t sText("text");
const utility::string_t sState("state");
if( bNull )
{
wcout << "jvalue must be null, setting some default values..." << endl;
json::value group;
group[sID] = json::value::string("1");
group[sText] = json::value::string("Hello");
group[sState] = json::value::string("closed");
answer[0] = group;
}
else
{
// To be written once the null case is sorted
}
}
);
}
int main()
{
uri_builder uri("http://w.x.y.z:1024/testrest");
http_listener listener(uri.to_uri());
listener.support(methods::POST, handle_post);
try
{
listener
.open()
.then([&listener]()
{
TRACE(L"\nStarting to listen\n");
})
.wait();
while (true);
}
catch (exception const & e)
{
wcout << e.what() << endl;
}
return 0;
}这将清晰地编译,我可以使用以下方法在linux服务器上启动服务:
./testrest &
Starting to listen为了帮助调试,我一直在使用curl作为POST客户端直接在同一台linux服务器上。我一直在使用以下命令发送具有0内容长度的POST请求:
curl -i -X POST -H 'Content-Type: application/json' http://w.x.y.z:1024/testrestcurl的输出如下:
HTTP/1.1 200 OK
Content-Length: 44
Content-Type: application/json
[{"id":"1","state":"closed","text":"Hello"}]来自我的服务的控制台消息如下:
Handle POST
Handle_request
POST /testrest HTTP/1.1
Accept: */*
Content-Type: application/json
Host: w.x.y.z:1024
User-Agent: curl/7.29.0
jvalue must be null, setting some default values...前两行对应于代码中的TRACE调用。中间部分由本节代码生成:
// Spit out the HTTP header to the console...
const auto HeaderString = request.to_string();
wcout << HeaderString.c_str() << endl;基于curl输出(它是一个字典对象数组,只有一个条目长度),我希望该服务能够很好地与客户机上的季叶玉 javascript一起工作。然而,事实并非如此。我的异步树从不填充,也看不到任何东西。
我怀疑编码有问题,所以我用web2py编写了另一个服务来测试它是否能在那里工作。我的default.py控制器中存在以下代码:
@service.json
def testweb2py():
aRet=[]
if request.post_vars.id is None:
mydict={'id':'1','text':'Hello','state':'closed'}
aRet.append(mydict)
return aRet在修改我的客户端easyui-tree web2py指向web2py URL之后,它将非常完美地填充,我可以看到节点。我使用curl访问了web2py service.json代码,以查看输出可能有哪些不同:
HTTP/1.1 200 OK
Date: Mon, 23 Jan 2017 18:17:17 GMT
Server: Apache/2.4.6 (Red Hat Enterprise Linux) OpenSSL/1.0.1e-fips mod_wsgi/3.4 Python/2.7.5
X-Powered-By: web2py
Expires: Mon, 23 Jan 2017 18:17:18 GMT
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Content-Length: 99
Content-Type: application/json; charset=utf-8
[{"text": "Hello", "state": "closed", "id": "1"}]除了内容标题是非常不同的,有一行我怀疑可能与它有关:
Content-Type: application/json; charset=utf-8在对cpprest服务的调用中,curl的头输出不包括charset=utf-8。如果我使用-o开关将curl输出转储到一个文件中,我看不到编码之间的明显区别。在json的格式中,我唯一能看到的不同之处是一些额外的空格和排序:
[{"text": "Hello", "state": "closed", "id": "1"}] // web2py version
[{"id":"1","state":"closed","text":"Hello"}] // cpprest version我无法控制json字典的发送顺序,但我怀疑这与此有任何关系。值条目前缀的额外空格似乎也不相关。
我已经在microsoft.github.io/cpprestsdk/index.html上翻阅了cpprest文档,并且找不到任何与设置输出编码有关的内容。有许多对http_request::reply的重写,包括设置内容类型的选项,我一直在用硬编码字符串调用它们,用于json主体和json/application; charset=utf-8的内容类型,但都没有效果。无论如何,我不知道如何在json::value对象中使用这些重写,所以我认为这不是最佳路径,也不是这个cpprest库的可行使用。
jeasyui的javascript代码似乎是故意混淆的,我对它如何处理来自POST电话的回复没有信心。也许熟悉jeasyui的人可以指出调试异步帖子的可行方法?
请帮帮我!
发布于 2017-01-27 21:46:05
所以我弄明白了到底发生了什么。在Chrome中打开developer工具控制台,发现以下错误消息:
XMLHttpRequest无法加载 http://w.x.y.z:1024/testrest。请求的资源上没有“访问-控制-允许-原产地”标题。因此,'http://w.x.y.z‘源不允许访问.
因此,这与我的json数据的格式或编码无关,而是json服务被识别为与生成客户机HTML的web服务器不同的资源,因此Chrome阻止了它。为了解决这个问题,我必须在发送回客户端的响应中添加一些头字段,并添加一个支持方法来处理来自任何可能需要它们的客户端的选项查询。
在main()函数中,我添加了:
listener.support(methods::OPTIONS, handle_options);然后,我编写了相应的函数:
void handle_options(http_request request)
{
http_response response(status_codes::OK);
response.headers().add(U("Allow"), U("POST, OPTIONS"));
// Modify "Access-Control-Allow-Origin" header below to suit your security needs. * indicates allow all clients
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("POST, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
request.reply(response);
}最后,我必须在我的request.reply中向handle_request中添加相同的标题:
http_response response(status_codes::OK);
// Without these headers, the client browser will likely refuse the data and eat it
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("POST, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
response.set_body(answer);
request.reply(response); 还有其他的问题..。最突出的事实是,jeasyui类easyui_tree不使用application/json的内容类型发布数据。相反,它发布了一个内容类型的application/x-www-form-urlencoded,因此我不得不添加一个函数来使用libcurl解析url编码。这还意味着将request.extract_json()替换为request.extract_string(),并对cpprest使用的相应lambda函数进行相关修改。
下面是最后一个示例代码,也许它对在这些领域工作的其他人很有用。这是一个完整的使用cpprest编写json服务的示例(在linux上),它响应来自easyui_tree的异步POST请求。依赖性: boost,cpprest和libcurl-devel。
#include <boost/algorithm/string/replace.hpp>
#include <cpprest/http_listener.h>
#include <cpprest/json.h>
#include <curl/curl.h>
#pragma comment(lib, "cpprestlib" )
using namespace web;
using namespace web::http;
using namespace web::http::experimental::listener;
#include <iostream>
#include <map>
#include <vector>
#include <set>
#include <string>
using namespace std;
#define TRACE(msg) wcout << msg
void build_json( const utility::string_t &source, json::value &jvalue )
{
// Use libcurl to unescape the POST body for us
vector<string> splitvec;
// We don't own the string created by curl_easy_unescape, so add a custom deleter
string text = shared_ptr<char>( curl_easy_unescape( 0, source.c_str(), 0, 0 ), curl_free).get();
// This works for this specific example of jeasyui, the class 'easyui-tree', which only passes id=... in the POST.
// Need custom handler to deal with more complicated data formats
boost::split( splitvec, text, boost::is_any_of("="));
if( splitvec.size() == 2 )
{
jvalue[splitvec.at(0)] = json::value::string(splitvec.at(1));
}
}
void handle_request(http_request request, function<void(const json::value &, json::value &, bool)> action)
{
json::value answer;
auto objHeader = request.headers();
auto sContentType = objHeader["Content-Type"];
// Two cases:
// 1) The very first call from easyui_tree, when the HTML is first loaded, will make a zero-length POST with no 'Content-Type' in the header
// 2) Subsequent calls from easyui_tree (e.g. when user opens a node) will have a Content-Type of 'application/x-www-form-urlencoded'
// Nowhere does easyui_tree send json data in the POST, although it expects json in the reply
if( sContentType.size() == 0 ||
!strncasecmp( sContentType.c_str(), "application/x-www-form-urlencoded", strlen("application/x-www-form-urlencoded") ) )
{
request
.extract_string()
.then([&answer, &action](pplx::task<utility::string_t> task) {
try
{
const auto & svalue = task.get();
json::value jvalue;
if ( svalue.size() == 0 )
{
action(jvalue, answer, true);
}
else
{
build_json( svalue, jvalue );
action(jvalue, answer, false);
}
}
catch (http_exception const & e)
{
wcout << "HTTP exception in handle_request: " << e.what() << endl;
}
})
.wait();
}
else
{
// This Content-Type doesn't appear with easyui_tree, but perhaps it's still useful for future cases...
if( !strncasecmp( sContentType.c_str(), "application/json", strlen("application/json") ) )
{
request
.extract_json()
.then([&answer, &action](pplx::task<json::value> task) {
try
{
const auto & jvalue = task.get();
if (!jvalue.is_null())
{
action(jvalue, answer, false);
}
else
{
action(jvalue, answer, true);
}
}
catch (http_exception const & e)
{
wcout << "HTTP exception in handle_request: " << e.what() << endl;
}
})
.wait();
}
}
http_response response(status_codes::OK);
// Without these headers, the client browser will likely refuse the data and eat it
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("POST, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
response.set_body(answer);
request.reply(response);
}
void handle_options(http_request request)
{
http_response response(status_codes::OK);
response.headers().add(U("Allow"), U("POST, OPTIONS"));
// Modify "Access-Control-Allow-Origin" header below to suit your security needs. * indicates allow all clients
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("POST, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
request.reply(response);
}
void handle_post(http_request request)
{
handle_request(
request,
[](const json::value & jvalue, json::value & answer, bool bInitialize)
{
if( bInitialize )
{
// First time the tree is being loaded, first id will be 16, which will yield us 16 child nodes when it POSTs back
json::value jreply;
jreply[U("id")] = json::value::string("16");
jreply[U("text")] = json::value::string("Parent");
jreply[U("state")] = json::value::string("closed");
answer[0] = jreply;
}
else
{
// User has opened a node
if( jvalue.type() == json::value::value_type::Object )
{
if( jvalue.has_field( "id" ) )
{
auto & key = jvalue.at( "id" );
if( key.is_string() )
{
auto value = key.as_string();
int id = atoi(value.c_str());
stringstream ss;
ss << (id / 2); // Each successive layer has half as many child nodes as the one prior
for( int i = 0; i < id; i++ )
{
json::value jreply;
jreply[U("id")] = json::value::string(ss.str());
jreply[U("text")] = json::value::string("Child");
jreply[U("state")] = json::value::string("closed");
answer[i] = jreply;
}
}
}
}
}
}
);
}
int main()
{
uri_builder uri("http://yourserver.com:1024/testrest");
http_listener listener(uri.to_uri());
listener.support(methods::POST, handle_post);
listener.support(methods::OPTIONS, handle_options);
try
{
listener
.open()
.then([&listener]()
{
TRACE(L"\nStarting to listen\n");
})
.wait();
while (true);
}
catch (exception const & e)
{
wcout << e.what() << endl;
}
return 0;
}当然,如果在标题中引用了所有jeasyui脚本,那么当然还有相应的HTML:
<ul id="tt" method="POST" class="easyui-tree" url="http://yourserver.com:1024/testrest">
</ul>https://stackoverflow.com/questions/41815713
复制相似问题