1.使用 Poco::Net::HTMLForm
使用 Poco::Net::HTMLForm 获取 数据的方式如下:
virtual void handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override
{
Poco::Net::HTMLForm form(req, req.stream());
for(const auto &it : form)
{
//遍历 form 数据
std::cout << it.first << ":" << it.second << std::endl;
}
}
Poco::Net::HTMLForm 对象初始化的时候,会首先读取 req.stream()
中的数据,然后对数据进行解析。
查看 poco/Net/src/HTMLForm.cpp
源码,发现HTMLForm支持两种格式的数据:
- multipart/form-data
- application/x-www-form-urlencoded
const std::string HTMLForm::ENCODING_URL = "application/x-www-form-urlencoded";
const std::string HTMLForm::ENCODING_MULTIPART = "multipart/form-data";
const int HTMLForm::UNKNOWN_CONTENT_LENGTH = -1;
当 Poco
检测到 Content-Type
是 multipart/form-data
的时候,就会执行 void HTMLForm::readMultipart(std::istream& istr, PartHandler& handler)
方法,如果不是multipart/form-data
,那么就执行 void HTMLForm::readUrl(std::istream& istr)
方法。
HTMLForm::readMultipart()
方法会把每一个part
作为一个name:value
进行解析;如果遇到文件 ,就直接忽略这个文件的内容。文件可以使用自定义的Poco::Net::PartHandler
去解析;HTMLForm::readUrl()
方法会把content
内的所有内容作为很多name:value
进行解析,=
作为name
和value
的分隔符,&
作为多个name:value
之间的分隔符。获取name
和value
后再进行urldecode
解码。
Poco::Net::HTMLForm 的缺点就是把内容强制作为 form 进行解析,因为它根本不检查 Content-Type 是什么。
可参考这篇文章:表单编码格式
2.直接读取 req.stream()
如果接收到的数据是 json,那么最好不要使用 Poco::Net::HTMLForm
,尤其是数据中有 ‘=’ 存在的时候,Poco::Net::HTMLForm 会直接把 ‘=’ 作为分隔符使用,'=‘左边的是 name,’='右边的是 value。
如何使用Poco
库解析 json
数据呢? 我们可以直接读取 req.stream()
,然后再解析json数据,就像下面这样:
void PeripheralGpsHandler::MethodPut(Poco::Net::HTTPServerRequest &request, Poco::Net::HTTPServerResponse &response)
{
int len = request.getContentLength();
if (len < 0)
{
return;
}
istream& in = request.stream();
Poco::JSON::Parser parser;
Poco::Dynamic::Var result = parser.parse(in);
Poco::JSON::Object::Ptr pObj = result.extract<Poco::JSON::Object::Ptr>();
if (pObj->has("value")) //可以先判断json对象中是否存在"value"字段
{
// 如果不判断json对象中是否存在"value"字段
// 直接使用`getValue`函数取值
// 那么如果没有 "value" 字段, POCO框架会抛出一个异常, 但是程序不会崩溃
std::string value = pObj->getValue<std::string>("value");
}
// `get`函数会返回一个 `Poco::Dynamic::Var`万能包装类型
// `Poco::Dynamic::Var` 提供 `convert`函数获取实际持有的内容
Poco::Dynamic::Var varValue = pObj->get("value");
if (!varValue.isEmpty())
{
std::string value = varValue.convert<std::string>();
}
附上一段Poco::JSON::Object
类中的源码
getValue()
函数和optValue()
函数非常常用
成员变量:
std::map<std::string, Dynamic::Var> _values;
//get
Dynamic::Var Object::get(const std::string& key) const
{
ValueMap::const_iterator it = _values.find(key);
if (it != _values.end())
{
return it->second;
}
return Var();
}
//getValue
template<typename T>
T getValue(const std::string& key) const
/// Retrieves the property with the given name and will
/// try to convert the value to the given template type.
/// The convert<T>() method of Var is called
/// which can also throw exceptions for invalid values.
/// Note: This will not work for an array or an object.
{
Dynamic::Var value = get(key);
return value.convert<T>();
}
// optValue,可以传递默认值
template<typename T>
T optValue(const std::string& key, const T& def) const
{
T value = def;
ValueMap::const_iterator it = _values.find(key);
if (it != _values.end() && ! it->second.isEmpty())
{
try
{
value = it->second.convert<T>();
}
catch (...)
{
// The default value will be returned
}
}
return value;
}
//getArray
Array::Ptr getArray(const std::string& key) const;
Array::Ptr Object::getArray(const std::string& key) const
{
ValueMap::const_iterator it = _values.find(key);
if ((it != _values.end()) && (it->second.type() == typeid(Array::Ptr)))
{
return it->second.extract<Array::Ptr>();
}
return 0;
}
// getObject
Object::Ptr getObject(const std::string& key) const;
Object::Ptr Object::getObject(const std::string& key) const
{
ValueMap::const_iterator it = _values.find(key);
if ((it != _values.end()) && (it->second.type() == typeid(Object::Ptr)))
{
return it->second.extract<Object::Ptr>();
}
return 0;
}
可以看到,
get
函数返回的Poco::Dynamic::Var
类型是重中之重,后面的函数getValue()
,getObject()
,getArray()
都是在这个类型的基础上调用的extract
或者convert
模板函数来实现的;
附上 convert
和 extract
函数源码
template <typename T>
T convert() const
{
VarHolder* pHolder = content();
if (!pHolder)
throw InvalidAccessException("Can not convert empty value.");
if (typeid(T) == pHolder->type()) return extract<T>();
T result;
pHolder->convert(result);
return result;
}
//-----------------------------------------------------------------------------
template <typename T>
const T& extract() const
{
VarHolder* pHolder = content();
if (pHolder && pHolder->type() == typeid(T))
{
VarHolderImpl<T>* pHolderImpl = static_cast<VarHolderImpl<T>*>(pHolder);
return pHolderImpl->value();
}
else if (!pHolder)
throw InvalidAccessException("Can not extract empty value.");
else
throw BadCastException(format("Can not convert %s to %s.",
std::string(pHolder->type().name()),
std::string(typeid(T).name())));
}
类型擦除
Poco::Dynamic::Var
是一个使用类型擦除技巧实现的万能包装类,它持有一个VarHolder*
类型的成员变量_pHolder
,这个VarHolder
类是非模板类,它有模板类型的子类VarHolderImpl
,可以用来包装任意类型的值,就是这样实现的类型擦除
;
以上源码可以看到,Poco::Dynamic::Var
提供的convert
函数,调用的底层extract
模板函数,原理是将成员变量_pHolder
向下转型为VatHolderImpl<T>*
类型的指针,然后再调用value()
函数取出它所持有的T
类型字段即可;
其实现原理可以参考这一篇博文: Any类实现
回到HTTP主题
-
服务端响应HTTP请求时,
req.stream()
函数返回的输入流就是HTTP请求的请求体内容 -
客户端/服务端使用
session.sendRequest(request);
或response.send();
发送/回复HTTP请求时,只是将HTTP头组建到std::ostream
,并返回ostream
,我们需要通过在返回的这个ostream
中追加body来实现HTTP Body的内容和传输
Poco::Net::HTTPClientSession session("ip", port);
session.setTimeout(Poco::Timespan(500)); //超时时间500ms
std::ostream& ostream = session.sendRequest(request);
QByteArray data = ui->comboBox->currentData().value<QByteArray>();
ostream .write(data.data(), data.size()); // 将需要发送的字节数组写入请求流中
ostream .flush(); // 刷新请求流,确保数据被发送
// 如果是发送Json数据,可以使用这几个函数:
Poco::JSON::Object json;
json.set("x", 132);
json.stringify(ostream ); // 1
Poco::JSON::Stringifier::stringify(json, ostream); // 2
std::ostringstream oss;
json.stringify(oss);
ostream << oss.str(); // 3
- 客户端接收HTTP响应时,
session.receiveResponse(response);
是获取HTTP响应的响应体。
//接收HTTP响应
Poco::Net::HTTPResponse response;
std::istream& istream = session.receiveResponse(response);
// 方式1, 从responseStream读取到 (o)stringstream 中
std::ostringstream ss;
Poco::StreamCopier::copyStream(istream, ss);
std::string responseData = ss.str();
// 直接使用 `Poco::StreamCopier::copyToString()`函数
std::string str;
Poco::StreamCopier::copyToString(istream, str);
简单记录下流操作
相关类和函数
将应用程序做为主体
1.来源于程序外部(网络、文件等)的数据,可以理解为输入
std::istream
表示输入流对象,功能是将程序外部的数据,读取到程序内部;使用read
函数,或是>>
操作符;
2.从程序内部产生,将要去往外部(网络,文件等)的数据,可以理解为输出
std::ostream
表示输出流对象,功能是将程序内部的数据,写入到程序外部;使用write
函数,或是<<
操作符
istream
类的 read
和 ostream
类的 write
函数原型如下 :
istream& read(char *buffer,int len);
ostream& write(const char * buffer,int len);
1.输入流读取 - read() 函数
read()
函数是 istream
类的一个成员函数, 用于从输入流中读取指定长度的数据并存储到指定的缓冲区中 , 函数原型如下 :
istream& read(char *buffer, int len);
函数参数 + 返回值:
- buffer : 指向要存储读取数据的字符数组的指针
- len : 要读取的字节数 ;
- 返回一个
istream
对象的引用 , 可以用于链式调用 ;
2、获取实际读取的字节数 - gcount() 函数
read()
函数可以指定要读取的字节数 ,但实际读取的字节数需要调用 istream
的 gcount()
函数来获取 , 其函数原型如下 :
streamsize gcount();
istream
的 gcount()
函数 返回一个 streamsize
类型的值 , 表示上次 输入操作 读取的字节数
;
如果上一次输入操作是读取一个字符 , 那么 gcount() 函数 返回的值将为 1 ;
如果上一次输入操作是读取一个完整的行 , 那么 gcount() 函数 返回的值将为该行的字节数 ;
3、输出流写入 - write()函数
write
函数是ostream
类的一个成员函数,用于将指定长度的数据写入到输出流中,原型如下
ostream& write(const char * buffer,int len);
函数参数 + 返回值
- buffer:指向将要写入到输出流中去的字符数组的指针。
- len:要写入的字节数。
- 返回一个
ostream
对象的引用,用于链式调用