Last active
March 13, 2017 08:31
-
-
Save dead-claudia/4d0777348f8a34ab9fd0dad3b437ad71 to your computer and use it in GitHub Desktop.
Sample server using stream function syntax (ported from my website's dev server)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from "fs" | |
import path from "path" | |
import express from "express" | |
import stylus from "stylus" | |
import morgan from "morgan" | |
import autoprefixer from "autoprefixer-stylus" | |
import pugLocals from "./pug-locals.js" | |
import generateBlog from "./generate-blog-posts.js" | |
const app = express() | |
function nocache(res) { | |
res.setHeader("Pragma", "no-cache") | |
res.setHeader("Cache-Control", "no-cache,private,no-store," + | |
"must-revalidate,max-stale=0,post-check=0,pre-check=0") | |
res.setHeader("Expires", 0) | |
} | |
app.set("views", "src") | |
app.set("view engine", "pug") | |
app.set("strict routing", true) | |
app.use((req, res, next) => { | |
nocache(res) | |
next() | |
}) | |
app.use(morgan("common")) | |
// Return a permission error on directory traversal | |
app.use((req, res, next) => { | |
if (path.posix.join("/", req.path) !== req.path) return res.sendStatus(403) | |
return next() | |
}) | |
const website = new express.Router({ | |
strict: app.get("strict routing"), | |
}) | |
// /license.*, .mixin.{html,css}, .pug, .ignore.* | |
website.get( | |
/^\/license[\/\.]|\.(mixin\.(html|css)|pug|ignore(\.[^\.]+))$/, | |
(req, res, next) => next({code: "ENOENT"})) | |
website.get("*.html", (req, res) => { | |
const file = req.path.slice(1) | |
return res.render(file.replace(/\.html$/, ".pug"), pugLocals(file, false)) | |
}) | |
website.get("*.css", stylus.middleware({ | |
src: path.resolve(__dirname, "../src"), | |
// This'll be cleared when doing a full compilation, anyways. | |
dest: path.resolve(__dirname, "../dist"), | |
force: true, | |
compile(str, path) { | |
return stylus(str) | |
.set("filename", path) | |
.use(autoprefixer()) | |
}, | |
})) | |
function getBlog(route, render) { | |
website.get(route, (req, res, next) => | |
generateBlog().then(data => render(req, res, data)).catch(next)) | |
} | |
getBlog("/blog.atom.xml", (req, res, data) => | |
res.type("xml").send(data.feed.render("atom-1.0"))) | |
getBlog("/blog.rss.xml", (req, res, data) => | |
res.type("xml").send(data.feed.render("rss-2.0"))) | |
getBlog("/blog.json", (req, res, data) => res.send({posts: data.posts})) | |
getBlog("/blog/*.md", (req, res, data) => | |
// Slice off the initial `/blog/` in req.path | |
res.send(data.compiled[req.path.slice(6)])) | |
const base = path.resolve(__dirname, "../src") | |
const read = root => (req, res, next) => { | |
const file = path.resolve(root, req.path.slice(1)) | |
return fs.stat(file, (err, stat) => { | |
if (err != null) return next(err) | |
if (!stat.isFile()) return res.redirect(`${req.baseUrl}${req.path}/`) | |
return fs.createReadStream(file) | |
.on("error", next) | |
.pipe(res.type(path.basename(file)).status(200)) | |
}) | |
} | |
website.get("*.css", read(path.resolve(__dirname, "../dist"))) | |
website.get("*.*", read(base)) | |
website.get("/", (req, res) => | |
res.render("index.pug", pugLocals("index.html", false))) | |
website.get("*", (req, res, next) => { | |
const file = path.resolve(base, req.path.slice(1)) | |
return fs.stat(file, (err, stat) => { | |
if (err != null) return next(err) | |
if (stat.isFile()) return next({code: "ENOENT"}) | |
const file = `${req.baseUrl}${req.path}/index` | |
return res.render(`${file}.jade`, pugLocals(`${file}.html`, false)) | |
}) | |
}) | |
app.use("/website", website) | |
app.use("*", (req, res) => res.sendStatus(404)) | |
app.listen(8080, () => | |
console.log("Server ready at http://localhost:8080/website")) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Assumes a hypothetical stream framework | |
import fs from "fs-promise" | |
import path from "path" | |
import * as framework from "framework" | |
import * as router from "framework-router" | |
import * as stylus from "framework-stylus" | |
import * as pug from "framework-pug" | |
import * as morgan from "framework-morgan" | |
import autoprefixer from "autoprefixer-stylus" | |
import pugLocals from "./pug-locals.js" | |
import generateBlog from "./generate-blog-posts.js" | |
const app = framework.create() | |
const styl = stylus.create({ | |
src: path.resolve(__dirname, "../src"), | |
// This'll be cleared when doing a full compilation, anyways. | |
dest: path.resolve(__dirname, "../dist"), | |
force: true, | |
compile(str, path) { | |
return stylus.stylus(str) | |
.set("filename", path) | |
.use(autoprefixer()) | |
}, | |
}) | |
const pug = pug.create({src: path.resolve(__dirname, "../src")}) | |
app.use(morgan.create("common")) | |
app.use((req, res, next) => { | |
const route = router.create(req, res, {strict: true}) | |
// Prevent caching | |
res.setHeader("Pragma", "no-cache") | |
res.setHeader("Cache-Control", "no-cache,private,no-store," + | |
"must-revalidate,max-stale=0,post-check=0,pre-check=0") | |
res.setHeader("Expires", 0) | |
// Return a permission error on directory traversal | |
if (route.path.startsWith(".") || route.path.startsWith("..")) { | |
res.sendStatus(403) | |
} else { | |
await next(route) | |
} | |
}) | |
app.use((req, res, route, next) => { | |
const resolved = route.descend("/website/**") | |
if (resolved == null) { | |
res.sendStatus(404) | |
} else { | |
await next(resolved) | |
} | |
}) | |
// /license.*, .mixin.{html,css}, .pug, .ignore.* | |
const ignored = /^license[\/\.]|\.(mixin\.(html|css)|pug|ignore(\.[^\.]+))$/ | |
app.use((req, res, resolved) => { | |
if (resolved.match(ignored)) { | |
throw {code: "ENOENT"} | |
} else if (resolved.match("*.html")) { | |
await pug.render( | |
req, res, | |
resolved.path.replace(/\.html$/, ".pug"), | |
pugLocals(resolved.path.slice(1), false)) | |
} else if (resolved.match("*.css")) { | |
const file = path.resolve(__dirname, "../dist", req.path.slice(1)) | |
await styl.render(req, res, resolved.path, file) | |
} else if (resolved.match("/blog.{atom,rss}.xml")) { | |
const {feed} = await generateBlog() | |
res.type("xml") | |
res.send(feed.render( | |
resolved.match("/blog.atom.xml") ? "atom-1.0" : "rss-2.0" | |
)) | |
} else if (resolved.match("/blog.json")) { | |
const {posts} = await generateBlog() | |
res.type("json") | |
res.send(JSON.stringify({posts})) | |
} else if (resolved.match("/blog/*.md")) { | |
const {compiled} = await generateBlog() | |
res.type("md") | |
// Slice off the initial `/blog/` in req.path | |
res.send(compiled[resolved.path.slice(6)]) | |
} else if (resolved.match("*.*")) { | |
const file = path.resolve(__dirname, "../src", req.path.slice(1)) | |
try { | |
const stat = await fs.stat(file) | |
res.type(path.basename(file)) | |
await new Promise((resolve, reject) => { | |
fs.createReadStream(file).on("error", reject) | |
.pipe(res).on("error", reject) | |
.on("close", resolve) | |
}) | |
res.end() | |
} catch (e) { | |
if (e.code !== "EISDIR") throw e | |
return route.redirect(req, res, `${route.path}/`) | |
} | |
} else if (resolved.match("/")) { | |
await pug.render(req, res, "index.pug", | |
pugLocals("index.html", false)) | |
} else { | |
const file = `${req.path}/index` | |
await pug.render(req, res, `${file}.jade`, | |
pugLocals(`${file}.html`, false)) | |
} | |
}) | |
app.listen(8080, () => console.log("Server ready at http://localhost:8080/website")) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The `framework` module is surprisingly straightforward, particularly for | |
// what's used within my server alone. | |
import http from "http" | |
const resUtils = { | |
send(body) { | |
if (!this.getHeader("Content-Type")) this.type("html") | |
// populate Content-Length | |
// convert chunk to Buffer; saves later double conversions | |
let chunk = Buffer.from(body, "utf-8") | |
this.setHeader("Content-Length", chunk.length) | |
// freshness | |
if (this.req.fresh) this.statusCode = 304 | |
// strip irrelevant headers | |
if (204 === this.statusCode || 304 === this.statusCode) { | |
this.removeHeader("Content-Type") | |
this.removeHeader("Content-Length") | |
this.removeHeader("Transfer-Encoding") | |
chunk = Buffer.alloc(0) | |
} | |
if (this.req.method === "HEAD") { | |
// skip body for HEAD | |
this.end() | |
} else { | |
// respond | |
this.end(chunk, "utf-8") | |
} | |
}, | |
type(type) { | |
return this.setHeader("Content-Type", | |
type.indexOf("/") < 0 ? mime.lookup(type) : type) | |
}, | |
sendStatus(status) { | |
var body = statusCodes[statusCode] || String(statusCode) | |
this.statusCode = statusCode | |
this.type("txt") | |
return this.send(body) | |
}, | |
} | |
export function create(settings={}) { | |
if (typeof settings === "function") [settings, init] = [{}, settings] | |
const onError = settings.onError || console.error | |
const server = (settings.mod || http).createServer() | |
const funcs = [] | |
async function run(i, req, res, ...args) { | |
if (i === funcs.length) return | |
return (0, funcs[i])(req, res, ...args, (...args) => { | |
return run(i + 1, req, res, ...args) | |
}) | |
} | |
server.on("request", (req, res) => { | |
Object.assign(res, {settings}, resUtils) | |
return run(0, req, res).catch(onError) | |
}) | |
server.use = funcs.push.bind(funcs) | |
return server | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment