QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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 
17 #include <QDateTime>
18 #include <QFileInfo>
19 #include <QDir>
20 #include <QDebug>
21 
22 #include "qgsmessagelog.h"
23 #include "qgsproject.h"
24 #include "qgsjsonutils.h"
25 #include "qgsvectorlayer.h"
26 
27 #include "qgsserverogcapihandler.h"
28 #include "qgsserverapiutils.h"
29 #include "qgsserverresponse.h"
30 #include "qgsserverinterface.h"
31 
32 #include "nlohmann/json.hpp"
33 #include "inja/inja.hpp"
34 
35 using namespace nlohmann;
36 using namespace inja;
37 
38 
39 
40 QVariantMap QgsServerOgcApiHandler::values( const QgsServerApiContext &context ) const
41 {
42  QVariantMap result ;
43  const auto constParameters { parameters( context ) };
44  for ( const auto &p : constParameters )
45  {
46  // value() calls the validators and throws an exception if validation fails
47  result[p.name()] = p.value( context );
48  }
49  const auto sanitizedPath { QgsServerOgcApi::sanitizeUrl( context.handlerPath( ) ).path() };
50  const auto match { path().match( sanitizedPath ) };
51  if ( match.hasMatch() )
52  {
53  const auto constNamed { path().namedCaptureGroups() };
54  // Get named path parameters
55  for ( const auto &name : constNamed )
56  {
57  if ( ! name.isEmpty() )
58  result[name] = QUrlQuery( match.captured( name ) ).toString() ;
59  }
60  }
61  return result;
62 }
63 
65 {
66  //qDebug() << "handler destroyed";
67 }
68 
70 {
71  const auto constContentTypes( contentTypes() );
72  return constContentTypes.size() > 0 ? constContentTypes.first() : QgsServerOgcApi::ContentType::JSON;
73 }
74 
75 QList<QgsServerOgcApi::ContentType> QgsServerOgcApiHandler::contentTypes() const
76 {
77  return mContentTypes;
78 }
79 
81 {
82  Q_UNUSED( context )
83  throw QgsServerApiNotImplementedException( QStringLiteral( "Subclasses must implement handleRequest" ) );
84 }
85 
86 QString QgsServerOgcApiHandler::contentTypeForAccept( const QString &accept ) const
87 {
88  const auto constContentTypes( QgsServerOgcApi::contentTypeMimes() );
89  for ( auto it = constContentTypes.constBegin();
90  it != constContentTypes.constEnd(); ++it )
91  {
92  const auto constValues = it.value();
93  for ( const auto &value : constValues )
94  {
95  if ( accept.contains( value, Qt::CaseSensitivity::CaseInsensitive ) )
96  {
97  return value;
98  }
99  }
100  }
101  // Log level info because this is not completely unexpected
102  QgsMessageLog::logMessage( QStringLiteral( "Content type for accept %1 not found!" ).arg( accept ),
103  QStringLiteral( "Server" ),
104  Qgis::MessageLevel::Info );
105 
106  return QString();
107 }
108 
109 void QgsServerOgcApiHandler::write( json &data, const QgsServerApiContext &context, const json &htmlMetadata ) const
110 {
111  const QgsServerOgcApi::ContentType contentType { contentTypeFromRequest( context.request() ) };
112  switch ( contentType )
113  {
114  case QgsServerOgcApi::ContentType::HTML:
115  data["handler"] = schema( context );
116  if ( ! htmlMetadata.is_null() )
117  {
118  data["metadata"] = htmlMetadata;
119  }
120  htmlDump( data, context );
121  break;
122  case QgsServerOgcApi::ContentType::GEOJSON:
123  case QgsServerOgcApi::ContentType::JSON:
124  case QgsServerOgcApi::ContentType::OPENAPI3:
125  jsonDump( data, context, QgsServerOgcApi::contentTypeMimes().value( contentType ).first() );
126  break;
127  case QgsServerOgcApi::ContentType::XML:
128  // Not handled yet
129  break;
130  }
131 }
132 
133 void QgsServerOgcApiHandler::write( QVariant &data, const QgsServerApiContext &context, const QVariantMap &htmlMetadata ) const
134 {
135  json j = QgsJsonUtils::jsonFromVariant( data );
136  json jm = QgsJsonUtils::jsonFromVariant( htmlMetadata );
137  QgsServerOgcApiHandler::write( j, context, jm );
138 }
139 
140 std::string QgsServerOgcApiHandler::href( const QgsServerApiContext &context, const QString &extraPath, const QString &extension ) const
141 {
142  QUrl url { context.request()->url() };
143  QString urlBasePath { context.matchedPath() };
144  const auto match { path().match( QgsServerOgcApi::sanitizeUrl( context.handlerPath( ) ).path( ) ) };
145  if ( match.captured().count() > 0 )
146  {
147  url.setPath( urlBasePath + match.captured( 0 ) );
148  }
149  else
150  {
151  url.setPath( urlBasePath );
152  }
153 
154  // Remove any existing extension
155  const auto suffixLength { QFileInfo( url.path() ).suffix().length() };
156  if ( suffixLength > 0 )
157  {
158  auto path {url.path()};
159  path.truncate( path.length() - ( suffixLength + 1 ) );
160  url.setPath( path );
161  }
162 
163  // Add extra path
164  url.setPath( url.path() + extraPath );
165 
166  // (re-)add extension
167  // JSON is the default anyway so we don't need to add it
168  if ( ! extension.isEmpty() )
169  {
170  // Remove trailing slashes if any.
171  QString path { url.path() };
172  while ( path.endsWith( '/' ) )
173  {
174  path.chop( 1 );
175  }
176  url.setPath( path + '.' + extension );
177  }
178  return QgsServerOgcApi::sanitizeUrl( url ).toString( QUrl::FullyEncoded ).toStdString();
179 
180 }
181 
182 void QgsServerOgcApiHandler::jsonDump( json &data, const QgsServerApiContext &context, const QString &contentType ) const
183 {
184  // Do not append timestamp to openapi
185  if ( ! QgsServerOgcApi::contentTypeMimes().value( QgsServerOgcApi::ContentType::OPENAPI3 ).contains( contentType, Qt::CaseSensitivity::CaseInsensitive ) )
186  {
187  QDateTime time { QDateTime::currentDateTime() };
188  time.setTimeSpec( Qt::TimeSpec::UTC );
189  data["timeStamp"] = time.toString( Qt::DateFormat::ISODate ).toStdString() ;
190  }
191  context.response()->setStatusCode( 200 );
192  context.response()->setHeader( QStringLiteral( "Content-Type" ), contentType );
193 #ifdef QGISDEBUG
194  context.response()->write( data.dump( 2 ) );
195 #else
196  context.response()->write( data.dump( ) );
197 #endif
198 }
199 
201 {
202  Q_UNUSED( context )
203  return nullptr;
204 }
205 
206 json QgsServerOgcApiHandler::link( const QgsServerApiContext &context, const QgsServerOgcApi::Rel &linkType, const QgsServerOgcApi::ContentType contentType, const std::string &title ) const
207 {
208  json l
209  {
210  {
211  "href", href( context, "/",
213  },
214  { "rel", QgsServerOgcApi::relToString( linkType ) },
215  { "type", QgsServerOgcApi::mimeType( contentType ) },
216  { "title", title != "" ? title : linkTitle() },
217  };
218  return l;
219 }
220 
222 {
223  const QgsServerOgcApi::ContentType currentCt { contentTypeFromRequest( context.request() ) };
224  json links = json::array();
225  const QList<QgsServerOgcApi::ContentType> constCts { contentTypes() };
226  for ( const auto &ct : constCts )
227  {
228  links.push_back( link( context, ( ct == currentCt ? QgsServerOgcApi::Rel::self :
229  QgsServerOgcApi::Rel::alternate ), ct,
230  linkTitle() + " as " + QgsServerOgcApi::contentTypeToStdString( ct ) ) );
231  }
232  return links;
233 }
234 
236 {
237  if ( ! context.project() )
238  {
239  throw QgsServerApiImproperlyConfiguredException( QStringLiteral( "Project is invalid or undefined" ) );
240  }
241  // Check collectionId
242  const QRegularExpressionMatch match { path().match( context.request()->url().path( ) ) };
243  if ( ! match.hasMatch() )
244  {
245  throw QgsServerApiNotFoundError( QStringLiteral( "Collection was not found" ) );
246  }
247  const QString collectionId { match.captured( QStringLiteral( "collectionId" ) ) };
248  // May throw if not found
249  return layerFromCollectionId( context, collectionId );
250 
251 }
252 
253 const QString QgsServerOgcApiHandler::staticPath( const QgsServerApiContext &context ) const
254 {
255  // resources/server/api + /static
256  return context.serverInterface()->serverSettings()->apiResourcesDirectory() + QStringLiteral( "/ogc/static" );
257 }
258 
259 const QString QgsServerOgcApiHandler::templatePath( const QgsServerApiContext &context ) const
260 {
261  // resources/server/api + /ogc/templates/ + operationId + .html
262  QString path { context.serverInterface()->serverSettings()->apiResourcesDirectory() };
263  path += QLatin1String( "/ogc/templates" );
264  path += context.apiRootPath();
265  path += '/';
266  path += QString::fromStdString( operationId() );
267  path += QLatin1String( ".html" );
268  return path;
269 }
270 
271 
272 void QgsServerOgcApiHandler::htmlDump( const json &data, const QgsServerApiContext &context ) const
273 {
274  context.response()->setHeader( QStringLiteral( "Content-Type" ), QStringLiteral( "text/html" ) );
275  auto path { templatePath( context ) };
276  if ( ! QFile::exists( path ) )
277  {
278  QgsMessageLog::logMessage( QStringLiteral( "Template not found error: %1" ).arg( path ), QStringLiteral( "Server" ), Qgis::MessageLevel::Critical );
279  throw QgsServerApiBadRequestException( QStringLiteral( "Template not found: %1" ).arg( QFileInfo( path ).fileName() ) );
280  }
281 
282  QFile f( path );
283  if ( ! f.open( QFile::ReadOnly | QFile::Text ) )
284  {
285  QgsMessageLog::logMessage( QStringLiteral( "Could not open template file: %1" ).arg( path ), QStringLiteral( "Server" ), Qgis::MessageLevel::Critical );
286  throw QgsServerApiInternalServerError( QStringLiteral( "Could not open template file: %1" ).arg( QFileInfo( path ).fileName() ) );
287  }
288 
289  try
290  {
291  // Get the template directory and the file name
292  QFileInfo pathInfo { path };
293  Environment env { QString( pathInfo.dir().path() + QDir::separator() ).toStdString() };
294 
295  // For template debugging:
296  env.add_callback( "json_dump", 0, [ = ]( Arguments & )
297  {
298  return data.dump();
299  } );
300 
301  // Path manipulation: appends a directory path to the current url
302  env.add_callback( "path_append", 1, [ = ]( Arguments & args )
303  {
304  auto url { context.request()->url() };
305  QFileInfo fi{ url.path() };
306  auto suffix { fi.suffix() };
307  auto fName { fi.filePath()};
308  if ( !suffix.isEmpty() )
309  {
310  fName.chop( suffix.length() + 1 );
311  }
312  // Chop any ending slashes
313  while ( fName.endsWith( '/' ) )
314  {
315  fName.chop( 1 );
316  }
317  fName += '/' + QString::fromStdString( args.at( 0 )->get<std::string>( ) );
318  if ( !suffix.isEmpty() )
319  {
320  fName += '.' + suffix;
321  }
322  fi.setFile( fName );
323  url.setPath( fi.filePath() );
324  return url.toString().toStdString();
325  } );
326 
327  // Path manipulation: removes the specified number of directory components from the current url path
328  env.add_callback( "path_chomp", 1, [ = ]( Arguments & args )
329  {
330  QUrl url { QString::fromStdString( args.at( 0 )->get<std::string>( ) ) };
331  QFileInfo fi{ url.path() };
332  auto suffix { fi.suffix() };
333  auto fName { fi.filePath()};
334  fName.chop( suffix.length() + 1 );
335  // Chomp last segment
336  fName = fName.replace( QRegularExpression( R"raw(\/[^/]+$)raw" ), QString() );
337  if ( !suffix.isEmpty() )
338  {
339  fName += '.' + suffix;
340  }
341  fi.setFile( fName );
342  url.setPath( fi.filePath() );
343  return url.toString().toStdString();
344  } );
345 
346  // Returns filtered links from a link list
347  // links_filter( <links>, <key>, <value> )
348  env.add_callback( "links_filter", 3, [ = ]( Arguments & args )
349  {
350  json links = args.at( 0 )->get<json>( );
351  if ( ! links.is_array() )
352  {
353  links = json::array();
354  }
355  std::string key { args.at( 1 )->get<std::string>( ) };
356  std::string value { args.at( 2 )->get<std::string>( ) };
357  json result = json::array();
358  for ( const auto &l : links )
359  {
360  if ( l[key] == value )
361  {
362  result.push_back( l );
363  }
364  }
365  return result;
366  } );
367 
368  // Returns a short name from content types
369  env.add_callback( "content_type_name", 1, [ = ]( Arguments & args )
370  {
371  const QgsServerOgcApi::ContentType ct { QgsServerOgcApi::contenTypeFromExtension( args.at( 0 )->get<std::string>( ) ) };
373  } );
374 
375  // Replace newlines with <br>
376  env.add_callback( "nl2br", 1, [ = ]( Arguments & args )
377  {
378  QString text { QString::fromStdString( args.at( 0 )->get<std::string>( ) ) };
379  return text.replace( '\n', QLatin1String( "<br>" ) ).toStdString();
380  } );
381 
382 
383  // Returns a list of parameter component data from components -> parameters by ref name
384  // parameter( <ref object> )
385  env.add_callback( "component_parameter", 1, [ = ]( Arguments & args )
386  {
387  json ret = json::array();
388  json ref = args.at( 0 )->get<json>( );
389  if ( ! ref.is_object() )
390  {
391  return ret;
392  }
393  try
394  {
395  QString name = QString::fromStdString( ref["$ref"] );
396  name = name.split( '/' ).last();
397  ret.push_back( data["components"]["parameters"][name.toStdString()] );
398  }
399  catch ( std::exception & )
400  {
401  // Do nothing
402  }
403  return ret;
404  } );
405 
406 
407  // Static: returns the full URL to the specified static <path>
408  env.add_callback( "static", 1, [ = ]( Arguments & args )
409  {
410  auto asset( args.at( 0 )->get<std::string>( ) );
411  QString matchedPath { context.matchedPath() };
412  // If its the root path '/' strip it!
413  if ( matchedPath == '/' )
414  {
415  matchedPath.clear();
416  }
417  return matchedPath.toStdString() + "/static/" + asset;
418  } );
419 
420  context.response()->write( env.render_file( pathInfo.fileName().toStdString(), data ) );
421  }
422  catch ( std::exception &e )
423  {
424  QgsMessageLog::logMessage( QStringLiteral( "Error parsing template file: %1 - %2" ).arg( path, e.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Critical );
425  throw QgsServerApiInternalServerError( QStringLiteral( "Error parsing template file: %1" ).arg( e.what() ) );
426  }
427 }
428 
430 {
431  // Fallback to default
432  QgsServerOgcApi::ContentType result { defaultContentType() };
433  bool found { false };
434  // First file extension ...
435  const QString extension { QFileInfo( request->url().path() ).suffix().toUpper() };
436  if ( ! extension.isEmpty() )
437  {
438  static QMetaEnum metaEnum { QMetaEnum::fromType<QgsServerOgcApi::ContentType>() };
439  bool ok { false };
440  const int ct { metaEnum.keyToValue( extension.toLocal8Bit().constData(), &ok ) };
441  if ( ok )
442  {
443  result = static_cast<QgsServerOgcApi::ContentType>( ct );
444  found = true;
445  }
446  else
447  {
448  QgsMessageLog::logMessage( QStringLiteral( "The client requested an unsupported extension: %1" ).arg( extension ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
449  }
450  }
451  // ... then "Accept"
452  const QString accept { request->header( QStringLiteral( "Accept" ) ) };
453  if ( ! found && ! accept.isEmpty() )
454  {
455  const QString ctFromAccept { contentTypeForAccept( accept ) };
456  if ( ! ctFromAccept.isEmpty() )
457  {
458  const auto constContentTypes( QgsServerOgcApi::contentTypeMimes() );
459  auto it = constContentTypes.constBegin();
460  while ( ! found && it != constContentTypes.constEnd() )
461  {
462  int idx = it.value().indexOf( ctFromAccept );
463  if ( idx >= 0 )
464  {
465  found = true;
466  result = it.key();
467  }
468  it++;
469  }
470  }
471  else
472  {
473  QgsMessageLog::logMessage( QStringLiteral( "The client requested an unsupported content type in Accept header: %1" ).arg( accept ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
474  }
475  }
476  // Validation: check if the requested content type (or an alias) is supported by the handler
477  if ( ! contentTypes().contains( result ) )
478  {
479  // Check aliases
480  bool found { false };
481  if ( QgsServerOgcApi::contentTypeAliases().contains( result ) )
482  {
483  const QList<QgsServerOgcApi::ContentType> constCt { contentTypes() };
484  for ( const auto &ct : constCt )
485  {
486  if ( QgsServerOgcApi::contentTypeAliases()[result].contains( ct ) )
487  {
488  result = ct;
489  found = true;
490  break;
491  }
492  }
493  }
494 
495  if ( ! found )
496  {
497  QgsMessageLog::logMessage( QStringLiteral( "Unsupported Content-Type: %1" ).arg( QgsServerOgcApi::contentTypeToString( result ) ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
498  throw QgsServerApiBadRequestException( QStringLiteral( "Unsupported Content-Type: %1" ).arg( QgsServerOgcApi::contentTypeToString( result ) ) );
499  }
500  }
501  return result;
502 }
503 
504 QString QgsServerOgcApiHandler::parentLink( const QUrl &url, int levels )
505 {
506  QString path { url.path() };
507  const QFileInfo fi { path };
508  const QString suffix { fi.suffix() };
509  if ( ! suffix.isEmpty() )
510  {
511  path.chop( suffix.length() + 1 );
512  }
513  while ( path.endsWith( '/' ) )
514  {
515  path.chop( 1 );
516  }
517  QRegularExpression re( R"raw(\/[^/]+$)raw" );
518  for ( int i = 0; i < levels ; i++ )
519  {
520  path = path.replace( re, QString() );
521  }
522  QUrl result( url );
523  QUrlQuery query( result );
524  QList<QPair<QString, QString> > qi;
525  const auto constItems { query.queryItems( ) };
526  for ( const auto &i : constItems )
527  {
528  if ( i.first.compare( QStringLiteral( "MAP" ), Qt::CaseSensitivity::CaseInsensitive ) == 0 )
529  {
530  qi.push_back( i );
531  }
532  }
533  // Make sure the parent link ends with a slash
534  if ( ! path.endsWith( '/' ) )
535  {
536  path.append( '/' );
537  }
538  QUrlQuery resultQuery;
539  resultQuery.setQueryItems( qi );
540  result.setQuery( resultQuery );
541  result.setPath( path );
542  return result.toString();
543 }
544 
546 {
547  const auto mapLayers { context.project()->mapLayersByShortName<QgsVectorLayer *>( collectionId ) };
548  if ( mapLayers.count() != 1 )
549  {
550  throw QgsServerApiNotFoundError( QStringLiteral( "Collection with given id (%1) was not found or multiple matches were found" ).arg( collectionId ) );
551  }
552  return mapLayers.first();
553 }
554 
556 {
557  static json defRes =
558  {
559  { "description", "An error occurred." },
560  {
561  "content", {
562  {
563  "application/json", {
564  {
565  "schema", {
566  { "$ref", "#/components/schemas/exception" }
567  }
568  }
569  }
570  },
571  {
572  "text/html", {
573  {
574  "schema", {
575  { "type", "string" }
576  }
577  }
578  }
579  }
580  }
581  }
582  };
583  return defRes;
584 }
585 
587 {
588  return QgsJsonUtils::jsonFromVariant( tags() );
589 }
590 
591 void QgsServerOgcApiHandler::setContentTypesInt( const QList<int> &contentTypes )
592 {
593  mContentTypes.clear();
594  for ( const int &i : std::as_const( contentTypes ) )
595  {
596  mContentTypes.push_back( static_cast<QgsServerOgcApi::ContentType>( i ) );
597  }
598 }
599 
600 void QgsServerOgcApiHandler::setContentTypes( const QList<QgsServerOgcApi::ContentType> &contentTypes )
601 {
602  mContentTypes = contentTypes;
603 }
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)
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.
The QgsServerApiContext class encapsulates the resources for a particular client request: the 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.
configuration error on the server prevents to serve the request, which would be valid otherwise.
Internal server error API exception.
Not found error API exception.
this method is not yet implemented
virtual QgsServerSettings * serverSettings()=0
Returns the server settings.
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 void handleRequest(const QgsServerApiContext &context) const SIP_THROW(QgsServerApiBadRequestException)
Handles the request within its context.
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 json schema(const QgsServerApiContext &context) const
Returns handler information from the context for the OPENAPI description (id, description and other m...
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 QVariantMap values(const QgsServerApiContext &context) const SIP_THROW(QgsServerApiBadRequestException)
Analyzes the incoming request context and returns the validated parameter map, throws QgsServerApiBad...
static json defaultResponse()
Returns the defaultResponse as JSON.
void jsonDump(json &data, const QgsServerApiContext &context, const QString &contentType=QStringLiteral("application/json")) const
Writes data to the context response stream as JSON (indented if debug is active), an optional content...
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,...
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,...
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.
static QgsServerOgcApi::ContentType contenTypeFromExtension(const std::string &extension)
Returns the Content-Type value corresponding to extension.
ContentType
Media types used for content negotiation, insert more specific first.
static QString contentTypeToString(const QgsServerOgcApi::ContentType &ct)
Returns the string representation of a ct (Content-Type) attribute.
Rel
Rel link types.
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.
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.
QgsServerRequest Class defining request interface passed to services QgsService::executeRequest() met...
virtual QString header(const QString &name) const
Returns the header value.
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 Header entry Add Header entry to the response Note that it is usually an error to set Header afte...
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 data sets.