QGIS API Documentation 4.1.0-Master (31622b25bb0)
Loading...
Searching...
No Matches
qgsserverogcapihandler.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsserverogcapihandler.cpp - QgsServerOgcApiHandler
3
4 ---------------------
5 begin : 10.7.2019
6 copyright : (C) 2019 by Alessandro Pasotti
7 email : elpaso at itopen dot it
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
18
19#include <nlohmann/json.hpp>
20
21#include "inja/inja.hpp"
22#include "qgsjsonutils.h"
23#include "qgsmessagelog.h"
24#include "qgsproject.h"
25#include "qgsserverapiutils.h"
26#include "qgsserverinterface.h"
27#include "qgsserverresponse.h"
28#include "qgsvectorlayer.h"
29
30#include <QDateTime>
31#include <QDebug>
32#include <QDir>
33#include <QFileInfo>
34#include <QString>
35
36using namespace Qt::StringLiterals;
37
38using namespace nlohmann;
39using namespace inja;
40
41
42QVariantMap QgsServerOgcApiHandler::values( const QgsServerApiContext &context ) const
43{
44 QVariantMap result;
45 const auto constParameters { parameters( context ) };
46 for ( const auto &p : constParameters )
47 {
48 // value() calls the validators and throws an exception if validation fails
49 result[p.name()] = p.value( context );
50 }
51 const auto sanitizedPath { QgsServerOgcApi::sanitizeUrl( context.handlerPath() ).path() };
52 const auto match { path().match( sanitizedPath ) };
53 if ( match.hasMatch() )
54 {
55 const auto constNamed { path().namedCaptureGroups() };
56 // Get named path parameters
57 for ( const auto &name : constNamed )
58 {
59 if ( !name.isEmpty() )
60 result[name] = QUrlQuery( match.captured( name ) ).toString();
61 }
62 }
63 return result;
64}
65
67{
68 //qDebug() << "handler destroyed";
69}
70
72{
73 const auto constContentTypes( contentTypes() );
74 return constContentTypes.size() > 0 ? constContentTypes.first() : QgsServerOgcApi::ContentType::JSON;
75}
76
77QList<QgsServerOgcApi::ContentType> QgsServerOgcApiHandler::contentTypes() const
78{
79 return mContentTypes;
80}
81
83{
84 Q_UNUSED( context )
85 throw QgsServerApiNotImplementedException( u"Subclasses must implement handleRequest"_s );
86}
87
88QString QgsServerOgcApiHandler::contentTypeForAccept( const QString &accept ) const
89{
90 const auto constContentTypes( QgsServerOgcApi::contentTypeMimes() );
91 for ( auto it = constContentTypes.constBegin(); it != constContentTypes.constEnd(); ++it )
92 {
93 const auto constValues = it.value();
94 for ( const auto &value : constValues )
95 {
96 if ( accept.contains( value, Qt::CaseSensitivity::CaseInsensitive ) )
97 {
98 return value;
99 }
100 }
101 }
102 // Log level info because this is not completely unexpected
103 QgsMessageLog::logMessage( u"Content type for accept %1 not found!"_s.arg( accept ), u"Server"_s, Qgis::MessageLevel::Info );
104
105 return QString();
106}
107
108void QgsServerOgcApiHandler::write( json &data, const QgsServerApiContext &context, const json &htmlMetadata ) const
109{
110 const QgsServerOgcApi::ContentType contentType { contentTypeFromRequest( context.request() ) };
111 switch ( contentType )
112 {
114 data["handler"] = schema( context );
115 if ( !htmlMetadata.is_null() )
116 {
117 data["metadata"] = htmlMetadata;
118 }
119 htmlDump( data, context );
120 break;
124 jsonDump( data, context, QgsServerOgcApi::contentTypeMimes().value( contentType ).first() );
125 break;
127 // Not handled yet
128 break;
130 // Handled separately in the handler if supported, so do nothing here
131 break;
132 }
133}
134
135void QgsServerOgcApiHandler::write( QVariant &data, const QgsServerApiContext &context, const QVariantMap &htmlMetadata ) const
136{
137 json j = QgsJsonUtils::jsonFromVariant( data );
138 json jm = QgsJsonUtils::jsonFromVariant( htmlMetadata );
139 QgsServerOgcApiHandler::write( j, context, jm );
140}
141
142std::string QgsServerOgcApiHandler::href( const QgsServerApiContext &context, const QString &extraPath, const QString &extension ) const
143{
144 QUrl url { context.request()->url() };
145 QString urlBasePath { context.matchedPath() };
146 const auto match { path().match( QgsServerOgcApi::sanitizeUrl( context.handlerPath() ).path() ) };
147 if ( match.captured().count() > 0 )
148 {
149 url.setPath( urlBasePath + match.captured( 0 ) );
150 }
151 else
152 {
153 url.setPath( urlBasePath );
154 }
155
156 // Remove any existing extension
157 const auto suffixLength { QFileInfo( url.path() ).suffix().length() };
158 if ( suffixLength > 0 )
159 {
160 auto path { url.path() };
161 path.truncate( path.length() - ( suffixLength + 1 ) );
162 url.setPath( path );
163 }
164
165 // Add extra path
166 url.setPath( url.path() + extraPath );
167
168 // (re-)add extension
169 // JSON is the default anyway so we don't need to add it
170 if ( !extension.isEmpty() )
171 {
172 // Remove trailing slashes if any.
173 QString path { url.path() };
174 while ( path.endsWith( '/' ) )
175 {
176 path.chop( 1 );
177 }
178 url.setPath( path + '.' + extension );
179 }
180 return QgsServerOgcApi::sanitizeUrl( url ).toString( QUrl::FullyEncoded ).toStdString();
181}
182
183void QgsServerOgcApiHandler::jsonDump( json &data, const QgsServerApiContext &context, const QString &contentType ) const
184{
185 // Do not append timestamp to openapi
186 if ( !QgsServerOgcApi::contentTypeMimes().value( QgsServerOgcApi::ContentType::OPENAPI3 ).contains( contentType, Qt::CaseSensitivity::CaseInsensitive ) )
187 {
188 QDateTime time { QDateTime::currentDateTime() };
189 time.setTimeSpec( Qt::TimeSpec::UTC );
190 data["timeStamp"] = time.toString( Qt::DateFormat::ISODate ).toStdString();
191 }
192 context.response()->setStatusCode( 200 );
193 context.response()->setHeader( u"Content-Type"_s, contentType );
194#ifdef QGISDEBUG
195 context.response()->write( data.dump( 2 ) );
196#else
197 context.response()->write( data.dump() );
198#endif
199}
200
202{
203 Q_UNUSED( context )
204 return nullptr;
205}
206
207json QgsServerOgcApiHandler::link( const QgsServerApiContext &context, const QgsServerOgcApi::Rel &linkType, const QgsServerOgcApi::ContentType contentType, const std::string &title ) const
208{
209 json l {
210 { "href", href( context, "/", QgsServerOgcApi::contentTypeToExtension( contentType ) ) },
212 { "type", QgsServerOgcApi::mimeType( contentType ) },
213 { "title", title != "" ? title : linkTitle() },
214 };
215 return l;
216}
217
219 const QgsServerApiContext &context, const QgsServerOgcApi::Rel &linkType, const QgsServerOgcApi::ContentType contentType, const QgsServerOgcApi::Profile &profile, const QString &title
220) const
221{
222 const QString profileStr { profile != QgsServerOgcApi::Profile::NONE ? QgsServerOgcApi::profileToString( profile ) : QString() };
223 QString hrefStr = QString::fromStdString( href( context, "/", QgsServerOgcApi::contentTypeToExtension( contentType ) ) );
224
225 if ( !profileStr.isEmpty() )
226 {
227 hrefStr += u"&profile="_s + profileStr;
228 }
229
230 QString titleStr = !title.isEmpty() ? title : QString::fromStdString( linkTitle() );
231 titleStr.replace( '\\', "\\\\"_L1 );
232 titleStr.replace( '"', "\\\""_L1 );
233
234 QString linkStr = u"<%1>; rel=\"%2\"; title=\"%3\"; type=\"%4\""_s.arg(
235 QString::fromStdString( href( context, "/", QgsServerOgcApi::contentTypeToExtension( contentType ) ) ),
236 QString::fromStdString( QgsServerOgcApi::relToString( linkType ) ),
237 titleStr,
238 QString::fromStdString( QgsServerOgcApi::mimeType( contentType ) )
239 );
240
241 if ( !profileStr.isEmpty() )
242 {
243 linkStr += QString( "; profile=\"%1\"" ).arg( profileStr );
244 }
245
246 return linkStr;
247}
248
250{
251 const QgsServerOgcApi::ContentType currentCt { contentTypeFromRequest( context.request() ) };
252 json links = json::array();
253 const QList<QgsServerOgcApi::ContentType> constCts { contentTypes() };
254 for ( const auto &ct : constCts )
255 {
256 links.push_back( link( context, ( ct == currentCt ? QgsServerOgcApi::Rel::self : QgsServerOgcApi::Rel::alternate ), ct, linkTitle() + " as " + QgsServerOgcApi::contentTypeToStdString( ct ) ) );
257 }
258 return links;
259}
260
262{
263 if ( !context.project() )
264 {
265 throw QgsServerApiImproperlyConfiguredException( u"Project is invalid or undefined"_s );
266 }
267 // Check collectionId
268 const QRegularExpressionMatch match { path().match( context.request()->url().path() ) };
269 if ( !match.hasMatch() )
270 {
271 throw QgsServerApiNotFoundError( u"Collection was not found"_s );
272 }
273 const QString collectionId { match.captured( u"collectionId"_s ) };
274 // May throw if not found
275 return layerFromCollectionId( context, collectionId );
276}
277
278const QString QgsServerOgcApiHandler::staticPath( const QgsServerApiContext &context ) const
279{
280 // resources/server/api + /static
281 return context.serverInterface()->serverSettings()->apiResourcesDirectory() + u"/ogc/static"_s;
282}
283
285{
286 // resources/server/api + /ogc/templates/ + apiRootPath() + / + operationId() + .html
287 QString path { context.serverInterface()->serverSettings()->apiResourcesDirectory() };
288 path += "/ogc/templates"_L1;
289 path += context.apiRootPath();
290 path += '/';
291 path += QString::fromStdString( operationId() );
292 path += ".html"_L1;
293 return path;
294}
295
296void QgsServerOgcApiHandler::htmlDump( const json &data, const QgsServerApiContext &context ) const
297{
298 context.response()->setHeader( u"Content-Type"_s, u"text/html"_s );
299 auto path { templatePath( context ) };
300 if ( !QFile::exists( path ) )
301 {
302 QgsMessageLog::logMessage( u"Template not found error: %1"_s.arg( path ), u"Server"_s, Qgis::MessageLevel::Critical );
303 throw QgsServerApiBadRequestException( u"Template not found: %1"_s.arg( QFileInfo( path ).fileName() ) );
304 }
305
306 QFile f( path );
307 if ( !f.open( QFile::ReadOnly | QFile::Text ) )
308 {
309 QgsMessageLog::logMessage( u"Could not open template file: %1"_s.arg( path ), u"Server"_s, Qgis::MessageLevel::Critical );
310 throw QgsServerApiInternalServerError( u"Could not open template file: %1"_s.arg( QFileInfo( path ).fileName() ) );
311 }
312
313 try
314 {
315 // Get the template directory and the file name
316 QFileInfo pathInfo { path };
317 Environment env { QString( pathInfo.dir().path() + QDir::separator() ).toStdString() };
318
319 // For template debugging:
320 env.add_callback( "json_dump", 0, [data]( Arguments & ) { return data.dump(); } );
321
322 // Path manipulation: appends a directory path to the current url
323 env.add_callback( "path_append", 1, [context]( Arguments &args ) {
324 auto url { context.request()->url() };
325 QFileInfo fi { url.path() };
326 auto suffix { fi.suffix() };
327 auto fName { fi.filePath() };
328 if ( !suffix.isEmpty() )
329 {
330 fName.chop( suffix.length() + 1 );
331 }
332 // Chop any ending slashes
333 while ( fName.endsWith( '/' ) )
334 {
335 fName.chop( 1 );
336 }
337 fName += '/' + QString::fromStdString( args.at( 0 )->get<std::string>() );
338 if ( !suffix.isEmpty() )
339 {
340 fName += '.' + suffix;
341 }
342 fi.setFile( fName );
343 url.setPath( fi.filePath() );
344 return url.toString().toStdString();
345 } );
346
347 // Path manipulation: removes the specified number of directory components from the current url path
348 env.add_callback( "path_chomp", 1, []( Arguments &args ) {
349 QUrl url { QString::fromStdString( args.at( 0 )->get<std::string>() ) };
350 QFileInfo fi { url.path() };
351 auto suffix { fi.suffix() };
352 auto fName { fi.filePath() };
353 fName.chop( suffix.length() + 1 );
354 // Chomp last segment
355 const thread_local QRegularExpression segmentRx( R"raw(\/[^/]+$)raw" );
356 fName = fName.replace( segmentRx, QString() );
357 if ( !suffix.isEmpty() )
358 {
359 fName += '.' + suffix;
360 }
361 fi.setFile( fName );
362 url.setPath( fi.filePath() );
363 return url.toString().toStdString();
364 } );
365
366 // Returns filtered links from a link list
367 // links_filter( <links>, <key>, <value> )
368 env.add_callback( "links_filter", 3, []( Arguments &args ) {
369 json links = args.at( 0 )->get<json>();
370 if ( !links.is_array() )
371 {
372 links = json::array();
373 }
374 std::string key { args.at( 1 )->get<std::string>() };
375 std::string value { args.at( 2 )->get<std::string>() };
376 json result = json::array();
377 for ( const auto &l : links )
378 {
379 if ( l[key] == value )
380 {
381 result.push_back( l );
382 }
383 }
384 return result;
385 } );
386
387 // Returns a short name from content types
388 env.add_callback( "content_type_name", 1, []( Arguments &args ) {
389 const QgsServerOgcApi::ContentType ct { QgsServerOgcApi::contentTypeFromExtension( args.at( 0 )->get<std::string>() ) };
391 } );
392
393 // Replace newlines with <br>
394 env.add_callback( "nl2br", 1, []( Arguments &args ) {
395 QString text { QString::fromStdString( args.at( 0 )->get<std::string>() ) };
396 return text.replace( '\n', "<br>"_L1 ).toStdString();
397 } );
398
399
400 // Returns a list of parameter component data from components -> parameters by ref name
401 // parameter( <ref object> )
402 env.add_callback( "component_parameter", 1, [data]( Arguments &args ) {
403 json ret = json::array();
404 json ref = args.at( 0 )->get<json>();
405 if ( !ref.is_object() )
406 {
407 return ret;
408 }
409 try
410 {
411 QString name = QString::fromStdString( ref["$ref"] );
412 name = name.split( '/' ).last();
413 ret.push_back( data["components"]["parameters"][name.toStdString()] );
414 }
415 catch ( std::exception & )
416 {
417 // Do nothing
418 }
419 return ret;
420 } );
421
422
423 // Static: returns the full URL to the specified static <path>
424 env.add_callback( "static", 1, [context]( Arguments &args ) {
425 auto asset( args.at( 0 )->get<std::string>() );
426 QString matchedPath { context.matchedPath() };
427 // If its the root path '/' strip it!
428 if ( matchedPath == '/' )
429 {
430 matchedPath.clear();
431 }
432 return matchedPath.toStdString() + "/static/" + asset;
433 } );
434
435
436 // Returns true if a string begins with the provided string prefix, false otherwise
437 env.add_callback( "starts_with", 2, []( Arguments &args ) { return string_view::starts_with( args.at( 0 )->get<std::string_view>(), args.at( 1 )->get<std::string_view>() ); } );
438
439 // Returns "null" string if object is null else string object representation
440 env.add_callback( "if_nullptr_null_str", 1, []( Arguments &args ) {
441 json jsonValue = args.at( 0 )->get<json>();
442 std::string out;
443 switch ( jsonValue.type() )
444 {
445 // avoid escaping string value
446 case json::value_t::string:
447 out = jsonValue.get<std::string>();
448 break;
449
450 case json::value_t::array:
451 case json::value_t::object:
452 if ( jsonValue.is_null() )
453 {
454 out = "null";
455 }
456 else
457 {
458 out = jsonValue.dump();
459 }
460
461 break;
462
463 // use dump() for all other value types
464 default:
465 out = jsonValue.dump();
466 }
467 return out;
468 } );
469
470 context.response()->write( env.render_file( pathInfo.fileName().toStdString(), data ) );
471 }
472 catch ( std::exception &e )
473 {
474 QgsMessageLog::logMessage( u"Error parsing template file: %1 - %2"_s.arg( path, e.what() ), u"Server"_s, Qgis::MessageLevel::Critical );
475 throw QgsServerApiInternalServerError( u"Error parsing template file: %1"_s.arg( e.what() ) );
476 }
477}
478
480{
481 // Fallback to default
483 bool found { false };
484 // First file extension ...
485 const QString extension { QFileInfo( request->url().path() ).suffix().toUpper() };
486 if ( !extension.isEmpty() )
487 {
488 static QMetaEnum metaEnum { QMetaEnum::fromType<QgsServerOgcApi::ContentType>() };
489 bool ok { false };
490 const int ct { metaEnum.keyToValue( extension.toLocal8Bit().constData(), &ok ) };
491 if ( ok )
492 {
493 result = static_cast<QgsServerOgcApi::ContentType>( ct );
494 found = true;
495 }
496 else
497 {
498 // Hardcoded aliases
499#if 0
500 // This not supported yet but I am leaving it here because
501 // I am very optimistic that it will be supported soon!
502
503 if ( ( extension.compare( u"JSONFG"_s, Qt::CaseSensitivity::CaseInsensitive ) == 0 ) || ( extension.compare( u"JSONFG-PLUS"_s, Qt::CaseSensitivity::CaseInsensitive ) == 0 ) )
504 {
506 found = true;
507 }
508 else
509#endif
510 if ( extension.compare( u"FGB"_s, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
511 {
513 found = true;
514 }
515 else
516 {
517 QgsMessageLog::logMessage( u"The client requested an unsupported extension: %1"_s.arg( extension ), u"Server"_s, Qgis::MessageLevel::Warning );
518 }
519 }
520 }
521 // ... then "Accept"
522 const QString accept { request->header( u"Accept"_s ) };
523 if ( !found && !accept.isEmpty() )
524 {
525 const QString ctFromAccept { contentTypeForAccept( accept ) };
526 if ( !ctFromAccept.isEmpty() )
527 {
528 const auto constContentTypes( QgsServerOgcApi::contentTypeMimes() );
529 auto it = constContentTypes.constBegin();
530 while ( !found && it != constContentTypes.constEnd() )
531 {
532 int idx = it.value().indexOf( ctFromAccept );
533 if ( idx >= 0 )
534 {
535 found = true;
536 result = it.key();
537 }
538 it++;
539 }
540 }
541 else
542 {
543 QgsMessageLog::logMessage( u"The client requested an unsupported content type in Accept header: %1"_s.arg( accept ), u"Server"_s, Qgis::MessageLevel::Warning );
544 }
545 }
546 // Validation: check if the requested content type (or an alias) is supported by the handler
547 if ( !contentTypes().contains( result ) )
548 {
549 // Check aliases
550 bool found { false };
551 if ( QgsServerOgcApi::contentTypeAliases().contains( result ) )
552 {
553 const QList<QgsServerOgcApi::ContentType> constCt { contentTypes() };
554 for ( const auto &ct : constCt )
555 {
556 if ( QgsServerOgcApi::contentTypeAliases()[result].contains( ct ) )
557 {
558 result = ct;
559 found = true;
560 break;
561 }
562 }
563 }
564
565 if ( !found )
566 {
567 QgsMessageLog::logMessage( u"Unsupported Content-Type: %1"_s.arg( QgsServerOgcApi::contentTypeToString( result ) ), u"Server"_s, Qgis::MessageLevel::Info );
568 throw QgsServerApiBadRequestException( u"Unsupported Content-Type: %1"_s.arg( QgsServerOgcApi::contentTypeToString( result ) ) );
569 }
570 }
571 return result;
572}
573
574QString QgsServerOgcApiHandler::parentLink( const QUrl &url, int levels )
575{
576 QString path { url.path() };
577 const QFileInfo fi { path };
578 const QString suffix { fi.suffix() };
579 if ( !suffix.isEmpty() )
580 {
581 path.chop( suffix.length() + 1 );
582 }
583 while ( path.endsWith( '/' ) )
584 {
585 path.chop( 1 );
586 }
587 const thread_local QRegularExpression re( R"raw(\/[^/]+$)raw" );
588 for ( int i = 0; i < levels; i++ )
589 {
590 path = path.replace( re, QString() );
591 }
592 QUrl result( url );
593 QUrlQuery query( result );
594 QList<QPair<QString, QString>> qi;
595 const auto constItems { query.queryItems() };
596 for ( const auto &i : constItems )
597 {
598 if ( i.first.compare( u"MAP"_s, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
599 {
600 qi.push_back( i );
601 }
602 }
603 // Make sure the parent link ends with a slash
604 if ( !path.endsWith( '/' ) )
605 {
606 path.append( '/' );
607 }
608 QUrlQuery resultQuery;
609 resultQuery.setQueryItems( qi );
610 result.setQuery( resultQuery );
611 result.setPath( path );
612 return result.toString();
613}
614
616{
617 const auto mapLayers { context.project()->mapLayersByShortName<QgsVectorLayer *>( collectionId ) };
618 if ( mapLayers.count() != 1 )
619 {
620 throw QgsServerApiNotFoundError( u"Collection with given id (%1) was not found or multiple matches were found"_s.arg( collectionId ) );
621 }
622 return mapLayers.first();
623}
624
626{
627 static json defRes
628 = { { "description", "An error occurred." }, { "content", { { "application/json", { { "schema", { { "$ref", "#/components/schemas/exception" } } } } }, { "text/html", { { "schema", { { "type", "string" } } } } } } } };
629 return defRes;
630}
631
636
638{
639 mContentTypes.clear();
640 for ( const int &i : std::as_const( contentTypes ) )
641 {
642 mContentTypes.push_back( static_cast<QgsServerOgcApi::ContentType>( i ) );
643 }
644}
645
646void QgsServerOgcApiHandler::setContentTypes( const QList<QgsServerOgcApi::ContentType> &contentTypes )
647{
648 mContentTypes = contentTypes;
649}
@ Warning
Warning message.
Definition qgis.h:162
@ Critical
Critical/error message.
Definition qgis.h:163
@ Info
Information message.
Definition qgis.h:161
static json jsonFromVariant(const QVariant &v)
Converts a QVariant v to a json object.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
QList< QgsMapLayer * > mapLayersByShortName(const QString &shortName) const
Retrieves a list of matching registered layers by layer shortName.
Bad request error API exception.
Encapsulates the resources for a particular client request.
const QgsProject * project() const
Returns the (possibly NULL) project.
QgsServerResponse * response() const
Returns the server response object.
QString handlerPath() const
Returns the handler component of the URL path, i.e.
const QgsServerRequest * request() const
Returns the server request object.
QgsServerInterface * serverInterface() const
Returns the server interface.
QString apiRootPath() const
Returns the API root path.
const QString matchedPath() const
Returns the initial part of the incoming request URL path that matches the API root path.
Raised when a configuration error on the server prevents to serve the request, which would be valid o...
Internal server error API exception.
Not found error API exception.
Raised when the client requested a method that is not yet implemented.
virtual QgsServerSettings * serverSettings()=0
Returns the server settings.
QString headerLink(const QgsServerApiContext &context, const QgsServerOgcApi::Rel &linkType=QgsServerOgcApi::Rel::self, const QgsServerOgcApi::ContentType contentType=QgsServerOgcApi::ContentType::JSON, const QgsServerOgcApi::Profile &profile=QgsServerOgcApi::Profile::NONE, const QString &title="") const
Builds and returns a header link to the resource.
void jsonDump(json &data, const QgsServerApiContext &context, const QString &contentType=u"application/json"_s) const
Writes data to the context response stream as JSON (indented if debug is active), an optional content...
std::string href(const QgsServerApiContext &context, const QString &extraPath=QString(), const QString &extension=QString()) const
Returns an URL to self, to be used for links to the current resources and as a base for constructing ...
virtual const QString templatePath(const QgsServerApiContext &context) const
Returns the HTML template path for the handler in the given context.
virtual const QString staticPath(const QgsServerApiContext &context) const
Returns the absolute path to the base directory where static resources for this handler are stored in...
virtual QVariantMap values(const QgsServerApiContext &context) const
Analyzes the incoming request context and returns the validated parameter map, throws QgsServerApiBad...
json jsonTags() const
Returns tags as JSON.
void htmlDump(const json &data, const QgsServerApiContext &context) const
Writes data as HTML to the response stream in context using a template.
virtual QgsServerOgcApi::ContentType defaultContentType() const
Returns the default response content type in case the client did not specifically ask for any particu...
QgsServerOgcApi::ContentType contentTypeFromRequest(const QgsServerRequest *request) const
Returns the content type from the request.
virtual QgsServerOgcApi::Rel linkType() const =0
Main role for the resource link.
virtual json schema(const QgsServerApiContext &context) const
Returns handler information from the context for the OPENAPI description (id, description and other m...
virtual QStringList tags() const
Tags.
void setContentTypes(const QList< QgsServerOgcApi::ContentType > &contentTypes)
Set the content types to contentTypes.
void write(json &data, const QgsServerApiContext &context, const json &htmlMetadata=nullptr) const
Writes data to the context response stream, content-type is calculated from the context request,...
QList< QgsServerOgcApi::ContentType > contentTypes() const
Returns the list of content types this handler can serve, default to JSON and HTML.
virtual std::string linkTitle() const =0
Title for the handler link.
virtual QList< QgsServerQueryStringParameter > parameters(const QgsServerApiContext &context) const
Returns a list of query string parameters.
virtual std::string operationId() const =0
Returns the operation id for template file names and other internal references.
static json defaultResponse()
Returns the defaultResponse as JSON.
QgsVectorLayer * layerFromContext(const QgsServerApiContext &context) const
Returns a vector layer instance from the "collectionId" parameter of the path in the given context,...
QString contentTypeForAccept(const QString &accept) const
Looks for the first ContentType match in the accept header and returns its mime type,...
virtual void handleRequest(const QgsServerApiContext &context) const
Handles the request within its context.
json link(const QgsServerApiContext &context, const QgsServerOgcApi::Rel &linkType=QgsServerOgcApi::Rel::self, const QgsServerOgcApi::ContentType contentType=QgsServerOgcApi::ContentType::JSON, const std::string &title="") const
Builds and returns a link to the resource.
json links(const QgsServerApiContext &context) const
Returns all the links for the given request context.
static QString parentLink(const QUrl &url, int levels=1)
Returns a link to the parent page up to levels in the HTML hierarchy from the given url,...
virtual QRegularExpression path() const =0
URL pattern for this handler, named capture group are automatically extracted and returned by values(...
void setContentTypesInt(const QList< int > &contentTypes)
Set the content types to contentTypes.
static QgsVectorLayer * layerFromCollectionId(const QgsServerApiContext &context, const QString &collectionId)
Returns a vector layer from the collectionId in the given context.
static QUrl sanitizeUrl(const QUrl &url)
Returns a sanitized url with extra slashes removed and the path URL component that always starts with...
static QString contentTypeToExtension(const QgsServerOgcApi::ContentType &ct)
Returns the file extension for a ct (Content-Type).
static const QMap< QgsServerOgcApi::ContentType, QStringList > contentTypeMimes()
Returns a map of contentType => list of mime types.
ContentType
Media types used for content negotiation, insert more specific first.
@ OPENAPI3
"application/openapi+json;version=3.0"
@ FLATGEOBUF
"application/flatgeobuf"
static QString contentTypeToString(const QgsServerOgcApi::ContentType &ct)
Returns the string representation of a ct (Content-Type) attribute.
static QString profileToString(const QgsServerOgcApi::Profile &profile)
Returns a string representation of the profile.
Rel
Rel link types.
@ alternate
Refers to a substitute for this context.
@ self
Conveys an identifier for the link’s context.
static std::string contentTypeToStdString(const QgsServerOgcApi::ContentType &ct)
Returns the string representation of a ct (Content-Type) attribute.
static std::string mimeType(const QgsServerOgcApi::ContentType &contentType)
Returns the mime-type for the contentType or an empty string if not found.
Profile
JSON profile.
static std::string relToString(const QgsServerOgcApi::Rel &rel)
Returns the string representation of rel attribute.
static const QHash< QgsServerOgcApi::ContentType, QList< QgsServerOgcApi::ContentType > > contentTypeAliases()
Returns contentType specializations (e.g.
static QgsServerOgcApi::ContentType contentTypeFromExtension(const std::string &extension)
Returns the Content-Type value corresponding to extension.
Defines requests passed to QgsService classes.
virtual QString header(const QString &name) const
Returns the header value.
QUrl url() const
Returns the request URL as seen by QGIS server.
virtual void write(const QString &data)
Write string This is a convenient method that will write directly to the underlying I/O device.
virtual void setHeader(const QString &key, const QString &value)=0
Set a single header value replacing any existing value(s) for the same key.
virtual void setStatusCode(int code)=0
Set the http status code.
QString apiResourcesDirectory() const
Returns the server-wide base directory where HTML templates and static assets (e.g.
Represents a vector layer which manages a vector based dataset.