change the way how winix answer is created

Now we can return ezc content and models serialized in the same json structure,
Xml and Csv are not implemented yet.
Ezc frames are returned in 'ezc_frames' field.
Main ezc stream is returned in 'main_stream' field.
Frame url parameter can take more than one frame (names separated by commas).
Honor Accept http header (AcceptParser).

Samples:
--------
http://domain.tld/dir/controller
returns html answer from the main ezc stream

http://domain.tld/dir/controller/container:raw
returns html answer from the main ezc stream (the same as above)

http://domain.tld/dir/controller/frame:abc
returns "abc" frame as html

http://domain.tld/dir/controller/container:json
returns all serialized models to json and no ezc streams

http://domain.tld/dir/controller/container:xml
returns all serialized models to xml and no ezc streams (not implemented yet)

http://domain.tld/dir/controller/container:json/frame:abc,xyz
returns all serialized models to json and two frames in 'ezc_frames' object

http://domain.tld/dir/controller/container:json/all_frames
returns all serialized models to json and all frames in 'ezc_frames' object

http://domain.tld/dir/controller/container:json/main_stream
returns all serialized models and the main ezc stream in 'main_stream' field

http://domain.tld/dir/controller/container:json/main_stream/all_frames
returns all serialized models to json, all frames and the main stream
This commit is contained in:
Tomasz Sowa 2022-02-01 18:44:23 +01:00
parent 227dd923d6
commit f7b5ac0dc8
17 changed files with 695 additions and 395 deletions

File diff suppressed because one or more lines are too long

View File

@ -199,8 +199,8 @@ app.o: ../../../winix/winixd/templates/indexpatterns.h
app.o: ../../../winix/winixd/templates/patterns.h
app.o: ../../../winix/winixd/templates/changepatterns.h compress.h
app.o: postparser.h httpsimpleparser.h cookieparser.h postmultiparser.h
app.o: acceptencodingparser.h acceptbaseparser.h winixrequest.h
app.o: ../../../winix/winixd/models/migration.h
app.o: acceptencodingparser.h acceptbaseparser.h acceptparser.h
app.o: winixrequest.h ../../../winix/winixd/models/migration.h
basethread.o: basethread.h synchro.h winixmodeldeprecated.h
basethread.o: ../../../winix/winixd/core/winixbase.h
basethread.o: ../../../winix/winixd/core/config.h

124
winixd/core/acceptparser.h Normal file
View File

@ -0,0 +1,124 @@
/*
* This file is a part of Winix
* and is distributed under the 2-Clause BSD licence.
* Author: Tomasz Sowa <t.sowa@ttmath.org>
*/
/*
* Copyright (c) 2022, Tomasz Sowa
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef headerfile_winix_core_acceptparser
#define headerfile_winix_core_acceptparser
#include "acceptbaseparser.h"
#include "log.h"
#include "header.h"
namespace Winix
{
class AcceptParser : public AcceptBaseParser
{
public:
static constexpr size_t MAX_CONTAINER_LENGTH = 16;
/*
* IMPROVEME add support for something like "text/html;level=1" (skip the level part)
*
* https://developer.mozilla.org/en-US/docs/Glossary/Quality_values
* Some syntax, like the one of Accept, allow additional specifiers like text/html;level=1.
* These increase the specificity of the value. Their use is extremely rare.
*
*
*/
void Parse(const wchar_t * str, std::vector<HeaderValue> & header_values, bool clear_header_values = true)
{
if( clear_header_values )
header_values.clear();
this->header_values = &header_values;
AcceptBaseParser::Parse(str);
std::sort(header_values.begin(), header_values.end(), [](HeaderValue & h1, HeaderValue & h2) -> bool {
return h1.weight > h2.weight;
});
PutToLog(header_values);
}
void Parse(const std::wstring & str, std::vector<HeaderValue> & header_values, bool clear_header_values = true)
{
Parse(str.c_str(), header_values, clear_header_values);
}
private:
std::vector<HeaderValue> * header_values;
void Param(const std::wstring & param, double q)
{
if( header_values->size() < MAX_CONTAINER_LENGTH && q > 0.0 )
{
if( q > 1.0 )
q = 1.0;
header_values->resize(header_values->size() + 1);
header_values->back().value = param;
header_values->back().weight = q;
}
}
void PutToLog(std::vector<HeaderValue> & header_values)
{
if( !header_values.empty() )
{
log << log3 << "AP: " << Header::accept << " header consists of: ";
HeaderValue::log_values(header_values, log);
log << logend;
}
else
{
log << log3 << "AP: there is no " << Header::accept << " header" << logend;
}
}
};
} // namespace Winix
#endif

View File

