polserver/pol-core/pol/module/httpmod.cpp

384 lines
8.4 KiB
C++
Raw Permalink Normal View History

/** @file
*
* @par History
*/
2016-02-11 22:54:43 +01:00
#include "httpmod.h"
#include "../../bscript/berror.h"
#include "../../bscript/impstr.h"
2016-02-11 22:54:43 +01:00
#include "../../clib/logfacility.h"
2019-03-04 21:41:27 +01:00
#include "../../clib/network/wnsckt.h"
2016-02-11 22:54:43 +01:00
#include "../../plib/systemstate.h"
#include "../uoexec.h"
#include <module_defs/http.h>
2016-02-11 22:54:43 +01:00
namespace Pol
{
namespace Core
{
std::string http_decodestr( const std::string& s );
std::string reasonPhrase( int code );
} // namespace Core
namespace Module
{
using namespace Bscript;
HttpExecutorModule::HttpExecutorModule( Bscript::Executor& exec, Clib::Socket&& isck )
: Bscript::TmplExecutorModule<HttpExecutorModule, Core::PolModule>( exec ),
sck_( std::move( isck ) ),
continuing_offset( 0 )
{
}
HttpExecutorModule::~HttpExecutorModule()
{
if ( sck_.connected() )
{
unsigned nsent;
std::string line;
// Since status line and headers are now controlled by script, check if they are sendable.
if ( !cannotSendStatus )
{
line += "HTTP/1.1 200 OK\n";
}
if ( !cannotSendHeaders )
{
if ( !hasCustomContentType )
{
line += "Content-Type: text/html\n\n";
}
else
{
line += "\n";
}
if ( !line.empty() )
{
sck_.send_nowait( (void*)( line.c_str() ), static_cast<unsigned int>( line.length() ),
&nsent );
}
}
}
}
BObjectImp* HttpExecutorModule::mf_WriteStatus()
{
int code;
const String* reason;
if ( !sck_.connected() )
{
exec.seterror( true );
return new BError( "Socket is disconnected" );
}
if ( getParam( 0, code ) && getStringParam( 1, reason ) )
{
if ( cannotSendStatus )
{
return new BError(
"Cannot send status after WriteStatus, WriteHeader, WriteHtml, or WriteHtmlRaw" );
}
unsigned nsent;
std::string line = "HTTP/1.1 " + std::to_string( code );
auto& reasonString = reason->value();
if ( reasonString.empty() )
{
std::string defaultReason = Core::reasonPhrase( code );
if ( !defaultReason.empty() )
{
line += " " + defaultReason + "\n";
}
}
else
{
line += " " + reasonString + "\n";
}
bool res =
sck_.send_nowait( (void*)( line.c_str() + continuing_offset ),
static_cast<unsigned int>( line.length() - continuing_offset ), &nsent );
if ( res )
{
cannotSendStatus = true;
continuing_offset = 0;
return new BLong( 1 );
}
else
{
continuing_offset += nsent;
auto& uoex = uoexec();
uoex.SleepForMs( 500u );
--uoex.PC;
return uoex.fparams[0]->impptr();
}
}
return new BError( "Invalid parameter type" );
}
BObjectImp* HttpExecutorModule::mf_WriteHeader()
{
const String* name;
const String* value;
if ( !sck_.connected() )
{
exec.seterror( true );
return new BError( "Socket is disconnected" );
}
if ( getStringParam( 0, name ) && getStringParam( 1, value ) )
{
if ( cannotSendHeaders )
{
return new BError( "Cannot send headers after WriteHtml or WriteHtmlRaw" );
}
if ( Clib::strlowerASCII( name->value() ) == "content-type" )
{
hasCustomContentType = true;
}
unsigned nsent;
std::string line;
if ( !cannotSendStatus )
{
line += "HTTP/1.1 200 OK\n";
}
line += name->value() + ": " + value->value() + "\n";
bool res =
sck_.send_nowait( (void*)( line.c_str() + continuing_offset ),
static_cast<unsigned int>( line.length() - continuing_offset ), &nsent );
if ( res )
{
cannotSendStatus = true;
continuing_offset = 0;
return new BLong( 1 );
}
else
{
continuing_offset += nsent;
auto& uoex = uoexec();
uoex.SleepForMs( 500u );
--uoex.PC;
return uoex.fparams[0]->impptr();
}
}
return new BError( "Invalid parameter type" );
}
BObjectImp* HttpExecutorModule::mf_WriteHtml()
{
const String* str;
if ( !sck_.connected() )
{
exec.seterror( true );
return new BError( "Socket is disconnected" );
2016-02-11 22:54:43 +01:00
}
if ( getStringParam( 0, str ) )
{
// TODO: some tricky stuff so if the socket blocks, the script goes to
// sleep for a bit and sends the rest later
unsigned nsent;
std::string s;
if ( !cannotSendStatus )
{
s += "HTTP/1.1 200 OK\n";
}
if ( !cannotSendHeaders )
{
if ( !hasCustomContentType )
{
s += "Content-Type: text/html\n";
}
s += "\n";
}
s += str->value();
bool res =
sck_.send_nowait( (void*)( s.c_str() + continuing_offset ),
static_cast<unsigned int>( s.length() - continuing_offset ), &nsent );
if ( res )
{
cannotSendStatus = true;
cannotSendHeaders = true;
continuing_offset = 0;
// we don't really care if this works or not, terribly.
sck_.send_nowait( "\n", 1, &nsent );
return new BLong( 1 );
}
else
2016-02-11 22:54:43 +01:00
{
continuing_offset += nsent;
auto& uoex = uoexec();
Testsuite reorganization, new feature to call exported functions (#237) * restructured pol testscripts pkg based (dedicated pkgroot) runs per pkg: setup.ecl if exists all scripts starting with "test" cleanup.ecl if exists return 1 for success everything else is an error * Scripts can now be loaded and exported functions called adapted testscripts to use this feature critical script handling missing * added support for used in critical scripts * use weakptr instead of pid moved keep_alive flag to uoexecutor * Function and nethod accept now a argument list * added docs * missing docs * faster shutdown time * fixed typo * fixed weird cornercase * shorter test functions names, the length is limited, renamed loadexported test script to have a dedicated testscript * generate tiledata/multi for small boat added generate scripts for generation.. adapted testdata set * boat tests * extended scripttests scheduler will now always know about the loaded script secured scheduler api * cleanuo scripts for boat and npc * shorter function for error return fixed cleanup: npcs have gravestones, chars was one off and scripted create of a char made it permanent logged in (until next restart), which also means that it cannot be deleted * bypass windows specific ecompile.cfg logic * fix for parallel execution * on windows its called .exe .. * use target binary directly, so cmake can replace it with the actual path use absolute path to testsuite disabled temporary the copy of ecompile * configure ecompile.cfg to add absolute path * early sleepms calls can lead to endless waiting * Code coverage (#260) * play with coverage reports * fix yml * use 0 as default for gcov cmake var * fixed used cov version, hopefully fixed now cmake * demangle function names * removed debug msg * added a few polcore tests (basically just for coverage) first set executor to noncritical before blocking * added https://coveralls.io/github/polserver/polserver * added a stackable item added a few backpack insert tests do coverage build only on push events not also for PR merge builds * added storage tests.. docs need fixes, the Imps are not working at all * fixed storage test to really move items * added a house corrected static/dynamic flag created a few house tests * updated yml file to new github api * corrected coverage discord job name * always put to runlist even if not necessary * extended aos prop tests * equip/unequip aos mod * extra ci step for shard test fixtures and activated verbose shard test * use extra ci step for all build configs, sorted testscripts for execution * fixed sorting * added param to syslog to not print the context (cleaner testscript visualization) * added build targets for tests * sleep api cleanup * more aos property tests * build for coveralls also on pullrequest * readded debug state reset on schedule * updated checkout ci action * updated checkout in all ci runs
2020-12-16 18:56:10 +01:00
uoex.SleepForMs( 500u );
--uoex.PC;
return uoex.fparams[0]->impptr();
}
2016-02-11 22:54:43 +01:00
}
else
{
return new BError( "Invalid parameter type" );
}
}
2016-02-11 22:54:43 +01:00
BObjectImp* HttpExecutorModule::mf_WriteHtmlRaw()
{
const String* str;
if ( !sck_.connected() )
{
exec.seterror( true );
return new BError( "Socket is disconnected" );
}
exec.makeString( 0 );
if ( getStringParam( 0, str ) )
{
// TODO: some tricky stuff so if the socket blocks, the script goes to
// sleep for a bit and sends the rest later
unsigned nsent;
std::string s;
if ( !cannotSendStatus )
{
s += "HTTP/1.1 200 OK\n";
}
if ( !cannotSendHeaders )
{
if ( !hasCustomContentType )
{
s += "Content-Type: text/html\n";
}
s += "\n";
}
s += str->value();
bool res =
sck_.send_nowait( (void*)( s.c_str() + continuing_offset ),
static_cast<unsigned int>( s.length() - continuing_offset ), &nsent );
if ( res )
{
cannotSendStatus = true;
cannotSendHeaders = true;
continuing_offset = 0;
return new BLong( 1 );
}
else
{
continuing_offset += nsent;
auto& uoex = uoexec();
Testsuite reorganization, new feature to call exported functions (#237) * restructured pol testscripts pkg based (dedicated pkgroot) runs per pkg: setup.ecl if exists all scripts starting with "test" cleanup.ecl if exists return 1 for success everything else is an error * Scripts can now be loaded and exported functions called adapted testscripts to use this feature critical script handling missing * added support for used in critical scripts * use weakptr instead of pid moved keep_alive flag to uoexecutor * Function and nethod accept now a argument list * added docs * missing docs * faster shutdown time * fixed typo * fixed weird cornercase * shorter test functions names, the length is limited, renamed loadexported test script to have a dedicated testscript * generate tiledata/multi for small boat added generate scripts for generation.. adapted testdata set * boat tests * extended scripttests scheduler will now always know about the loaded script secured scheduler api * cleanuo scripts for boat and npc * shorter function for error return fixed cleanup: npcs have gravestones, chars was one off and scripted create of a char made it permanent logged in (until next restart), which also means that it cannot be deleted * bypass windows specific ecompile.cfg logic * fix for parallel execution * on windows its called .exe .. * use target binary directly, so cmake can replace it with the actual path use absolute path to testsuite disabled temporary the copy of ecompile * configure ecompile.cfg to add absolute path * early sleepms calls can lead to endless waiting * Code coverage (#260) * play with coverage reports * fix yml * use 0 as default for gcov cmake var * fixed used cov version, hopefully fixed now cmake * demangle function names * removed debug msg * added a few polcore tests (basically just for coverage) first set executor to noncritical before blocking * added https://coveralls.io/github/polserver/polserver * added a stackable item added a few backpack insert tests do coverage build only on push events not also for PR merge builds * added storage tests.. docs need fixes, the Imps are not working at all * fixed storage test to really move items * added a house corrected static/dynamic flag created a few house tests * updated yml file to new github api * corrected coverage discord job name * always put to runlist even if not necessary * extended aos prop tests * equip/unequip aos mod * extra ci step for shard test fixtures and activated verbose shard test * use extra ci step for all build configs, sorted testscripts for execution * fixed sorting * added param to syslog to not print the context (cleaner testscript visualization) * added build targets for tests * sleep api cleanup * more aos property tests * build for coveralls also on pullrequest * readded debug state reset on schedule * updated checkout ci action * updated checkout in all ci runs
2020-12-16 18:56:10 +01:00
uoex.SleepForMs( 500u );
--uoex.PC;
return uoex.fparams[0]->impptr();
}
}
else
{
return new BError( "Invalid parameter type" );
}
}
2016-02-11 22:54:43 +01:00
#if 0
2018-04-03 04:13:03 +02:00
BObjectImp* HttpExecutorModule::mf_WriteHtml()
{
const String* str;
if (getStringParam( 0, str ))
{
// TODO: some tricky stuff so if the socket blocks, the script goes to
// sleep for a bit and sends the rest later
http_writeline( sck_, str->value() );
return new BLong(1);
}
else
{
return new BError( "Invalid parameter type" );
}
}
2016-02-11 22:54:43 +01:00
#endif
BObjectImp* HttpExecutorModule::mf_QueryParam()
{
const String* str;
if ( getStringParam( 0, str ) )
{
QueryParamMap::iterator itr = params_.find( str->data() );
if ( itr != params_.end() )
return new String( ( *itr ).second );
else
return new BLong( 0 );
}
else
{
return new BError( "Invalid parameter type" );
}
}
2016-02-11 22:54:43 +01:00
BObjectImp* HttpExecutorModule::mf_QueryIP()
{
return new String( query_ip_ );
}
2016-02-11 22:54:43 +01:00
// query_string is everything after the '?'
void HttpExecutorModule::read_query_string( const std::string& query_string )
{
if ( !query_string.empty() )
{
std::string::size_type brk;
std::string::size_type start = 0;
do
{
brk = query_string.find( '&', start );
std::string param =
query_string.substr( start, ( brk == std::string::npos ) ? brk : brk - start );
std::string name, value;
std::string::size_type eq = param.find( '=' );
if ( eq != std::string::npos )
{
name = param.substr( 0, eq );
value = Core::http_decodestr( param.substr( eq + 1 ) );
}
else
{
name = param;
value = "";
}
params_[name.c_str()] = value;
if ( Plib::systemstate.config.web_server_debug )
INFO_PRINTLN( "http-param: '{}', '{}'", param, Core::http_decodestr( param ) );
start = brk + 1;
} while ( brk != std::string::npos );
2016-02-11 22:54:43 +01:00
}
}
void HttpExecutorModule::read_query_ip()
{
query_ip_ = sck_.getpeername();
}
size_t HttpExecutorModule::sizeEstimate() const
{
size_t size = sizeof( *this );
for ( const auto& v : params_ )
size += v.first.capacity() + v.second.capacity() + ( sizeof( void* ) * 3 + 1 ) / 2;
return size;
}
} // namespace Module
} // namespace Pol