История неудачных попыток создать удобный парсер HTTP-полей для RESTinio продолжается. Результат третьей попытки под катом. Рассказ о первой попытке здесь, а о второй -- здесь.
Анализируя результат второй попытки появилась, как тогда казалось, стройная идея по разделению парсера на такие понятия, как value_producer, value_transformer и value_consumer. По первым прикидкам казалось, что это позволит делать декларативное описание структуры значения HTTP-поля на основании грамматики этого поля из RFC.
Оно-то как-то так и оказалось. Но уж слишком многословно и мудрено.
В качестве примера под катом находится класс для разбора значения HTTP-поля Cache-Control. Содержимое этого поля в RFC определяется как:
Cache-Control = 1#cache-directive
cache-directive = token [ "=" ( token / quoted-string ) ]
При этом конструкция вида 1#element определяется вот так:
1#element => *( "," OWS ) element *( OWS "," [ OWS element ] )
Т.е. правило для Cache-Control можно развернуть так:
Cache-Control = *( "," OWS ) cache-directive *( OWS "," [ OWS cache-directive ] )
cache-directive = token [ "=" ( token / quoted-string ) ]
Т.е. этому правилу может удовлетворять, например, такая последовательность:
, , , max-age=5, , , no-transform, ,,, ,
Итак, вот что получилось:
struct cache_control_value_t { using directive_container_t = std::map< std::string, restinio::optional_t<std::string> >; directive_container_t m_directives; struct details { template<typename T> struct container_adaptor { using container_type = directive_container_t; using value_type = restinio::optional_t< std::pair< std::string, restinio::optional_t<std::string> > >; static void store( container_type & dest, value_type && what ) { if( what ) dest.emplace( std::move(*what) ); } }; }; static auto make_parser() { using namespace restinio::http_field_parser; using directive_t = std::pair< std::string, restinio::optional_t<std::string> >; return produce< cache_control_value_t >( repeat< nothing_t >( 0, N, symbol(',') >> skip(), rfc::ows() >> skip() ) >> skip(), produce< directive_t >( rfc::token() >> to_lower() >> &directive_t::first, optional< std::string >( symbol('=') >> skip(), alternatives< std::string >( rfc::token() >> to_lower(), rfc::quoted_string() ) >> as_result() ) >> &directive_t::second ) >> custom_consumer( [](auto & dest, directive_t && v) { dest.m_directives.emplace( std::move(v) ); } ), repeat< directive_container_t, details::container_adaptor >( 0, N, produce< restinio::optional_t<directive_t> >( rfc::ows() >> skip(), symbol(',') >> skip(), optional< directive_t >( rfc::ows() >> skip(), rfc::token() >> to_lower() >> &directive_t::first, optional< std::string >( symbol('=') >> skip(), alternatives< std::string >( rfc::token() >> to_lower(), rfc::quoted_string() ) >> as_result() ) >> &directive_t::second ) >> as_result() ) >> as_result() ) >> custom_consumer( [](auto & dest, directive_container_t && v) { // There is no a good way except the copy from // 'v' to 'dest' one-by-one. for( auto & item : v ) dest.m_directives[ item.first ] = item.second; } ) ) >> as_result(); } static std::pair< bool, cache_control_value_t > try_parse( string_view_t what ) { return restinio::http_field_parser::try_parse_field_value< cache_control_value_t >( what, make_parser() ); } }; |
Кроме осознания того, что мне предстоит четвертая итерация, есть еще два важных вывода из содеянного:
- возможность упороться шаблонами еще не означает, что ими нужно упарываться. Не нужно так делать :(
- очередное подтверждение того, что простота не предшествует сложности, а следует за ней. И в данном конкретном случае меня ждет еще очень длинный путь.
На вопрос "А нафига это все вообще?" пока ответ простой: "Хочется сделать один раз нормально, чтобы потом иметь возможность декларативно описывать содержимое HTTP-поля". Или, другими словами, "лучше день потерять, затем за 5 минут долететь". Но с каждым подходом уверенность в том, что это правильный ответ, снижается. Но пока не до критического уровня ;)
Upd. Вот до чего удалось сократить после некоторого анализа получившегося варианта:
struct cache_control_value_t { using directive_t = std::pair< std::string, restinio::optional_t<std::string> >; using directive_container_t = std::map< std::string, restinio::optional_t<std::string> >; directive_container_t m_directives; static auto make_parser() { using namespace restinio::http_field_parser; return produce< cache_control_value_t >( one_or_more_of< directive_container_t >( produce< directive_t >( rfc::token() >> to_lower() >> &directive_t::first, optional< std::string >( symbol('=') >> skip(), alternatives< std::string >( rfc::token() >> to_lower(), rfc::quoted_string() ) >> as_result() ) >> &directive_t::second ) ) >> &cache_control_value_t::m_directives ) >> as_result(); } static std::pair< bool, cache_control_value_t > try_parse( string_view_t what ) { using namespace restinio::http_field_parser; return try_parse_field_value< cache_control_value_t >( what, make_parser() ); } }; |
Комментариев нет:
Отправить комментарий