-
Notifications
You must be signed in to change notification settings - Fork 39
/
Copy pathindex.lua
251 lines (225 loc) · 6.55 KB
/
index.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
-- Load https://github.com/bungle/lua-resty-template
local template = require 'resty.template'
-- Load redis
local redis = require "resty.redis"
local cjson = require "cjson"
local markdown = require "markdown"
-- Load our blog atom generator
local atom = require "atom"
-- We need os for date formatting
local os = require "os"
-- use nginx $root variable for template dir, needs trailing slash
local TEMPLATEDIR = ngx.var.root .. 'lua/';
-- The git repository storing the markdown files. Needs trailing slash
local BLAGDIR = TEMPLATEDIR .. 'md/'
local BLAGTITLE = 'hveem.no'
local BLAGURL = 'http://hveem.no/'
local BLAGAUTHOR = 'Tor Hveem'
-- URL base
local BASE = '/'
-- the redis db handle
local red = nil
local function filename2title(filename)
title = filename:gsub('.md$', ''):gsub('-', ' '):gsub("^%l", string.upper)
return title
end
local function slugify(title)
slug = title:gsub(' ', '-')
return slug
end
-- Swap key and values in a table
local function swap(t)
local a = {}
for k, v in pairs(t) do
a[v] = k
end
return a
end
-- Helper to iterate a table by sorted keys
local function itersort (t, f)
local a = {}
-- Sort on timestamp key reverse
f = function(a,b) return tonumber(a)>tonumber(b) end
for n in pairs(t) do table.insert(a, n) end
table.sort(a, f)
local i = 0 -- iterator variable
local iter = function () -- iterator function
i = i + 1
if a[i] == nil then return nil
else return a[i], t[a[i]]
end
end
return iter
end
-- Date formatter helper
local function blogdate(timestamp)
return os.date('!%d %B %Y', timestamp)
end
-- Return a table with post date as key and title as val
local function posts_with_dates(limit)
local posts, err = red:zrevrange('posts', 0, limit, 'withscores')
if err then return {} end
posts = red:array_to_hash(posts)
return swap(posts)
end
--
-- Index view
--
local function index()
-- increment index counter
local counter, err = red:incr("index_visist_counter")
if err then ngx.log(ngx.ERR, 'Error with visit counter: '..err) end
-- Get 10 posts
local posts = posts_with_dates(10)
-- load template
local context = {
title = BLAGTITLE,
counter = tostring(counter),
posts = posts,
}
-- render template with context
template.render('index.html', context)
end
-- helper function to return blog content given a title
local function blogcontent(page)
-- Check if the page is in redis cache
-- check if the page cache needs updating
local post, err = red:get('post:'..page..':log')
if err or post == ngx.null then
ngx.say('Error fetching post from database')
return 500
end
local postlog = cjson.decode(post)
local lastupdate = 0
for ref, attrs in pairs(postlog) do
local logdate = attrs.timestamp
if logdate > lastupdate then
lastupdate = logdate
end
end
local lastgenerated, err = red:get('post:'..page..':cached')
local nocache = true
if lastgenerated == ngx.null or err then
lastgenerated = 0
nocache = true
else
lastgenerated = tonumber(lastgenerated)
end
if lastupdate <= lastgenerated then nocache = false end
local mdhtml = ''
if nocache then
local mdfile = BLAGDIR .. page .. '.md'
local mdfilefp = assert(io.open(mdfile, 'r'))
local mdcontent = mdfilefp:read('*a')
mdhtml = markdown(mdcontent)
mdfilefp:close()
local ok, err = red:set('post:'..page..':cached', lastupdate)
local ok, err = red:set('post:'..page..':md', mdhtml)
else
mdhtml = red:get('post:'..page..':md')
end
return mdhtml
end
--
-- Atom feed view
--
local function feed()
-- increment feed counter
local counter, err = red:incr("feed:visit")
-- Get 10 posts
local posts = posts_with_dates(10)
-- Get the HTML content for the content
local htmlposts = {}
for date, ptitle in pairs(posts) do
htmlposts[ptitle] = blogcontent(ptitle)
end
-- Set correct content type
ngx.header.content_type = 'application/atom+xml'
ngx.print( atom.generate_xml(BLAGTITLE, BLAGURL, BLAGAUTHOR .. "'s blog", BLAGAUTHOR, 'feed/', posts, htmlposts) )
end
--
-- the about view
--
local function about()
-- increment about counter
local counter, err = red:incr("about_visist_counter")
local context = {title = 'My lua micro web framework', counter = tostring(counter) }
template.render('about.html', context)
end
--
-- blog view for a single post
--
local function blog(match)
local page = match[1]
-- Checkf the requests page exists as a key in the sorted set
local date, err = red:zscore('posts', page)
-- No match, return 404
if err or date == ngx.null then
return ngx.HTTP_NOT_FOUND
end
local mdhtml = blogcontent(page)
-- increment visist counter
local counter, err = red:incr(page..":visit")
-- Get more posts to be linked
local posts = posts_with_dates(5)
local ctx = {
created = blogdate(date),
content = mdhtml,
title = filename2title(page),
filename2title = filename2title,
posts = posts,
counter = counter,
}
template.render('blog.html', ctx)
end
--
-- Initialise db
--
local function init_db()
-- Start redis connection
red = redis:new()
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "failed to connect: ", err)
return
end
end
--
-- End db, we could close here, but park it in the pool instead
--
local function end_db()
-- put it into the connection pool of size 100,
-- with 0 idle timeout
local ok, err = red:set_keepalive(0, 100)
if not ok then
ngx.log(ngx.ERR, "failed to set keepalive: ", err)
return
end
end
-- mapping patterns to views
local routes = {
['$'] = index,
['about$'] = about,
['feed/$'] = feed,
['(.*)$'] = blog,
}
-- Enable access to some functions in template
template.itersort = itersort
template.filename2title = filename2title
template.blogdate = blogdate
template.swap = swap
-- Set the content type
ngx.header.content_type = 'text/html';
-- iterate route patterns and find view
for pattern, view in pairs(routes) do
local uri = '^' .. BASE .. pattern
local match = ngx.re.match(ngx.var.uri, uri, "oj") -- regex mather in compile mode
if match then
init_db()
exit = view(match) or ngx.HTTP_OK
end_db()
ngx.exit( exit )
end
end
-- no match, return 404
--ngx.exit( ngx.HTTP_NOT_FOUND )