add simple HTTP server
s-ol
3 years ago
45 | 45 | |
46 | 46 | - [MoonScript][moonscript]: `luarocks install moonscript` |
47 | 47 | - [lua-sqlite3](https://luarocks.org/modules/moteus/sqlite3): `luarocks install sqlite3` |
48 | - [lua-http](https://github.com/daurnimator/lua-http): `luarocks install http` | |
48 | 49 | - [discount](https://luarocks.org/modules/craigb/discount): `luarocks install discount` (requires libmarkdown2) |
49 | 50 | - [busted](https://olivinelabs.com/busted/): `luarocks install busted` (for testing only) |
50 | 51 |
29 | 29 | |
30 | 30 | key, value |
31 | 31 | |
32 | ||
33 | 32 | with SQLStore name: output, verbose: true |
34 | 33 | import_fileder = (fileder, dirpath) -> |
35 | 34 | for file in lfs.dir dirpath |
72 | 72 | " |
73 | 73 | \close! |
74 | 74 | |
75 | import SQLStore from require 'mmm.mmmfs.drivers.sql' | |
76 | import Fileder, Key from require 'mmm.mmmfs.fileder' | |
77 | ||
78 | -- split filename into dirname + basename | |
79 | dir_base = (path) -> | |
80 | dir, base = path\match '(.-)([^/]-)$' | |
81 | if dir and #dir > 0 | |
82 | dir = dir\sub 1, #dir - 1 | |
83 | ||
84 | dir, base | |
85 | ||
86 | load_tree = (store, root='') -> | |
87 | fileders = setmetatable {}, | |
88 | __index: (path) => | |
89 | with val = Fileder {} | |
90 | .path = path | |
91 | rawset @, path, val | |
92 | ||
93 | root = fileders[root] | |
94 | root.facets['name: alpha'] = '' | |
95 | for fn, ft in store\list_facets root.path | |
96 | val = store\load_facet root.path, fn, ft | |
97 | root.facets[Key fn, ft] = val | |
98 | ||
99 | for path in store\list_all_fileders root.path | |
100 | fileder = fileders[path] | |
101 | ||
102 | parent, name = dir_base path | |
103 | fileder.facets['name: alpha'] = name | |
104 | table.insert fileders[parent].children, fileder | |
105 | ||
106 | for fn, ft in store\list_facets path | |
107 | val = store\load_facet path, fn, ft | |
108 | fileder.facets[Key fn, ft] = val | |
109 | ||
110 | root | |
111 | 75 | |
112 | 76 | tree = load_tree SQLStore :name |
113 | 77 |
0 | add = (tmpl) -> | |
1 | package.path ..= ";#{tmpl}.lua" | |
2 | package.moonpath ..= ";#{tmpl}.moon" | |
3 | ||
4 | add '?' | |
5 | add '?.server' | |
6 | add '?/init' | |
7 | add '?/init.server' | |
8 | ||
9 | require 'mmm' | |
10 | import dir_base, load_tree from require 'build.util' | |
11 | import Key from require 'mmm.mmmfs.fileder' | |
12 | import SQLStore from require 'mmm.mmmfs.drivers.sql' | |
13 | ||
14 | server = require 'http.server' | |
15 | headers = require 'http.headers' | |
16 | ||
17 | tojson = (obj) -> | |
18 | switch type obj | |
19 | when 'string' | |
20 | string.format '%q', obj | |
21 | when 'table' | |
22 | if obj[1] or not next obj | |
23 | "[#{table.concat [tojson c for c in *obj], ','}]" | |
24 | else | |
25 | "{#{table.concat ["#{tojson k}: #{tojson v}" for k,v in pairs obj], ', '}}" | |
26 | when 'number' | |
27 | tostring obj | |
28 | when 'boolean' | |
29 | tostring obj | |
30 | when nil | |
31 | 'null' | |
32 | ||
33 | class Server | |
34 | new: (@tree, opts={}) => | |
35 | opts = {k,v for k,v in pairs opts} | |
36 | opts.host = 'localhost' unless opts.host | |
37 | opts.port = 8000 unless opts.port | |
38 | opts.onstream = @\stream | |
39 | opts.onerror = @\error | |
40 | ||
41 | @server = server.listen opts | |
42 | ||
43 | listen: => | |
44 | assert @server\listen! | |
45 | ||
46 | _, ip, port = @server\localname! | |
47 | print "SV", "running at #{ip}:#{port}" | |
48 | assert @server\loop! | |
49 | ||
50 | handle: (method, path, facet) => | |
51 | fileder = @tree\walk path | |
52 | switch method | |
53 | when 'GET', 'HEAD' | |
54 | if fileder and facet | |
55 | -- fileder and facet given | |
56 | if not fileder\has_facet facet.name | |
57 | return 404, "facet '#{facet.name}' not found in fileder '#{path}'" | |
58 | ||
59 | val = fileder\get facet | |
60 | if val | |
61 | 200, val | |
62 | else | |
63 | 406, 'cant convert facet' | |
64 | elseif fileder | |
65 | -- no facet given | |
66 | facets = [{k.name, k.type} for k,v in pairs fileder.facets] | |
67 | children = [f.path for f in *fileder.children] | |
68 | print facets | |
69 | print children | |
70 | contents = tojson :facets, :children | |
71 | 200, contents | |
72 | else | |
73 | -- fileder not found | |
74 | 404, "fileder '#{path}' not found" | |
75 | else | |
76 | 501, 'not implemented' | |
77 | ||
78 | stream: (sv, stream) => | |
79 | req = stream\get_headers! | |
80 | method = req\get ':method' | |
81 | path = req\get ':path' | |
82 | ||
83 | path, facet = dir_base path | |
84 | print "'#{path}', '#{facet}'" | |
85 | facet = if #facet > 0 | |
86 | facet = '' if facet == ':' | |
87 | accept = req\get 'mmm-accept' | |
88 | Key facet, accept or 'text/html' | |
89 | else | |
90 | nil | |
91 | ||
92 | status, body = @handle method, path, facet | |
93 | ||
94 | res = headers.new! | |
95 | response_type = if status > 299 then 'text/plain' | |
96 | else if facet then facet.type | |
97 | else 'text/plain' | |
98 | res\append ':status', tostring status | |
99 | res\append 'content-type', response_type | |
100 | ||
101 | if method == 'HEAD' | |
102 | stream\write_headers res, true | |
103 | else | |
104 | stream\write_headers res, false | |
105 | stream\write_chunk body, true | |
106 | ||
107 | error: (sv, ctx, op, err, errno) => | |
108 | msg = "#{op} on #{tostring ctx} failed" | |
109 | msg = "#{msg}: #{err}" if err | |
110 | print msg | |
111 | ||
112 | -- usage: | |
113 | -- moon server.moon [db.sqlite3] | |
114 | { file } = arg | |
115 | ||
116 | tree = load_tree SQLStore :file | |
117 | server = Server tree | |
118 | server\listen! |
0 | 0 | require 'lfs' |
1 | import Fileder, Key from require 'mmm.mmmfs.fileder' | |
1 | 2 | |
2 | 3 | get_path = (root) -> |
3 | 4 | cwd = lfs.currentdir! |
10 | 11 | |
11 | 12 | path |
12 | 13 | |
14 | -- split filename into dirname + basename | |
15 | dir_base = (path) -> | |
16 | dir, base = path\match '(.-)([^/]-)$' | |
17 | if dir and #dir > 0 | |
18 | dir = dir\sub 1, #dir - 1 | |
19 | ||
20 | dir, base | |
21 | ||
22 | load_tree = (store, root='') -> | |
23 | fileders = setmetatable {}, | |
24 | __index: (path) => | |
25 | with val = Fileder {} | |
26 | .path = path | |
27 | rawset @, path, val | |
28 | ||
29 | root = fileders[root] | |
30 | root.facets['name: alpha'] = '' | |
31 | for fn, ft in store\list_facets root.path | |
32 | val = store\load_facet root.path, fn, ft | |
33 | root.facets[Key fn, ft] = val | |
34 | ||
35 | for path in store\list_all_fileders root.path | |
36 | fileder = fileders[path] | |
37 | ||
38 | parent, name = dir_base path | |
39 | fileder.facets['name: alpha'] = name | |
40 | table.insert fileders[parent].children, fileder | |
41 | ||
42 | for fn, ft in store\list_facets path | |
43 | val = store\load_facet path, fn, ft | |
44 | fileder.facets[Key fn, ft] = val | |
45 | ||
46 | root | |
47 | ||
13 | 48 | { |
14 | 49 | :get_path |
50 | :dir_base | |
51 | :load_tree | |
15 | 52 | } |
122 | 122 | [name for name in pairs names] |
123 | 123 | |
124 | 124 | -- check whether a facet is directly available |
125 | -- when passing a Key, set type to false to check for name only | |
125 | 126 | has: (...) => |
126 | 127 | want = Key ... |
127 | 128 | |
129 | 130 | continue if key.original |
130 | 131 | |
131 | 132 | if key.name == want.name and key.type == want.type |
133 | return key | |
134 | ||
135 | -- check whether any facet with that name exists | |
136 | has_facet: (want) => | |
137 | for key in pairs @facets | |
138 | continue if key.original | |
139 | ||
140 | if key.name == want | |
132 | 141 | return key |
133 | 142 | |
134 | 143 | -- find facet and type according to criteria, nil if no value or conversion path |