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

A blog server

A blog server needs to respond in the same manner to network requests as any other web server so that it can be used by the existing clients. The main clients to the server will be web browsers, such as Google Chrome and Internet Explorer.

A blog server needs to run as a reliable application on different operating systems. Later in this chapter, we will take a look at how a Dart application can be deployed—helper applications and dependencies are required to achieve this.

Introducing the HTTP protocol

The HTTP protocol is straightforward and lightweight. Generally, a server responds to a request with a set of headers stating that the request was successful or not. It then sends the details of what it is going to send, and the content itself, before closing the connection.

The HTTP protocol requires that all headers must be sent before any content. Dart helps enforce this, and if you try to modify the headers once some content has been sent, HttpException will be thrown.

Starting up the server

The main.dart entry point for a blog server is similar to the Hello World example. The actual functionality of serving the content is handled by the BlogServerApp class, of which a single instance is initialized:

import 'dart:io';
import 'package:blogserver/blogserver.dart';

main() {
  print("starting");
  var blogServer = new BlogServerApp();
  print("Starting blog server...");

  HttpServer.bind('127.0.0.1', 8080).then((server) {
    server.listen(blogServer.handleRequest);
  });
}

It is good practice to give some feedback to the user when the server has started. This server is single-threaded, with all requests being handled by one process in a serial manner.

Storing the blog posts format

Blog posts are held in a simple text file format, with the filename being the blog post ID and a txt file extension:

1st Line - Date
2nd Line - Title
3rd Line until end of file - Post Body

A blog post in 1.txt will look as follows:

01/05/2015
Giraffe Facts
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>

The content is the standard text Lorem ipsum, which has been used by typesetters for centuries.

Reading text files

Now that we are working outside the browser, it is possible to work with the file system more directly. Let's take a look at how to read in a text file, and print out each line to the standard output:

void main() {
  File myfile = new File("readme.txt");
  myfile.readAsLines().then(
      (List<String> lines)
      {lines.forEach( (line) => print(line) ); }
      );
}

To read in a blog post in our defined format, the list of lines needs a little more processing. This is implemented in the process method that is called from the BlogPost constructor in the blog.dart source file:

  BlogPost(String filename, this._id) {
    File postFile = new File(filename);
    _source = postFile.readAsLinesSync();
    process();
  }

The file is read using the synchronous version of readAsLines, so the process call does not need to be placed in a then handler, unlike the previous example:

  process() {

    _html = "";
    _date = _source[0];
    _title = _source[1];

    _html = "<h2>$_title</h2><b>$_date</b><br/>";
    _html += "<img src=\"$_id.png\" align=\"left\">";
    _source.sublist(2).forEach((line) => _html += line);
    _html += "<br/><a href=\"post$_id.html\">Permalink</a>";
  }

The first two lines are selected using the [] method. The remaining lines are broken off from the list into a sublist, and are directly used to build up the HTML file that will be the output. The content is assumed to be a valid HTML.

Reading a folder of files

In the project, the posts subfolder of the content folder contains all the blog content. To obtain a list of all the files to load, the Directory object can be used to obtain the list, which can be processed with forEach:

  void _loadPostList() {
    Directory blogContent = new Directory(_pathPosts);
    blogContent.list().forEach((File f) {
      String postFilename = path.basename(f.path).replaceAll(".txt", "");
      int id = int.parse(postFilename);
      IDs.add(id);
      postPaths[id] = f.path;
    }).then((v) {
      IDs.sort();
      IDs = IDs.reversed.toList();
    });
  }

The file paths to the blog posts are stored in Map, so that they can be retrieved by the id value. IDs are stored in a list that can then be ordered (there is no guarantee that the filesystem will supply the list of filenames in any particular order), and so that we can easily retrieve posts with the most recently published post first.

Request handling

The incoming HttpRequest object contains a wealth of information about the client—the request made and the network connection to the server:

  handleRequest(HttpRequest request) {

    if (request.uri.path.endsWith(".html")) {
      _serveTextFile(request);

    } else if (request.uri.path.endsWith(".png")) {
      _servePngFile(request);

    }  else if (request.uri.path == "/robots.txt") {
      _serveRobotsFile(request);
    }
    else {
      _serve404(request);
    }
  }

In the context of a blog server, we are most interested in the Uri (Uniform resource identifier) property, more commonly called a URL, of the request object that gives the path of the resource that is requested.

For example, if the request sent to the server is http://127.0.0.1:8080/post6.html?test=false (the query string is not used by the blog server), the value of request.uri would be /post6.html?test=false and request.uri.path would have the value /post6.html.

For the page, image, or file that is being requested, the request.uri.path property gives the desired details. The requests for the robots.txt file are handled differently than blog posts and images, and this will be detailed in a later section.

Serving text

If a page is requested, we need to provide feedback to the client making the request. Our response should be firstly to tell the client what format of data we will be responding with (the content type), also known as a MIME type. Secondly, we want to let the client know that we have understood the request (HttpStatus.OK), and are about to sent the requested content:

  void _serveTextFile(HttpRequest request) {
    String content = _getContent(request.uri.path.toString());

    request.response
      ..headers.set('Content-Type', 'text/html')
      ..statusCode = HttpStatus.OK
      ..write("""<html>
      <head><title>$BlogTitle</title></head>
      <body>
      $content
      </body>
      </html>""")
      ..close();

  }

