从Poco::Net::HTTPServerRequest 中获取Content

文章讲述了在处理HTTP请求时,如何使用Poco::Net::HTMLForm解析multipart/form-data和非multipart/form-data格式的数据,以及在遇到JSON数据时,直接读取req.stream()进行解析的替代方法。作者还讨论了Poco::Net::HTMLForm的局限性及处理JSON数据的正确做法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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-Typemultipart/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 进行解析,=作为namevalue 的分隔符,&作为多个 name:value 之间的分隔符。获取namevalue后再进行 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模板函数来实现的;

附上 convertextract函数源码

	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 类的 readostream 类的 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() 函数可以指定要读取的字节数 ,但实际读取的字节数需要调用 istreamgcount() 函数来获取 , 其函数原型如下 :

streamsize gcount();

istreamgcount() 函数 返回一个 streamsize 类型的值 , 表示上次 输入操作 读取的字节数 ;
如果上一次输入操作是读取一个字符 , 那么 gcount() 函数 返回的值将为 1 ;
如果上一次输入操作是读取一个完整的行 , 那么 gcount() 函数 返回的值将为该行的字节数 ;

3、输出流写入 - write()函数

write函数是ostream类的一个成员函数,用于将指定长度的数据写入到输出流中,原型如下

ostream& write(const char * buffer,int len);

函数参数 + 返回值

  • buffer:指向将要写入到输出流中去的字符数组的指针。
  • len:要写入的字节数。
  • 返回一个ostream对象的引用,用于链式调用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值