- Dart:Scalable Application Development
- Davy Mitchell Sergey Akopkokhyants Ivo Balbaert
- 1769字
- 2021-07-09 18:56:21
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.
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.
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:

Once the ID is extracted, the blog post is retrieved and the HTML file is returned to be served as a text HTML file.
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:

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.
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.
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.
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:

A single image can be requested directly outside the HTML page.
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:

Let's hope that the blog server does not need to serve many of these pages!
- Apache Oozie Essentials
- Building a Home Security System with Raspberry Pi
- 劍指JVM:虛擬機實踐與性能調優
- Vue.js 3.x從入門到精通(視頻教學版)
- 算法精粹:經典計算機科學問題的Python實現
- MySQL數據庫管理與開發(慕課版)
- HTML5+CSS3網頁設計
- 名師講壇:Spring實戰開發(Redis+SpringDataJPA+SpringMVC+SpringSecurity)
- PLC應用技術(三菱FX2N系列)
- Principles of Strategic Data Science
- 自學Python:編程基礎、科學計算及數據分析(第2版)
- Greenplum構建實時數據倉庫實踐
- Visual Basic語言程序設計基礎(第3版)
- Ionic3與CodePush初探:支持跨平臺與熱更新的App開發技術
- 高質量程序設計指南:C++/C語言