@ -386,6 +386,7 @@ bool App::Init()
cookie_parser.set_dependency(&winix_model);
accept_encoding_parser.set_dependency(&winix_base);
accept_parser.set_dependency(&winix_base);
plugin.Call((Session*)0, WINIX_PLUGIN_INIT);
@ -777,7 +778,7 @@ void App::SaveSessionsIfNeeded()
// !! IMPROVE ME change to a better name
void App::MakeEzcGenerator()
void App::UseEzcGenerator()
{
// if( cur.request->page_generated || !cur.request->redirect_to.empty() || !cur.request->x_sendfile.empty() )
// return;
@ -823,23 +824,19 @@ void App::CheckPostRedirect()
void App::AddDefaultModels()
{
// there is no need to add default models if we return a binary stream
if( cur.request->answer_source != Request::AnswerSource::answer_bin_stream )
if( cur.request->function && cur.request->function->register_default_models )
{
if( cur.request->function && cur.request->function->register_default_models )
// may it would be better do not return cur.request by default?
cur.request->models.Add(L"request", cur.request);
if( cur.session && cur.session->puser )
{
// may it would be better do not return cur.request by default?
cur.request->models.Add(L"request", cur.request);
cur.request->models.Add(L"user", *cur.session->puser);
}
if( cur.session && cur.session->puser )
{
cur.request->models.Add(L"user", *cur.session->puser);
}
if( cur.request->is_item )
{
cur.request->models.Add(L"item", cur.request->item);
}
if( cur.request->is_item )
{
cur.request->models.Add(L"item", cur.request->item);
}
}
}
@ -857,8 +854,7 @@ void App::Make()
return;
}
if( !cur.request->PrepareAnswerType() )
return;
cur.request->PrepareAnswerType();
if( cur.session->ip_ban && cur.session->ip_ban->IsIPBanned() )
{
@ -958,6 +954,7 @@ void App::ReadRequest()
cookie_parser.Parse(cur.request->env_http_cookie, cur.request->cookie_tab);
accept_encoding_parser.ParseAndLog(cur.request->env_http_accept_encoding);
accept_parser.Parse(cur.request->env_http_accept, cur.request->accept_mime_types);
if( config.log_env_variables )
LogEnvironmentVariables();
@ -987,7 +984,9 @@ void App::SetEnv(const char * name, std::wstring & env)
/*
* IMPROVE ME take it from cur.request.headers_in?
*/
void App::ReadEnvVariables()
{
SetEnv("REQUEST_METHOD", cur.request->env_request_method);
@ -1000,6 +999,7 @@ void App::ReadEnvVariables()
SetEnv("HTTP_USER_AGENT", cur.request->env_http_user_agent);
SetEnv("HTTP_COOKIE", cur.request->env_http_cookie);
SetEnv("HTTP_ACCEPT_ENCODING", cur.request->env_http_accept_encoding);
SetEnv("HTTP_ACCEPT", cur.request->env_http_accept);
}
@ -1116,7 +1116,6 @@ void App::ReadEnvRemoteIP()
void App::CheckRequestMethod()
{
cur.request->method = Request::unknown_method;
@ -1432,28 +1431,28 @@ void App::PrepareHeaderContentType()
{
if( !cur.request->out_headers.has_key(Winix::Header::content_type) )
{
if( cur.request->answer_source == Request::AnswerSource::answer_bin_stream )
if( cur.request->container_type == Request::ContainerType::container_json )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_octet_stream);
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_json_utf8);
}
else
if( cur.request->container_type == Request::ContainerType::container_xml )
{
if( cur.request->answer_container == Request::AnswerContainer::answer_json )
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_xml_utf8);
}
else
if( cur.request->container_type == Request::ContainerType::container_csv )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::text_csv_utf8);
}
else
if( cur.request->container_type == Request::ContainerType::container_raw )
{
if( cur.request->send_bin_stream )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_json_utf8);
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_octet_stream);
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_xml )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::application_xml_utf8);
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_csv )
{
cur.request->out_headers.add(Winix::Header::content_type, Winix::Header::text_csv_utf8);
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_text )
{
switch( config.content_type_header )
{
@ -1828,83 +1827,167 @@ void App::SendAnswer()
// what about method HEAD?
if( !cur.request->redirect_to.empty() || !cur.request->x_sendfile.empty() )
{
Send8bitOutput(output_8bit);
Send8bitOutput(output_8bit); // send empty content
return;
}
if( cur.request->answer_source == Request::AnswerSource::answer_bin_stream )
plugin.Call(WINIX_CONTENT_MAKE);
if( cur.request->use_ezc_engine )
{
UseEzcGenerator();
}
if( cur.request->container_type == Request::ContainerType::container_raw )
{
PrepareRawAnswer();
}
else
if( cur.request->container_type == Request::ContainerType::container_json )
{
PrepareJsonAnswer();
}
else
if( cur.request->container_type == Request::ContainerType::container_xml )
{
PrepareXmlAnswer();
}
else
if( cur.request->container_type == Request::ContainerType::container_csv )
{
PrepareCsvAnswer();
}
Send8bitOutput(output_8bit);
}
void App::PrepareRawAnswer()
{
if( cur.request->send_bin_stream )
{
Send8bitOutput(cur.request->out_bin_stream);
}
else
if( cur.request->send_main_stream )
{
// is this plugin call correct here?
plugin.Call(WINIX_CONTENT_MAKE);
if( cur.request->answer_source == Request::AnswerSource::answer_models && cur.request->use_ezc_engine )
{
MakeEzcGenerator(); // give me a better name
if( !cur.request->frame.empty() || cur.request->send_all_frames )
{
cur.request->answer_source = Request::AnswerSource::answer_frame_streams;
}
else
{
cur.request->answer_source = Request::AnswerSource::answer_main_stream;
}
}
if( cur.request->answer_source == Request::AnswerSource::answer_main_stream )
{
const wchar_t * field_name = nullptr;
if( cur.request->answer_container == Request::AnswerContainer::answer_xml )
field_name = config.xml_root.c_str();
SerializeStream(cur.request->out_main_stream.get_buffer(), field_name);
}
else
if( cur.request->answer_source == Request::AnswerSource::answer_frame_streams )
{
SerializeFrames();
}
else
if( cur.request->answer_source == Request::AnswerSource::answer_models )
{
SerializeModels();
}
Send8bitOutput(output_8bit);
FilterHtmlIfNeeded(cur.request->out_main_stream.get_buffer(), output_8bit, false);
}
else
if( cur.request->send_all_frames )
{
SerializeAllFrames();
}
else
if( !cur.request->send_frames.empty() )
{
SerializeSpecificFrames();
}
}
void App::SerializeStream(const pt::WTextStream & input_stream, const wchar_t * field_name)
void App::PrepareJsonAnswer()
{
switch( cur.request->answer_container )
output_8bit << '{';
PrepareContenerizedAnswer();
output_8bit << '}';
}
void App::PrepareXmlAnswer()
{
output_8bit << '<';
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
PrepareContenerizedAnswer();
output_8bit << "</";
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
}
void App::PrepareCsvAnswer()
{
PrepareContenerizedAnswer();
}
void App::PrepareContenerizedAnswer()
{
bool put_separator = false;
if( cur.request->serialize_models )
{
case Request::AnswerContainer::answer_json:
SerializeStreamJson(input_stream, field_name);
break;
SerializeModels();
put_separator = true;
}
case Request::AnswerContainer::answer_xml:
SerializeStreamXml(input_stream, field_name);
break;
if( cur.request->send_bin_stream )
{
PutSeparatorIfNeeded(put_separator);
case Request::AnswerContainer::answer_csv:
SerializeStreamCsv(input_stream, field_name);
break;
// IMPLEMENT ME serialize binary stream as base64 and put in 'bin_stream' field
pt::WTextStream str;
str << "NOT IMPLEMENTED YET";
SerializeStream(str, config.bin_stream_field.c_str());
put_separator = true;
}
case Request::AnswerContainer::answer_text:
default:
FilterHtmlIfNeeded(input_stream, output_8bit);
break;
if( cur.request->send_main_stream )
{
PutSeparatorIfNeeded(put_separator);
SerializeStream(cur.request->out_main_stream.get_buffer(), config.main_stream_field.c_str());
put_separator = true;
}
if( cur.request->send_all_frames || !cur.request->send_frames.empty() )
{
PutSeparatorIfNeeded(put_separator);
SerializeFieldJson(config.ezc_frames_field.c_str());
output_8bit << "{";
if( cur.request->send_all_frames )
{
SerializeAllFrames();
}
else
if( !cur.request->send_frames.empty() )
{
SerializeSpecificFrames();
}
output_8bit << "}";
put_separator = true;
}
}
void App::SerializeStreamJson(const pt::WTextStream & input_stream, const wchar_t * field_name)
void App::PutSeparatorIfNeeded(bool put_separator)
{
if( put_separator )
{
switch( cur.request->container_type )
{
case Request::ContainerType::container_json:
output_8bit << ",";
break;
case Request::ContainerType::container_xml:
break;
case Request::ContainerType::container_csv:
output_8bit << ";";
break;
case Request::ContainerType::container_raw:
default:
break;
}
}
}
void App::SerializeFieldJson(const wchar_t * field_name)
{
if( field_name )
{
@ -1912,7 +1995,37 @@ void App::SerializeStreamJson(const pt::WTextStream & input_stream, const wchar_
pt::esc_to_json(field_name, output_8bit);
output_8bit << "\":";
}
}
void App::SerializeStream(const pt::WTextStream & input_stream, const wchar_t * field_name)
{
switch( cur.request->container_type )
{
case Request::ContainerType::container_json:
SerializeStreamJson(input_stream, field_name);
break;
case Request::ContainerType::container_xml:
SerializeStreamXml(input_stream, field_name);
break;
case Request::ContainerType::container_csv:
SerializeStreamCsv(input_stream, field_name);
break;
case Request::ContainerType::container_raw:
default:
FilterHtmlIfNeeded(input_stream, output_8bit, false);
break;
}
}
void App::SerializeStreamJson(const pt::WTextStream & input_stream, const wchar_t * field_name)
{
SerializeFieldJson(field_name);
output_8bit << '"';
if( config.html_filter && cur.request->use_html_filter )
@ -1982,39 +2095,6 @@ void App::SerializeStreamCsv(const pt::WTextStream & input_stream, const wchar_t
}
void App::SerializeFrames()
{
if( cur.request->answer_container == Request::AnswerContainer::answer_json )
{
output_8bit << '{';
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_xml )
{
output_8bit << '<';
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
}
if( cur.request->frame.empty() || cur.request->send_all_frames )
SerializeAllFrames();
else
SerializeOneFrame();
if( cur.request->answer_container == Request::AnswerContainer::answer_json )
{
output_8bit << '}';
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_xml )
{
output_8bit << "</";
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
}
}
void App::SerializeAllFrames()
{
auto i = cur.request->out_streams.streams_map.begin();
@ -2022,12 +2102,12 @@ void App::SerializeAllFrames()
for( ; i != cur.request->out_streams.streams_map.end() ; ++i)
{
if( cur.request->answer_container == Request::AnswerContainer::answer_json && !is_first )
if( cur.request->container_type == Request::ContainerType::container_json && !is_first )
{
output_8bit << ',';
}
if( cur.request->answer_container == Request::AnswerContainer::answer_xml && i->first.empty() )
if( cur.request->container_type == Request::ContainerType::container_xml && i->first.empty() )
{
log << log2 << "App: I cannot serialize a frame with an empty name to xml (frame skipped)" << logend;
}
@ -2041,51 +2121,46 @@ void App::SerializeAllFrames()
}
void App::SerializeOneFrame()
void App::SerializeSpecificFrames()
{
auto i = cur.request->out_streams.streams_map.find(cur.request->frame);
bool is_first = true;
if( i != cur.request->out_streams.streams_map.end() )
for(std::wstring & frame: cur.request->send_frames)
{
SerializeStream(i->second->get_buffer(), cur.request->frame.c_str());
}
else
{
log << log2 << "App: there is no such a frame: " << cur.request->frame << logend;
// return 404 in such a case?
auto i = cur.request->out_streams.streams_map.find(frame);
if( i != cur.request->out_streams.streams_map.end() )
{
if( cur.request->container_type == Request::ContainerType::container_json && !is_first )
{
output_8bit << ',';
}
SerializeStream(i->second->get_buffer(), frame.c_str());
is_first = false;
}
else
{
log << log2 << "App: there is no such a frame: " << frame << logend;
}
}
}
void App::SerializeModels()
{
Ezc::Models::ModelsMap models_map = cur.request->models.GetMap();
auto i = models_map.begin();
if( cur.request->answer_container == Request::AnswerContainer::answer_json )
{
output_8bit << '{';
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_xml )
{
output_8bit << '<';
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
log << log2 << "App: serializing models to xml not implemented yet" << logend;
}
bool is_first = true;
for( ; i != models_map.end() ; ++i)
{
if( cur.request->answer_container == Request::AnswerContainer::answer_json && !is_first )
if( cur.request->container_type == Request::ContainerType::container_json && !is_first )
{
output_8bit << ',';
}
if( cur.request->answer_container == Request::AnswerContainer::answer_xml && i->first.empty() )
if( cur.request->container_type == Request::ContainerType::container_xml && i->first.empty() )
{
log << log2 << "App: I cannot serialize a model with an empty name to xml (model skipped)" << logend;
}
@ -2096,40 +2171,27 @@ void App::SerializeModels()
is_first = false;
}
if( cur.request->answer_container == Request::AnswerContainer::answer_json )
{
output_8bit << '}';
}
else
if( cur.request->answer_container == Request::AnswerContainer::answer_xml )
{
output_8bit << "</";
pt::esc_to_xml(config.xml_root, output_8bit);
output_8bit << '>';
}
}
void App::SerializeModel(morm::Wrapper & wrapper, const wchar_t * field_name)
{
switch( cur.request->answer_container )
switch( cur.request->container_type )
{
case Request::AnswerContainer::answer_json:
case Request::ContainerType::container_json:
SerializeModelJson(wrapper, field_name);
break;
case Request::AnswerContainer::answer_xml:
case Request::ContainerType::container_xml:
SerializeModelXml(wrapper, field_name);
break;
case Request::AnswerContainer::answer_csv:
case Request::ContainerType::container_csv:
SerializeModelCsv(wrapper, field_name);
break;
case Request::AnswerContainer::answer_text:
case Request::ContainerType::container_raw:
default:
SerializeModelCsv(wrapper, field_name);
break;
}
}
@ -2137,12 +2199,7 @@ void App::SerializeModel(morm::Wrapper & wrapper, const wchar_t * field_name)
void App::SerializeModelJson(morm::Wrapper & wrapper, const wchar_t * field_name)
{
if( field_name )
{
output_8bit << '"';
pt::esc_to_json(field_name, output_8bit);
output_8bit << "\":";
}
SerializeFieldJson(field_name);
if( wrapper.model )
{

View File

@ -54,6 +54,7 @@
#include "cookieparser.h"
#include "postmultiparser.h"
#include "acceptencodingparser.h"
#include "acceptparser.h"
#include "winixrequest.h"
#include "log/log.h"
#include "filelog.h"
@ -140,6 +141,7 @@ private:
CookieParser cookie_parser;
AcceptEncodingParser accept_encoding_parser;
AcceptParser accept_parser;
Compress compress;
FCGX_Request fcgi_request;
int fcgi_socket;
@ -199,7 +201,7 @@ private:
void CheckIfNeedSSLredirect();
void SetLocale();
void CheckPostRedirect();
void MakeEzcGenerator();
void UseEzcGenerator();
void AddDefaultModels();
void Make();
void SaveSessionsIfNeeded(); // !! IMPROVE ME wywalic do menagera sesji??
@ -207,14 +209,24 @@ private:
void SendData(const BinaryPage & page, FCGX_Stream * out);
void ReadRequest();
void SendAnswer();
void PrepareRawAnswer();
void PrepareJsonAnswer();
void PrepareXmlAnswer();
void PrepareCsvAnswer();
void PrepareContenerizedAnswer();
void PutSeparatorIfNeeded(bool put_separator);
void SerializeFieldJson(const wchar_t * field_name);
void SerializeStream(const pt::WTextStream & input_stream, const wchar_t * field_name);
void SerializeStreamJson(const pt::WTextStream & input_stream, const wchar_t * field_name);
void SerializeStreamXml(const pt::WTextStream & input_stream, const wchar_t * field_name);
void SerializeStreamCsv(const pt::WTextStream & input_stream, const wchar_t * field_name);
void SerializeFrames();
void SerializeAllFrames();
void SerializeOneFrame();
void SerializeSpecificFrames();
void SerializeModels();
void Send8bitOutput(BinaryPage & output);

View File

@ -284,10 +284,17 @@ void Config::AssignValues(bool stdout_is_closed)
ezc_max_elements = Size(L"ezc_max_elements", 50000);
ezc_max_loop_elements = Size(L"ezc_max_loop_elements", 5000);
ezc_out_streams_size = Size(L"ezc_out_streams_size", 128);
request_frame_parameter = Text(L"request_frame_parameter", L"frame");
request_all_frames_parameter = Text(L"request_all_frames_parameter", L"allframes");
request_frame_parameter = Text(L"request_frame_parameter", L"frame");
request_all_frames_parameter = Text(L"request_all_frames_parameter", L"all_frames");
request_main_stream_parameter = Text(L"request_main_stream_parameter", L"main_stream");
request_frame_parameter_max_length = Size(L"request_frame_parameter_max_length", 128);
request_frame_parameter_max_frames = Size(L"request_frame_parameter_max_frames", 16);
xml_root = Text(L"xml_root", L"winix");
bin_stream_field = Text(L"bin_stream_field", L"bin_stream");
main_stream_field = Text(L"main_stream_field", L"main_stream");
ezc_frames_field = Text(L"ezc_frames_field", L"ezc_frames");
account_need_email_verification = Bool(L"account_need_email_verification", true);
reset_password_code_expiration_time = Long(L"reset_password_code_expiration_time", 86400);

View File

@ -676,15 +676,40 @@ public:
// default: frame
std::wstring request_frame_parameter;
// the name of the url parameter for returning all frames, e.g. https://domain.tld/mydir/myfunction/allframes
// default: allframes
// the name of the url parameter for returning all frames, e.g. https://domain.tld/mydir/myfunction/all_frames
// default: all_frames
std::wstring request_all_frames_parameter;
// the name of the url parameter for returning the main ezc stream, e.g. https://domain.tld/mydir/myfunction/main_stream
// default: main_stream
std::wstring request_main_stream_parameter;
// max lenght of the url frame parameter
// default: 128
size_t request_frame_parameter_max_length;
// max number of frames in the frame url parameter (they are separated by a comma)
// default: 16
// if you need more frames you can use all_frames url parameter to return all frames
size_t request_frame_parameter_max_frames;
// the name of the root element when serializing request answer to xml
// default: winix
std::wstring xml_root;
// when true then when a user want to create a new account
// the name of the field of the binary stream when serializing a request
// default: bin_stream
std::wstring bin_stream_field;
// the name of the field of the main ezc stream when serializing a request
// default: main_stream
std::wstring main_stream_field;
// the name of the field (object) of the ezc frames when serializing a request
// default: ezc_frames
std::wstring ezc_frames_field;
// when true then when a user want to create a new account
// he has to provide his email and a message will be sent back to him
// with a link to activate the account
// default: true

View File

@ -5,7 +5,7 @@
*/
/*
* Copyright (c) 2021, Tomasz Sowa
* Copyright (c) 2021-2022, Tomasz Sowa
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -35,6 +35,8 @@
#ifndef headerfile_winix_core_header
#define headerfile_winix_core_header
#include "log.h"
namespace Winix
{
@ -47,7 +49,13 @@ public:
* headers' names
*/
static constexpr const wchar_t * content_type = L"Content-Type";
static constexpr const wchar_t * accept = L"Accept";
/*
* headers' names lower case
*/
static constexpr const wchar_t * content_type_lc = L"content-type";
static constexpr const wchar_t * accept_lc = L"accept";
/*
* headers' values
@ -61,6 +69,10 @@ public:
static constexpr const wchar_t * text_csv = L"text/csv";
static constexpr const wchar_t * text_javascript = L"text/javascript";
static constexpr const wchar_t * all_all = L"*/*";
static constexpr const wchar_t * text_all = L"text/*";
static constexpr const wchar_t * application_all = L"application/*";
static constexpr const wchar_t * text_html_utf8 = L"text/html; charset=UTF-8";
static constexpr const wchar_t * application_json_utf8 = L"application/json; charset=UTF-8";
static constexpr const wchar_t * application_xml_utf8 = L"application/xml; charset=UTF-8";
@ -68,6 +80,41 @@ public:
static constexpr const wchar_t * text_csv_utf8 = L"text/csv; charset=UTF-8";
static constexpr const wchar_t * text_javascript_utf8 = L"text/javascript; charset=UTF-8";
};
class HeaderValue
{
public:
std::wstring value;
double weight; // q parameter in some headers
HeaderValue()
{
weight = 0.0;
}
static void log_values(const std::vector<HeaderValue> & header_values, Log & log)
{
char buf[64];
bool is_first = true;
for(const HeaderValue & h: header_values)
{
snprintf(buf, sizeof(buf)/sizeof(char), "%.3f", h.weight);
if( !is_first )
log << ", ";
log << h.value << ";q=" << buf;
is_first = false;
}
}
};
}

View File

@ -1601,5 +1601,36 @@ void timespec_to_stream_with_unit(timespec & val, pt::Stream & stream)
}
void slice_by(const std::wstring & str, wchar_t c, std::vector<std::wstring> & out)
{
std::wstring tmp;
for(size_t i = 0 ; i < str.size() ; ++i)
{
if( str[i] == c )
{
if( !tmp.empty() )
{
out.push_back(tmp);
tmp.clear();
}
}
else
{
tmp += str[i];
}
}
if( !tmp.empty() )
{
out.push_back(tmp);
}
}
} // namespace Winix

View File

@ -1029,6 +1029,8 @@ void timespec_to_stream(timespec & val, pt::Stream & stream);
void timespec_to_stream_with_unit(timespec & val, pt::Stream & stream);
void slice_by(const std::wstring & str, wchar_t c, std::vector<std::wstring> & out);
} // namespace Winix

View File

@ -120,6 +120,7 @@ void Request::Clear()
env_http_host.clear();
env_http_user_agent.clear();
env_http_accept_encoding.clear();
env_http_accept.clear();
env_fcgi_role.clear();
env_content_type.clear();
env_https.clear();
@ -134,11 +135,16 @@ void Request::Clear()
param_tab.clear();
anchor.clear();
answer_source = AnswerSource::answer_models;
answer_container = AnswerContainer::answer_text;
use_ezc_engine = true;
frame.clear();
send_bin_stream = false;
send_main_stream = false;
send_all_frames = false;
send_frames.clear();
use_ezc_engine = false;
serialize_models = false;
accept_mime_types.clear();
container_type = ContainerType::container_raw;
status = WINIX_ERR_OK;
@ -206,30 +212,81 @@ void Request::RequestEnds()
bool Request::PrepareAnswerType()
void Request::PrepareAnswerType()
{
answer_source = AnswerSource::answer_models;
answer_container = AnswerContainer::answer_text;
use_ezc_engine = true;
CheckAcceptHeader();
CheckContainerParameter();
frame = ParamValue(config->request_frame_parameter);
serialize_models = (container_type != ContainerType::container_raw);
send_all_frames = (ParamValuep(config->request_all_frames_parameter) != nullptr);
send_main_stream = (ParamValuep(config->request_main_stream_parameter) != nullptr);
use_html_filter = config->html_filter;
PrepareFrameNames();
bool ok = true;
if( container_type == ContainerType::container_raw && !send_all_frames && send_frames.empty() )
{
send_main_stream = true;
}
// IMPLEMENT ME add checking for Accept header;
ok = ok && CheckContainerParameter();
ok = ok && CheckAnswerParameter();
return ok;
use_ezc_engine = send_main_stream || send_all_frames || !send_frames.empty();
}
// IMPROVE ME give me a better name
bool Request::CheckContainerParameter()
void Request::CheckAcceptHeader()
{
if( !accept_mime_types.empty() )
{
bool found = false;
for(HeaderValue & h: accept_mime_types)
{
if( h.value == Header::text_html || h.value == Header::application_xhtml_xml ||
h.value == Header::text_all || h.value == Header::all_all)
{
container_type = ContainerType::container_raw;
found = true;
break;
}
else
if( h.value == Header::application_json || h.value == Header::application_all )
{
container_type = ContainerType::container_json;
found = true;
break;
}
else
if( h.value == Header::application_xml )
{
container_type = ContainerType::container_xml;
found = true;
break;
}
else
if( h.value == Header::text_csv )
{
container_type = ContainerType::container_csv;
found = true;
break;
}
}
if( !found )
{
Log * log = get_logger();
if( log )
{
(*log) << log2 << "App: an unknown " << Header::accept << " headers: ";
HeaderValue::log_values(accept_mime_types, *log);
(*log) << " (skipping)" << logend;
}
}
}
}
void Request::CheckContainerParameter()
{
std::wstring * container = ParamValuep(L"container");
@ -238,24 +295,24 @@ bool Request::CheckContainerParameter()
// IMPROVEME do a plugin call here
// if a plugin can consume this then don't check text/json/xml/csv and just return true
if( *container == L"text" )
if( *container == L"raw" )
{
answer_container = Request::AnswerContainer::answer_text;
container_type = ContainerType::container_raw;
}
else
if( *container == L"json" )
{
answer_container = Request::AnswerContainer::answer_json;
container_type = ContainerType::container_json;
}
else
if( *container == L"xml" )
{
answer_container = Request::AnswerContainer::answer_xml;
container_type = ContainerType::container_xml;
}
else
if( *container == L"csv" )
{
answer_container = Request::AnswerContainer::answer_csv;
container_type = ContainerType::container_csv;
}
else
{
@ -263,50 +320,43 @@ bool Request::CheckContainerParameter()
if( log )
{
(*log) << log2 << "App: an unknown 'container' parameter: " << *container << logend;
// IMPROVE ME set status to 404
return false;
(*log) << log2 << "App: an unknown container url parameter: " << *container << " (skipping)" << logend;
}
}
}
return true;
}
// IMPROVE ME give me a better name
bool Request::CheckAnswerParameter()
void Request::PrepareFrameNames()
{
std::wstring * answer = ParamValuep(L"answer");
Config * config = get_config();
Log * log = get_logger();
if( answer )
if( config && log )
{
// IMPROVEME do a plugin call here
// if a plugin can consume this then don't check html/data and just return true
const std::wstring & frame = ParamValue(config->request_frame_parameter);
if( *answer == L"html" )
if( frame.size() <= config->request_frame_parameter_max_length )
{
use_ezc_engine = true;
}
else
if( *answer == L"data" )
{
use_ezc_engine = false;
}
else
{
Log * log = get_logger();
send_frames.clear();
slice_by(frame, ',', send_frames);
std::sort(send_frames.begin(), send_frames.end());
auto frames_end = std::unique(send_frames.begin(), send_frames.end());
send_frames.erase(frames_end, send_frames.end());
if( log )
if( send_frames.size() > config->request_frame_parameter_max_frames )
{
(*log) << log2 << "App: an unknown 'answer' parameter: " << *answer << logend;
// IMPROVE ME set status to 404
return false;
send_frames.clear();
(*log) << log2 << "Request: the number of frames exceeds " << config->request_frame_parameter_max_frames
<< " (skipping frames)" << logend;
}
}
else
{
(*log) << log2 << "Request: the length of the frame url parameter exceeds " << config->request_frame_parameter_max_length
<< " characters (skiping frames)" << logend;
}
}
return true;
}
@ -504,6 +554,31 @@ void Request::last_item_wrapper(morm::Wrapper & wrapper)
}
bool Request::has_frame(const wchar_t * frame)
{
for(std::wstring & f: send_frames)
{
if( f == frame )
return true;
}
return false;
}
bool Request::has_frame(const std::wstring & frame)
{
for(std::wstring & f: send_frames)
{
if( f == frame )
return true;
}
return false;
}
} // namespace Winix

