This article is an extension of my series on the Development of Flutter full Stack. In the Course of Flutter, we have written a registration and login case to explain in detail how common registration, login, AND API authentication functions are implemented in combination with the front and back ends. The background service uses the well-known Aqueduct framework in Dart language.

Recent iterations of the Dart version, particularly incompatible air safety features, have resulted in the suspension of maintenance of aqueduct, the server-side framework used in the course. The company maintaining the framework does not have the energy to make it compatible with the new version, and there is currently no community to take over the project, which may be permanently discontinued. Subsequently, Angel, Dart’s second largest server-side framework, went out of maintenance, which is a major disappointment for Dart. Our course has been published for a long time, and the cases in it can only be run using the old version now.

In order for you to have a better experience and keep up with the new Dart features, I wrote a lightweight Dart HTTP server framework that supports air safety features: Arowana

The word arowana means arowana fish. Arowana was first recorded in Shanhai Jing in China:

“Arowana tombs live in the north, like a beaver. One is “fish duan”. That is, there is a divine riding on it to perform nine wild.”

Arowana has whiskers, pectoral fins like dragon claws, plus the body of gold scales (or red scales), so the shape of the dragon. Plus its hovering clever, luxurious and dignified, quiet anruizi, when moving fast such as rabbit, so its god is more like the Dragon. At present, arowana is an endangered species listed on the IUCN Red List of Threatened Species.

The Arowana framework is based on the shelf library provided by the Dart language to process HTTP requests. However, the shelf function is weak, and Arowana enhances some functions of the shelf library. At the same time, I also refer to Gin framework of Go language that I like, and write a high-performance routing component based on prefix tree search, which completely avoids regular matching. As we know, the poor performance of re processing in the Dart language can be called a performance bottleneck at AOT compilation time. Arowana also refers to Aqueduct, which encapsulates the operations for simultaneous processing of Isolate, making concurrent processing easier. Arowana doesn’t have many dependencies, especially on some dynamic features (reflection), so it can run on mobile.

The Dart language is notoriously lacking in a good database ORM framework, in part because Dart is not reflective enough to even use reflection in Flutter. Therefore, Arowana does not provide or integrate database modules. You can freely combine the currently available database connection modules to manipulate data using SQL. Here’s a simple example:

class MyAChannel extends DefaultChannel {
  @override
  Future prepare() async {
    print('current isolate [${Isolate.current.debugName}] ');
  }

  @override
  void entryPoint() {
    // Register the GET request route
    get('/hello', (r) {
      return Response.ok('

Hello, arowana!

'
, headers: {'content-type': 'text/html; charset=UTF-8'}); }); }}Copy the code

To use in Flutter:

void main() {
  var app = Application(MyAChannel());
  // numberOfInstances: Start two IP phone models to process the request
  app.start(numberOfInstances: 2,consoleLogging: true);
  runApp(const MyApp());
}
Copy the code

Registration login implementation

Here is a complete example of how we used Arowana to rewrite the registration and login of the Whole Flutter Development course:

Add dependencies. At present, Arowana is in experimental version, there is no release repository, use Git protocol to add dependencies:

dependencies:
  dart_jsonwebtoken: ^ 2.3.2
  arowana:
    git: https://github.com/arcticfox1919/arowana.git
  sqlite3: ^ 1.2.0
  crypto: ^ 3.0.1
Copy the code

Code structure:

First write a Channel and register the route:

/// server.dart

class MyAChannel extends DefaultChannel{
  late SqliteDb db;

  @override
  Future prepare() async{
    db = SqliteDb.Connect();
  }

  @override
  void entryPoint() {
    post('/register', RegisterController(db));
    post('/login', LoginController(db));

    // Packet routing
    var r = group('/info');
    // Set the authentication middleware for the group
    r.use(Auth.bearer(AuthVerifier()));

    r.get('/list', (request){
      return ResponseX.ok({
        'language': [{'id': 1.'name': 'dart'},
          {'id': 2.'name': 'java'},
          {'id': 3.'name': 'c'},
          {'id': 4.'name': 'golang'},
          {'id': 5.'name': 'python'}}]); }); }}Copy the code

Routing usage, similar to Gin, uses packet routing, which sets up an authentication middleware for all child routes under ‘/info’. Access child routes must be authenticated. Arowana provides a simple authentication middleware called Auth. Developers only need to implement their own AuthVerifier to customize the validation logic, which inherits from the AuthValidator.

The routes of the two POST requests implement registration and login functions respectively, and both have similar logic, passing data in JSON format:

/// ctrl.dart

