前期准备
安装软件与库
需要安装- Postgres (建议 9 或以上版本)
- libpq-dev
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}
#{foreach(#XXX)}
即在 XXX 对象上作循环#{[YYY]}
即每次循环的循环变量的 YYY 属性
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: 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>
数据存入
建立文件 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)
)
chunks
列表用来存放每次传来的 POST 请求数据块. 如果一次发送参数数据量较大, 那么请求体中的数据需要多次传输, 每次传输触发一次request
对象事件, 需要在内存中将这次的数据小块缓存下来 (通过request.on('data', callback)
设置的回调即起到此作用)- 所有数据传送完毕后,
request
对象会产生 on end 事件, 通过request.on('end', callback)
设置的回调这时被调用 - 现在代码中的
client.query
函数执行插入操作. SQL 中的$1
会由接下来的列表参数填充, 防注入 - 向
writeHead
中传入 302 进行跳转 (目标地址是后面字典中的 '/'), 现在的诸多 web 框架对此货的封装都相当完备几乎不怎么见得到这样的裸 API
app: {
'/':: require('./index'),
'/leavemessage':: require('./leavemessage'),
}
all:main.js index.js leavemessage.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(),
}}))
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
不用我容易么, 另外不打算提供对循环的支持)生成的代码太残了.
致谢
- JS 代码格式化站点 http://jsbeautifier.org/