Initial version.

This commit is contained in:
Jonathan Bernard
2025-07-26 19:30:54 -05:00
commit 5674c1e95c
6 changed files with 128 additions and 0 deletions
+100
View File
@@ -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)