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