Skip to content
Browse files

Fix file server

  • Loading branch information...
1 parent c7f7337 commit b71c398792e5c00918ae5b1db88b6d57f0736ea8 @spencertipping committed May 16, 2011
Showing with 24 additions and 14 deletions.
  1. +12 −7 montenegro
  2. +4 −5 montenegro.server.js
  3. +1 −1 montenegro.server.js.sdocp
  4. +1 −1 montenegro.server.min.js
  5. +5 −0 test/file.js
  6. +1 −0 test/file.js.sdocp
View
19 montenegro
@@ -1367,7 +1367,7 @@ This one loads all of the others (though it lets you specify whether you want in
configuration('montenegro', function () {this.configure('montenegro.methods montenegro.events montenegro.fixes montenegro.rpc montenegro.dom')});
__ba020c1380510106b099bbf35892472b
-meta::sdoc('js::montenegro.server', <<'__f8970a1ab871b1cd511f77494c1d916d');
+meta::sdoc('js::montenegro.server', <<'__578b9babfcb76307f49543dd07b25840');
Montenegro server library | Spencer Tipping
Licensed under the terms of the MIT source code license
@@ -1555,11 +1555,10 @@ Sometimes you want to serve files from a directory. This is a fairly simple serv
e.file_extension_mimetypes = {css: 'text/css', html: 'text/html', js: 'application/javascript', '': 'text/plain'},
e.file(url, filename) = this /se[l/cps[(req, res) <- this.on(new RegExp('^#{url.replace(/\/$/, "")}(/|$)'), 'GET', _)]
- [req.url = req.url.replace(/\?.*$/, ''),
-
- res.writeHead(200, {'content-type': content_type_for(req.url)}), read_stream.pipe(res),
- where[read_stream = fs.createReadStream('#{filename}#{sanitize(req.url.substring(url.length))}'),
- content_type_for(url) = /\.(\w+)$/.exec(url) /re[_ && _[1] /re[e.file_extension_mimetypes[_] || e.file_extension_mimetypes['']]]]]]]}).
+ [res.writeHead(200, {'content-type': content_type_for(new_url)}), read_stream.pipe(res),
+ where*[new_url = req.url.replace(/\?.*$/, ''),
+ read_stream = fs.createReadStream('#{filename}#{sanitize(new_url.substring(url.length))}'),
+ content_type_for(url) = /\.(\w+)$/.exec(url) /re[_ && _[1] /re[e.file_extension_mimetypes[_] || e.file_extension_mimetypes['']]]]]]]}).
Alias configuration.
Gives you the ability to alias content with or without redirects. For example:
@@ -1584,7 +1583,7 @@ This configuration bundles all of the configurations together.
configuration('montenegro', function () {this.configure('montenegro.html montenegro.route.url montenegro.server montenegro.server.rpc montenegro.server.html montenegro.server.file',
'montenegro.server.alias montenegro.server.proxy')});
-__f8970a1ab871b1cd511f77494c1d916d
+__578b9babfcb76307f49543dd07b25840
meta::sdoc('js::test/chat-server', <<'__3d69a36b765b14d32157c88c485ec402');
A trivial chat server.
@@ -1598,6 +1597,12 @@ caterwaul.clone('std seq montenegro')(function () {
let*[send = caterwaul.montenegro.rpc('/chat/send')] in
$('body').append(html[div.log(input /enter_key(fn_[send($(this).val()), $(this).val('')]))])])]})();
__3d69a36b765b14d32157c88c485ec402
+meta::sdoc('js::test/file', <<'__7d72bbcdd95c2ce559df15914e54f4bb');
+File server test app
+
+caterwaul.clone('std seq continuation montenegro')(function () {
+ caterwaul.montenegro.server(8080) /se[_.file('/', './')]})();
+__7d72bbcdd95c2ce559df15914e54f4bb
meta::sdoc('js::test/paint-app', <<'__4141b34bbe6c3cda548684c343b346bd');
Collaborative paint test application
View
9 montenegro.server.js
@@ -185,11 +185,10 @@
e.file_extension_mimetypes = {css: 'text/css', html: 'text/html', js: 'application/javascript', '': 'text/plain'},
e.file(url, filename) = this /se[l/cps[(req, res) <- this.on(new RegExp('^#{url.replace(/\/$/, "")}(/|$)'), 'GET', _)]
- [req.url = req.url.replace(/\?.*$/, ''),
-
- res.writeHead(200, {'content-type': content_type_for(req.url)}), read_stream.pipe(res),
- where[read_stream = fs.createReadStream('#{filename}#{sanitize(req.url.substring(url.length))}'),
- content_type_for(url) = /\.(\w+)$/.exec(url) /re[_ && _[1] /re[e.file_extension_mimetypes[_] || e.file_extension_mimetypes['']]]]]]]}).
+ [res.writeHead(200, {'content-type': content_type_for(new_url)}), read_stream.pipe(res),
+ where*[new_url = req.url.replace(/\?.*$/, ''),
+ read_stream = fs.createReadStream('#{filename}#{sanitize(new_url.substring(url.length))}'),
+ content_type_for(url) = /\.(\w+)$/.exec(url) /re[_ && _[1] /re[e.file_extension_mimetypes[_] || e.file_extension_mimetypes['']]]]]]]}).
// Alias configuration.
// Gives you the ability to alias content with or without redirects. For example:
View
2 montenegro.server.js.sdocp
@@ -1 +1 @@
-sdocp('sdoc::js::montenegro.server', 'Montenegro server library | Spencer Tipping\nLicensed under the terms of the MIT source code license\n\nIntroduction.\nMontenegro extends Caterwaul (http://spencertipping.com/caterwaul) to operate in a node.js environment and provide an RPC endpoint.\n\n caterwaul.\n\nNode.js variables and Montenegro reference.\nCaterwaul has a problem with node.js variables. Specifically, code that it compiles can\'t reach the \'require\' variable, which ends up being really important. To fix this, Montenegro binds that\nvariable within any compiled function by using a macro.\n\n configuration(\'montenegro.core\', function () {this.shallow(\'montenegro\', {require: require})}).\n\nURL router.\nMontenegro gives you a quick proxy function to route requests based on URL patterns. This makes a suitable server if you want to promote it into one (and in fact it is the function you get\nback when you create a new server). Configuration is done like this:\n\n| var router = caterwaul.montenegro.route.url();\n router.on(\'/foo\', \'GET\', fn[request, response][response /se[_.writeHead(200), _.end(\'bar\')]]);\n router.not_found(request, response) = response /se[_.writeHead(404), _.end(\'Bummer dude, not found\')];\n router.on(\'/services\', \'GET\', router.service_listing); // Show a list of registered URLs\n\nBecause routers provide the same interface they accept, you can nest them and create proxies. The last matching pattern is the one that handles the URL, so you can always refine URL matches\n(or override them) by adding new on() handlers.\n\nEach service built with this interface exposes a list of methods. You can enable it by using the server.service_listing method as the target of an \'on\' invocation. You may not want to do this,\nbut if exposing the request handlers is a security flaw then there is probably a larger problem with the design of the application.\n\n tconfiguration(\'std seq\', \'montenegro.route.url\', function () {\n this.configure(\'montenegro.core\').montenegro /se[(_.route = _.route || {}) /se[\n _.url() = l*[result(request, response) = result.route(request, response)] in\n result /se[_.handlers = seq[~[]],\n _.on(pattern, method, handler) = this /se[_.handlers.push({url: pattern, method: method, handler: handler})],\n _.route(request, response) = this /se[(_.handler_for(request.url, request.method) || _.not_found).call(_, request, response)],\n\n _.not_found(request, response) = response /se[_.writeHead(404), _.end(\'#{request.url} was not found.\')],\n\n _.service_listing(req, res) = res /se.r[r.writeHead(200), r.end(seq[_.handlers *[\'#{_.url} (#{_.method})\']].join(\'\\n\'))],\n\n _.handlers_for(url, method) = seq[this.handlers %[(_.url.test ? _.url.test(url) : _.url === url) && (! _.method || _.method === method)] *[_.handler]],\n _.handler_for(url, method) = this.handlers_for(url, method).pop()]]]}).\n\nServer construction.\nYou construct a Montenegro server instance by calling montenegro.server(port). The server starts running immediately. Each server has an internal routing table that maps URL patterns to\nrequest handlers. (A request handler is just a function that Node\'s createServer would accept.)\n\n tconfiguration(\'std seq\', \'montenegro.server\', function () {\n l[require = this.configure(\'montenegro.core\').montenegro.require] in this.configure(\'montenegro.route.url\').montenegro /se[\n _.server(port) = caterwaul.util.merge(_.route.url(), _.server.extensions) /se[require(\'http\').createServer(_).listen(port || 8080, \'0.0.0.0\')],\n _.server.extensions = {}]}).\n\nTrivial HTML construction.\nThis gives you a quick way to throw a page together. The key here is that you quote a syntax tree that will end up being executed on the client-side when jQuery loads. For example, to say\nhello world:\n\n| response /se[_.writeHead(200, {\'content-type\': \'text/html\'}),\n _.end(montenegro.html(qs[$(\'body\').append(html[h1(\'Hello world!\')])]))];\n\nThis builds a client page that loads caterwaul.all.js, montenegro.client.js, and jQuery. By default, caterwaul.all.js and montenegro.client.js come from my webserver (which sometimes is down),\nbut you can change where it requests these scripts by setting _.html.caterwaul_path, _.html.montenegro_path, and _.html.jquery_path.\n\n tconfiguration(\'std\', \'montenegro.html\', function () {\n this.configure(\'montenegro.core\').montenegro /se[\n _.html(t) = l*[html_header() = l[s(src) = \'<script src="#{src}"></script>\'] in\n \'<!doctype html><html><head>#{s(_.html.jquery_path)}#{s(_.html.caterwaul_path)}#{s(_.html.montenegro_path)}\',\n wrap_initializer(s) = \'<script>$(caterwaul.clone("std opt continuation seq parser montenegro")(#{s}))</script>\',\n html_footer() = \'</head><body></body></html>\'] in\n html_header() + wrap_initializer(qs[function () {return _t}].replace({_t: t}).serialize()) + html_footer(),\n\n _.html /se[_.caterwaul_path = \'http://spencertipping.com/caterwaul/caterwaul.all.js\',\n _.montenegro_path = \'http://spencertipping.com/montenegro/montenegro.client.js\',\n _.jquery_path = \'http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js\']]}).\n\nRPC endpoints.\nYou can create an RPC service on a URL. The RPC endpoint wraps the function in a CPS-converted HTTP request/response proxy that listens for POST requests on a given URL, expects a JSON array\nin the body, and converts the body into a list of parameters for the function you specify. Your function can access the reply continuation by either returning normally or invoking \'this\' on\nthe reply object.\n\nAll listeners are CPS-converted, so you can have coroutine-based communication between the client and server. For example, this is a broadcast chat server (which relies on singly re-entrant\ncontinuations for replies, if you want to think about it as a regular procedure call):\n\n| var clients = seq[~[]];\n caterwaul.montenegro.server(8080) /se[_.rpc(\'/chat\', fn_[clients.push(this)]).\n rpc(\'/chat/send\', fn[message][seq[clients *![_(message)]], clients = seq[~[]], this(\'OK\')])];\n\nThe client code for this example is in montenegro.client.js.sdoc.\n\nRPC services can provide documentation. This is an optional second parameter, e.g:\n\n| chat_service.rpc(\'/chat\', \'Clients should long-loop this URL to be notified of messages that are sent.\', fn_[...]);\n\nAny clients who GET the URL will be served the documentation string as plain text. If you don\'t specify any documentation, GET requests will be sent a generic \'there\'s a service here, but no\ndocumentation for it\' message as plain text. The service will also send potentially useful diagnostic messages with 400 error codes if you\'re using it incorrectly.\n\n tconfiguration(\'std continuation\', \'montenegro.server.rpc\', function () {\n l*[html = this.configure(\'montenegro.html\').montenegro.html] in\n\n this.configure(\'montenegro.server\').montenegro.server.extensions /se[\n _.rpc(url, _documentation, _fn) =\n this /se.t[install_json_post_handler(t),\n install_documentation_handler(t),\n install_test_page(t),\n\n where*[documentation = _fn ? _documentation : \'#{url} service (no documentation provided)\',\n fn = _fn || _documentation,\n\n rpc = _.rpc,\n\n install_json_post_handler(server) = server.on(url, \'POST\', fn[req, res][json_from(req)(fn[json][fn.apply(json_to(res), json)])]),\n install_documentation_handler(server) = server.on(\'#{url}/doc\', \'GET\', fn[req, res][res /se[_.writeHead(200, {\'content-type\': \'text/plain\'}), _.end(doc)]]),\n install_test_page(server) = server.on(url, \'GET\', fn[req, res][res /se[_.writeHead(200, {\'content-type\': \'text/html\'}), _.end(rpc.testpage())]]),\n\n json_from(request)(cc) = l[pieces = []] in request /se[_.on(\'data\', pieces/mb/push),\n _.on(\'end\', fn_[unwind_protect[rpc.error(e)][cc(JSON.parse(pieces.join(\'\')))]])],\n\n error_to (response)(e) = response /se[_.writeHead(400, {\'content-type\': \'text/plain\'}), _.end(e.toString())],\n json_to (response)() = l[as = Array.prototype.slice.call(arguments)] in\n response /se[_.writeHead(200, {\'content-type\': \'application/json\'}), _.end(JSON.stringify(as))]]],\n\n Error trapping.\n If an error occurs, the client receives the toString() produced by the error object and a stack trace is logged to the console. However, you may want to do something different. If you do,\n change montenegro.server.rpc.error(e).\n\n _.rpc.error(e) = e /se[console.log(_)],\n\n Test pages.\n If you use the server as shown above, you\'ll get a test page for each RPC endpoint. For example, the test page for the \'/chat\' URL is \'/chat\'. You can navigate to this page and send requests\n to the RPC to verify that it\'s working correctly. This is enabled in production-mode as well as development mode; it\'s my attempt to encode Kerckhoffs\' principle\n (http://en.wikipedia.org/wiki/Kerckhoffs\'_principle) into the framework to prevent bad security decisions.\n\n _.rpc.testpage() = html(qs[$(\'head\').append(html[link *rel(\'stylesheet\') *href(\'http://fonts.googleapis.com/css?family=Droid+Sans+Mono&subset=latin\'),\n link *rel(\'stylesheet\') *href(\'http://spencertipping.com/montenegro/style/testpage.css\')]),\n\n $(\'body\').append(html[div(div.header(h1(\'RPC shell\'), h2.documentation(span.loading(\'loading documentation...\'))),\n p(\'You can evaluate code below. \', code(\'rpc()\'), \' is the RPC connector function for the API, and \', code(\'log()\'),\n \' can be used to log values. Your code will be macroexpanded under std, seq, opt, parser, montenegro, and continuation.\'),\n div(button.run(\'Run\')),\n textarea.code /val(\'l/cps[x <- rpc("Hello world", _)][log(x)]\'),\n div.log)]),\n\n window.rpc = caterwaul.montenegro.rpc(url),\n $(\'.run\').click(fn_[unwind_protect[error(e)][caterwaul.clone(\'std seq continuation opt montenegro\')(\'(function () {#{$("textarea.code").val()}})\')()]]),\n $.get(\'#{url}/doc\', fn[doc][$(\'.documentation\').empty().append(doc)]),\n\n where*[entry(x) = html[div.entry(code(x), \' \', a(\'[x]\')/click(fn_[$(this).parent().remove()]))],\n log = window.log(x) = $(\'div.log\').append(entry(JSON.stringify(x))),\n error = window.error(x) = $(\'div.log\').append(entry(x.toString()).addClass(\'error\')),\n url = document.location.href]])]}).\n\nHTML server configuration.\nYou can send HTML pages to the client by writing initialization functions. To send a hello world page, for example:\n\n| montenegro.server(8080).html(\'/hello\', qs[$(\'body\').append(html[h1(\'Hello world!\')])]);\n\nThe client file contains full documentation for the html[] macro (the client ends up macroexpanding the code above).\n\n tconfiguration(\'std\', \'montenegro.server.html\', function () {\n l[html = this.configure(\'montenegro.html\').montenegro.html] in\n this.configure(\'montenegro.server\').montenegro.server.extensions /se[\n _.html(url, t) = l[s = html(t)] in this /se[_.on(url, \'GET\', fn[req, res][res /se[_.writeHead(200, {\'content-type\': \'text/html\'}), _.end(s)]])]]}).\n\nProxy configuration.\nForwards headers both ways, changing only the \'host\' header for sending. You can specify functions to intercept the request/response data to transform it in some way.\n\n tconfiguration(\'std seq continuation\', \'montenegro.server.proxy\', function () {\n l[http = this.configure(\'montenegro.core\').montenegro.require(\'http\')] in\n this.configure(\'montenegro.server\').montenegro.server.extensions /se[\n _.proxy(url, options) = l/cps[(req, res) <- this.on(new RegExp(\'^#{url}\'), null, _)]\n [l[req0 = proxy_request_for(req, url)]\n [req.pipe(req0),\n l/cps[res0 <- req0.on(\'response\', _)]\n [res.writeHead(res0.statusCode, res0.headers), res0.pipe(res)]]],\n\n where*[parts_for(url) = /^\\/?([^:\\/]+)(:?\\d*)(\\/?.*)$/.exec(url), host_for(parts) = parts && parts[1], port_for(parts) = parts && Number(parts[2].substring(1)) || 80,\n proxy_request_for(req, base_url) = l*[parts = parts_for(req.url.replace(base_url, \'\')), host = host_for(parts), port = port_for(parts)] in\n http.createClient(port, host).request(req.method, parts && parts[3] || \'/\', caterwaul.util.merge({}, req.headers, {host: host}))]]}).\n\nFile server configuration.\nSometimes you want to serve files from a directory. This is a fairly simple service to do that. I imagine there are security problems with it.\n\n tconfiguration(\'std continuation\', \'montenegro.server.file\', function () {\n l[sanitize(s) = s.replace(/\\.\\+/g, \'.\'), fs = this.configure(\'montenegro.core\').montenegro.require(\'fs\')] in\n this.configure(\'montenegro.server\').montenegro.server.extensions /se.e[\n e.file_extension_mimetypes = {css: \'text/css\', html: \'text/html\', js: \'application/javascript\', \'\': \'text/plain\'},\n\n e.file(url, filename) = this /se[l/cps[(req, res) <- this.on(new RegExp(\'^#{url.replace(/\\/$/, "")}(/|$)\'), \'GET\', _)]\n [req.url = req.url.replace(/\\?.*$/, \'\'),\n\n res.writeHead(200, {\'content-type\': content_type_for(req.url)}), read_stream.pipe(res),\n where[read_stream = fs.createReadStream(\'#{filename}#{sanitize(req.url.substring(url.length))}\'),\n content_type_for(url) = /\\.(\\w+)$/.exec(url) /re[_ && _[1] /re[e.file_extension_mimetypes[_] || e.file_extension_mimetypes[\'\']]]]]]]}).\n\nAlias configuration.\nGives you the ability to alias content with or without redirects. For example:\n\n| some_server.alias(\'/\', \'/index.html\'); // A server-side redirect (no 30x return code)\n some_server.alias(\'/foo\', \'/bar\', \'POST\'); // Alias POST requests instead of everything\n some_server.alias(/^\\/foo/(.*)$/, \'/bar/$1\'); // Alias all URLs starting with /foo/ to /bar/whatever\n some_server.redirect(\'/\', \'/index.html\'); // A client-side redirect (301 error code)\n some_server.redirect(\'/\', \'/index.html\', {code: 302}); // A client-side redirect with a custom code\n some_server.redirect(\'/foo\', \'/bar\', {method: \'POST\'}); // Issue redirect for POSTs instaed of GETs\n\nSpecifying a method of null indicates that all methods should be aliased.\n\n tconfiguration(\'std continuation\', \'montenegro.server.alias\', function () {\n this.configure(\'montenegro.server\').montenegro.server.extensions /se[\n _.alias(from, to, method) = this /se[_.on(from, method, fn[req, res][_(req /se[_.url = from.test ? _.url.replace(from, to) : to], res)])],\n _.redirect(from, to, options) = this /se[l/cps[(req, res) <- this.on(from, options /re[_ && _.method], _)] in\n res /se[_.writeHead(options /re[_ && _.code] || 301, {location: to}), res.end()]]]}).\n\nFinal configuration.\nThis configuration bundles all of the configurations together.\n\n configuration(\'montenegro\', function () {this.configure(\'montenegro.html montenegro.route.url montenegro.server montenegro.server.rpc montenegro.server.html montenegro.server.file\',\n \'montenegro.server.alias montenegro.server.proxy\')});');
+sdocp('sdoc::js::montenegro.server', 'Montenegro server library | Spencer Tipping\nLicensed under the terms of the MIT source code license\n\nIntroduction.\nMontenegro extends Caterwaul (http://spencertipping.com/caterwaul) to operate in a node.js environment and provide an RPC endpoint.\n\n caterwaul.\n\nNode.js variables and Montenegro reference.\nCaterwaul has a problem with node.js variables. Specifically, code that it compiles can\'t reach the \'require\' variable, which ends up being really important. To fix this, Montenegro binds that\nvariable within any compiled function by using a macro.\n\n configuration(\'montenegro.core\', function () {this.shallow(\'montenegro\', {require: require})}).\n\nURL router.\nMontenegro gives you a quick proxy function to route requests based on URL patterns. This makes a suitable server if you want to promote it into one (and in fact it is the function you get\nback when you create a new server). Configuration is done like this:\n\n| var router = caterwaul.montenegro.route.url();\n router.on(\'/foo\', \'GET\', fn[request, response][response /se[_.writeHead(200), _.end(\'bar\')]]);\n router.not_found(request, response) = response /se[_.writeHead(404), _.end(\'Bummer dude, not found\')];\n router.on(\'/services\', \'GET\', router.service_listing); // Show a list of registered URLs\n\nBecause routers provide the same interface they accept, you can nest them and create proxies. The last matching pattern is the one that handles the URL, so you can always refine URL matches\n(or override them) by adding new on() handlers.\n\nEach service built with this interface exposes a list of methods. You can enable it by using the server.service_listing method as the target of an \'on\' invocation. You may not want to do this,\nbut if exposing the request handlers is a security flaw then there is probably a larger problem with the design of the application.\n\n tconfiguration(\'std seq\', \'montenegro.route.url\', function () {\n this.configure(\'montenegro.core\').montenegro /se[(_.route = _.route || {}) /se[\n _.url() = l*[result(request, response) = result.route(request, response)] in\n result /se[_.handlers = seq[~[]],\n _.on(pattern, method, handler) = this /se[_.handlers.push({url: pattern, method: method, handler: handler})],\n _.route(request, response) = this /se[(_.handler_for(request.url, request.method) || _.not_found).call(_, request, response)],\n\n _.not_found(request, response) = response /se[_.writeHead(404), _.end(\'#{request.url} was not found.\')],\n\n _.service_listing(req, res) = res /se.r[r.writeHead(200), r.end(seq[_.handlers *[\'#{_.url} (#{_.method})\']].join(\'\\n\'))],\n\n _.handlers_for(url, method) = seq[this.handlers %[(_.url.test ? _.url.test(url) : _.url === url) && (! _.method || _.method === method)] *[_.handler]],\n _.handler_for(url, method) = this.handlers_for(url, method).pop()]]]}).\n\nServer construction.\nYou construct a Montenegro server instance by calling montenegro.server(port). The server starts running immediately. Each server has an internal routing table that maps URL patterns to\nrequest handlers. (A request handler is just a function that Node\'s createServer would accept.)\n\n tconfiguration(\'std seq\', \'montenegro.server\', function () {\n l[require = this.configure(\'montenegro.core\').montenegro.require] in this.configure(\'montenegro.route.url\').montenegro /se[\n _.server(port) = caterwaul.util.merge(_.route.url(), _.server.extensions) /se[require(\'http\').createServer(_).listen(port || 8080, \'0.0.0.0\')],\n _.server.extensions = {}]}).\n\nTrivial HTML construction.\nThis gives you a quick way to throw a page together. The key here is that you quote a syntax tree that will end up being executed on the client-side when jQuery loads. For example, to say\nhello world:\n\n| response /se[_.writeHead(200, {\'content-type\': \'text/html\'}),\n _.end(montenegro.html(qs[$(\'body\').append(html[h1(\'Hello world!\')])]))];\n\nThis builds a client page that loads caterwaul.all.js, montenegro.client.js, and jQuery. By default, caterwaul.all.js and montenegro.client.js come from my webserver (which sometimes is down),\nbut you can change where it requests these scripts by setting _.html.caterwaul_path, _.html.montenegro_path, and _.html.jquery_path.\n\n tconfiguration(\'std\', \'montenegro.html\', function () {\n this.configure(\'montenegro.core\').montenegro /se[\n _.html(t) = l*[html_header() = l[s(src) = \'<script src="#{src}"></script>\'] in\n \'<!doctype html><html><head>#{s(_.html.jquery_path)}#{s(_.html.caterwaul_path)}#{s(_.html.montenegro_path)}\',\n wrap_initializer(s) = \'<script>$(caterwaul.clone("std opt continuation seq parser montenegro")(#{s}))</script>\',\n html_footer() = \'</head><body></body></html>\'] in\n html_header() + wrap_initializer(qs[function () {return _t}].replace({_t: t}).serialize()) + html_footer(),\n\n _.html /se[_.caterwaul_path = \'http://spencertipping.com/caterwaul/caterwaul.all.js\',\n _.montenegro_path = \'http://spencertipping.com/montenegro/montenegro.client.js\',\n _.jquery_path = \'http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js\']]}).\n\nRPC endpoints.\nYou can create an RPC service on a URL. The RPC endpoint wraps the function in a CPS-converted HTTP request/response proxy that listens for POST requests on a given URL, expects a JSON array\nin the body, and converts the body into a list of parameters for the function you specify. Your function can access the reply continuation by either returning normally or invoking \'this\' on\nthe reply object.\n\nAll listeners are CPS-converted, so you can have coroutine-based communication between the client and server. For example, this is a broadcast chat server (which relies on singly re-entrant\ncontinuations for replies, if you want to think about it as a regular procedure call):\n\n| var clients = seq[~[]];\n caterwaul.montenegro.server(8080) /se[_.rpc(\'/chat\', fn_[clients.push(this)]).\n rpc(\'/chat/send\', fn[message][seq[clients *![_(message)]], clients = seq[~[]], this(\'OK\')])];\n\nThe client code for this example is in montenegro.client.js.sdoc.\n\nRPC services can provide documentation. This is an optional second parameter, e.g:\n\n| chat_service.rpc(\'/chat\', \'Clients should long-loop this URL to be notified of messages that are sent.\', fn_[...]);\n\nAny clients who GET the URL will be served the documentation string as plain text. If you don\'t specify any documentation, GET requests will be sent a generic \'there\'s a service here, but no\ndocumentation for it\' message as plain text. The service will also send potentially useful diagnostic messages with 400 error codes if you\'re using it incorrectly.\n\n tconfiguration(\'std continuation\', \'montenegro.server.rpc\', function () {\n l*[html = this.configure(\'montenegro.html\').montenegro.html] in\n\n this.configure(\'montenegro.server\').montenegro.server.extensions /se[\n _.rpc(url, _documentation, _fn) =\n this /se.t[install_json_post_handler(t),\n install_documentation_handler(t),\n install_test_page(t),\n\n where*[documentation = _fn ? _documentation : \'#{url} service (no documentation provided)\',\n fn = _fn || _documentation,\n\n rpc = _.rpc,\n\n install_json_post_handler(server) = server.on(url, \'POST\', fn[req, res][json_from(req)(fn[json][fn.apply(json_to(res), json)])]),\n install_documentation_handler(server) = server.on(\'#{url}/doc\', \'GET\', fn[req, res][res /se[_.writeHead(200, {\'content-type\': \'text/plain\'}), _.end(doc)]]),\n install_test_page(server) = server.on(url, \'GET\', fn[req, res][res /se[_.writeHead(200, {\'content-type\': \'text/html\'}), _.end(rpc.testpage())]]),\n\n json_from(request)(cc) = l[pieces = []] in request /se[_.on(\'data\', pieces/mb/push),\n _.on(\'end\', fn_[unwind_protect[rpc.error(e)][cc(JSON.parse(pieces.join(\'\')))]])],\n\n error_to (response)(e) = response /se[_.writeHead(400, {\'content-type\': \'text/plain\'}), _.end(e.toString())],\n json_to (response)() = l[as = Array.prototype.slice.call(arguments)] in\n response /se[_.writeHead(200, {\'content-type\': \'application/json\'}), _.end(JSON.stringify(as))]]],\n\n Error trapping.\n If an error occurs, the client receives the toString() produced by the error object and a stack trace is logged to the console. However, you may want to do something different. If you do,\n change montenegro.server.rpc.error(e).\n\n _.rpc.error(e) = e /se[console.log(_)],\n\n Test pages.\n If you use the server as shown above, you\'ll get a test page for each RPC endpoint. For example, the test page for the \'/chat\' URL is \'/chat\'. You can navigate to this page and send requests\n to the RPC to verify that it\'s working correctly. This is enabled in production-mode as well as development mode; it\'s my attempt to encode Kerckhoffs\' principle\n (http://en.wikipedia.org/wiki/Kerckhoffs\'_principle) into the framework to prevent bad security decisions.\n\n _.rpc.testpage() = html(qs[$(\'head\').append(html[link *rel(\'stylesheet\') *href(\'http://fonts.googleapis.com/css?family=Droid+Sans+Mono&subset=latin\'),\n link *rel(\'stylesheet\') *href(\'http://spencertipping.com/montenegro/style/testpage.css\')]),\n\n $(\'body\').append(html[div(div.header(h1(\'RPC shell\'), h2.documentation(span.loading(\'loading documentation...\'))),\n p(\'You can evaluate code below. \', code(\'rpc()\'), \' is the RPC connector function for the API, and \', code(\'log()\'),\n \' can be used to log values. Your code will be macroexpanded under std, seq, opt, parser, montenegro, and continuation.\'),\n div(button.run(\'Run\')),\n textarea.code /val(\'l/cps[x <- rpc("Hello world", _)][log(x)]\'),\n div.log)]),\n\n window.rpc = caterwaul.montenegro.rpc(url),\n $(\'.run\').click(fn_[unwind_protect[error(e)][caterwaul.clone(\'std seq continuation opt montenegro\')(\'(function () {#{$("textarea.code").val()}})\')()]]),\n $.get(\'#{url}/doc\', fn[doc][$(\'.documentation\').empty().append(doc)]),\n\n where*[entry(x) = html[div.entry(code(x), \' \', a(\'[x]\')/click(fn_[$(this).parent().remove()]))],\n log = window.log(x) = $(\'div.log\').append(entry(JSON.stringify(x))),\n error = window.error(x) = $(\'div.log\').append(entry(x.toString()).addClass(\'error\')),\n url = document.location.href]])]}).\n\nHTML server configuration.\nYou can send HTML pages to the client by writing initialization functions. To send a hello world page, for example:\n\n| montenegro.server(8080).html(\'/hello\', qs[$(\'body\').append(html[h1(\'Hello world!\')])]);\n\nThe client file contains full documentation for the html[] macro (the client ends up macroexpanding the code above).\n\n tconfiguration(\'std\', \'montenegro.server.html\', function () {\n l[html = this.configure(\'montenegro.html\').montenegro.html] in\n this.configure(\'montenegro.server\').montenegro.server.extensions /se[\n _.html(url, t) = l[s = html(t)] in this /se[_.on(url, \'GET\', fn[req, res][res /se[_.writeHead(200, {\'content-type\': \'text/html\'}), _.end(s)]])]]}).\n\nProxy configuration.\nForwards headers both ways, changing only the \'host\' header for sending. You can specify functions to intercept the request/response data to transform it in some way.\n\n tconfiguration(\'std seq continuation\', \'montenegro.server.proxy\', function () {\n l[http = this.configure(\'montenegro.core\').montenegro.require(\'http\')] in\n this.configure(\'montenegro.server\').montenegro.server.extensions /se[\n _.proxy(url, options) = l/cps[(req, res) <- this.on(new RegExp(\'^#{url}\'), null, _)]\n [l[req0 = proxy_request_for(req, url)]\n [req.pipe(req0),\n l/cps[res0 <- req0.on(\'response\', _)]\n [res.writeHead(res0.statusCode, res0.headers), res0.pipe(res)]]],\n\n where*[parts_for(url) = /^\\/?([^:\\/]+)(:?\\d*)(\\/?.*)$/.exec(url), host_for(parts) = parts && parts[1], port_for(parts) = parts && Number(parts[2].substring(1)) || 80,\n proxy_request_for(req, base_url) = l*[parts = parts_for(req.url.replace(base_url, \'\')), host = host_for(parts), port = port_for(parts)] in\n http.createClient(port, host).request(req.method, parts && parts[3] || \'/\', caterwaul.util.merge({}, req.headers, {host: host}))]]}).\n\nFile server configuration.\nSometimes you want to serve files from a directory. This is a fairly simple service to do that. I imagine there are security problems with it.\n\n tconfiguration(\'std continuation\', \'montenegro.server.file\', function () {\n l[sanitize(s) = s.replace(/\\.\\+/g, \'.\'), fs = this.configure(\'montenegro.core\').montenegro.require(\'fs\')] in\n this.configure(\'montenegro.server\').montenegro.server.extensions /se.e[\n e.file_extension_mimetypes = {css: \'text/css\', html: \'text/html\', js: \'application/javascript\', \'\': \'text/plain\'},\n\n e.file(url, filename) = this /se[l/cps[(req, res) <- this.on(new RegExp(\'^#{url.replace(/\\/$/, "")}(/|$)\'), \'GET\', _)]\n [res.writeHead(200, {\'content-type\': content_type_for(new_url)}), read_stream.pipe(res),\n where*[new_url = req.url.replace(/\\?.*$/, \'\'),\n read_stream = fs.createReadStream(\'#{filename}#{sanitize(new_url.substring(url.length))}\'),\n content_type_for(url) = /\\.(\\w+)$/.exec(url) /re[_ && _[1] /re[e.file_extension_mimetypes[_] || e.file_extension_mimetypes[\'\']]]]]]]}).\n\nAlias configuration.\nGives you the ability to alias content with or without redirects. For example:\n\n| some_server.alias(\'/\', \'/index.html\'); // A server-side redirect (no 30x return code)\n some_server.alias(\'/foo\', \'/bar\', \'POST\'); // Alias POST requests instead of everything\n some_server.alias(/^\\/foo/(.*)$/, \'/bar/$1\'); // Alias all URLs starting with /foo/ to /bar/whatever\n some_server.redirect(\'/\', \'/index.html\'); // A client-side redirect (301 error code)\n some_server.redirect(\'/\', \'/index.html\', {code: 302}); // A client-side redirect with a custom code\n some_server.redirect(\'/foo\', \'/bar\', {method: \'POST\'}); // Issue redirect for POSTs instaed of GETs\n\nSpecifying a method of null indicates that all methods should be aliased.\n\n tconfiguration(\'std continuation\', \'montenegro.server.alias\', function () {\n this.configure(\'montenegro.server\').montenegro.server.extensions /se[\n _.alias(from, to, method) = this /se[_.on(from, method, fn[req, res][_(req /se[_.url = from.test ? _.url.replace(from, to) : to], res)])],\n _.redirect(from, to, options) = this /se[l/cps[(req, res) <- this.on(from, options /re[_ && _.method], _)] in\n res /se[_.writeHead(options /re[_ && _.code] || 301, {location: to}), res.end()]]]}).\n\nFinal configuration.\nThis configuration bundles all of the configurations together.\n\n configuration(\'montenegro\', function () {this.configure(\'montenegro.html montenegro.route.url montenegro.server montenegro.server.rpc montenegro.server.html montenegro.server.file\',\n \'montenegro.server.alias montenegro.server.proxy\')});');
View
2 montenegro.server.min.js
@@ -1 +1 @@
-caterwaul.configuration('montenegro.core',function (){this.shallow('montenegro',{require:require})}).tconfiguration('std seq','montenegro.route.url',function (){this.configure('montenegro.core').montenegro/se[(_.route=_.route||{})/se[_.url()=l*[result(request,response)=result.route(request,response)] in result/se[_.handlers=seq[ ~[]],_.on(pattern,method,handler)=this/se[_.handlers.push({url:pattern,method:method,handler:handler})],_.route(request,response)=this/se[(_.handler_for(request.url,request.method)||_.not_found).call(_,request,response)],_.not_found(request,response)=response/se[_.writeHead(404),_.end('#{request.url} was not found.')],_.service_listing(req,res)=res/se.r[r.writeHead(200),r.end(seq[_.handlers*['#{_.url} (#{_.method})']].join('\n'))],_.handlers_for(url,method)=seq[this.handlers%[(_.url.test?_.url.test(url):_.url===url)&&( !_.method||_.method===method)]*[_.handler]],_.handler_for(url,method)=this.handlers_for(url,method).pop()]]]}).tconfiguration('std seq','montenegro.server',function (){l[require=this.configure('montenegro.core').montenegro.require] in this.configure('montenegro.route.url').montenegro/se[_.server(port)=caterwaul.util.merge(_.route.url(),_.server.extensions)/se[require('http').createServer(_).listen(port||8080,'0.0.0.0')],_.server.extensions={}]}).tconfiguration('std','montenegro.html',function (){this.configure('montenegro.core').montenegro/se[_.html(t)=l*[html_header()=l[s(src)='<script src="#{src}"></script>'] in '<!doctype html><html><head>#{s(_.html.jquery_path)}#{s(_.html.caterwaul_path)}#{s(_.html.montenegro_path)}',wrap_initializer(s)='<script>$(caterwaul.clone("std opt continuation seq parser montenegro")(#{s}))</script>',html_footer()='</head><body></body></html>'] in html_header()+wrap_initializer(qs[function (){return _t}].replace({_t:t}).serialize())+html_footer(),_.html/se[_.caterwaul_path='http://spencertipping.com/caterwaul/caterwaul.all.js',_.montenegro_path='http://spencertipping.com/montenegro/montenegro.client.js',_.jquery_path='http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js']]}).tconfiguration('std continuation','montenegro.server.rpc',function (){l*[html=this.configure('montenegro.html').montenegro.html] in this.configure('montenegro.server').montenegro.server.extensions/se[_.rpc(url,_documentation,_fn)=this/se.t[install_json_post_handler(t),install_documentation_handler(t),install_test_page(t),where*[documentation=_fn?_documentation:'#{url} service (no documentation provided)',fn=_fn||_documentation,rpc=_.rpc,install_json_post_handler(server)=server.on(url,'POST',fn[req,res][json_from(req)(fn[json][fn.apply(json_to(res),json)])]),install_documentation_handler(server)=server.on('#{url}/doc','GET',fn[req,res][res/se[_.writeHead(200,{'content-type':'text/plain'}),_.end(doc)]]),install_test_page(server)=server.on(url,'GET',fn[req,res][res/se[_.writeHead(200,{'content-type':'text/html'}),_.end(rpc.testpage())]]),json_from(request)(cc)=l[pieces=[]] in request/se[_.on('data',pieces/mb/push),_.on('end',fn_[unwind_protect[rpc.error(e)][cc(JSON.parse(pieces.join('')))]])],error_to(response)(e)=response/se[_.writeHead(400,{'content-type':'text/plain'}),_.end(e.toString())],json_to(response)()=l[as=Array.prototype.slice.call(arguments)] in response/se[_.writeHead(200,{'content-type':'application/json'}),_.end(JSON.stringify(as))]]],_.rpc.error(e)=e/se[console.log(_)],_.rpc.testpage()=html(qs[$('head').append(html[link*rel('stylesheet')*href('http://fonts.googleapis.com/css?family=Droid+Sans+Mono&subset=latin'),link*rel('stylesheet')*href('http://spencertipping.com/montenegro/style/testpage.css')]),$('body').append(html[div(div.header(h1('RPC shell'),h2.documentation(span.loading('loading documentation...'))),p('You can evaluate code below. ',code('rpc()'),' is the RPC connector function for the API, and ',code('log()'),' can be used to log values. Your code will be macroexpanded under std, seq, opt, parser, montenegro, and continuation.'),div(button.run('Run')),textarea.code/val('l/cps[x <- rpc("Hello world", _)][log(x)]'),div.log)]),window.rpc=caterwaul.montenegro.rpc(url),$('.run').click(fn_[unwind_protect[error(e)][caterwaul.clone('std seq continuation opt montenegro')('(function () {#{$("textarea.code").val()}})')()]]),$.get('#{url}/doc',fn[doc][$('.documentation').empty().append(doc)]),where*[entry(x)=html[div.entry(code(x),' ',a('[x]')/click(fn_[$(this).parent().remove()]))],log=window.log(x)=$('div.log').append(entry(JSON.stringify(x))),error=window.error(x)=$('div.log').append(entry(x.toString()).addClass('error')),url=document.location.href]])]}).tconfiguration('std','montenegro.server.html',function (){l[html=this.configure('montenegro.html').montenegro.html] in this.configure('montenegro.server').montenegro.server.extensions/se[_.html(url,t)=l[s=html(t)] in this/se[_.on(url,'GET',fn[req,res][res/se[_.writeHead(200,{'content-type':'text/html'}),_.end(s)]])]]}).tconfiguration('std seq continuation','montenegro.server.proxy',function (){l[http=this.configure('montenegro.core').montenegro.require('http')] in this.configure('montenegro.server').montenegro.server.extensions/se[_.proxy(url,options)=l/cps[(req,res)< -this.on(new RegExp('^#{url}'),null,_)][l[req0=proxy_request_for(req,url)][req.pipe(req0),l/cps[res0< -req0.on('response',_)][res.writeHead(res0.statusCode,res0.headers),res0.pipe(res)]]],where*[parts_for(url)=/^\/?([^:\/]+)(:?\d*)(\/?.*)$/.exec(url),host_for(parts)=parts&&parts[1],port_for(parts)=parts&&Number(parts[2].substring(1))||80,proxy_request_for(req,base_url)=l*[parts=parts_for(req.url.replace(base_url,'')),host=host_for(parts),port=port_for(parts)] in http.createClient(port,host).request(req.method,parts&&parts[3]||'/',caterwaul.util.merge({},req.headers,{host:host}))]]}).tconfiguration('std continuation','montenegro.server.file',function (){l[sanitize(s)=s.replace(/\.\+/g,'.'),fs=this.configure('montenegro.core').montenegro.require('fs')] in this.configure('montenegro.server').montenegro.server.extensions/se.e[e.file_extension_mimetypes={css:'text/css',html:'text/html',js:'application/javascript','':'text/plain'},e.file(url,filename)=this/se[l/cps[(req,res)< -this.on(new RegExp('^#{url.replace(/\/$/, "")}(/|$)'),'GET',_)][req.url=req.url.replace(/\?.*$/,''),res.writeHead(200,{'content-type':content_type_for(req.url)}),read_stream.pipe(res),where[read_stream=fs.createReadStream('#{filename}#{sanitize(req.url.substring(url.length))}'),content_type_for(url)=/\.(\w+)$/.exec(url)/re[_&&_[1]/re[e.file_extension_mimetypes[_]||e.file_extension_mimetypes['']]]]]]]}).tconfiguration('std continuation','montenegro.server.alias',function (){this.configure('montenegro.server').montenegro.server.extensions/se[_.alias(from,to,method)=this/se[_.on(from,method,fn[req,res][_(req/se[_.url=from.test?_.url.replace(from,to):to],res)])],_.redirect(from,to,options)=this/se[l/cps[(req,res)< -this.on(from,options/re[_&&_.method],_)] in res/se[_.writeHead(options/re[_&&_.code]||301,{location:to}),res.end()]]]}).configuration('montenegro',function (){this.configure('montenegro.html montenegro.route.url montenegro.server montenegro.server.rpc montenegro.server.html montenegro.server.file','montenegro.server.alias montenegro.server.proxy')});
+caterwaul.configuration('montenegro.core',function (){this.shallow('montenegro',{require:require})}).tconfiguration('std seq','montenegro.route.url',function (){this.configure('montenegro.core').montenegro/se[(_.route=_.route||{})/se[_.url()=l*[result(request,response)=result.route(request,response)] in result/se[_.handlers=seq[ ~[]],_.on(pattern,method,handler)=this/se[_.handlers.push({url:pattern,method:method,handler:handler})],_.route(request,response)=this/se[(_.handler_for(request.url,request.method)||_.not_found).call(_,request,response)],_.not_found(request,response)=response/se[_.writeHead(404),_.end('#{request.url} was not found.')],_.service_listing(req,res)=res/se.r[r.writeHead(200),r.end(seq[_.handlers*['#{_.url} (#{_.method})']].join('\n'))],_.handlers_for(url,method)=seq[this.handlers%[(_.url.test?_.url.test(url):_.url===url)&&( !_.method||_.method===method)]*[_.handler]],_.handler_for(url,method)=this.handlers_for(url,method).pop()]]]}).tconfiguration('std seq','montenegro.server',function (){l[require=this.configure('montenegro.core').montenegro.require] in this.configure('montenegro.route.url').montenegro/se[_.server(port)=caterwaul.util.merge(_.route.url(),_.server.extensions)/se[require('http').createServer(_).listen(port||8080,'0.0.0.0')],_.server.extensions={}]}).tconfiguration('std','montenegro.html',function (){this.configure('montenegro.core').montenegro/se[_.html(t)=l*[html_header()=l[s(src)='<script src="#{src}"></script>'] in '<!doctype html><html><head>#{s(_.html.jquery_path)}#{s(_.html.caterwaul_path)}#{s(_.html.montenegro_path)}',wrap_initializer(s)='<script>$(caterwaul.clone("std opt continuation seq parser montenegro")(#{s}))</script>',html_footer()='</head><body></body></html>'] in html_header()+wrap_initializer(qs[function (){return _t}].replace({_t:t}).serialize())+html_footer(),_.html/se[_.caterwaul_path='http://spencertipping.com/caterwaul/caterwaul.all.js',_.montenegro_path='http://spencertipping.com/montenegro/montenegro.client.js',_.jquery_path='http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js']]}).tconfiguration('std continuation','montenegro.server.rpc',function (){l*[html=this.configure('montenegro.html').montenegro.html] in this.configure('montenegro.server').montenegro.server.extensions/se[_.rpc(url,_documentation,_fn)=this/se.t[install_json_post_handler(t),install_documentation_handler(t),install_test_page(t),where*[documentation=_fn?_documentation:'#{url} service (no documentation provided)',fn=_fn||_documentation,rpc=_.rpc,install_json_post_handler(server)=server.on(url,'POST',fn[req,res][json_from(req)(fn[json][fn.apply(json_to(res),json)])]),install_documentation_handler(server)=server.on('#{url}/doc','GET',fn[req,res][res/se[_.writeHead(200,{'content-type':'text/plain'}),_.end(doc)]]),install_test_page(server)=server.on(url,'GET',fn[req,res][res/se[_.writeHead(200,{'content-type':'text/html'}),_.end(rpc.testpage())]]),json_from(request)(cc)=l[pieces=[]] in request/se[_.on('data',pieces/mb/push),_.on('end',fn_[unwind_protect[rpc.error(e)][cc(JSON.parse(pieces.join('')))]])],error_to(response)(e)=response/se[_.writeHead(400,{'content-type':'text/plain'}),_.end(e.toString())],json_to(response)()=l[as=Array.prototype.slice.call(arguments)] in response/se[_.writeHead(200,{'content-type':'application/json'}),_.end(JSON.stringify(as))]]],_.rpc.error(e)=e/se[console.log(_)],_.rpc.testpage()=html(qs[$('head').append(html[link*rel('stylesheet')*href('http://fonts.googleapis.com/css?family=Droid+Sans+Mono&subset=latin'),link*rel('stylesheet')*href('http://spencertipping.com/montenegro/style/testpage.css')]),$('body').append(html[div(div.header(h1('RPC shell'),h2.documentation(span.loading('loading documentation...'))),p('You can evaluate code below. ',code('rpc()'),' is the RPC connector function for the API, and ',code('log()'),' can be used to log values. Your code will be macroexpanded under std, seq, opt, parser, montenegro, and continuation.'),div(button.run('Run')),textarea.code/val('l/cps[x <- rpc("Hello world", _)][log(x)]'),div.log)]),window.rpc=caterwaul.montenegro.rpc(url),$('.run').click(fn_[unwind_protect[error(e)][caterwaul.clone('std seq continuation opt montenegro')('(function () {#{$("textarea.code").val()}})')()]]),$.get('#{url}/doc',fn[doc][$('.documentation').empty().append(doc)]),where*[entry(x)=html[div.entry(code(x),' ',a('[x]')/click(fn_[$(this).parent().remove()]))],log=window.log(x)=$('div.log').append(entry(JSON.stringify(x))),error=window.error(x)=$('div.log').append(entry(x.toString()).addClass('error')),url=document.location.href]])]}).tconfiguration('std','montenegro.server.html',function (){l[html=this.configure('montenegro.html').montenegro.html] in this.configure('montenegro.server').montenegro.server.extensions/se[_.html(url,t)=l[s=html(t)] in this/se[_.on(url,'GET',fn[req,res][res/se[_.writeHead(200,{'content-type':'text/html'}),_.end(s)]])]]}).tconfiguration('std seq continuation','montenegro.server.proxy',function (){l[http=this.configure('montenegro.core').montenegro.require('http')] in this.configure('montenegro.server').montenegro.server.extensions/se[_.proxy(url,options)=l/cps[(req,res)< -this.on(new RegExp('^#{url}'),null,_)][l[req0=proxy_request_for(req,url)][req.pipe(req0),l/cps[res0< -req0.on('response',_)][res.writeHead(res0.statusCode,res0.headers),res0.pipe(res)]]],where*[parts_for(url)=/^\/?([^:\/]+)(:?\d*)(\/?.*)$/.exec(url),host_for(parts)=parts&&parts[1],port_for(parts)=parts&&Number(parts[2].substring(1))||80,proxy_request_for(req,base_url)=l*[parts=parts_for(req.url.replace(base_url,'')),host=host_for(parts),port=port_for(parts)] in http.createClient(port,host).request(req.method,parts&&parts[3]||'/',caterwaul.util.merge({},req.headers,{host:host}))]]}).tconfiguration('std continuation','montenegro.server.file',function (){l[sanitize(s)=s.replace(/\.\+/g,'.'),fs=this.configure('montenegro.core').montenegro.require('fs')] in this.configure('montenegro.server').montenegro.server.extensions/se.e[e.file_extension_mimetypes={css:'text/css',html:'text/html',js:'application/javascript','':'text/plain'},e.file(url,filename)=this/se[l/cps[(req,res)< -this.on(new RegExp('^#{url.replace(/\/$/, "")}(/|$)'),'GET',_)][res.writeHead(200,{'content-type':content_type_for(new_url)}),read_stream.pipe(res),where*[new_url=req.url.replace(/\?.*$/,''),read_stream=fs.createReadStream('#{filename}#{sanitize(new_url.substring(url.length))}'),content_type_for(url)=/\.(\w+)$/.exec(url)/re[_&&_[1]/re[e.file_extension_mimetypes[_]||e.file_extension_mimetypes['']]]]]]]}).tconfiguration('std continuation','montenegro.server.alias',function (){this.configure('montenegro.server').montenegro.server.extensions/se[_.alias(from,to,method)=this/se[_.on(from,method,fn[req,res][_(req/se[_.url=from.test?_.url.replace(from,to):to],res)])],_.redirect(from,to,options)=this/se[l/cps[(req,res)< -this.on(from,options/re[_&&_.method],_)] in res/se[_.writeHead(options/re[_&&_.code]||301,{location:to}),res.end()]]]}).configuration('montenegro',function (){this.configure('montenegro.html montenegro.route.url montenegro.server montenegro.server.rpc montenegro.server.html montenegro.server.file','montenegro.server.alias montenegro.server.proxy')});
View
5 test/file.js
@@ -0,0 +1,5 @@
+// File server test app
+
+caterwaul.clone('std seq continuation montenegro')(function () {
+ caterwaul.montenegro.server(8080) /se[_.file('/', './')]})();
+// Generated by SDoc
View
1 test/file.js.sdocp
@@ -0,0 +1 @@
+sdocp('sdoc::js::test/file', 'File server test app\n\ncaterwaul.clone(\'std seq continuation montenegro\')(function () {\n caterwaul.montenegro.server(8080) /se[_.file(\'/\', \'./\')]})();');

0 comments on commit b71c398

Please sign in to comment.
Something went wrong with that request. Please try again.