About
RSS

Bit Focus


NodeJS liteview Postgres Stekinscript 搭建留言板 [完]

Posted at 2012-11-19 14:57:13 | Updated at 2018-08-15 15:43:10

上篇

前期准备

安装软件与库

    需要安装
    npm 安装 Postgres 库
npm install pg

配置数据库与表

    参考这篇文章前半部分配置 Postgres 数据库.
    如果已经为 PG 用户 psuser 授权使用 mydb 这个数据库, 现在进入数据库
psql -Upsuser -dmydb -W
Password for user psuser:
建立数据表用于存放留言
mydb=> CREATE TABLE message (
mydb(>   id SERIAL,
mydb(>   content TEXT,
mydb(>   post_time TIMESTAMP DEFAULT NOW(),
mydb(>   PRIMARY KEY(id)
mydb(> );
NOTICE:  CREATE TABLE will create implicit sequence "message_id_seq" for serial column "message.id"
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "message_pkey" for table "message"
CREATE TABLE

显示留言信息

更新页面模板

    之前的页面模板实在显得太单薄, 下面修改一下准备放入留言数据
<html>
<head><title>Guestbook</title></head>
<body>
#{foreach(#messages)}
<div>
    <p style='font-size: 0.6em'>#{[post_time]}</p>
    <p style='margin-left: 20px'>#{[content]}</p>
</div>
#{end}
    liteview 文档如是说
    因此上面的页面模板需要传入留言对象列表, 每个留言对象有属性 post_time, content. 这命名与数据库表是一致的.
    然后改一下 index.stkn, 放假数据进去先看看效果
view: require('liteview').create()

exports.get: (request, response):
    response.writeHead(200, { 'Content-Type':: 'text/html' })
    response.end(view.render('index.html', {messages: [
        {
            post_time: '1970-01-01 00:00:00',
            content: 'Nostalgia',
        },
        {
            post_time: '1970-01-01 00:00:00',
            content: 'Eternal Rite',
        },
        {
            post_time: '1970-01-01 00:00:00',
            content: 'Shinto Shrine',
        },
    ]}))
    刷下页面, 这些东西就出现了.

从数据库加载内容

    巧妇难为无米之炊, 现在首先要做的是向数据库插入一些数据. 还是更刚才建表一样, 进入 psql, 使用下面的语句插入一些水帖
INSERT INTO message (content) VALUES ('Message 0');
INSERT INTO message (content) VALUES ('Message 1');
INSERT INTO message (content) VALUES ('Message 2');
    下面终于到正篇了, 通过 pg 模块加载数据. 修改 index.stkn
pg: require('pg')

connect_url: 'tcp://psuser:123456@localhost:5432/mydb'

func error(response)
    response.writeHead(500, { 'Content-Type':: 'text/html' })
    response.end('<html><body>Server error')

exports.get: (request, response):
    pg.connect(connect_url, (err, client):
        if err
            console.log('Could not connect to database')
            error(response)
            return
        client.query('SELECT content, post_time FROM message', (err, result):
            if err
                console.log('Failed to execute SQL')
                error(response)
                return
            response.writeHead(200, { 'Content-Type':: 'text/html' })
            response.end(view.render('index.html', { messages: result.rows }))
        )
    )
其中 connect_url 的格式为
tcp:// 用户名 : 密码 @localhost:5432/ 库名
    现在刷新页面, 看到的就是从数据库中查到的数据了.
    附生成的 index.js
(function () {
    function s_error(s_response) {
        s_response.writeHead(500, {
            "Content-Type": "text/html"
        });
        s_response.end("<html><body>Server error");
    }
    const s_view = require("liteview").create();
    const s_pg = require("pg");
    exports.get = function (s_request, s_response) {
        s_pg.connect("tcp://me:123456@localhost:5432/mydb", function (s_err, s_client) {
            if (s_err) {
                console.log("Could not connect to database");
                s_error(s_response);
                return;
            } else {}
            s_client.query("SELECT content, post_time FROM message", function (s_err, s_result) {
                if (s_err) {
                    console.log("Failed to execute SQL");
                    s_error(s_response);
                    return;
                } else {}
                s_response.writeHead(200, {
                    "Content-Type": "text/html"
                });
                s_response.end(s_view.render("index.html", {
                    "messages": s_result.rows
                }));
            });
        });
    };
})();

将留言存入数据库

添加表单

    既然已经知道怎么 (用裸 SQL 而不是 ORM 的方式) 查询数据, 那么插入数据也应该八九不离十. 下面先在 index.html 上来一发留言表单
<html>
<head><title>Guestbook</title></head>
<body>
#{foreach(#messages)}
<div>
    <p style='font-size: 0.6em'>#{[post_time]}</p>
    <p style='margin-left: 20px'>#{[content]}</p>
</div>
#{end}
<form method='post' action='/leavemessage'>
    <textarea placeholder='Leave your message' name='content'></textarea>
    <input type='submit'>
</form>
    这个表单将名为 content 的数据以 POST 方式提交至 /leavemessage 处. 那么下面要做的就是去那里建个函数守株待兔.

数据存入

    建立文件 leavemessage.stkn, 内容为
qs: require('querystring')
pg: require('pg')

connect_url: 'tcp://psuser:123456@localhost:5432/mydb'

exports.post: (request, response):
    chunks: []
    request.on('end', ():
        pg.connect(connect_url, (err, client):
            if err
                console.log('Could not connect to database')
                response.writeHead(500, { 'Content-Type':: 'text/html' })
                response.end('<html><body>Server error')
                return
            client.query('INSERT INTO message (content) VALUES ($1)',
                         [qs.parse(chunks.join('')).content],
                         (err, result):
                response.writeHead(302, { Location: '/' })
                response.end()
            )
        )
    )
    request.on('data', (data):
        chunks.push(data)
    )
    对其中一些代码的解释
    然后修改一下 main.stkn, 将 leavemessage 模块加入
app: {
    '/':: require('./index'),
    '/leavemessage':: require('./leavemessage'),
}
    记得修改 Makefile
all:main.js index.js leavemessage.js
    JS 稍后在结尾处打包放送.

防止 XSS

    废话少说, 打开 index.stkn, 加上一行导入
sanitize: require('validator').sanitize
    再修改这一行
response.end(view.render('index.html', { messages: result.rows }))

response.end(view.render('index.html', { messages: result.rows |: {
    post_time: $.post_time,
    content: sanitize($.content).entityEncode(),
}}))
    使用映射管道来解决这个问题, 不要再写 for 循环, 如果觉得 JS 列表的 map 函数不好用也没关系, 现在有了新的选择.
    下面是生成的全部 JS 代码. 正篇内容到此完结.
/* main.js */
(function () {
    const s_http = require("http");
    const s_url = require("url");
    const s_app = ({
        "/": require("./index"),
        "/leavemessage": require("./leavemessage")
    });
    s_http.createServer(function (s_request, s_response) {
        const s_path = s_url.parse(s_request.url).pathname;
        const s_method = s_request.method.toLowerCase();
        console.log(((("Request to " + s_path) + " method=") + s_method));
        if (s_app[s_path]) {
            if (s_app[s_path][s_method]) {
                s_app[s_path][s_method](s_request, s_response);
            } else {
                s_response.writeHead((405), ({
                    "Content-Type": "text/html"
                }));
                s_response.end("<html><body>Method not allowed");
            }
        } else {
            s_response.writeHead((404), ({
                "Content-Type": "text/html"
            }));
            s_response.end("<html><body>Not found");
        }
    }).listen((8888), "127.0.0.1");
    console.log("Server running at local host port=8888");
})();

/* index.js */
(function () {
    function s_error(s_response) {
        s_response.writeHead((500), ({
            "Content-Type": "text/html"
        }));
        s_response.end("<html><body>Server error");
    }
    const s_view = require("liteview").create();
    const s_pg = require("pg");
    const s_sanitize = require("validator").sanitize;
    exports.get = function (s_request, s_response) {
        s_pg.connect("tcp://me:123456@localhost:5432/mydb", function (s_err, s_client) {
            if (s_err) {
                console.log("Could not connect to database");
                s_error(s_response);
                return;
            } else {}
            s_client.query("SELECT content, post_time FROM message", function (s_err, s_result) {
                if (s_err) {
                    console.log("Failed to execute SQL");
                    s_error(s_response);
                    return;
                } else {}
                s_response.writeHead((200), ({
                    "Content-Type": "text/html"
                }));
                s_response.end(s_view.render("index.html", ({
                    "messages": (function (iterlist) {
                        var r = [];
                        for (var iterindex = 0; iterindex < iterlist.length; ++iterindex) {
                            var iterelement = iterlist[iterindex];
                            r.push(({
                                "post_time": iterelement.post_time,
                                    "content": s_sanitize(iterelement.content).entityEncode()
                            }));
                        }
                        return r;
                    })(s_result.rows)
                })));
            });
        });
    };
})();

/* leavemessage.js */
(function () {
    const s_qs = require("querystring");
    const s_pg = require("pg");
    exports.post = function (s_request, s_response) {
        const s_chunks = [];
        s_request.on("end", function () {
            s_pg.connect("tcp://me:123456@localhost:5432/mydb", function (s_err, s_client) {
                if (s_err) {
                    console.log("Could not connect to database");
                    s_response.writeHead((500), ({
                        "Content-Type": "text/html"
                    }));
                    s_response.end("<html><body>Server error");
                    return;
                } else {}
                s_client.query("INSERT INTO message (content) VALUES ($1)", [s_qs.parse(s_chunks.join("")).content], function (s_err, s_result) {
                    s_response.writeHead((302), ({
                        "Location": "/"
                    }));
                    s_response.end();
                });
            });
        });
        s_request.on("data", function (s_data) {
            s_chunks.push(s_data);
        });
    };
})();

结语

需要改进的地方

    如果 JS 异步调用无法绕过的话, 那么即使 Stekinscript 语法上有什么改进, 最终也只是从大括号地狱跌落到小括号地狱. (引用某笑话, "偷到最后一页代码, 结果全是结束括号").
    没有 JS 中一些基本语言元素的支持, 比如 new 关键字. (我弄个教程一个 new不用我容易么, 另外不打算提供对循环的支持)
    生成的代码太残了.

致谢

Post tags:   Postgres  Stekin  Tutorial  Web Server  NodeJS

Leave a comment:




Creative Commons License Your comment will be licensed under
CC-NC-ND 3.0


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site