View File

@ -190,6 +190,7 @@ public:
std::wstring env_http_host;
std::wstring env_http_user_agent;
std::wstring env_http_accept_encoding;
std::wstring env_http_accept;
std::wstring env_fcgi_role;
std::wstring env_content_type;
std::wstring env_https;
@ -249,180 +250,87 @@ public:
*
*/
// the algorithm how a request's answer is created
// ------------------------------------------------------------------------------------------
//
// at the beginning of a request winix sets
// answer_source to models
// answer_container to text
// use_ezc_engine to true
// the algorithm how a request's container is selected is shown below:
// (the whole answer's algorightm is implemented in PrepareAnswerType() method)
// ------------------------------------------------------------------------------------------
//
// next answer_container and use_ezc_engine can be changed in the following way:
// at the beginning we set container_type to "raw" meaning simple text or html, then
// we check the "Accept" http header, if it is set then we set container_type accordingly:
//
// 1. winix will look for 'Accept' http header and depending on the header winix will set:
// Accept | container_type
// -----------------------------------------------
// text/html | container_raw
// application/xhtml+xml | container_raw
// application/json | container_json
// application/xml | container_xml
// text/csv | container_csv
//
// Accept | answer_container | use_ezc_engine
// ------------------------------------|-----------------
// application/json | json | false
// application/xml | xml | false
// text/csv | csv | false
// next we check "container" url parameter, if it is set then we set container_type accordingly
// ("container" url parameter has higher precedence than "Accept" http header):
//
// container | container_type
// -----------------------------------------------
// raw | container_raw
// json | container_json
// xml | container_xml
// csv | container_csv
//
//
// Samples:
//
// 2. next answer_container is set depending on 'container' url parameter
// container | answer_container
// ---------------------------------------------------------
// not present | don't change the value
// text | text
// json | json
// xml | xml
// csv | csv
// http://domain.tld/dir/controller
// returns html answer from the main ezc stream
//
// use_ezc_engine is set depending on 'answer' url parameter:
// answer | use_ezc_engine
// ---------------------------------
// not present | don't change the value
// html | true
// data | false
// http://domain.tld/dir/controller/container:raw
// returns html answer from the main ezc stream (the same as above)
//
// if 'answer' is html then we take into account two more parameters:
// frame: frame_name (empty default) - if set then winix returns this specific frame
// allframes: (if present then winix returns all frames)
// http://domain.tld/dir/controller/frame:abc
// returns "abc" frame as html
//
// http://domain.tld/dir/controller/container:json
// returns all serialized models to json and no ezc streams
//
// http://domain.tld/dir/controller/container:xml
// returns all serialized models to xml and no ezc streams
//
// http://domain.tld/dir/controller/container:json/frame:abc,xyz
// returns all serialized models to json and two frames in 'ezc_frames' object
//
// http://domain.tld/dir/controller/container:json/all_frames
// returns all serialized models to json and all frames in 'ezc_frames' object
//
// http://domain.tld/dir/controller/container:json/main_stream
// returns all serialized models and the main ezc stream in 'main_stream' field
//
// the whole algorithm how the answer is created is shown below:
// http://domain.tld/dir/controller/container:json/main_stream/all_frames
// returns all serialized models to json, all frames and the main stream
//
//
// answer_source: bin_stream
// |--------------->-------------- send out_bin_stream
// |
// |
// |
// |
// | answer_source: models and use_ezc_engine: true
// |------------------------>------------------------
// | |
// | use ezc engine
// | for converting models
// | to out_main_stream and frame_streams
// | |
// | change answer_source to
// | frame_stream (if there is 'allframes' parameter or 'frame' parameter is not empty)
// | or to main_stream otherwise
// | |
// |-------------------------<-----------------------
// |
// |
// |
// |
// |
// | answer_source: main_stream
// |----------------------->-------------------------
// | |
// | depending on answer_container
// | |
// | --------------------------------------------------------------------
// | | | | |
// | text json xml csv
// | | | | |
// | send send send send
// | out_main_stream out_main_stream out_main_stream out_main_stream
// | as json text in one cell in first csv cell
// | (without making e.g.
// | an object) <winix>
// | e.g. "text" text
// | </winix>
// |
// |
// |
// | answer_source: frame_streams
// |-------->-------
// | |
// | |
// | depending on
// | 'frame' string variable and 'allframes'
// | |
// | |
// | | is 'frame' string empty or there is 'allframes' parameter
// | |----------------------->---------------------
// | | |
// | | depending on answer_container
// | | |
// | | --------------------------------------------------------------------
// | | | | | |
// | | text json xml csv
// | | | | | |
// | | send text serialize serialize serialize
// | | from all frames all frames all frames all frames
// | | one by one to json to xml to csv
// | |
// | |
// | |
// | |
// | | is 'frame' string not empty and there is no 'allframes' parameter
// | |----------------------->---------------------
// | |
// | depending on answer_container
// | |
// | --------------------------------------------------------------------
// | | | | |
// | text json xml csv
// | | | | |
// | send text serialize serialize serialize
// | from one frame one frame one frame one frame
// | to json to xml to csv
// |
// |
// |
// |
// |
// |
// | answer_source: models
// |------------------------>------------------------
// |
// depending on answer_container
// |
// --------------------------------------------------------------------
// | | | |
// text json xml csv
// | | | |
// serialize serialize models serialize models serialize models
// models to to json to xml to csv
// csv
// but return
// as text/plain
// change maybe answer_bin_stream -> source_bin_stream?
enum AnswerSource
{
answer_bin_stream,
answer_models,
answer_main_stream,
answer_frame_streams,
};
bool send_bin_stream;
bool send_main_stream;
bool send_all_frames;
std::vector<std::wstring> send_frames;
bool use_ezc_engine;
bool serialize_models;
// change maybe answer_text -> container_text?
enum AnswerContainer
enum ContainerType
{
answer_text,
answer_json,
answer_xml,
answer_csv,
container_raw,
container_json,
container_xml,
container_csv,
};
AnswerSource answer_source;
AnswerContainer answer_container;
ContainerType container_type;
bool use_ezc_engine;
std::wstring frame;
bool send_all_frames;
// at the beginning those with higher priority
std::vector<HeaderValue> accept_mime_types;
// request status
@ -516,9 +424,7 @@ public:
void RequestEnds();
void Clear();
bool PrepareAnswerType();
bool CheckContainerParameter();
bool CheckAnswerParameter();
void PrepareAnswerType();
bool IsParam(const wchar_t * param_name);
bool IsParam(const std::wstring & param_name);
@ -550,7 +456,8 @@ public:
template<typename NameType, typename ValueType>
void AddCookie(const NameType & name, const ValueType & value, pt::Date & expires);
bool has_frame(const wchar_t * frame);
bool has_frame(const std::wstring & frame);
private:
@ -562,6 +469,9 @@ private:
void ClearOutputStreams();
void CheckAcceptHeader();
void CheckContainerParameter();
void PrepareFrameNames();
void current_dir(morm::Wrapper & wrapper);
void last_item_wrapper(morm::Wrapper & wrapper);

