Initial version.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import std/[json, nre, sequtils, strutils]
|
||||
import cliutils, docopt, mummy, namespaced_logging
|
||||
import webby/httpheaders
|
||||
|
||||
const USAGE = """
|
||||
Usage:
|
||||
short_url serve [options]
|
||||
|
||||
Options:
|
||||
|
||||
-c, --config <cfgFile> Use the given config file (defaults to ./short-url.cfg.json)
|
||||
--debug Enable debug-level logging.
|
||||
--trace Enable trace-level logging (takes precedence over --debug).
|
||||
"""
|
||||
|
||||
const VERSION = "1.0.0"
|
||||
const CT_TXT = "text/plain"
|
||||
|
||||
type
|
||||
ShortUrlsConfig = object
|
||||
mappings: seq[tuple[src, dst: string]]
|
||||
port: int
|
||||
cfg: CombinedConfig
|
||||
|
||||
Context = object
|
||||
mappings: seq[tuple[src:string, regex: Regex, tmpl: string]]
|
||||
logger: Logger
|
||||
|
||||
proc loadConfig(args: Table[string, Value]): ShortUrlsConfig =
|
||||
let cfgFile =
|
||||
if args["--config"]: $args["--config"]
|
||||
else: findConfigFile("short-url.cfg.json")
|
||||
|
||||
result.cfg = initCombinedConfig(filename = cfgFile, docopt = args)
|
||||
result.port = parseInt(result.cfg.getVal("port", "80"))
|
||||
|
||||
result.mappings = @[]
|
||||
for (k, v) in pairs(result.cfg.getJson("mappings")):
|
||||
result.mappings.add((k, v.getStr))
|
||||
|
||||
proc initMappings(m: seq[tuple[src, dst: string]]):
|
||||
seq[tuple[src: string, regex: Regex, tmpl: string]] =
|
||||
result = m.mapIt((it.src, re(it.src), it.dst))
|
||||
|
||||
proc makeHandler(mappings: seq[tuple[src,dst: string]], logSvc: LogService): RequestHandler =
|
||||
let ctx = Context(
|
||||
mappings: initMappings(mappings),
|
||||
logger: threadLocalRef(logSvc).getLogger("pbm/short_url/response"))
|
||||
|
||||
return proc(req: Request) {.gcsafe.} =
|
||||
|
||||
if req.path == "/internal/health":
|
||||
ctx.logger.info("Successful health check")
|
||||
req.respond(200, @[("Content-Type", CT_TXT)], "healthy and happy")
|
||||
return
|
||||
|
||||
for (src, regex, tmpl) in ctx.mappings:
|
||||
if match(req.uri, regex).isSome:
|
||||
ctx.logger.debug(%*{
|
||||
"msg": "found match",
|
||||
"matching_pattern": %src,
|
||||
"uri": %req.uri,
|
||||
"tmpl": %tmpl })
|
||||
|
||||
req.respond(302,
|
||||
toWebby(@[("Location", replace(req.uri, regex, tmpl))]),
|
||||
"")
|
||||
return
|
||||
|
||||
ctx.logger.debug(%*{
|
||||
"msg": "no match found",
|
||||
"uri": %req.uri,
|
||||
"patterns": %mapIt(mappings, it.src) })
|
||||
|
||||
req.respond(404, @[("Content-Type", CT_TXT)], "not found")
|
||||
|
||||
when isMainModule:
|
||||
var logSvc = initLogService()
|
||||
try:
|
||||
|
||||
let llsvc = threadLocalRef(logSvc)
|
||||
llsvc.addAppender(initConsoleLogAppender(formatter = formatJsonStructuredLog))
|
||||
llsvc.setRootThreshold(lvlInfo)
|
||||
let logger = llsvc.getLogger("pbm/short_url")
|
||||
|
||||
let args = docopt(USAGE, version = VERSION)
|
||||
let cfg = loadConfig(args)
|
||||
|
||||
if args["--trace"]: logSvc.setRootThreshold(lvlAll)
|
||||
elif args["--debug"]: logSvc.setRootThreshold(lvlDebug)
|
||||
|
||||
let server = newServer(makeHandler(cfg.mappings, logSvc))
|
||||
logger.info("short_url v$# listening for requests on $#" %
|
||||
[ VERSION, $cfg.port ])
|
||||
server.serve(Port(cfg.port), address = "0.0.0.0")
|
||||
|
||||
except Exception:
|
||||
let ex = getCurrentException()
|
||||
threadLocalRef(logSvc).getLogger("pbm/short_url")
|
||||
.fatal(msg = ex.msg, err = ex)
|
||||
Reference in New Issue
Block a user