官术网_书友最值得收藏!

A blog editor

A blog will have an administration section that will allow us to update the blog using the Web. This will be accessible through the address http://localhost:8080/admin, using the admin login as the username and Password1 as the password (both case sensitive).

Tip

Never ever ever use such a simple login in a real application!

The admin section of the blog's website will consist of web forms. The forms will use the post method so that the blog server will have to detect this correctly in the _handleRequest method of the BlogServerApp class to handle the request. This is exposed by the method property of the request object, as shown in the following code snippet:

if (request.method == 'POST') {
  _handleFormPost(request);
}

POST requests still have a URI associated with them, allowing a decision over how to handle the input. For detailed information, let's take a look at the following code snippet:

 void _handleFormPost(HttpRequest request) {
  if (request.uri.path == "/login") _performLogin(request);
  if (request.uri.path == "/add") _performNewPost(request);
 }

The handling of the login process and the new blog post are rather different.

Password protection

The /admin path serves the web form that allows the entry of the username and password required to access the admin features, as shown in the following screenshot:

Password protection

The username and password will be verified, and if they are correct, the form to add a blog post will be shown to you. Let's take a look at the following code snippet:

  void _performLogin(HttpRequest request) {
    request.listen((List<int> buffer) {
      String page = _checkAdminLogin(buffer);

      request.response
        ..statusCode = HttpStatus.OK
        ..headers.set('Content-Type', 'text/html')
        ..write(page)
        ..close();
    }, onDone: () => request.response.close());
  }

The checkAdminLogin method performs the checking of the login details and returns either the web form or a message stating that the login attempt has failed.

Encryption

Security is an important feature of any administration feature of a web application. We will not want to store the password on the site in plain text! A typical way to store the password is a one-way hash, which makes the password hard to crack even if the attacker has the hash value.

The crypto package provides a range of encryption options. The SHA algorithm will be used for this purpose for the blog, as follows:

  String _checkAdminLogin(List<int> buffer) {
    var sha = new SHA256();
    sha.add(buffer);
    var digest = sha.close();
    String hex = CryptoUtils.bytesToHex(digest);
    String page = "";

    if (hex != expectedHash) {
      page = wrongPassword;
    } else {
      page = addForm;
    }

    return page;
  }

Conveniently, the input for the hash is a List<int> list, and this is exactly what is received from the web form input into the request's listen handler. The SHA256 algorithm simply takes a body of data and then has the close method called to return the calculated hash.

Note

SHA stands for Secure Hash Algorithm. The 256 refers to the word size. If you have ever installed a package on a Linux system, then you have used SHA-256. The crypto-currency Bitcoin also uses SHA for some of its operations.

As both the username and password have to be correct, the entire input from the form can simply be hashed and compared against a previously calculated value. For easy storage, the hash is converted to a hexadecimal string, as follows:

const String expectedHash = "bdf252c8385e7c4ae76bca280acd8d44f85d7fdb56f1bfb44f5749ff549ba2f6";

This is stored as a constant in content.dart.

Handling more complex forms

Of course, form handling is not always as straightforward as the login page. Typically, form elements need to be split apart and handled separately. The served page after logging into the administration page will allow the user to add a new post to the blog. The user will supply the post title and body text and the post ID number and date will be generated by the application, as shown in the next screenshot:

Handling more complex forms

Processing the form

The _performNewPost method calls the getFormData utility function to split the data into a more manageable list. This rather low-level web programming is typically wrapped up by most Dart web frameworks, as shown in the following code snippet:

 List _getFormData(List<int> buffer) {
    var encodedData = new String.fromCharCodes(buffer);
    List pieces = encodedData.split("&");
    List data = [];
    List finalData = [];

    pieces
        .forEach((dateItem) =>
            data.add(dateItem.substring(dateItem.indexOf("=") + 1)));

    data.forEach(
        (encodedItem) => finalData.add(Uri.decodeQueryComponent(encodedItem)));

    return finalData;
  }

The data is first split into data from each control, and then the data is decoded using the Uri.decodeQueryComponent component to the raw data before it is returned to the caller.

Saving data to a disk

The data has been processed to a manageable list, so now we only need to construct a blog post and save it to the filesystem. This is carried out by the _performNewPost method of the BlogServerApp method. Let's take a look at the following code snippet:

      List formData = _getFormData(buffer);
      String newID = hostedBlog.getNextPostID();
      String filename = "$newID.txt";
      var p = path.join("content", "posts");
      p = path.join(p, filename);

      File postFile = new File(p);
      String post = BlogPost.createBlogPost(formData[0], formData[1]);
      postFile.writeAsStringSync(post);

The blog object returns the next free PostId, which is used in the file name, and the full path is constructed using path.join from the path package. The createBlogPost static method on the BlogPost class is used to add extra data in the defined format. This is written to the constructed path using the synchronous write function.

Serving a default graphic

This version of the add form does not allow a user to set a graphic file, which each post requires. If the blog post ID is 8, then 8.png will be requested. Rather than not loading any file, a default image (default.png) will be loaded up. Let's take a look at the following screenshot:

Serving a default graphic

If the file is not found on the filesystem (such as the fictitious 1234.png image!), then 6.png is loaded and served instead, as shown in the following code snippet:

  File getBlogImage(int index) {
    String path;
    if (imgPaths.containsKey(index)) {
      path = imgPaths[index];
    } else {
      path = _pathDefaultImg;
    }

    return new File(path);
  }

This is implemented by simply testing the imgPaths map for the requested index variable. If the key is not present in the imgPaths map, the default image path is used.

Refreshing the blog

The content of the blog is read when the blog server is started. As this content has now changed with the new blog post, this list must be refreshed using the BlogServerApp method called initBlog, as follows:

  initBlog() async {
    _cache = {};
    IDs = new List<int>();
    postPaths = new Map<int, String>();
    imgPaths = new Map<int, String>();
    await _loadPostList();
    _loadImgList();
  }

The BlogServerApp and _performNewPost methods perform the task of calling initBlog before serving the new post, as follows:

hostedBlog.initBlog();

req.response
 ..statusCode = HttpStatus.OK
 ..headers.set('Content-Type', 'text/html')
 ..redirect(new Uri(path: "post$newID.html"))
 ..close();

Once the blog is reinitialized, the application serves the new blog post to the user as a single page.

主站蜘蛛池模板: 茶陵县| 遂昌县| 临夏县| 南昌市| 永泰县| 靖宇县| 专栏| 邢台市| 镇巴县| 洛川县| 宁国市| 图片| 诸城市| 鄂伦春自治旗| 霍城县| 皋兰县| 南平市| 斗六市| 阿荣旗| 土默特左旗| 射洪县| 双柏县| 安达市| 阿拉善盟| 黄平县| 甘谷县| 修水县| 太谷县| 繁峙县| 泸西县| 六枝特区| 永德县| 阳城县| 泾川县| 淳化县| 武宣县| 清苑县| 陵川县| 清水县| 平果县| 开平市|