|
@@ -21,6 +21,7 @@
|
|
|
#include "http/http_parser.h"
|
|
#include "http/http_parser.h"
|
|
|
#include "json/cJSON.h"
|
|
#include "json/cJSON.h"
|
|
|
#include "searchdaemon.h"
|
|
#include "searchdaemon.h"
|
|
|
|
|
+#include "searchdha.h"
|
|
|
|
|
|
|
|
struct EscapeJsonString_t
|
|
struct EscapeJsonString_t
|
|
|
{
|
|
{
|
|
@@ -258,102 +259,67 @@ static void HttpBuildReply ( CSphVector<BYTE> & dData, ESphHttpStatus eCode, con
|
|
|
memcpy ( dData.Begin() + iHeaderLen, sBody, iBodyLen );
|
|
memcpy ( dData.Begin() + iHeaderLen, sBody, iBodyLen );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-static void HttpErrorReply ( CSphVector<BYTE> & dData, ESphHttpStatus eCode, const char * sTemplate, ... )
|
|
|
|
|
|
|
+
|
|
|
|
|
+static void HttpErrorReply ( CSphVector<BYTE> & dData, ESphHttpStatus eCode, const char * szError )
|
|
|
{
|
|
{
|
|
|
cJSON * pError = cJSON_CreateObject();
|
|
cJSON * pError = cJSON_CreateObject();
|
|
|
assert ( pError );
|
|
assert ( pError );
|
|
|
|
|
|
|
|
- char sErr[1008] = "\0";
|
|
|
|
|
- if ( sTemplate && *sTemplate!='\0' )
|
|
|
|
|
- {
|
|
|
|
|
- va_list ap;
|
|
|
|
|
- va_start ( ap, sTemplate );
|
|
|
|
|
- vsnprintf ( sErr, sizeof(sErr), sTemplate, ap );
|
|
|
|
|
- va_end ( ap );
|
|
|
|
|
-
|
|
|
|
|
- cJSON_AddStringToObject ( pError, "error", sErr );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- CSphString sJsonError;
|
|
|
|
|
- sJsonError.Adopt ( cJSON_Print ( pError ) );
|
|
|
|
|
|
|
+ cJSON_AddStringToObject ( pError, "error", szError );
|
|
|
|
|
+ CSphString sJsonError = sphJsonToString ( pError );
|
|
|
cJSON_Delete ( pError );
|
|
cJSON_Delete ( pError );
|
|
|
|
|
|
|
|
HttpBuildReply ( dData, eCode, sJsonError.cstr(), sJsonError.Length(), false );
|
|
HttpBuildReply ( dData, eCode, sJsonError.cstr(), sJsonError.Length(), false );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-enum ESphHttpEndpoint
|
|
|
|
|
-{
|
|
|
|
|
- SPH_HTTP_ENDPOINT_INDEX,
|
|
|
|
|
- SPH_HTTP_ENDPOINT_SEARCH,
|
|
|
|
|
- SPH_HTTP_ENDPOINT_SQL,
|
|
|
|
|
- SPH_HTTP_ENDPOINT_JSON_SEARCH,
|
|
|
|
|
- SPH_HTTP_ENDPOINT_JSON_INDEX,
|
|
|
|
|
- SPH_HTTP_ENDPOINT_JSON_CREATE,
|
|
|
|
|
- SPH_HTTP_ENDPOINT_JSON_INSERT,
|
|
|
|
|
- SPH_HTTP_ENDPOINT_JSON_REPLACE,
|
|
|
|
|
- SPH_HTTP_ENDPOINT_JSON_UPDATE,
|
|
|
|
|
- SPH_HTTP_ENDPOINT_JSON_DELETE,
|
|
|
|
|
- SPH_HTTP_ENDPOINT_JSON_BULK,
|
|
|
|
|
-
|
|
|
|
|
- SPH_HTTP_ENDPOINT_TOTAL,
|
|
|
|
|
-
|
|
|
|
|
- SPH_HTTP_ENDPOINT_MISSED,
|
|
|
|
|
- SPH_HTTP_ENDPOINT_DEFAULT = SPH_HTTP_ENDPOINT_MISSED
|
|
|
|
|
-};
|
|
|
|
|
|
|
|
|
|
-const char * g_sEndpoints[] = { "index.html", "search", "sql", "json/search", "json/index", "json/create", "json/insert", "json/replace", "json/update", "json/delete", "json/bulk" };
|
|
|
|
|
-STATIC_ASSERT ( sizeof(g_sEndpoints)/sizeof(g_sEndpoints[0])==SPH_HTTP_ENDPOINT_TOTAL, SPH_HTTP_ENDPOINT_SHOULD_BE_SAME_AS_SPH_HTTP_ENDPOINT_TOTAL );
|
|
|
|
|
|
|
+using OptionsHash_t = SmallStringHash_T<CSphString>;
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-struct HttpRequestParser_t : public ISphNoncopyable
|
|
|
|
|
|
|
+class HttpRequestParser_c : public ISphNoncopyable
|
|
|
{
|
|
{
|
|
|
- SmallStringHash_T<CSphString> m_hOptions;
|
|
|
|
|
- CSphString m_sRawBody;
|
|
|
|
|
-
|
|
|
|
|
- ESphHttpEndpoint m_eEndpoint;
|
|
|
|
|
- bool m_bKeepAlive;
|
|
|
|
|
-
|
|
|
|
|
- CSphString m_sCurField;
|
|
|
|
|
-
|
|
|
|
|
- const char * m_sError;
|
|
|
|
|
- CSphString m_sInvalidEndpoint;
|
|
|
|
|
|
|
+public:
|
|
|
|
|
+ bool Parse ( const BYTE * pData, int iDataLen );
|
|
|
|
|
+ bool ParseList ( const char * sAt, int iLen );
|
|
|
|
|
|
|
|
- HttpRequestParser_t ();
|
|
|
|
|
- ~HttpRequestParser_t () {}
|
|
|
|
|
|
|
+ const CSphString & GetBody() const { return m_sRawBody; }
|
|
|
|
|
+ ESphHttpEndpoint GetEndpoint() const { return m_eEndpoint; }
|
|
|
|
|
+ const OptionsHash_t & GetOptions() const { return m_hOptions; }
|
|
|
|
|
+ const CSphString & GetInvalidEndpoint() const { return m_sInvalidEndpoint; }
|
|
|
|
|
+ const char * GetError() const { return m_szError; }
|
|
|
|
|
+ bool GetKeepAlive() const { return m_bKeepAlive; }
|
|
|
|
|
|
|
|
- bool Parse ( const CSphVector<BYTE> & dData );
|
|
|
|
|
- bool ParseList ( const char * sAt, int iLen );
|
|
|
|
|
|
|
+ static int ParserUrl ( http_parser * pParser, const char * sAt, size_t iLen );
|
|
|
|
|
+ static int ParserHeaderField ( http_parser * pParser, const char * sAt, size_t iLen );
|
|
|
|
|
+ static int ParserHeaderValue ( http_parser * pParser, const char * sAt, size_t iLen );
|
|
|
|
|
+ static int ParserBody ( http_parser * pParser, const char * sAt, size_t iLen );
|
|
|
|
|
|
|
|
- static int ParserUrl ( http_parser * pParser, const char * sAt, size_t iLen );
|
|
|
|
|
- static int ParserHeaderField ( http_parser * pParser, const char * sAt, size_t iLen );
|
|
|
|
|
- static int ParserHeaderValue ( http_parser * pParser, const char * sAt, size_t iLen );
|
|
|
|
|
- static int ParserBody ( http_parser * pParser, const char * sAt, size_t iLen );
|
|
|
|
|
|
|
+private:
|
|
|
|
|
+ bool m_bKeepAlive {false};
|
|
|
|
|
+ const char * m_szError {nullptr};
|
|
|
|
|
+ ESphHttpEndpoint m_eEndpoint {SPH_HTTP_ENDPOINT_TOTAL};
|
|
|
|
|
+ CSphString m_sInvalidEndpoint;
|
|
|
|
|
+ CSphString m_sRawBody;
|
|
|
|
|
+ CSphString m_sCurField;
|
|
|
|
|
+ OptionsHash_t m_hOptions;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-HttpRequestParser_t::HttpRequestParser_t ()
|
|
|
|
|
-{
|
|
|
|
|
- m_eEndpoint = SPH_HTTP_ENDPOINT_DEFAULT;
|
|
|
|
|
- m_bKeepAlive = false;
|
|
|
|
|
- m_sError = NULL;
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-bool HttpRequestParser_t::Parse ( const CSphVector<BYTE> & dData )
|
|
|
|
|
|
|
+bool HttpRequestParser_c::Parse ( const BYTE * pData, int iDataLen )
|
|
|
{
|
|
{
|
|
|
http_parser_settings tParserSettings;
|
|
http_parser_settings tParserSettings;
|
|
|
http_parser_settings_init ( &tParserSettings );
|
|
http_parser_settings_init ( &tParserSettings );
|
|
|
- tParserSettings.on_url = HttpRequestParser_t::ParserUrl;
|
|
|
|
|
- tParserSettings.on_header_field = HttpRequestParser_t::ParserHeaderField;
|
|
|
|
|
- tParserSettings.on_header_value = HttpRequestParser_t::ParserHeaderValue;
|
|
|
|
|
- tParserSettings.on_body = HttpRequestParser_t::ParserBody;
|
|
|
|
|
|
|
+ tParserSettings.on_url = HttpRequestParser_c::ParserUrl;
|
|
|
|
|
+ tParserSettings.on_header_field = HttpRequestParser_c::ParserHeaderField;
|
|
|
|
|
+ tParserSettings.on_header_value = HttpRequestParser_c::ParserHeaderValue;
|
|
|
|
|
+ tParserSettings.on_body = HttpRequestParser_c::ParserBody;
|
|
|
|
|
|
|
|
http_parser tParser;
|
|
http_parser tParser;
|
|
|
tParser.data = this;
|
|
tParser.data = this;
|
|
|
http_parser_init ( &tParser, HTTP_REQUEST );
|
|
http_parser_init ( &tParser, HTTP_REQUEST );
|
|
|
- int iParsed = http_parser_execute ( &tParser, &tParserSettings, (const char *)dData.Begin(), dData.GetLength() );
|
|
|
|
|
-
|
|
|
|
|
- if ( iParsed!=dData.GetLength() )
|
|
|
|
|
|
|
+ int iParsed = http_parser_execute ( &tParser, &tParserSettings, (const char *)pData, iDataLen );
|
|
|
|
|
+ if ( iParsed!=iDataLen )
|
|
|
{
|
|
{
|
|
|
- m_sError = http_errno_description ( (http_errno)tParser.http_errno );
|
|
|
|
|
|
|
+ m_szError = http_errno_description ( (http_errno)tParser.http_errno );
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -397,7 +363,7 @@ static void UriPercentReplace ( CSphString & sEntity )
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
-bool HttpRequestParser_t::ParseList ( const char * sAt, int iLen )
|
|
|
|
|
|
|
+bool HttpRequestParser_c::ParseList ( const char * sAt, int iLen )
|
|
|
{
|
|
{
|
|
|
const char * sCur = sAt;
|
|
const char * sCur = sAt;
|
|
|
const char * sEnd = sAt + iLen;
|
|
const char * sEnd = sAt + iLen;
|
|
@@ -436,21 +402,8 @@ bool HttpRequestParser_t::ParseList ( const char * sAt, int iLen )
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-static ESphHttpEndpoint ParseEndpointPath ( const char * sPath, int iLen )
|
|
|
|
|
-{
|
|
|
|
|
- if ( !iLen )
|
|
|
|
|
- return SPH_HTTP_ENDPOINT_INDEX;
|
|
|
|
|
-
|
|
|
|
|
- for ( int i=0; i<SPH_HTTP_ENDPOINT_TOTAL; i++ )
|
|
|
|
|
- {
|
|
|
|
|
- if ( strncmp ( sPath, g_sEndpoints[i], Min ( iLen, (int)strlen ( g_sEndpoints[i] ) ) )==0 )
|
|
|
|
|
- return (ESphHttpEndpoint)i;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return SPH_HTTP_ENDPOINT_MISSED;
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-int HttpRequestParser_t::ParserUrl ( http_parser * pParser, const char * sAt, size_t iLen )
|
|
|
|
|
|
|
+int HttpRequestParser_c::ParserUrl ( http_parser * pParser, const char * sAt, size_t iLen )
|
|
|
{
|
|
{
|
|
|
http_parser_url tUri;
|
|
http_parser_url tUri;
|
|
|
if ( http_parser_parse_url ( sAt, iLen, 0, &tUri ) )
|
|
if ( http_parser_parse_url ( sAt, iLen, 0, &tUri ) )
|
|
@@ -468,42 +421,42 @@ int HttpRequestParser_t::ParserUrl ( http_parser * pParser, const char * sAt, si
|
|
|
sPath++;
|
|
sPath++;
|
|
|
iPathLen--;
|
|
iPathLen--;
|
|
|
}
|
|
}
|
|
|
- ESphHttpEndpoint eEndpoint = ParseEndpointPath ( sPath, iPathLen );
|
|
|
|
|
- ( (HttpRequestParser_t *)pParser->data )->m_eEndpoint = eEndpoint;
|
|
|
|
|
- if ( eEndpoint==SPH_HTTP_ENDPOINT_MISSED )
|
|
|
|
|
- {
|
|
|
|
|
- ( (HttpRequestParser_t *)pParser->data )->m_sInvalidEndpoint.SetBinary ( sPath, iPathLen );
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ CSphString sEndpoint ( sPath, iPathLen );
|
|
|
|
|
+ ESphHttpEndpoint eEndpoint = sphStrToHttpEndpoint ( sEndpoint );
|
|
|
|
|
+ ( (HttpRequestParser_c *)pParser->data )->m_eEndpoint = eEndpoint;
|
|
|
|
|
+ if ( eEndpoint==SPH_HTTP_ENDPOINT_TOTAL )
|
|
|
|
|
+ ( (HttpRequestParser_c *)pParser->data )->m_sInvalidEndpoint.SetBinary ( sPath, iPathLen );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if ( ( tUri.field_set & uQuery )!=0 )
|
|
if ( ( tUri.field_set & uQuery )!=0 )
|
|
|
- ( (HttpRequestParser_t *)pParser->data )->ParseList ( sAt + tUri.field_data[UF_QUERY].off, tUri.field_data[UF_QUERY].len );
|
|
|
|
|
|
|
+ ( (HttpRequestParser_c *)pParser->data )->ParseList ( sAt + tUri.field_data[UF_QUERY].off, tUri.field_data[UF_QUERY].len );
|
|
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-int HttpRequestParser_t::ParserHeaderField ( http_parser * pParser, const char * sAt, size_t iLen )
|
|
|
|
|
|
|
+int HttpRequestParser_c::ParserHeaderField ( http_parser * pParser, const char * sAt, size_t iLen )
|
|
|
{
|
|
{
|
|
|
assert ( pParser->data );
|
|
assert ( pParser->data );
|
|
|
- ( (HttpRequestParser_t *)pParser->data )->m_sCurField.SetBinary ( sAt, iLen );
|
|
|
|
|
|
|
+ ( (HttpRequestParser_c *)pParser->data )->m_sCurField.SetBinary ( sAt, iLen );
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-int HttpRequestParser_t::ParserHeaderValue ( http_parser * pParser, const char * sAt, size_t iLen )
|
|
|
|
|
|
|
+int HttpRequestParser_c::ParserHeaderValue ( http_parser * pParser, const char * sAt, size_t iLen )
|
|
|
{
|
|
{
|
|
|
assert ( pParser->data );
|
|
assert ( pParser->data );
|
|
|
CSphString sVal;
|
|
CSphString sVal;
|
|
|
sVal.SetBinary ( sAt, iLen );
|
|
sVal.SetBinary ( sAt, iLen );
|
|
|
- HttpRequestParser_t * pHttpParser = (HttpRequestParser_t *)pParser->data;
|
|
|
|
|
|
|
+ HttpRequestParser_c * pHttpParser = (HttpRequestParser_c *)pParser->data;
|
|
|
pHttpParser->m_hOptions.Add ( sVal, pHttpParser->m_sCurField );
|
|
pHttpParser->m_hOptions.Add ( sVal, pHttpParser->m_sCurField );
|
|
|
pHttpParser->m_sCurField = "";
|
|
pHttpParser->m_sCurField = "";
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-int HttpRequestParser_t::ParserBody ( http_parser * pParser, const char * sAt, size_t iLen )
|
|
|
|
|
|
|
+int HttpRequestParser_c::ParserBody ( http_parser * pParser, const char * sAt, size_t iLen )
|
|
|
{
|
|
{
|
|
|
assert ( pParser->data );
|
|
assert ( pParser->data );
|
|
|
- HttpRequestParser_t * pHttpParser = (HttpRequestParser_t *)pParser->data;
|
|
|
|
|
|
|
+ HttpRequestParser_c * pHttpParser = (HttpRequestParser_c *)pParser->data;
|
|
|
pHttpParser->ParseList ( sAt, iLen );
|
|
pHttpParser->ParseList ( sAt, iLen );
|
|
|
pHttpParser->m_sRawBody.SetBinary ( sAt, iLen );
|
|
pHttpParser->m_sRawBody.SetBinary ( sAt, iLen );
|
|
|
return 0;
|
|
return 0;
|
|
@@ -554,106 +507,12 @@ static const char * g_sIndexPage =
|
|
|
"</body>"
|
|
"</body>"
|
|
|
"</html>";
|
|
"</html>";
|
|
|
|
|
|
|
|
-static void HttpHandlerPage ( bool bPage, const CSphString & sInvalidEndpoint, CSphVector<BYTE> & dData )
|
|
|
|
|
-{
|
|
|
|
|
- if ( bPage )
|
|
|
|
|
- {
|
|
|
|
|
- StringBuilder_c sIndexPage;
|
|
|
|
|
- sIndexPage.Appendf ( g_sIndexPage, SPHINX_VERSION );
|
|
|
|
|
- HttpBuildReply ( dData, SPH_HTTP_STATUS_200, sIndexPage.cstr(), sIndexPage.Length(), true );
|
|
|
|
|
- } else
|
|
|
|
|
- {
|
|
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_501, "/%s - unsupported endpoint", sInvalidEndpoint.cstr() );
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-struct HttpQuerySettings_t
|
|
|
|
|
-{
|
|
|
|
|
- bool m_bProfile;
|
|
|
|
|
- QueryType_e m_eQueryType;
|
|
|
|
|
- bool m_bAttrHighlight;
|
|
|
|
|
-
|
|
|
|
|
- HttpQuerySettings_t ()
|
|
|
|
|
- : m_bProfile ( false )
|
|
|
|
|
- , m_eQueryType ( QUERY_SQL )
|
|
|
|
|
- , m_bAttrHighlight ( false )
|
|
|
|
|
- {}
|
|
|
|
|
-};
|
|
|
|
|
|
|
|
|
|
-static QueryParser_i * ParseQuerySql ( CSphQuery & tQuery, const HttpRequestParser_t & tParser, CSphVector<BYTE> & dData, HttpQuerySettings_t & tSettings, CSphString & /*sWarning*/ )
|
|
|
|
|
|
|
+static void HttpHandlerIndexPage ( CSphVector<BYTE> & dData )
|
|
|
{
|
|
{
|
|
|
- const CSphString * pRawQl = tParser.m_hOptions ( "query" );
|
|
|
|
|
- if ( !pRawQl || pRawQl->IsEmpty() )
|
|
|
|
|
- {
|
|
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_400, "query missed" );
|
|
|
|
|
- return NULL;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- CSphString sError;
|
|
|
|
|
- CSphVector<SqlStmt_t> dStmt;
|
|
|
|
|
- if ( !sphParseSqlQuery ( pRawQl->cstr(), pRawQl->Length(), dStmt, sError, SPH_COLLATION_DEFAULT ) )
|
|
|
|
|
- {
|
|
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_400, sError.cstr() );
|
|
|
|
|
- return NULL;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- tQuery = dStmt[0].m_tQuery;
|
|
|
|
|
- if ( dStmt.GetLength()>1 )
|
|
|
|
|
- {
|
|
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_501, "multiple queries not supported" );
|
|
|
|
|
- return NULL;
|
|
|
|
|
- } else if ( dStmt[0].m_eStmt!=STMT_SELECT )
|
|
|
|
|
- {
|
|
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_501, "only SELECT queries supported" );
|
|
|
|
|
- return NULL;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- tSettings.m_eQueryType = QUERY_SQL;
|
|
|
|
|
-
|
|
|
|
|
- return sphCreatePlainQueryParser();
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-static QueryParser_i * ParseQueryPlain ( CSphQuery & tQuery, const HttpRequestParser_t & tParser, CSphVector<BYTE> & dData, HttpQuerySettings_t & tSettings, CSphString & /*sWarning*/ )
|
|
|
|
|
-{
|
|
|
|
|
- CSphString sError;
|
|
|
|
|
- ParseSearchOptions ( tParser.m_hOptions, tQuery );
|
|
|
|
|
- if ( !tQuery.ParseSelectList ( sError ) )
|
|
|
|
|
- {
|
|
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_400, sError.cstr() );
|
|
|
|
|
- return NULL;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if ( !tQuery.m_sSortBy.IsEmpty() )
|
|
|
|
|
- tQuery.m_eSort = SPH_SORT_EXTENDED;
|
|
|
|
|
-
|
|
|
|
|
- tSettings.m_eQueryType = QUERY_SQL;
|
|
|
|
|
-
|
|
|
|
|
- return sphCreatePlainQueryParser();
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-static QueryParser_i * ParseQueryJson ( CSphQuery & tQuery, const HttpRequestParser_t & tParser, CSphVector<BYTE> & dData, HttpQuerySettings_t & tSettings, CSphString & sWarning )
|
|
|
|
|
-{
|
|
|
|
|
- CSphString sError;
|
|
|
|
|
- if ( !sphParseJsonQuery ( tParser.m_sRawBody.cstr(), tQuery, tSettings.m_bProfile, tSettings.m_bAttrHighlight, sError, sWarning ) )
|
|
|
|
|
- {
|
|
|
|
|
- sError.SetSprintf( "Error parsing json query: %s", sError.cstr() );
|
|
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_400, sError.cstr() );
|
|
|
|
|
- return NULL;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- tSettings.m_eQueryType = QUERY_JSON;
|
|
|
|
|
-
|
|
|
|
|
- return sphCreateJsonQueryParser();
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-void EncodeResultPlain ( const AggrResult_t & tRes, const CSphQuery & , CSphQueryProfile *, bool , CSphString & sResult )
|
|
|
|
|
-{
|
|
|
|
|
- JsonEscapedBuilder tResBuilder;
|
|
|
|
|
- EncodeResultJson ( tRes, tResBuilder );
|
|
|
|
|
- sResult.Adopt ( tResBuilder.Leak() );
|
|
|
|
|
|
|
+ StringBuilder_c sIndexPage;
|
|
|
|
|
+ sIndexPage.Appendf ( g_sIndexPage, SPHINX_VERSION );
|
|
|
|
|
+ HttpBuildReply ( dData, SPH_HTTP_STATUS_200, sIndexPage.cstr(), sIndexPage.Length(), true );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -698,55 +557,100 @@ const char * CSphQueryProfileJson::GetResultAsStr() const
|
|
|
return nullptr;
|
|
return nullptr;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
+class JsonRequestBuilder_c : public IRequestBuilder_t
|
|
|
|
|
+{
|
|
|
|
|
+public:
|
|
|
|
|
+ JsonRequestBuilder_c ( const CSphString & sQuery, const SqlStmt_t & /*tStmt*/, ESphHttpEndpoint eEndpoint )
|
|
|
|
|
+ : m_eEndpoint ( eEndpoint )
|
|
|
|
|
+ {
|
|
|
|
|
+ // fixme: we can implement replacing indexes in a string (without parsing) if it becomes a performance issue
|
|
|
|
|
+ m_pQuery = cJSON_Parse ( sQuery.cstr() );
|
|
|
|
|
+ assert ( m_pQuery );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ~JsonRequestBuilder_c()
|
|
|
|
|
+ {
|
|
|
|
|
+ cJSON_Delete ( m_pQuery );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void BuildRequest ( const AgentConn_t & tAgent, ISphOutputBuffer & tOut ) const override
|
|
|
|
|
+ {
|
|
|
|
|
+ // replace "index" value in the json query
|
|
|
|
|
+ cJSON_DeleteItemFromObject ( m_pQuery, "index" );
|
|
|
|
|
+ cJSON_AddStringToObject ( m_pQuery, "index", tAgent.m_tDesc.m_sIndexes.cstr() );
|
|
|
|
|
+ CSphString sRequest = sphJsonToString ( m_pQuery );
|
|
|
|
|
+ CSphString sEndpoint = sphHttpEndpointToStr ( m_eEndpoint );
|
|
|
|
|
+
|
|
|
|
|
+ tOut.SendWord ( SEARCHD_COMMAND_JSON );
|
|
|
|
|
+ tOut.SendWord ( VER_COMMAND_JSON );
|
|
|
|
|
+ tOut.SendInt ( sEndpoint.Length() + sRequest.Length() + 8 );
|
|
|
|
|
+
|
|
|
|
|
+ tOut.SendString ( sEndpoint.cstr() );
|
|
|
|
|
+ tOut.SendString ( sRequest.cstr() );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+private:
|
|
|
|
|
+ cJSON * m_pQuery {nullptr};
|
|
|
|
|
+ ESphHttpEndpoint m_eEndpoint {SPH_HTTP_ENDPOINT_TOTAL};
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
-using ParseQueryFunc_fn = QueryParser_i * (CSphQuery &, const HttpRequestParser_t &, CSphVector<BYTE> &, HttpQuerySettings_t &, CSphString & );
|
|
|
|
|
-using ResultEncodeFunc_fn = void ( const AggrResult_t &, const CSphQuery &, CSphQueryProfile *, bool, CSphString & );
|
|
|
|
|
|
|
|
|
|
-static void HttpHandlerSearch ( ParseQueryFunc_fn * pParseQueryFunc, ResultEncodeFunc_fn * pResultEncodeFunc, const HttpRequestParser_t & tParser, int iCID, CSphVector<BYTE> & dData )
|
|
|
|
|
|
|
+class JsonReplyParser_c : public IReplyParser_t
|
|
|
{
|
|
{
|
|
|
- assert ( pParseQueryFunc && pResultEncodeFunc );
|
|
|
|
|
|
|
+public:
|
|
|
|
|
+ JsonReplyParser_c ( int & iAffected, int & iWarnings )
|
|
|
|
|
+ : m_iAffected ( iAffected )
|
|
|
|
|
+ , m_iWarnings ( iWarnings )
|
|
|
|
|
+ {}
|
|
|
|
|
|
|
|
- HttpQuerySettings_t tQuerySettings;
|
|
|
|
|
- CSphQuery tQuery;
|
|
|
|
|
- CSphString sWarning;
|
|
|
|
|
- QueryParser_i * pQueryParser = pParseQueryFunc ( tQuery, tParser, dData, tQuerySettings, sWarning );
|
|
|
|
|
- if ( !pQueryParser )
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ virtual bool ParseReply ( MemInputBuffer_c & tReq, AgentConn_t & ) const
|
|
|
|
|
+ {
|
|
|
|
|
+ CSphString sEndpoint = tReq.GetString();
|
|
|
|
|
+ ESphHttpEndpoint eEndpoint = sphStrToHttpEndpoint ( sEndpoint );
|
|
|
|
|
+ if ( eEndpoint!=SPH_HTTP_ENDPOINT_JSON_UPDATE && eEndpoint!=SPH_HTTP_ENDPOINT_JSON_DELETE )
|
|
|
|
|
+ return false;
|
|
|
|
|
|
|
|
- CSphQueryProfileJson tProfile;
|
|
|
|
|
|
|
+ DWORD uLength = tReq.GetDword();
|
|
|
|
|
+ CSphFixedVector<BYTE> dResult ( uLength );
|
|
|
|
|
+ tReq.GetBytes ( dResult.Begin(), uLength );
|
|
|
|
|
|
|
|
- tQuery.m_pQueryParser = pQueryParser;
|
|
|
|
|
- CSphScopedPtr<ISphSearchHandler> tHandler ( sphCreateSearchHandler ( 1, pQueryParser, tQuerySettings.m_eQueryType, true, iCID ) );
|
|
|
|
|
|
|
+ return sphGetResultStats ( (const char *)dResult.Begin(), m_iAffected, m_iWarnings, eEndpoint==SPH_HTTP_ENDPOINT_JSON_UPDATE );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if ( tQuerySettings.m_bProfile )
|
|
|
|
|
- tHandler->SetProfile ( &tProfile );
|
|
|
|
|
|
|
+protected:
|
|
|
|
|
+ int & m_iAffected;
|
|
|
|
|
+ int & m_iWarnings;
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
- tHandler->SetQuery ( 0, tQuery );
|
|
|
|
|
|
|
|
|
|
- // search
|
|
|
|
|
- tHandler->RunQueries();
|
|
|
|
|
|
|
+class JsonParserFactory_c : public QueryParserFactory_i
|
|
|
|
|
+{
|
|
|
|
|
+public:
|
|
|
|
|
+ JsonParserFactory_c ( ESphHttpEndpoint eEndpoint )
|
|
|
|
|
+ : m_eEndpoint ( eEndpoint )
|
|
|
|
|
+ {}
|
|
|
|
|
|
|
|
- if ( tQuerySettings.m_bProfile )
|
|
|
|
|
- tProfile.Stop();
|
|
|
|
|
|
|
+ virtual QueryParser_i * CreateQueryParser() const override
|
|
|
|
|
+ {
|
|
|
|
|
+ return sphCreateJsonQueryParser();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- AggrResult_t * pRes = tHandler->GetResult ( 0 );
|
|
|
|
|
- if ( !pRes->m_sError.IsEmpty() )
|
|
|
|
|
|
|
+ IRequestBuilder_t * CreateRequestBuilder ( const CSphString & sQuery, const SqlStmt_t & tStmt ) const override
|
|
|
{
|
|
{
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_500, pRes->m_sError.cstr() );
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ return new JsonRequestBuilder_c ( sQuery, tStmt, m_eEndpoint );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // fixme: handle more than one warning at once?
|
|
|
|
|
- if ( pRes->m_sWarning.IsEmpty() )
|
|
|
|
|
- pRes->m_sWarning = sWarning;
|
|
|
|
|
-
|
|
|
|
|
- CSphString sResult;
|
|
|
|
|
- pResultEncodeFunc ( *pRes, tQuery, tQuerySettings.m_bProfile ? &tProfile : NULL, tQuerySettings.m_bAttrHighlight, sResult );
|
|
|
|
|
- HttpBuildReply ( dData, SPH_HTTP_STATUS_200, sResult.cstr(), sResult.Length(), false );
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ IReplyParser_t * CreateReplyParser ( int & iUpdated, int & iWarnings ) const override
|
|
|
|
|
+ {
|
|
|
|
|
+ return new JsonReplyParser_c ( iUpdated, iWarnings );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+private:
|
|
|
|
|
+ ESphHttpEndpoint m_eEndpoint {SPH_HTTP_ENDPOINT_TOTAL};
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
|
|
+//////////////////////////////////////////////////////////////////////////
|
|
|
class HttpErrorReporter_c : public StmtErrorReporter_i
|
|
class HttpErrorReporter_c : public StmtErrorReporter_i
|
|
|
{
|
|
{
|
|
|
public:
|
|
public:
|
|
@@ -772,278 +676,602 @@ void HttpErrorReporter_c::Error ( const char * /*sStmt*/, const char * sError, M
|
|
|
m_sError = sError;
|
|
m_sError = sError;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
+// all the handlers for http queries
|
|
|
|
|
|
|
|
-void CreateHttpResponse ( bool bResult, cJSON * pResult, CSphVector<BYTE> & dData )
|
|
|
|
|
|
|
+class HttpHandler_c
|
|
|
{
|
|
{
|
|
|
- assert ( pResult );
|
|
|
|
|
|
|
+public:
|
|
|
|
|
+ HttpHandler_c ( const CSphString & sQuery, int iCID, bool bNeedHttpResponse )
|
|
|
|
|
+ : m_sQuery ( sQuery )
|
|
|
|
|
+ , m_bNeedHttpResponse ( bNeedHttpResponse )
|
|
|
|
|
+ {}
|
|
|
|
|
|
|
|
- CSphString sResult;
|
|
|
|
|
- sResult.Adopt ( cJSON_Print ( pResult ) );
|
|
|
|
|
- HttpBuildReply ( dData, bResult ? SPH_HTTP_STATUS_200 : SPH_HTTP_STATUS_500, sResult.cstr(), sResult.Length(), false );
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ virtual bool Process () = 0;
|
|
|
|
|
+
|
|
|
|
|
+ CSphVector<BYTE> & GetResult()
|
|
|
|
|
+ {
|
|
|
|
|
+ return m_dData;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+protected:
|
|
|
|
|
+ const CSphString & m_sQuery;
|
|
|
|
|
+ int m_iCID {0};
|
|
|
|
|
+ bool m_bNeedHttpResponse {false};
|
|
|
|
|
+ CSphVector<BYTE> m_dData;
|
|
|
|
|
|
|
|
-static bool ProcessInsert ( SqlStmt_t & tStmt, SphDocID_t tDocId, bool bReplace, cJSON * & pResult )
|
|
|
|
|
-{
|
|
|
|
|
- CSphSessionAccum tAcc ( false );
|
|
|
|
|
- CSphString sWarning;
|
|
|
|
|
- HttpErrorReporter_c tReporter;
|
|
|
|
|
- sphHandleMysqlInsert ( tReporter, tStmt, bReplace, true, sWarning, tAcc, SPH_COLLATION_DEFAULT );
|
|
|
|
|
|
|
+ void ReportError ( const char * szError, ESphHttpStatus eStatus )
|
|
|
|
|
+ {
|
|
|
|
|
+ if ( m_bNeedHttpResponse )
|
|
|
|
|
+ HttpErrorReply ( m_dData, eStatus, szError );
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ m_dData.Resize ( strlen(szError)+1 );
|
|
|
|
|
+ memcpy ( m_dData.Begin(), szError, m_dData.GetLength() );
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if ( tReporter.IsError() )
|
|
|
|
|
- pResult = sphEncodeInsertErrorJson ( tStmt.m_sIndex.cstr(), tReporter.GetError() );
|
|
|
|
|
- else
|
|
|
|
|
- pResult = sphEncodeInsertResultJson ( tStmt.m_sIndex.cstr(), bReplace, tDocId );
|
|
|
|
|
|
|
+ void BuildReply ( const CSphString & sResult, ESphHttpStatus eStatus )
|
|
|
|
|
+ {
|
|
|
|
|
+ if ( m_bNeedHttpResponse )
|
|
|
|
|
+ HttpBuildReply ( m_dData, eStatus, sResult.cstr(), sResult.Length(), false );
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ m_dData.Resize ( sResult.Length()+1 );
|
|
|
|
|
+ memcpy ( m_dData.Begin(), sResult.cstr(), m_dData.GetLength() );
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
- return !tReporter.IsError();
|
|
|
|
|
-}
|
|
|
|
|
|
|
+
|
|
|
|
|
+class HttpOptionsTraits_c
|
|
|
|
|
+{
|
|
|
|
|
+protected:
|
|
|
|
|
+ const OptionsHash_t & m_tOptions;
|
|
|
|
|
+
|
|
|
|
|
+ HttpOptionsTraits_c ( const OptionsHash_t & tOptions )
|
|
|
|
|
+ : m_tOptions ( tOptions )
|
|
|
|
|
+ {}
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
|
|
|
|
|
-static void HttpHandlerInsert ( const HttpRequestParser_t & tParser, CSphVector<BYTE> & dData, bool bReplace )
|
|
|
|
|
|
|
+class HttpSearchHandler_c : public HttpHandler_c, public HttpOptionsTraits_c
|
|
|
{
|
|
{
|
|
|
- SqlStmt_t tStmt;
|
|
|
|
|
- SphDocID_t tDocId;
|
|
|
|
|
- CSphString sError;
|
|
|
|
|
- if ( !sphParseJsonInsert ( tParser.m_sRawBody.cstr(), tStmt, tDocId, bReplace, sError ) )
|
|
|
|
|
|
|
+public:
|
|
|
|
|
+ HttpSearchHandler_c ( const CSphString & sQuery, const OptionsHash_t & tOptions, int iCID, bool bNeedHttpResponse )
|
|
|
|
|
+ : HttpHandler_c ( sQuery, iCID, bNeedHttpResponse )
|
|
|
|
|
+ , HttpOptionsTraits_c ( tOptions )
|
|
|
|
|
+ {}
|
|
|
|
|
+
|
|
|
|
|
+ virtual bool Process () override
|
|
|
{
|
|
{
|
|
|
- sError.SetSprintf( "Error parsing json query: %s", sError.cstr() );
|
|
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_400, sError.cstr() );
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ CSphQuery tQuery;
|
|
|
|
|
+ CSphString sWarning;
|
|
|
|
|
+ QueryParser_i * pQueryParser = PreParseQuery();
|
|
|
|
|
+ if ( !pQueryParser )
|
|
|
|
|
+ return false;
|
|
|
|
|
+
|
|
|
|
|
+ m_tQuery.m_pQueryParser = pQueryParser;
|
|
|
|
|
+ CSphScopedPtr<ISphSearchHandler> tHandler ( sphCreateSearchHandler ( 1, pQueryParser, m_eQueryType, true, m_iCID ) );
|
|
|
|
|
+
|
|
|
|
|
+ CSphQueryProfileJson tProfile;
|
|
|
|
|
+ if ( m_bProfile )
|
|
|
|
|
+ tHandler->SetProfile ( &tProfile );
|
|
|
|
|
+
|
|
|
|
|
+ tHandler->SetQuery ( 0, m_tQuery );
|
|
|
|
|
+
|
|
|
|
|
+ // search
|
|
|
|
|
+ tHandler->RunQueries();
|
|
|
|
|
+
|
|
|
|
|
+ if ( m_bProfile )
|
|
|
|
|
+ tProfile.Stop();
|
|
|
|
|
+
|
|
|
|
|
+ AggrResult_t * pRes = tHandler->GetResult ( 0 );
|
|
|
|
|
+ if ( !pRes->m_sError.IsEmpty() )
|
|
|
|
|
+ {
|
|
|
|
|
+ ReportError ( pRes->m_sError.cstr(), SPH_HTTP_STATUS_500 );
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // fixme: handle more than one warning at once?
|
|
|
|
|
+ if ( pRes->m_sWarning.IsEmpty() )
|
|
|
|
|
+ pRes->m_sWarning = m_sWarning;
|
|
|
|
|
+
|
|
|
|
|
+ CSphString sResult = EncodeResult ( *pRes, m_bProfile ? &tProfile : NULL );
|
|
|
|
|
+ BuildReply ( sResult, SPH_HTTP_STATUS_200 );
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- cJSON * pResult = NULL;
|
|
|
|
|
- bool bResult = ProcessInsert ( tStmt, tDocId, bReplace, pResult );
|
|
|
|
|
- CreateHttpResponse ( bResult, pResult, dData );
|
|
|
|
|
- cJSON_Delete ( pResult );
|
|
|
|
|
-}
|
|
|
|
|
|
|
+protected:
|
|
|
|
|
+ bool m_bProfile {false};
|
|
|
|
|
+ bool m_bAttrHighlight {false};
|
|
|
|
|
+ QueryType_e m_eQueryType {QUERY_SQL};
|
|
|
|
|
+ CSphQuery m_tQuery;
|
|
|
|
|
+ CSphString m_sWarning;
|
|
|
|
|
+
|
|
|
|
|
+ virtual QueryParser_i * PreParseQuery() = 0;
|
|
|
|
|
+ virtual CSphString EncodeResult ( const AggrResult_t & tRes, CSphQueryProfile * pProfile ) = 0;
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
|
|
|
|
|
-class JsonParserFactory_c : public QueryParserFactory_i
|
|
|
|
|
|
|
+class HttpSearchHandler_Plain_c : public HttpSearchHandler_c
|
|
|
{
|
|
{
|
|
|
public:
|
|
public:
|
|
|
- virtual QueryParser_i * Create() const override
|
|
|
|
|
|
|
+ HttpSearchHandler_Plain_c ( const CSphString & sQuery, const OptionsHash_t & tOptions, int iCID, bool bNeedHttpResponse )
|
|
|
|
|
+ : HttpSearchHandler_c ( sQuery, tOptions, iCID, bNeedHttpResponse )
|
|
|
|
|
+ {}
|
|
|
|
|
+
|
|
|
|
|
+protected:
|
|
|
|
|
+ QueryParser_i * PreParseQuery() override
|
|
|
{
|
|
{
|
|
|
- return sphCreateJsonQueryParser();
|
|
|
|
|
|
|
+ CSphString sError;
|
|
|
|
|
+ ParseSearchOptions ( m_tOptions, m_tQuery );
|
|
|
|
|
+ if ( !m_tQuery.ParseSelectList ( sError ) )
|
|
|
|
|
+ {
|
|
|
|
|
+ ReportError ( sError.cstr(), SPH_HTTP_STATUS_400 );
|
|
|
|
|
+ return NULL;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ( !m_tQuery.m_sSortBy.IsEmpty() )
|
|
|
|
|
+ m_tQuery.m_eSort = SPH_SORT_EXTENDED;
|
|
|
|
|
+
|
|
|
|
|
+ m_eQueryType = QUERY_SQL;
|
|
|
|
|
+
|
|
|
|
|
+ return sphCreatePlainQueryParser();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ CSphString EncodeResult ( const AggrResult_t & tRes, CSphQueryProfile * /*pProfile*/ ) override
|
|
|
|
|
+ {
|
|
|
|
|
+ JsonEscapedBuilder tResBuilder;
|
|
|
|
|
+ EncodeResultJson ( tRes, tResBuilder );
|
|
|
|
|
+ CSphString sResult;
|
|
|
|
|
+ sResult.Adopt ( tResBuilder.Leak() );
|
|
|
|
|
+ return sResult;
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
-static bool ProcessUpdate ( SqlStmt_t & tStmt, SphDocID_t tDocId, int iCID, cJSON * & pResult )
|
|
|
|
|
|
|
+class HttpSearchHandler_SQL_c : public HttpSearchHandler_Plain_c
|
|
|
{
|
|
{
|
|
|
- HttpErrorReporter_c tReporter;
|
|
|
|
|
- CSphString sWarning;
|
|
|
|
|
- JsonParserFactory_c tFactory;
|
|
|
|
|
- sphHandleMysqlUpdate ( tReporter, tFactory, tStmt, "", sWarning, iCID );
|
|
|
|
|
-
|
|
|
|
|
- if ( tReporter.IsError() )
|
|
|
|
|
- pResult = sphEncodeInsertErrorJson ( tStmt.m_sIndex.cstr(), tReporter.GetError() );
|
|
|
|
|
- else
|
|
|
|
|
- pResult = sphEncodeUpdateResultJson ( tStmt.m_sIndex.cstr(), tDocId, tReporter.GetAffectedRows() );
|
|
|
|
|
|
|
+public:
|
|
|
|
|
+ HttpSearchHandler_SQL_c ( const CSphString & sQuery, const OptionsHash_t & tOptions, int iCID, bool bNeedHttpResponse )
|
|
|
|
|
+ : HttpSearchHandler_Plain_c ( sQuery, tOptions, iCID, bNeedHttpResponse )
|
|
|
|
|
+ {}
|
|
|
|
|
|
|
|
- return !tReporter.IsError();
|
|
|
|
|
-}
|
|
|
|
|
|
|
+protected:
|
|
|
|
|
+ QueryParser_i * PreParseQuery() override
|
|
|
|
|
+ {
|
|
|
|
|
+ const CSphString * pRawQl = m_tOptions ( "query" );
|
|
|
|
|
+ if ( !pRawQl || pRawQl->IsEmpty() )
|
|
|
|
|
+ {
|
|
|
|
|
+ ReportError ( "query missing", SPH_HTTP_STATUS_400 );
|
|
|
|
|
+ return NULL;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ CSphString sError;
|
|
|
|
|
+ CSphVector<SqlStmt_t> dStmt;
|
|
|
|
|
+ if ( !sphParseSqlQuery ( pRawQl->cstr(), pRawQl->Length(), dStmt, sError, SPH_COLLATION_DEFAULT ) )
|
|
|
|
|
+ {
|
|
|
|
|
+ ReportError ( sError.cstr(), SPH_HTTP_STATUS_400 );
|
|
|
|
|
+ return NULL;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-static bool ProcessDelete ( SqlStmt_t & tStmt, SphDocID_t tDocId, int iCID, cJSON * & pResult )
|
|
|
|
|
-{
|
|
|
|
|
- CSphSessionAccum tAcc ( false );
|
|
|
|
|
- HttpErrorReporter_c tReporter;
|
|
|
|
|
- CSphString sWarning;
|
|
|
|
|
- JsonParserFactory_c tFactory;
|
|
|
|
|
- sphHandleMysqlDelete ( tReporter, tFactory, tStmt, "", true, tAcc, iCID );
|
|
|
|
|
-
|
|
|
|
|
- if ( tReporter.IsError() )
|
|
|
|
|
- pResult = sphEncodeInsertErrorJson ( tStmt.m_sIndex.cstr(), tReporter.GetError() );
|
|
|
|
|
- else
|
|
|
|
|
- pResult = sphEncodeDeleteResultJson ( tStmt.m_sIndex.cstr(), tDocId, tReporter.GetAffectedRows() );
|
|
|
|
|
|
|
+ m_tQuery = dStmt[0].m_tQuery;
|
|
|
|
|
+ if ( dStmt.GetLength()>1 )
|
|
|
|
|
+ {
|
|
|
|
|
+ ReportError ( "multiple queries not supported", SPH_HTTP_STATUS_501 );
|
|
|
|
|
+ return NULL;
|
|
|
|
|
+ } else if ( dStmt[0].m_eStmt!=STMT_SELECT )
|
|
|
|
|
+ {
|
|
|
|
|
+ ReportError ( "only SELECT queries are supported", SPH_HTTP_STATUS_501 );
|
|
|
|
|
+ return NULL;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return !tReporter.IsError();
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ m_eQueryType = QUERY_SQL;
|
|
|
|
|
|
|
|
|
|
+ return sphCreatePlainQueryParser();
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
-using ParseRequestFunc_fn = bool ( const char *, SqlStmt_t &, SphDocID_t &, CSphString & );
|
|
|
|
|
-using Process_fn = bool ( SqlStmt_t &, SphDocID_t, int, cJSON * & );
|
|
|
|
|
|
|
|
|
|
-static void HttpHandlerUpdateDelete ( const HttpRequestParser_t & tParser, int iCID, CSphVector<BYTE> & dData, ParseRequestFunc_fn * pParseRequestFunc, Process_fn * pProcessFunc )
|
|
|
|
|
|
|
+class HttpHandler_JsonSearch_c : public HttpSearchHandler_c
|
|
|
{
|
|
{
|
|
|
- assert ( pParseRequestFunc && pProcessFunc );
|
|
|
|
|
|
|
+public:
|
|
|
|
|
+ HttpHandler_JsonSearch_c ( const CSphString & sQuery, const OptionsHash_t & tOptions, int iCID, bool bNeedHttpResponse )
|
|
|
|
|
+ : HttpSearchHandler_c ( sQuery, tOptions, iCID, bNeedHttpResponse )
|
|
|
|
|
+ {}
|
|
|
|
|
|
|
|
- SqlStmt_t tStmt;
|
|
|
|
|
- SphDocID_t tDocId = DOCID_MAX;
|
|
|
|
|
- CSphString sError;
|
|
|
|
|
- if ( !pParseRequestFunc ( tParser.m_sRawBody.cstr(), tStmt, tDocId, sError ) )
|
|
|
|
|
|
|
+ QueryParser_i * PreParseQuery() override
|
|
|
{
|
|
{
|
|
|
- sError.SetSprintf( "Error parsing json query: %s", sError.cstr() );
|
|
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_400, sError.cstr() );
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ CSphString sError;
|
|
|
|
|
+ if ( !sphParseJsonQuery ( m_sQuery.cstr(), m_tQuery, m_bProfile, m_bAttrHighlight, sError, m_sWarning ) )
|
|
|
|
|
+ {
|
|
|
|
|
+ sError.SetSprintf( "Error parsing json query: %s", sError.cstr() );
|
|
|
|
|
+ ReportError ( sError.cstr(), SPH_HTTP_STATUS_400 );
|
|
|
|
|
+ return NULL;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ m_eQueryType = QUERY_JSON;
|
|
|
|
|
+
|
|
|
|
|
+ return sphCreateJsonQueryParser();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- cJSON * pResult = NULL;
|
|
|
|
|
- bool bResult = pProcessFunc ( tStmt, tDocId, iCID, pResult );
|
|
|
|
|
- CreateHttpResponse ( bResult, pResult, dData );
|
|
|
|
|
- cJSON_Delete ( pResult );
|
|
|
|
|
-}
|
|
|
|
|
|
|
+protected:
|
|
|
|
|
+ CSphString EncodeResult ( const AggrResult_t & tRes, CSphQueryProfile * pProfile ) override
|
|
|
|
|
+ {
|
|
|
|
|
+ return sphEncodeResultJson ( tRes, m_tQuery, pProfile, m_bAttrHighlight );
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
|
|
|
|
|
-static void AddResultToBulk ( cJSON * pRoot, CSphString & sStmt, cJSON * pResult )
|
|
|
|
|
|
|
+class HttpJsonInsertTraits_c
|
|
|
{
|
|
{
|
|
|
- assert ( pRoot && pResult );
|
|
|
|
|
|
|
+protected:
|
|
|
|
|
+ bool ProcessInsert ( SqlStmt_t & tStmt, SphDocID_t tDocId, bool bReplace, cJSON * & pResult )
|
|
|
|
|
+ {
|
|
|
|
|
+ CSphSessionAccum tAcc ( false );
|
|
|
|
|
+ CSphString sWarning;
|
|
|
|
|
+ HttpErrorReporter_c tReporter;
|
|
|
|
|
+ sphHandleMysqlInsert ( tReporter, tStmt, bReplace, true, sWarning, tAcc, SPH_COLLATION_DEFAULT );
|
|
|
|
|
|
|
|
- cJSON * pItem = cJSON_CreateObject();
|
|
|
|
|
- assert ( pItem );
|
|
|
|
|
- cJSON_AddItemToArray ( pRoot, pItem );
|
|
|
|
|
|
|
+ if ( tReporter.IsError() )
|
|
|
|
|
+ pResult = sphEncodeInsertErrorJson ( tStmt.m_sIndex.cstr(), tReporter.GetError() );
|
|
|
|
|
+ else
|
|
|
|
|
+ pResult = sphEncodeInsertResultJson ( tStmt.m_sIndex.cstr(), bReplace, tDocId );
|
|
|
|
|
|
|
|
- cJSON_AddItemToObject( pItem, sStmt.cstr(), pResult );
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ return !tReporter.IsError();
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
|
|
|
|
|
-static void HttpHandlerBulk ( const HttpRequestParser_t & tParser, int iCID, CSphVector<BYTE> & dData )
|
|
|
|
|
|
|
+class HttpHandler_JsonInsert_c : public HttpHandler_c, public HttpJsonInsertTraits_c
|
|
|
{
|
|
{
|
|
|
- if ( !tParser.m_hOptions.Exists ("Content-Type") )
|
|
|
|
|
- {
|
|
|
|
|
- CSphString sError = "Content-Type must be set";
|
|
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_400, sError.cstr() );
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+public:
|
|
|
|
|
+ HttpHandler_JsonInsert_c ( const CSphString & sQuery, bool bReplace, bool bNeedHttpResponse )
|
|
|
|
|
+ : HttpHandler_c ( sQuery, 0, bNeedHttpResponse )
|
|
|
|
|
+ , m_bReplace ( bReplace )
|
|
|
|
|
+ {}
|
|
|
|
|
|
|
|
- if ( tParser.m_hOptions["Content-Type"].ToLower() != "application/x-ndjson" )
|
|
|
|
|
|
|
+ bool Process () override
|
|
|
{
|
|
{
|
|
|
- CSphString sError = "Content-Type must be application/x-ndjson";
|
|
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_400, sError.cstr() );
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ SqlStmt_t tStmt;
|
|
|
|
|
+ SphDocID_t tDocId = DOCID_MAX;
|
|
|
|
|
+ CSphString sError;
|
|
|
|
|
+ if ( !sphParseJsonInsert ( m_sQuery.cstr(), tStmt, tDocId, m_bReplace, sError ) )
|
|
|
|
|
+ {
|
|
|
|
|
+ sError.SetSprintf( "Error parsing json query: %s", sError.cstr() );
|
|
|
|
|
+ ReportError ( sError.cstr(), SPH_HTTP_STATUS_400 );
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ cJSON * pResult = NULL;
|
|
|
|
|
+ bool bResult = ProcessInsert ( tStmt, tDocId, m_bReplace, pResult );
|
|
|
|
|
+
|
|
|
|
|
+ CSphString sResult = sphJsonToString ( pResult );
|
|
|
|
|
+ BuildReply( sResult, bResult ? SPH_HTTP_STATUS_200 : SPH_HTTP_STATUS_500 );
|
|
|
|
|
+
|
|
|
|
|
+ cJSON_Delete ( pResult );
|
|
|
|
|
+ return bResult;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- cJSON * pRoot = cJSON_CreateObject();
|
|
|
|
|
- cJSON * pItems = cJSON_CreateArray();
|
|
|
|
|
- assert ( pRoot && pItems );
|
|
|
|
|
- cJSON_AddItemToObject ( pRoot, "items", pItems );
|
|
|
|
|
|
|
+private:
|
|
|
|
|
+ bool m_bReplace {false};
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
- char * p = const_cast<char*>(tParser.m_sRawBody.cstr());
|
|
|
|
|
|
|
|
|
|
- bool bResult = false;
|
|
|
|
|
- while ( *p )
|
|
|
|
|
|
|
+class HttpJsonUpdateTraits_c
|
|
|
|
|
+{
|
|
|
|
|
+protected:
|
|
|
|
|
+ bool ProcessUpdate ( const char * szRawRequest, const SqlStmt_t & tStmt, SphDocID_t tDocId, int iCID, cJSON * & pResult )
|
|
|
{
|
|
{
|
|
|
- while ( sphIsSpace(*p) )
|
|
|
|
|
- p++;
|
|
|
|
|
|
|
+ HttpErrorReporter_c tReporter;
|
|
|
|
|
+ CSphString sWarning;
|
|
|
|
|
+ JsonParserFactory_c tFactory ( SPH_HTTP_ENDPOINT_JSON_UPDATE );
|
|
|
|
|
+ sphHandleMysqlUpdate ( tReporter, tFactory, tStmt, szRawRequest, sWarning, iCID );
|
|
|
|
|
+
|
|
|
|
|
+ if ( tReporter.IsError() )
|
|
|
|
|
+ pResult = sphEncodeInsertErrorJson ( tStmt.m_sIndex.cstr(), tReporter.GetError() );
|
|
|
|
|
+ else
|
|
|
|
|
+ pResult = sphEncodeUpdateResultJson ( tStmt.m_sIndex.cstr(), tDocId, tReporter.GetAffectedRows() );
|
|
|
|
|
+
|
|
|
|
|
+ return !tReporter.IsError();
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
- char * szStmt = p;
|
|
|
|
|
- while ( *p && *p!='\r' && *p!='\n' )
|
|
|
|
|
- p++;
|
|
|
|
|
|
|
|
|
|
- if ( p-szStmt==0 )
|
|
|
|
|
- break;
|
|
|
|
|
|
|
+class HttpHandler_JsonUpdate_c : public HttpHandler_c, HttpJsonUpdateTraits_c
|
|
|
|
|
+{
|
|
|
|
|
+public:
|
|
|
|
|
+ HttpHandler_JsonUpdate_c ( const CSphString & sQuery, int iCID, bool bNeedHttpResponse )
|
|
|
|
|
+ : HttpHandler_c ( sQuery, iCID, bNeedHttpResponse )
|
|
|
|
|
+ {}
|
|
|
|
|
|
|
|
- *p++ = '\0';
|
|
|
|
|
|
|
+ bool Process () override
|
|
|
|
|
+ {
|
|
|
SqlStmt_t tStmt;
|
|
SqlStmt_t tStmt;
|
|
|
SphDocID_t tDocId = DOCID_MAX;
|
|
SphDocID_t tDocId = DOCID_MAX;
|
|
|
- CSphString sStmt;
|
|
|
|
|
CSphString sError;
|
|
CSphString sError;
|
|
|
- if ( !sphParseJsonStatement ( szStmt, tStmt, sStmt, tDocId, sError ) )
|
|
|
|
|
|
|
+ if ( !ParseQuery ( tStmt, tDocId, sError ) )
|
|
|
{
|
|
{
|
|
|
sError.SetSprintf( "Error parsing json query: %s", sError.cstr() );
|
|
sError.SetSprintf( "Error parsing json query: %s", sError.cstr() );
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_400, sError.cstr() );
|
|
|
|
|
- cJSON_Delete ( pRoot );
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ ReportError ( sError.cstr(), SPH_HTTP_STATUS_400 );
|
|
|
|
|
+ return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
cJSON * pResult = NULL;
|
|
cJSON * pResult = NULL;
|
|
|
- bResult = false;
|
|
|
|
|
|
|
+ bool bResult = ProcessQuery ( tStmt, tDocId, pResult );
|
|
|
|
|
|
|
|
- switch ( tStmt.m_eStmt )
|
|
|
|
|
- {
|
|
|
|
|
- case STMT_INSERT:
|
|
|
|
|
- case STMT_REPLACE:
|
|
|
|
|
- bResult = ProcessInsert ( tStmt, tDocId, tStmt.m_eStmt==STMT_REPLACE, pResult );
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case STMT_UPDATE:
|
|
|
|
|
- bResult = ProcessUpdate ( tStmt, tDocId, iCID, pResult );
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case STMT_DELETE:
|
|
|
|
|
- bResult = ProcessDelete ( tStmt, tDocId, iCID, pResult );
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- default:
|
|
|
|
|
- sError = "Unknown statement";
|
|
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_400, sError.cstr() );
|
|
|
|
|
- cJSON_Delete ( pRoot );
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ CSphString sResult = sphJsonToString ( pResult );
|
|
|
|
|
+ BuildReply( sResult, bResult ? SPH_HTTP_STATUS_200 : SPH_HTTP_STATUS_500 );
|
|
|
|
|
+
|
|
|
|
|
+ cJSON_Delete ( pResult );
|
|
|
|
|
+ return bResult;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- AddResultToBulk ( pItems, sStmt, pResult );
|
|
|
|
|
-
|
|
|
|
|
- // no further than the first error
|
|
|
|
|
- if ( !bResult )
|
|
|
|
|
- break;
|
|
|
|
|
|
|
+protected:
|
|
|
|
|
+ virtual bool ParseQuery ( SqlStmt_t & tStmt, SphDocID_t & tDocId, CSphString & sError )
|
|
|
|
|
+ {
|
|
|
|
|
+ return sphParseJsonUpdate ( m_sQuery.cstr(), tStmt, tDocId, sError );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- while ( sphIsSpace(*p) )
|
|
|
|
|
- p++;
|
|
|
|
|
|
|
+ virtual bool ProcessQuery ( const SqlStmt_t & tStmt, SphDocID_t tDocId, cJSON * & pResult )
|
|
|
|
|
+ {
|
|
|
|
|
+ return ProcessUpdate ( m_sQuery.cstr(), tStmt, tDocId, m_iCID, pResult );
|
|
|
}
|
|
}
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
- cJSON_AddBoolToObject ( pRoot, "errors", bResult ? 0 : 1 );
|
|
|
|
|
|
|
|
|
|
- CreateHttpResponse ( bResult, pRoot, dData );
|
|
|
|
|
- cJSON_Delete ( pRoot );
|
|
|
|
|
-}
|
|
|
|
|
|
|
+class HttpJsonDeleteTraits_c
|
|
|
|
|
+{
|
|
|
|
|
+protected:
|
|
|
|
|
+ bool ProcessDelete ( const char * szRawRequest, const SqlStmt_t & tStmt, SphDocID_t tDocId, int iCID, cJSON * & pResult )
|
|
|
|
|
+ {
|
|
|
|
|
+ CSphSessionAccum tAcc ( false );
|
|
|
|
|
+ HttpErrorReporter_c tReporter;
|
|
|
|
|
+ CSphString sWarning;
|
|
|
|
|
+ JsonParserFactory_c tFactory ( SPH_HTTP_ENDPOINT_JSON_DELETE );
|
|
|
|
|
+ sphHandleMysqlDelete ( tReporter, tFactory, tStmt, szRawRequest, true, tAcc, iCID );
|
|
|
|
|
+
|
|
|
|
|
+ if ( tReporter.IsError() )
|
|
|
|
|
+ pResult = sphEncodeInsertErrorJson ( tStmt.m_sIndex.cstr(), tReporter.GetError() );
|
|
|
|
|
+ else
|
|
|
|
|
+ pResult = sphEncodeDeleteResultJson ( tStmt.m_sIndex.cstr(), tDocId, tReporter.GetAffectedRows() );
|
|
|
|
|
+
|
|
|
|
|
+ return !tReporter.IsError();
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
|
|
|
|
|
-bool sphLoopClientHttp ( CSphVector<BYTE> & dData, int iCID )
|
|
|
|
|
|
|
+class HttpHandler_JsonDelete_c : public HttpHandler_JsonUpdate_c, public HttpJsonDeleteTraits_c
|
|
|
{
|
|
{
|
|
|
- HttpRequestParser_t tParser;
|
|
|
|
|
- if ( !tParser.Parse ( dData ) )
|
|
|
|
|
|
|
+public:
|
|
|
|
|
+ HttpHandler_JsonDelete_c ( const CSphString & sQuery, int iCID, bool bNeedHttpResponse )
|
|
|
|
|
+ : HttpHandler_JsonUpdate_c ( sQuery, iCID, bNeedHttpResponse )
|
|
|
|
|
+ {}
|
|
|
|
|
+
|
|
|
|
|
+protected:
|
|
|
|
|
+ bool ParseQuery ( SqlStmt_t & tStmt, SphDocID_t & tDocId, CSphString & sError ) override
|
|
|
|
|
+ {
|
|
|
|
|
+ return sphParseJsonDelete ( m_sQuery.cstr(), tStmt, tDocId, sError );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool ProcessQuery ( const SqlStmt_t & tStmt, SphDocID_t tDocId, cJSON * & pResult ) override
|
|
|
{
|
|
{
|
|
|
- HttpErrorReply ( dData, SPH_HTTP_STATUS_400, tParser.m_sError );
|
|
|
|
|
- return tParser.m_bKeepAlive;
|
|
|
|
|
|
|
+ return ProcessDelete ( m_sQuery.cstr(), tStmt, tDocId, m_iCID, pResult );
|
|
|
}
|
|
}
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class HttpHandler_JsonBulk_c : public HttpHandler_c, public HttpOptionsTraits_c, public HttpJsonInsertTraits_c, public HttpJsonUpdateTraits_c, public HttpJsonDeleteTraits_c
|
|
|
|
|
+{
|
|
|
|
|
+public:
|
|
|
|
|
+ HttpHandler_JsonBulk_c ( const CSphString & sQuery, const OptionsHash_t & tOptions, int iCID, bool bNeedHttpResponse )
|
|
|
|
|
+ : HttpHandler_c ( sQuery, iCID, bNeedHttpResponse )
|
|
|
|
|
+ , HttpOptionsTraits_c ( tOptions )
|
|
|
|
|
+ {}
|
|
|
|
|
|
|
|
- switch ( tParser.m_eEndpoint )
|
|
|
|
|
|
|
+ bool Process () override
|
|
|
|
|
+ {
|
|
|
|
|
+ if ( !m_tOptions.Exists ("Content-Type") )
|
|
|
|
|
+ {
|
|
|
|
|
+ ReportError ( "Content-Type must be set", SPH_HTTP_STATUS_400 );
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ( m_tOptions["Content-Type"].ToLower() != "application/x-ndjson" )
|
|
|
|
|
+ {
|
|
|
|
|
+ ReportError ( "Content-Type must be application/x-ndjson", SPH_HTTP_STATUS_400 );
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ cJSON * pRoot = cJSON_CreateObject();
|
|
|
|
|
+ cJSON * pItems = cJSON_CreateArray();
|
|
|
|
|
+ assert ( pRoot && pItems );
|
|
|
|
|
+ cJSON_AddItemToObject ( pRoot, "items", pItems );
|
|
|
|
|
+
|
|
|
|
|
+ // fixme: we're modifying the original query at this point
|
|
|
|
|
+ char * p = const_cast<char*>(m_sQuery.cstr());
|
|
|
|
|
+
|
|
|
|
|
+ bool bResult = false;
|
|
|
|
|
+ while ( *p )
|
|
|
|
|
+ {
|
|
|
|
|
+ while ( sphIsSpace(*p) )
|
|
|
|
|
+ p++;
|
|
|
|
|
+
|
|
|
|
|
+ char * szStmt = p;
|
|
|
|
|
+ while ( *p && *p!='\r' && *p!='\n' )
|
|
|
|
|
+ p++;
|
|
|
|
|
+
|
|
|
|
|
+ if ( p-szStmt==0 )
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ *p++ = '\0';
|
|
|
|
|
+ SqlStmt_t tStmt;
|
|
|
|
|
+ SphDocID_t tDocId = DOCID_MAX;
|
|
|
|
|
+ CSphString sStmt;
|
|
|
|
|
+ CSphString sError;
|
|
|
|
|
+ CSphString sQuery;
|
|
|
|
|
+ if ( !sphParseJsonStatement ( szStmt, tStmt, sStmt, sQuery, tDocId, sError ) )
|
|
|
|
|
+ {
|
|
|
|
|
+ sError.SetSprintf( "Error parsing json query: %s", sError.cstr() );
|
|
|
|
|
+ ReportError ( sError.cstr(), SPH_HTTP_STATUS_400 );
|
|
|
|
|
+ cJSON_Delete ( pRoot );
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ cJSON * pResult = NULL;
|
|
|
|
|
+ bResult = false;
|
|
|
|
|
+
|
|
|
|
|
+ switch ( tStmt.m_eStmt )
|
|
|
|
|
+ {
|
|
|
|
|
+ case STMT_INSERT:
|
|
|
|
|
+ case STMT_REPLACE:
|
|
|
|
|
+ bResult = ProcessInsert ( tStmt, tDocId, tStmt.m_eStmt==STMT_REPLACE, pResult );
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case STMT_UPDATE:
|
|
|
|
|
+ bResult = ProcessUpdate ( sQuery.cstr(), tStmt, tDocId, m_iCID, pResult );
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case STMT_DELETE:
|
|
|
|
|
+ bResult = ProcessDelete ( sQuery.cstr(), tStmt, tDocId, m_iCID, pResult );
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ default:
|
|
|
|
|
+ ReportError ( "Unknown statement", SPH_HTTP_STATUS_400 );
|
|
|
|
|
+ cJSON_Delete ( pRoot );
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ AddResult ( pItems, sStmt, pResult );
|
|
|
|
|
+
|
|
|
|
|
+ // no further than the first error
|
|
|
|
|
+ if ( !bResult )
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ while ( sphIsSpace(*p) )
|
|
|
|
|
+ p++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ cJSON_AddBoolToObject ( pRoot, "errors", bResult ? 0 : 1 );
|
|
|
|
|
+
|
|
|
|
|
+ CSphString sResult = sphJsonToString ( pRoot );
|
|
|
|
|
+ BuildReply ( sResult, bResult ? SPH_HTTP_STATUS_200 : SPH_HTTP_STATUS_500 );
|
|
|
|
|
+ cJSON_Delete ( pRoot );
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+private:
|
|
|
|
|
+ void AddResult ( cJSON * pRoot, CSphString & sStmt, cJSON * pResult )
|
|
|
|
|
+ {
|
|
|
|
|
+ assert ( pRoot && pResult );
|
|
|
|
|
+
|
|
|
|
|
+ cJSON * pItem = cJSON_CreateObject();
|
|
|
|
|
+ assert ( pItem );
|
|
|
|
|
+ cJSON_AddItemToArray ( pRoot, pItem );
|
|
|
|
|
+
|
|
|
|
|
+ cJSON_AddItemToObject( pItem, sStmt.cstr(), pResult );
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+static HttpHandler_c * CreateHttpHandler ( ESphHttpEndpoint eEndpoint, const CSphString & sQuery, const OptionsHash_t & tOptions, int iCID, bool bNeedHttpResonse )
|
|
|
|
|
+{
|
|
|
|
|
+ switch ( eEndpoint )
|
|
|
{
|
|
{
|
|
|
case SPH_HTTP_ENDPOINT_SEARCH:
|
|
case SPH_HTTP_ENDPOINT_SEARCH:
|
|
|
- HttpHandlerSearch ( ParseQueryPlain, EncodeResultPlain, tParser, iCID, dData );
|
|
|
|
|
- break;
|
|
|
|
|
|
|
+ return new HttpSearchHandler_Plain_c ( sQuery, tOptions, iCID, bNeedHttpResonse );
|
|
|
|
|
|
|
|
case SPH_HTTP_ENDPOINT_SQL:
|
|
case SPH_HTTP_ENDPOINT_SQL:
|
|
|
- HttpHandlerSearch ( ParseQuerySql, EncodeResultPlain, tParser, iCID, dData );
|
|
|
|
|
- break;
|
|
|
|
|
|
|
+ return new HttpSearchHandler_SQL_c ( sQuery, tOptions, iCID, bNeedHttpResonse );
|
|
|
|
|
|
|
|
case SPH_HTTP_ENDPOINT_JSON_SEARCH:
|
|
case SPH_HTTP_ENDPOINT_JSON_SEARCH:
|
|
|
- HttpHandlerSearch ( ParseQueryJson, sphEncodeResultJson, tParser, iCID, dData );
|
|
|
|
|
- break;
|
|
|
|
|
|
|
+ return new HttpHandler_JsonSearch_c ( sQuery, tOptions, iCID, bNeedHttpResonse );
|
|
|
|
|
|
|
|
case SPH_HTTP_ENDPOINT_JSON_INDEX:
|
|
case SPH_HTTP_ENDPOINT_JSON_INDEX:
|
|
|
case SPH_HTTP_ENDPOINT_JSON_CREATE:
|
|
case SPH_HTTP_ENDPOINT_JSON_CREATE:
|
|
|
case SPH_HTTP_ENDPOINT_JSON_INSERT:
|
|
case SPH_HTTP_ENDPOINT_JSON_INSERT:
|
|
|
case SPH_HTTP_ENDPOINT_JSON_REPLACE:
|
|
case SPH_HTTP_ENDPOINT_JSON_REPLACE:
|
|
|
- HttpHandlerInsert ( tParser, dData, tParser.m_eEndpoint==SPH_HTTP_ENDPOINT_JSON_INDEX || tParser.m_eEndpoint==SPH_HTTP_ENDPOINT_JSON_REPLACE );
|
|
|
|
|
- break;
|
|
|
|
|
|
|
+ return new HttpHandler_JsonInsert_c ( sQuery, eEndpoint==SPH_HTTP_ENDPOINT_JSON_INDEX || eEndpoint==SPH_HTTP_ENDPOINT_JSON_REPLACE, bNeedHttpResonse );
|
|
|
|
|
|
|
|
case SPH_HTTP_ENDPOINT_JSON_UPDATE:
|
|
case SPH_HTTP_ENDPOINT_JSON_UPDATE:
|
|
|
- HttpHandlerUpdateDelete ( tParser, iCID, dData, sphParseJsonUpdate, ProcessUpdate );
|
|
|
|
|
- break;
|
|
|
|
|
|
|
+ return new HttpHandler_JsonUpdate_c ( sQuery, iCID, bNeedHttpResonse );
|
|
|
|
|
|
|
|
case SPH_HTTP_ENDPOINT_JSON_DELETE:
|
|
case SPH_HTTP_ENDPOINT_JSON_DELETE:
|
|
|
- HttpHandlerUpdateDelete ( tParser, iCID, dData, sphParseJsonDelete, ProcessDelete );
|
|
|
|
|
- break;
|
|
|
|
|
|
|
+ return new HttpHandler_JsonDelete_c ( sQuery, iCID, bNeedHttpResonse );
|
|
|
|
|
|
|
|
case SPH_HTTP_ENDPOINT_JSON_BULK:
|
|
case SPH_HTTP_ENDPOINT_JSON_BULK:
|
|
|
- HttpHandlerBulk ( tParser, iCID, dData );
|
|
|
|
|
- break;
|
|
|
|
|
|
|
+ return new HttpHandler_JsonBulk_c ( sQuery, tOptions, iCID, bNeedHttpResonse );
|
|
|
|
|
|
|
|
- case SPH_HTTP_ENDPOINT_INDEX:
|
|
|
|
|
- HttpHandlerPage ( true, tParser.m_sInvalidEndpoint, dData );
|
|
|
|
|
|
|
+ default:
|
|
|
break;
|
|
break;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- case SPH_HTTP_ENDPOINT_MISSED:
|
|
|
|
|
- default:
|
|
|
|
|
- HttpHandlerPage ( false, tParser.m_sInvalidEndpoint, dData );
|
|
|
|
|
|
|
+ return nullptr;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+bool sphProcessHttpQuery ( ESphHttpEndpoint eEndpoint, const CSphString & sQuery, const SmallStringHash_T<CSphString> & tOptions, int iCID, CSphVector<BYTE> & dResult, bool bNeedHttpResponse )
|
|
|
|
|
+{
|
|
|
|
|
+ CSphScopedPtr<HttpHandler_c> pHandler ( CreateHttpHandler ( eEndpoint, sQuery, tOptions, iCID, bNeedHttpResponse ) );
|
|
|
|
|
+ if ( !pHandler.Ptr() )
|
|
|
|
|
+ return false;
|
|
|
|
|
+
|
|
|
|
|
+ pHandler->Process();
|
|
|
|
|
+ dResult = std::move ( pHandler->GetResult() );
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+bool sphLoopClientHttp ( const BYTE * pRequest, int iRequestLen, CSphVector<BYTE> & dResult, int iCID )
|
|
|
|
|
+{
|
|
|
|
|
+ HttpRequestParser_c tParser;
|
|
|
|
|
+ if ( !tParser.Parse ( pRequest, iRequestLen ) )
|
|
|
|
|
+ {
|
|
|
|
|
+ HttpErrorReply ( dResult, SPH_HTTP_STATUS_400, tParser.GetError() );
|
|
|
|
|
+ return tParser.GetKeepAlive();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ESphHttpEndpoint eEndpoint = tParser.GetEndpoint();
|
|
|
|
|
+ if ( !sphProcessHttpQuery ( eEndpoint, tParser.GetBody(), tParser.GetOptions(), iCID, dResult, true ) )
|
|
|
|
|
+ {
|
|
|
|
|
+ if ( eEndpoint==SPH_HTTP_ENDPOINT_INDEX )
|
|
|
|
|
+ HttpHandlerIndexPage ( dResult );
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ CSphString sError;
|
|
|
|
|
+ sError.SetSprintf ( "/%s - unsupported endpoint", tParser.GetInvalidEndpoint().cstr() );
|
|
|
|
|
+ HttpErrorReply ( dResult, SPH_HTTP_STATUS_501, sError.cstr() );
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return tParser.m_bKeepAlive;
|
|
|
|
|
|
|
+ return tParser.GetKeepAlive();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
void sphHttpErrorReply ( CSphVector<BYTE> & dData, ESphHttpStatus eCode, const char * szError )
|
|
void sphHttpErrorReply ( CSphVector<BYTE> & dData, ESphHttpStatus eCode, const char * szError )
|
|
|
{
|
|
{
|
|
|
- HttpErrorReply( dData, eCode, "%s", szError );
|
|
|
|
|
|
|
+ HttpErrorReply ( dData, eCode, szError );
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+const char * g_dEndpoints[] = { "index.html", "search", "sql", "json/search", "json/index", "json/create", "json/insert", "json/replace", "json/update", "json/delete", "json/bulk" };
|
|
|
|
|
+STATIC_ASSERT ( sizeof(g_dEndpoints)/sizeof(g_dEndpoints[0])==SPH_HTTP_ENDPOINT_TOTAL, SPH_HTTP_ENDPOINT_SHOULD_BE_SAME_AS_SPH_HTTP_ENDPOINT_TOTAL );
|
|
|
|
|
+
|
|
|
|
|
+ESphHttpEndpoint sphStrToHttpEndpoint ( const CSphString & sEndpoint )
|
|
|
|
|
+{
|
|
|
|
|
+ for ( int i = 0; i < SPH_HTTP_ENDPOINT_TOTAL; i++ )
|
|
|
|
|
+ if ( sEndpoint==g_dEndpoints[i] )
|
|
|
|
|
+ return ESphHttpEndpoint(i);
|
|
|
|
|
+
|
|
|
|
|
+ return SPH_HTTP_ENDPOINT_TOTAL;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+CSphString sphHttpEndpointToStr ( ESphHttpEndpoint eEndpoint )
|
|
|
|
|
+{
|
|
|
|
|
+ assert ( eEndpoint>=SPH_HTTP_ENDPOINT_INDEX && eEndpoint<SPH_HTTP_ENDPOINT_TOTAL );
|
|
|
|
|
+ return g_dEndpoints[eEndpoint];
|
|
|
}
|
|
}
|