- Dart:Scalable Application Development
- Davy Mitchell Sergey Akopkokhyants Ivo Balbaert
- 1030字
- 2021-07-09 18:56:28
Creating the web service
Now that the data collection and storage has been re-factored, it is time to build a web service. In some ways, this is similar to the blog server we created previously. However, we want to be ambitious, and so will plan for future development by starting to build a web API for our data.
The REST architecture style is very popular as it is well suited to JSON and being consumed by client web applications.
Note
REST is an architecture style for web services that has gained acceptance as a less complex alternative to WSDL and SOAP.
REST builds on HTTP and uses the verbs GET
, POST
, PUT,
and DELETE
. It can use any Internet format as a data type (XML, images, and so on), but usually uses JSON.
There is no agreed standard for RESTful web APIs, as it is a style rather than a protocol. This means that it is very flexible, but it can also be hard to get an answer to questions such as "is this the right way to do it?". The usual advice is to be pragmatic and follow REST as far as makes sense.
For more information, see http://www.restapitutorial.com/.
Using the package rpc
A RESTful API has a number of conventions and expectations. Fortunately, there is an existing Dart package, authored by the Dart development team, that covers most of the implementation detail.
Note
The rpc
package is available from Pub at https://pub.dartlang.org/packages/rpc.
The source code is available from GitHub at https://github.com/dart-lang/rpc.
As a bonus, the rpc
package also includes discoverability features (via Google's Discovery Document, https://developers.google.com/discovery/v1/reference/apis), which allow the easy creation of client code in any supporting language.
Initiating the API server
In the main.dart
file, the server is set up and set to serve on the local machine using port 8080
, as shown in the following code snippet:
library io_rpc_sample; import 'dart:io'; import 'package:georestwebservice/georestwebservice.dart'; import 'package:rpc/rpc.dart'; final ApiServer _apiServer = new ApiServer(apiPrefix: '/api', prettyPrint: true); main() async { _apiServer.addApi(new Quake()); _apiServer.enableDiscoveryApi(); HttpServer server = await HttpServer.bind(InternetAddress.ANY_IP_V4, 8080); server.listen(_apiServer.httpRequestHandler); }
The _apiServer
instance is set up with two parameters. The first sets an expected prefix for the API so that valid URLs will start with the form http://127.0.0.1:8080/api
. The second parameter, prettyPrint
, formats the JSON for easy viewing in the browser or another consuming application that may include a debugger. This is particularly useful when the JSON is heavily nested.
The _apiServer
instance is then configured with a Quake
object. This object contains the implementation of the API methods. The Quake
class definition is located in the file georestwebservice.dart
, and is heavily annotated. Let's have a look at the following code snippet:
@ApiClass( name: 'quake', version: 'v1', description: 'Rest API for Earthquake feature data.') class Quake { ... }
The @ApiClass
annotation exposes this class as an API on the rpc
server. This allows separate class to handle the implementation per API name and per version. The different versions are accessed by changing the version or name in the URL that is being requested from the server.
Exposing methods
The simplest method is the classic Hello World example:
@ApiMethod(path: 'hello') QuakeResponse hello() { return new QuakeResponse()..result = 'Hello world.'; }
The path
setting in the attribute defines the name part of the URL. A QuakeResponse
object is returned with a string set as the result, as shown in the following screenshot:

The QuakeResponse
class is a simple class that is automatically converted to a JSON response by the rpc
package, as shown in the following code snippet:
class QuakeResponse { String result; QuakeResponse(); }
Classes used as a response are required to be concrete (not abstract) and to have a constructor with no parameters. Also, the constructor must not be a named constructor.
Error handling of incorrect requests
The rpc
package handles any URLs for incorrect methods automatically, and provides an error message in JSON format:

The error handling also covers an exception or other error occurring within the implementation of an API method, as shown here:
@ApiMethod(path: 'implementationerror') QuakeResponse implementationError() { throw new Exception(); }
This doomed-to-fail method will produce a valid JSON response that the client application can handle:

Serving the latest information
For the grid view data display, the most important URL for the web API will be http://127.0.0.1:8080/api/quake/v1/latest
:
@ApiMethod(path: 'latest') List<String> latest() { DaoQuakeAPI qa = new DaoQuakeAPI(); return qa.fetchTopTenLatest(); }
The rpc
package handles the conversion to JSON of several common return types. The List<String>
is probably the most common collection.
Supplying the data
The DaoQuakeAPI
class is implemented in the file daoapi.dart
and retrieves the features list from the database, as shown in the following code snippet:
Future<List<String>> fetchTopTenLatest() async { var dbConn; try { dbConn = await connect(uri); var query = """ select geojson from dm_quakefeatures order by modified_date desc limit 10 """; List<Row> dbResults = await dbConn.query(query).toList(); List<String> results = new List<String>(); dbResults.forEach((record) { results.add(record[0]); }); return results; } catch (exception, stackTrace) { print(exception); print(stackTrace); } finally { dbConn.close(); } }
The results are returned as a list of records, and each record returned is a List
object. This means that dbr[0]
needs to be specified, as we only require the string in the first field.
Discovering the API
The rpc
package provides built-in discoverability for the API. This is provided as a JSON definition of the service that is accessed from the API server:
http://127.0.0.1:8080/api/discovery/v1/apis/quake/v1/rest
This definition would be consumed by development tools to produce wrapper objects and supporting code in the required language. Let's have a look at the output in the following screenshot:

The header contains identifying information, like the version, and key usage data, such as the base URL for calling methods.
The schemas
section summarizes all the data types used by the API, the methods lists, and the available function calls that can be made. In addition, the resources section (empty for this project) would contain the objects that group API methods together.
- Kibana Essentials
- VSTO開發入門教程
- Java程序設計:原理與范例
- BIM概論及Revit精講
- BeagleBone Black Cookbook
- Learning Continuous Integration with TeamCity
- Python圖形化編程(微課版)
- OpenCV with Python Blueprints
- Greenplum構建實時數據倉庫實踐
- Drupal 8 Development Cookbook(Second Edition)
- Java高手是怎樣煉成的:原理、方法與實踐
- SAP Web Dynpro for ABAP開發技術詳解:基礎應用
- ArcPy and ArcGIS(Second Edition)
- Python數據預處理技術與實踐
- Flask開發Web搜索引擎入門與實戰