import uri, cgi, tables, logging, strutils, re, options import jester/private/utils when useHttpBeast: import httpbeast except Settings import options, httpcore type NativeRequest* = httpbeast.Request else: import asynchttpserver type NativeRequest* = asynchttpserver.Request type Request* = object req: NativeRequest patternParams: Option[Table[string, string]] reMatches: array[MaxSubpatterns, string] settings*: Settings proc body*(req: Request): string = ## Body of the request, only for POST. ## ## You're probably looking for ``formData`` ## instead. when useHttpBeast: req.req.body.get("") else: req.req.body proc headers*(req: Request): HttpHeaders = ## Headers received with the request. ## Retrieving these is case insensitive. when useHttpBeast: if req.req.headers.isNone: newHttpHeaders() else: req.req.headers.get() else: req.req.headers proc path*(req: Request): string = ## Path of request without the query string. when useHttpBeast: let p = req.req.path.get("") let queryStart = p.find('?') if unlikely(queryStart != -1): return p[0 .. queryStart-1] else: return p else: let u = req.req.url return u.path proc reqMethod*(req: Request): HttpMethod = ## Request method, eg. HttpGet, HttpPost when useHttpBeast: req.req.httpMethod.get() else: req.req.reqMethod proc reqMeth*(req: Request): HttpMethod {.deprecated.} = req.reqMethod proc ip*(req: Request): string = ## IP address of the requesting client. when useHttpBeast: result = req.req.ip else: result = req.req.hostname let headers = req.headers if headers.hasKey("REMOTE_ADDR"): result = headers["REMOTE_ADDR"] if headers.hasKey("x-forwarded-for"): result = headers["x-forwarded-for"] proc params*(req: Request): Table[string, string] = ## Parameters from the pattern and the query string. if req.patternParams.isSome(): result = req.patternParams.get() else: result = initTable[string, string]() when useHttpBeast: let query = req.req.path.get("").parseUri().query else: let query = req.req.url.query try: for key, val in cgi.decodeData(query): result[key] = val except CgiError: logging.warn("Incorrect query. Got: $1" % [query]) let contentType = req.headers.getOrDefault("Content-Type") if contentType.startswith("application/x-www-form-urlencoded"): try: parseUrlQuery(req.body, result) except: logging.warn("Could not parse URL query.") proc formData*(req: Request): MultiData = let contentType = req.headers.getOrDefault("Content-Type") if contentType.startsWith("multipart/form-data"): result = parseMPFD(contentType, req.body) proc matches*(req: Request): array[MaxSubpatterns, string] = req.reMatches proc secure*(req: Request): bool = if req.headers.hasKey("x-forwarded-proto"): let proto = req.headers["x-forwarded-proto"] case proto.toLowerAscii() of "https": result = true of "http": result = false else: logging.warn("Unknown x-forwarded-proto ", proto) proc port*(req: Request): int = if (let p = req.headers.getOrDefault("SERVER_PORT"); p != ""): result = p.parseInt else: result = if req.secure: 443 else: 80 proc host*(req: Request): string = req.headers.getOrDefault("HOST") proc appName*(req: Request): string = ## This is set by the user in ``run``, it is ## overriden by the "SCRIPT_NAME" scgi ## parameter. req.settings.appName proc stripAppName(path, appName: string): string = result = path if appname.len > 0: var slashAppName = appName if slashAppName[0] != '/' and path[0] == '/': slashAppName = '/' & slashAppName if path.startsWith(slashAppName): if slashAppName.len() == path.len: return "/" else: return path[slashAppName.len .. path.len-1] else: raise newException(ValueError, "Expected script name at beginning of path. Got path: " & path & " script name: " & slashAppName) proc pathInfo*(req: Request): string = ## This is ``.path`` without ``.appName``. req.path.stripAppName(req.appName) # TODO: Can cookie keys be duplicated? proc cookies*(req: Request): Table[string, string] = ## Cookies from the browser. if (let cookie = req.headers.getOrDefault("Cookie"); cookie != ""): result = parseCookies(cookie) else: result = initTable[string, string]() #[ Protected procs ]# proc initRequest*(req: NativeRequest, settings: Settings): Request {.inline.} = Request( req: req, settings: settings ) proc getNativeReq*(req: Request): NativeRequest = req.req #[ Only to be used by our route macro. ]# proc setPatternParams*(req: var Request, p: Table[string, string]) = req.patternParams = some(p) proc setReMatches*(req: var Request, r: array[MaxSubpatterns, string]) = req.reMatches = r