View File

@ -91,7 +91,7 @@ void Download::MakeGet()
}
cur->request->x_sendfile.clear();
cur->request->answer_source = Request::AnswerSource::answer_bin_stream;
cur->request->send_bin_stream = true;
}
}
else

View File

@ -172,7 +172,7 @@ void Emacs::MakePost()
system->RedirectToLastFunction(nullptr, false);
answer.add(L"redirect_to", cur->request->redirect_to);
if( cur->request->answer_container != Request::AnswerContainer::answer_text )
if( cur->request->container_type != Request::ContainerType::container_raw )
{
cur->request->redirect_to.clear();
}

View File

@ -184,5 +184,6 @@ main.o: ../../../winix/winixd/core/cookieparser.h
main.o: ../../../winix/winixd/core/postmultiparser.h
main.o: ../../../winix/winixd/core/acceptencodingparser.h
main.o: ../../../winix/winixd/core/acceptbaseparser.h
main.o: ../../../winix/winixd/core/acceptparser.h
main.o: ../../../winix/winixd/core/winixrequest.h
main.o: ../../../winix/winixd/core/version.h

View File

@ -1051,6 +1051,8 @@ filters.o: ../../../pikotools/src/convert/convert.h
filters.o: ../../../pikotools/src/convert/inttostr.h
filters.o: ../../../pikotools/src/convert/patternreplacer.h
filters.o: ../../../pikotools/src/convert/double.h
filters.o: ../../../pikotools/src/convert/misc.h
filters.o: ../../../pikotools/src/utf8/utf8_stream.h
generic.o: templates.h ../../../ezc/src/ezc.h ../../../ezc/src/generator.h
generic.o: ../../../ezc/src/blocks.h ../../../ezc/src/item.h
generic.o: ../../../ezc/src/cache.h ../../../ezc/src/functions.h

View File

@ -383,7 +383,14 @@ void winix_is_htmx_request(Info & i)
void winix_frame_is(Info & i)
{
i.res = cur->request->frame == i.par;
for(std::wstring & f: cur->request->send_frames)
{
if( f == i.par )
{
i.res = true;
break;
}
}
}