class RegisterController{
  SqliteDb db;
  RegisterController(this.db);

  Future<Response> call(Request request)async{
    var body = await request.body;
    var json = body.json;
    if(json ! =null) {
      // Construct the User entity class from the request parameters
      var user = User.from(json);
      if (user.check()) {
        user.save(db);
        // Register successfully, return the token to the client
        returnResponseX.token(user.generateToken()); }}return ResponseX.badRequest('Invalid username or password! '); }}class LoginController{
  SqliteDb db;
  LoginController(this.db);

  Future<Response> call(Request request) async {
    var body = await request.body;
    var json = body.json;
    if(json ! =null) {
      var user = User.from(json);
      if (user.verify(db)) {
        // Successful login, return token to client
        returnResponseX.token(user.generateToken()); }}return ResponseX.badRequest('Invalid username or password! '); }}Copy the code

If you submit a username and password using a form, you need to specify the application/x-www-form-urlencode type in your request header. On the server side, you need to get the parameters as follows:

var body = await request.body;
var form = body.formParams;
if(form ! =null) {
    print(form['uname']);
    print(form['passwd']);
}
Copy the code

Look at the logic of the User class:

class User {
  int? id;
  String? uname;
  String? passwd;

  User.from(Map<String.dynamic> json) {
    uname = json['uname'];
    passwd = json['passwd'];
  }
  
  // Check whether a valid username and password have been submitted
  boolcheck() => uname ! =null&& uname! .isNotEmpty && passwd ! =null&& passwd! .isNotEmpty;// Verify login
  bool verify(SqliteDb db) {
    if(! check())return false;
    // Hash the passwordpasswd = md5.convert(utf8.encode(passwd!) ).toString();// Go to the database to check whether the user name is registered and the password is correct
    var u = db.getUser(this);
    if(u == null) return false;
    id = u.id;
    return true;
  }
  
  // Save the username and password to the database
  void save(SqliteDb db){
    if(check()){
      // hash passwdpasswd = md5.convert(utf8.encode(passwd!) ).toString();var u = db.save(this); id = u.id; }}// Generate a JWT format token
  AuthToken generateToken({Duration expiration = const Duration(hours: 24) {})var now = DateTime.now();
    var expirationDate = now.add(expiration);
    var t = createToken(
        this, expirationDate.millisecondsSinceEpoch~/1000);
    returnAuthToken(t, now, expirationDate); }}Copy the code

The logic is relatively simple. JWT format is mainly used to generate tokens. We have already introduced JWT in the Flutter course, but another OAuth2.0 generated Token is used in the case of the course at that time.

Schematic diagram:

Finally, take a look at the validator implementation:

class AuthVerifier extends AuthValidator{

  @overrideFutureOr<Authorization? > validate<T>(AuthorizationParser<T> parser, T authorizationData) {if (parser is AuthorizationBearerParser) {
      return _verify(authorizationData as String);
    }
    throw ArgumentError(
        "Invalid 'parser' for 'AuthValidator.validate'. Use 'AuthorizationBearerHeader'."); } FutureOr<Authorization? > _verify(String accessToken) async {
    try {
      // Verify the tokens brought by the client
      final jwt = verifyToken(accessToken);
      return Authorization((jwt.payload as Map) ['id'].toString(), this);
    } on JWTUndefinedError catch(e){
      print(e.error);
      // The verification fails. The issued Token has expired. The client needs to refresh the Token or log in again
      if(e.error is JWTExpiredError){
        throw TokenExpiredException('Error: the token has expired, please refresh'); }}}}Copy the code

Here, another library, DARt_jsonWebToken, is used for JWT Token processing. Provide Auth middleware with our self-defined validator. When accessing the child route /info/list registered with authentication middleware, the requested token will be verified first. If no login or token expires, the verification fails and the access to this route is not allowed.

Finally, we can use the Postman tool to verify the results:

Access the interface that requires authentication

Complete sample code, please visithere

The Dart language from starter to in-depth series of articles:

Dart Language — 45 minutes Quick Start (PART 1)

Dart Language — 45 minutes Quick Start (part 2)

Dart asynchronous programming in detail

Dart language asynchronous Stream details

Dart language standard flow and file manipulation

Dart Network programming

Dart crawler development experience

Dart full stack server development

Dart FFI calls C hybrid programming

Dart and Lua invocation in LuaDardo

Pay attention to me, not bad!


Follow the public account: the path of programming from 0 to 1

The course will be updated continuously in the future. Keep following up the Flutter technology iteration. Follow me and you will not be disappointed!

Or follow video lessons from bloggers