1. Asynchronous operations under the single-threaded model

Dart is a single-threaded model, single-threaded model, single-threaded model!!

What is single threading: are you fighting alone

What is asynchronous: For example, if you want to boil water (time-consuming operation), you don’t need to wait for the water to boil before you can do the next thing (sweeping the floor) just fire (method call), then you can sweep the floor (perform the method below the asynchronous task), boil the water buzz (callback), and flush the water (process the result of the asynchronous task).

Dart asynchronous programming: Future and Stream

Future is equivalent to a 40m machete, Stream is equivalent to a bundle of 40m machetes Dart provides the keywords async and await, the equivalent of ordinary handy little daggers

ReadFile (name) {var file = file (name); return file.readAsString(); } readOk() async{var result = await readFile(r"C:\Users\Administrator\ desktop.txt "); print(result); } main() { readOk(); Print (" What number am I?" ); }Copy the code

Dart Asynchronous processing

The Future of writing:

Main () {File(r"C:\Users\Administrator\ desktop.txt ").readasString ().then((result) {print(result); }); Print (" What number am I?" ); }Copy the code

I/O operations in Dart

Path_provider: ^0.4.1: Provides three paths, just use them

LocalPath () async {try {print(' temporary directory: '+ (await getTemporaryDirectory()).path); / / - / data/user / 0 / com. Toly1994. Toly/cache print (' document directory: + (await getApplicationDocumentsDirectory ()). The path). / / - / data/user / 0 / com. Toly1994. Toly/app_flutter print (' sd card catalog: + (await external.getexternalstoragedirectory ()). The path). //----/storage/emulated/0 } catch (err) { print(err); }}Copy the code

Simple_permissions: ^0.1.9: Dynamic permission requests are provided

readFormSD() async { try { var perm = SimplePermissions.requestPermission(Permission.ReadExternalStorage); var sdPath = getExternalStorageDirectory(); Sdpath.then ((file) {perm.then((v) async {var res = await readFile(file.path + "/ asynlong.txt "); print(res); }); }); } catch (err) { print(err); }}Copy the code

Dart network request operations

1. Initiate an HTTP request using the HttpClient

The Dart IO library provides classes for Http requests that can be initiated directly using HttpClient. There are five steps to making a request using HttpClient:

Create an HttpClient

 HttpClient httpClient = new HttpClient();

Copy the code

Open the Http connection and set the request header

HttpClientRequest request = await httpClient.getUrl(uri);

Copy the code

This step can use any Http method, such as httpClient.post(…). , httpClient. Delete (…). And so on. If the Query parameter is included, it can be added when building the URI, for example:

Uri uri=Uri(scheme: "https", host: "flutterchina.club", queryParameters: {

    "xx":"xx",

    "yy":"dd"

  });
Copy the code

Request headers can be set using HttpClientRequest, for example:

request.headers.add("user-agent", "test");

Copy the code

If the request body method is post or PUT, you can send the request body via the HttpClientRequest object.

String payload="..." ; request.add(utf8.encode(payload)); //request.addStream(_inputStream); // Input streams can be added directlyCopy the code

Waiting to connect to the server

HttpClientResponse response = await request.close();

Copy the code

When this is done, the request information is sent to the server, returning an HttpClientResponse object containing the response header and the response Stream (the Stream of the response body), which can then be read to retrieve the response content.

Reading response content

String responseBody = await response.transform(utf8.decoder).join();

Copy the code

We get the data returned by the server by reading the response stream, and when reading we can set the encoding format, in this case UTF8.

The request ends, and the HttpClient is closed

httpClient.close();

Copy the code

After the client is disabled, all requests initiated by the client are terminated.

The sample


import 'dart:convert';

import 'dart:io';

Copy the code

//MARK: Create a network request

Void requestData(@required String URL) Async {try {// Request // create an httpClient httpClient _httpClient = httpClient (); // Open the HTTP link HttpClientRequest request = await _httpClient.geturl (uri.parse (url)); // Request. Headers. Add (" user-agent ", "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1"); // Waiting to connect to the server (sending requests to the server) HttpClientResponse Response = await Request.close (); String String = await response.transform(utf8.decoder).join(); Print (string); text = string; _httpClient.close(); _httpClient.close(); } catch (e) {print(" error "+ e); } finally {setState(() {print(" request end "); }); }}Copy the code

HttpClient configuration

HttpClient has a number of properties that can be configured.

idleTimeout

The keep-alive field in the request header is the value of the keep-alive field. To avoid frequent connections, the httpClient keeps the connection open for a period of time after the request ends.

connectionTimeout

The timeout value for establishing a connection to the server, beyond which a SocketException will be thrown.

maxConnectionsPerHost

Maximum number of simultaneous connections allowed to the same host.

autoUncompress

Content-encoding in the request header, if set to true, the content-encoding value in the request header is the list of current HttpClient supported compression algorithms, currently only “gzip”.

userAgent

Corresponding to the User-Agent field in the request header.

For these properties, you can set the header directly through HttpClientRequest. However, setting the header through HttpClient applies to the entire HttpClient. Settings via HttpClientRequest only apply to the current request

HTTP Request Authentication

The Http Authentication mechanism can be used to protect non-public resources. If authentication is enabled on the Http server, then the user needs to carry the user credentials when making requests. If you access a resource with Basic authentication enabled in your browser, browsing will bring up a login box, such as:

Let’s start by looking at the Basic process:

The client sends the HTTP request to the server, and the server verifies whether the user has logged in and authenticated. If not, the server returns a 401 unsazied to the client and adds a ‘www-authenticate’ field to the response header, for example: WWW-Authenticate: Basic realm=”admin”

“Basic” is the authentication mode, and “Realm” is the grouping of user roles. Groups can be added in the background.

After obtaining the response code, the client encodes the user name and password in Base64 (user name: password), sets the request header Authorization, and continues the access: Authorization: Basic YXXFISDJFISJFGIJIJG

The server validates the user credentials and returns the resource content if it passes.

Note that in addition to Basic authentication, Http also has: The HttpClient of Flutter supports only Basic and Digest authentication. The biggest difference between the two authentication modes is that the content of a user’s credential is not specified when the user sends the credential. The former is simply encoded by Base64 (reversible), while the latter carries out hashing, which is relatively safer. However, for security purposes, both Basic authentication and Digest authentication should be performed under Https protocol, which can prevent packet capture and man-in-the-middle attack.

HttpClient provides Http authentication methods and attributes.

AddCredentials (Uri URL, String realm, HttpClientCredentials) This method is used to add user credentials, such as: HttpClient. AddCredentials (_uri, "admin", the new HttpClientBasicCredentials (" username ", "password"), / / Basic authentication credentials);Copy the code

If it is a Digest authentication, can create a Digest authentication credentials: HttpClientDigestCredentials (” username “, “password”)

Authenticate (Future F (Uri URL, String Scheme, String realm)) this is a setter, type is a callback that httpClient calls when the server needs a user credential that has not been added, In this callback, addCredential() is typically called to dynamically add user credentials, for example:

httpClient.authenticate=(Uri url, String scheme, String realm) async{

  if(url.host=="xx.com" && realm=="admin"){

    httpClient.addCredentials(url,

      "admin",

      new HttpClientBasicCredentials("username","pwd"), 

    );

    return true;

  }

  return false;

};

Copy the code

One suggestion is that if authentication is required for all requests, addCredentials() should be called at HttpClient initialization to add global credentials, rather than adding them dynamically.

The agent

FindProxy can be used to set the proxy policy, for example, we want to send all requests through the proxy server (192.168.1.2:8888) :

Client.findproxy = (uri) {// If you need to filter the URI, manually check return "PROXY 192.168.1.2:8888"; };Copy the code

The findProxy callback returns a string that follows the format of the browser PAC script (see the API documentation for details), or “DIRECT” if no proxy is required.

In APP development, we often need to capture packets for debugging, and the capture software (such as Charles) is a proxy, then we can send the request to our capture software, and we can see the requested data in the capture software.

HttpClient provides the corresponding Proxy authentication methods and attributes:

set authenticateProxy(

    Future<bool> f(String host, int port, String scheme, String realm));

void addProxyCredentials(

    String host, int port, String realm, HttpClientCredentials credentials);
Copy the code

The methods of using them are the same as addCredentials and Authenticate described in the section “HTTP Request Authentication” above.

Certificate of calibration

In Https, to prevent man-in-the-middle attacks by forging certificates, clients should verify self-signed or non-CA-issued certificates. The HttpClient logic for certificate verification is as follows:

If the requested Https certificate is issued by a trusted CA, the access host is included in the certificate’s domain list (or matches the wildcard rule), and the certificate has not expired, authentication passes.

If the first step fails, but the certificate was added to the trust chain by the SecurityContext when HttpClient was created, then the server returns a certificate that is in the trust chain.

If both 1 and 2 validations fail, the badCertificateCallback callback is invoked if the user provides it, allowing the link to continue if the callback returns true, or terminating if it returns false.

To sum up, our certificate validation actually provides a badCertificateCallback callback, as illustrated by an example below.

The sample

Assuming that our background service uses a self-signed certificate in PEM format and we save the contents of the certificate in a local string, our verification logic is as follows:

String PEM="XXXXX"; // Can read from file... httpClient.badCertificateCallback=(X509Certificate cert, String host, int port){ if(cert.pem==PEM){ return true; } return false; };Copy the code

X509Certificate is the standard format of the certificate, which contains all information except the private key of the certificate. Readers can consult the document by themselves. In addition, the above example does not validate host because it is already our server (not a middleman) as long as the certificate content returned by the server is the same as the local save. Host validation is usually done to prevent certificate and domain name mismatches.

For self-signed certificates, we can also add them to the local certificate trust chain so that the certificate will pass automatically when it validates without going back to the badCertificateCallback callback:

SecurityContext sc=new SecurityContext();

//file is the certificate path

sc.setTrustedCertificates(file);

// Create an HttpClient

HttpClient httpClient = new HttpClient(context: sc);

Note that the certificate format set through setTrustedCertificates() must be PEM or PKCS12. If the certificate format is PKCS12, you need to pass in the certificate password, which will expose the certificate password in the code. Therefore, PKCS12 is not recommended for client certificate verification.

conclusion

It is worth noting that HttpClient provides these properties and methods that will eventually be used in the request header. We can do this manually by setting the header. We provide these methods just for the convenience of developers. In addition, Http is a very important and most used network protocol, and every developer should be familiar with Http.

2. Perform network operations

Dio HTTP library

From the previous section, we can find that using HttpClient directly to initiate network requests is quite troublesome, many things have to be handled manually, if it involves file upload/download, Cookie management, etc., will be very tedious. Fortunately, the Dart community has some third-party HTTP request libraries that make it much easier to initiate HTTP requests. In this section, we’ll take a look at the popular DIO library.

Dio is a powerful Dart Http request library that supports Restful apis, FormData, interceptors, request cancellation, Cookie management, file upload/download, timeouts, and more.

The introduction of

Dio: ^ 2.1.3

Import and create dio instance:

import ‘package:dio/dio.dart’;

Dio dio = new Dio();

The next step is to use dio instances to make network requests. Note that a DIO instance can make multiple HTTP requests. In general, dio should use singleton mode when your APP has only one HTTP data source.

The sample

Initiate a GET request: Response Response; response=await dio.get("/test? id=12&name=wendu") print(response.data.toString()); For GET requests, we can pass the query argument through the object. The above code is equivalent to:  response=await dio.get("/test",queryParameters:{"id":12,"name":"wendu"}) print(response); Response =await dio. POST ("/test",data:{"id":12,"name":"wendu"}) response= await Future.wait([dio.post("/info"),dio.get("/token")]); Download file: Response =await dio.download("https://www.google.com/",_savePath); FormData: FormData FormData = new FormData. From ({"name": "wendux", "age": 25,}); response = await dio.post("/info", data: formData)Copy the code

If the data being sent is FormData, dio sets the contentType of the request header to “multipart/form-data.”

Upload multiple files via FormData:

FormData formData = new FormData.from({ "name": "wendux", "age": 25, "file1": new UploadFileInfo(new File("./upload.txt"), "upload1.txt"), "file2": New UploadFileInfo(new File("./upload.txt"), "upload2.txt"), [ new UploadFileInfo(new File("./example/upload.txt"), "upload.txt"), new UploadFileInfo(new File("./example/upload.txt"), "upload.txt") ] }); response = await dio.post("/info", data: formData)Copy the code

It is worth mentioning that dio still uses requests from HttpClient internally, so proxy, request authentication, certificate verification, etc. are the same as HttpClient. We can set this in the onHttpClientCreate callback, for example:

(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {// Set the proxy client.findProxy = (uri) { Return "PROXY 192.168.1.2 instead: 8888"; }; / / calibration certificate httpClient. BadCertificateCallback = (X509Certificate cert, String host, int port) {if (cert. Pem = = pem) {return true; } return false; }; };Copy the code