Once these items are set, the content for the page can be served to the requesting client, using the response property of the request object. Finally, the close() method is called, so that the client knows that the server has finished sending content.

Note

MIME stands for Multipurpose Internet Mail Extensions. It is an IETF standard that is used to indicate the type of data that a file contains. This list is continually getting longer as more types of data are shared online. For more details, refer to

http://www.iana.org/assignments/media-types/media-types.xhtml.

Robots.txt

People with web browsers are only one type of web users. There are numerous bots and spiders out on the live Internet. To give some guidance to the nonhuman visitors, the robots.txt standard tells the bot whether they are welcome, and if it is a search engine, then how to index it. For the purpose of the blog server, we want to welcome search engines to index the content to bring in more readers.

The text content of the response for this will be:

User-agent: *
Disallow:

The robots.txt request needs to be handled and served in the same element as a regular text page:

  void _serveRobotsFile(HttpRequest request) {
    request.response
      ..statusCode = HttpStatus.OK
      ..headers.set('Content-Type', 'text/html')
      ..write(RobotsTxt)
      ..close();
  }

The page is served with a positive OK HTTP status.

Rendering a single blog post

The blog posts will be served on a single page for the permanent links from Uris, such as http://127.0.0.1:8080/post6.html, and the _getContent method parses the Uri path to get the ID number of the post:

   if (path.startsWith("/post")) {
     String idfromUrl =
         path.replaceFirst("/post", "").replaceFirst(".html", "");
     int id = int.parse(idfromUrl);
     return hostedBlog.getBlogPost(id).HTML;
}

The output is shown in the following screenshot:

Rendering a single blog post

Once the ID is extracted, the blog post is retrieved and the HTML file is returned to be served as a text HTML file.

Rendering the index page

A request for the index.html page from this web server will show the five most recent blog posts, with the newest one at the top of the page, as shown in the following screenshot:

Rendering the index page

The blog posts are joined together and served as a single page:

  String getFrontPage() {
    String frontPage = "";

    IDs.sublist(0, min(5, IDs.length)).forEach((int postID) {
      BlogPost post = getBlogPost(postID);
      frontPage += post.HTML + "<hr/>";
    });

    return frontPage;
  }

The list of IDs processed previously is used to get the five most recent posts (or less) and the content for the posts is fetched and joined in a single HTML element.

Serving images

The HTTP header information for the PNG file type is the MIME type and the number of bytes in the image. A status code OK is also returned:

  void _servePngFile(HttpRequest request) {
    var imgp = request.uri.path.toString();
    imgp = imgp.replaceFirst(".png", "").replaceFirst("/", "");

    image = hostedBlog.getBlogImage(int.parse(imgp));
    image.readAsBytes().then((raw) {
      request.response
        ..statusCode = HttpStatus.OK
        ..headers.set('Content-Type', 'image/png')
        ..headers.set('Content-Length', raw.length)
        ..add(raw)
        ..close();
    });
  }

Serving an image or other binary files requires a little more information upfront, but once that is done, it is simply a matter of transferring the raw bytes and closing the connection.

Locating the file

The getBlogImage method of the Blog class returns the PNG file for display on the blog post. The Blog class builds up a list of image filenames when _loadImgList is called in the constructor. This is in the same fashion as the previously described _loadPostList function:

  File getBlogImage(int index) {
    return new File(imgPaths[index]);
  }

The ID is used as a key to get a value from a map, and the full file path is returned. The web server's Uri to a file is completely independent of the location of the filesystem, and it is the logic of the application which determines which file is to be delivered to the client.

Serving a single image file

The server is unaware of the complete structure of the web page (HTML and images) served for index.html. It simply returns responses to the web browser's HTTP requests in a singular manner:

Serving a single image file

A single image can be requested directly outside the HTML page.

Serving a 404 error

Even nontechnical web users are familiar with the 404 error that indicates a page on a website cannot be found:

  void _serve404(HttpRequest request) {
    request.response
      ..statusCode = HttpStatus.NOT_FOUND
      ..headers.set('Content-Type', 'text/html')
      ..write(page404)
      ..close();
  }

This page is a regular text page (the constant string page404 contains the content), and the client is further informed of the 404 status by setting the status code to NOT_FOUND, as shown in the following screenshot:

Serving a 404 error

Let's hope that the blog server does not need to serve many of these pages!

主站蜘蛛池模板: 天全县| 碌曲县| 广灵县| 额尔古纳市| 舒城县| 宝丰县| 白河县| 平泉县| 团风县| 饶平县| 宝鸡市| 读书| 开化县| 宁德市| 夏津县| 汝州市| 襄城县| 登封市| 大埔区| 涞水县| 大化| SHOW| 疏勒县| 玛沁县| 疏勒县| 东莞市| 武定县| 蕲春县| 正定县| 金川县| 灌云县| 札达县| 宜黄县| 茌平县| 裕民县| 从化市| 沽源县| 永福县| 温宿县| 都昌县| 黄平县|