commit c9329772b7d5427289ddb534b35bb2321f4bb50d
parent 05c65de8e553773ddd61438d9b77955b8669eee5
Author: ctucx <c@ctu.cx>
Date: Mon, 20 Apr 2020 02:51:40 +0200
parent 05c65de8e553773ddd61438d9b77955b8669eee5
Author: ctucx <c@ctu.cx>
Date: Mon, 20 Apr 2020 02:51:40 +0200
foo
2 files changed, 355 insertions(+), 149 deletions(-)
A
|
219
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M
|
285
++++++++++++++++++++++++++++++++++++++-----------------------------------------
diff --git a/src/asyncHttpServer.nim b/src/asyncHttpServer.nim @@ -0,0 +1,218 @@ +import asyncdispatch, asynchttpserver, macros +import strutils, parseutils +import cookies, uri, times, os, re, json +import tables + +export asyncdispatch, asynchttpserver, cookies, uri +export strutils, re +export tables, json + + +type + ServerRef* = ref object + port*: uint16 + address*: string + server*: AsyncHttpServer + +proc newServer* (address: string = "0.0.0.0", port: uint16 = 5000): ServerRef = + return ServerRef( + address: address, + port: port, + server: newAsyncHttpServer() + ) + +proc cookies*(req: Request): Table[string, string] = + result = initTable[string, string]() + + if (let cookie = req.headers.getOrDefault("Cookie"); cookie != ""): + var i = 0 + while true: + i += skipWhile(cookie, {' ', '\t'}, i) + var keystart = i + i += skipUntil(cookie, {'='}, i) + var keyend = i-1 + if i >= len(cookie): break + inc(i) # skip '=' + var valstart = i + i += skipUntil(cookie, {';'}, i) + result[substr(cookie, keystart, keyend)] = substr(cookie, valstart, i-1) + if i >= len(cookie): break + inc(i) # skip ';' + +proc parseQuery*(data: string): Table[string, string] = + result = initTable[string, string]() + + let data = data.split("&") + for i in data: + let timed = i.split("=") + if timed.len > 1: + result.add(decodeUrl(timed[0]), decodeUrl(timed[1])) + +proc daysForward*(days: int): DateTime = + return getTime().utc + initInterval(days = days) + +template setCookie* (key: string, value: string, time: DateTime) = + headers.add("Set-Cookie", setCookie(key, value, time, request.headers["host"], noName=true)) + +proc redirect* (request: Request, url: string) {.async.}= + await request.respond(Http303, "", newHttpHeaders([("Location", url)])) + +proc respondJson* (request: Request, httpCode: HttpCode, status: string, message: string, data: JsonNode) {.async.} = + let response = %* { + "status": status, + "msg": message, + "data": data + } + + await request.respond(httpCode, $response, newHttpHeaders([("Content-Type","application/json")])) + +macro pages*(server: ServerRef, body: untyped): untyped = + ## This macro provides convenient page adding. + ## + ## `body` should be StmtList. + ## page type can be: + ## - ``equals`` + ## - ``startsWith`` + ## - ``endsWith`` + ## - ``regex`` + ## - ``notfound`` - this page uses without URL argument. + ## + ## When a new request to the server is received, variables are automatically created: + ## - ``request`` - new Request. + ## - ``url`` - matched URL. + ## - ``equals`` - URL is request.url.path + ## - ``startsWith`` - URL is text after `startswith`. + ## - ``endsWith`` - URL is text before `endswith`. + ## - ``regex`` - `url` param not created. + ## - ``notfound`` - `url` param not created. + ## - ``decoded_url`` - URL always is request.url.path + var + stmtlist = newStmtList() + notfound_declaration = false + + stmtlist.add( + newNimNode(nnkLetSection).add( # let urlParams: JsonNode = await parseQuery(request) + newNimNode(nnkIdentDefs).add( # let decode_url: string = decodeUrl(request.url.path) + ident("decoded_url"), + ident("string"), + newCall( + "decodeUrl", + newNimNode(nnkDotExpr).add( + newNimNode(nnkDotExpr).add( + ident("request"), ident("url") + ), + ident("path") + ) + ) + ) + ) + ) + stmtlist.add(newNimNode(nnkIfStmt)) + var ifstmtlist = stmtlist[1] + + for i in body: # for each page in statment list. + let + current = if i.len == 3: $i[0] else: "equals" + path = if i.len == 3: i[1] else: i[0] + slist = if i.len == 3: i[2] else: i[1] + if (i.kind == nnkCall and + (path.kind == nnkStrLit or path.kind == nnkCallStrLit or path.kind == nnkEmpty) and + slist.kind == nnkStmtList): + if current == "equals": + slist.insert(0, # let url: string = `path` + newNimNode(nnkLetSection).add( + newNimNode(nnkIdentDefs).add( + ident("url"), ident("string"), path + ) + ) + ) + ifstmtlist.add( # decoded_url == `path` + newNimNode(nnkElifBranch).add( + newCall("==", path, ident("decoded_url")), + slist + ) + ) + elif current == "startsWith": + slist.insert(0, # let url = decoded_url[`path`.len..^1] + newNimNode(nnkLetSection).add( + newNimNode(nnkIdentDefs).add( + ident("url"), + ident("string"), + newCall( + "[]", + ident("decoded_url"), + newCall("..^", newCall("len", path), newLit(1)) + ) + ) + ) + ) + ifstmtlist.add( # decode_url.startsWith(`path`) + newNimNode(nnkElifBranch).add( + newCall("startsWith", ident("decoded_url"), path), + slist + ) + ) + elif current == "endsWith": + slist.insert(0, # let url: string = decoded_url[0..^`path`.len] + newNimNode(nnkLetSection).add( + newNimNode(nnkIdentDefs).add( + ident("url"), + ident("string"), + newCall( + "[]", + ident("decoded_url"), + newCall( + "..^", newLit(0), newCall("+", newLit(1), newCall("len", path)) + ) + ) + ) + ) + ) + ifstmtlist.add( # decode_url.endsWith(`path`) + newNimNode(nnkElifBranch).add( + newCall("endsWith", ident("decoded_url"), path), + slist + ) + ) + elif current == "regex": + ifstmtlist.add( # decode_url.match(`path`) + newNimNode(nnkElifBranch).add( + newCall("match", ident("decoded_url"), path), + slist)) + elif current == "notfound": + notfound_declaration = true + ifstmtlist.add(newNimNode(nnkElse).add(slist)) + + if not notfound_declaration: + ifstmtlist.add( + newNimNode(nnkElse).add( + newCall( # await request.respond(Http404, "Not found") + "await", + newCall("respond", ident("request"), ident("Http404"), newLit("Not found")) + ) + ) + ) + + result = newNimNode(nnkProcDef).add( + ident("receivepages"), # procedure name. + newEmptyNode(), # for template and macros + newEmptyNode(), # generics + newNimNode(nnkFormalParams).add( # proc params + newEmptyNode(), # return type + newNimNode(nnkIdentDefs).add( # param + ident("request"), # param name + ident("Request"), # param type + newEmptyNode() # param default value + ) + ), + newNimNode(nnkPragma).add( # pragma declaration + ident("async"), + ident("gcsafe") + ), + newEmptyNode(), + stmtlist) + +macro start*(server: ServerRef): untyped = + result = quote do: + echo "Server starts on http://", `server`.address, ":", `server`.port + waitFor `server`.server.serve(Port(`server`.port), receivepages, `server`.address)+ \ No newline at end of file
diff --git a/src/fb_exporter.nim b/src/fb_exporter.nim @@ -1,38 +1,25 @@ -import asynchttpserver -import asyncdispatch -import json -import strutils -import times +import times, posix +import asyncHttpServer -# config -var httpPort {.threadvar.}: uint16 -var authToken {.threadvar.}: string +proc CtrlCHook() {.noconv.} = + echo "Ctrl+C fired! \nStopping Server now!" + quit() -# state -var mobiled_lastUpdated:int64 = 0 -var mobiled_data {.threadvar.}: JsonNode - -var inetstat_lastUpdated:int64 = 0 -var inetstat_data {.threadvar.}: JsonNode - - -# main -proc isInt*(s: string): bool = +proc isInt* (s: string): bool = try: discard s.parseInt() result = true except: discard -proc isFloat*(s: string): bool = +proc isFloat* (s: string): bool = try: discard s.parseFloat() result = true except: discard - -proc parse_ctlmgr(n: string): JsonNode = +proc parseCtlMgr* (n: string): JsonNode = var input = n.split("\n") input.del(0) @@ -75,136 +62,135 @@ proc parse_ctlmgr(n: string): JsonNode = data[last_group].add(entry[0], %(value)) return data -proc processHttpClient(req: Request) {.async.} = - if req.reqMethod == HttpGet: - - if inetstat_data != nil or mobiled_data != nil: - if req.url.path == "/metrics": - var res = "" - - var total_transmit = 0 - var total_receive = 0 - var month_transmit = 0 - var month_receive = 0 - var today_transmit = 0 - var today_receive = 0 - - var upstream = 0 - var downstream = 0 - - var cell0_technology = "" - var cell0_quality = 0 - var cell0_band = 0 - var cell0_distance = 0 - - var cell1_technology = "" - var cell1_quality = 0 - var cell1_band = 0 - var cell1_distance = 0 - - if inetstat_lastUpdated != 0: - if inetstat_data.hasKey("Total0"): - #total - if inetstat_data["Total0"].hasKey("BytesSentHigh") and inetstat_data["Total0"].hasKey("BytesSentLow"): - total_transmit = inetstat_data["Total0"]["BytesSentHigh"].getInt() shl 32 + inetstat_data["Total0"]["BytesSentLow"].getInt() - if inetstat_data["Total0"].hasKey("BytesReceivedHigh") and inetstat_data["Total0"].hasKey("BytesReceivedLow"): - total_receive = inetstat_data["Total0"]["BytesReceivedHigh"].getInt() shl 32 + inetstat_data["Total0"]["BytesReceivedLow"].getInt() - - #month - if inetstat_data["ThisMonth0"].hasKey("BytesSentHigh") and inetstat_data["ThisMonth0"].hasKey("BytesSentLow"): - month_transmit = inetstat_data["ThisMonth0"]["BytesSentHigh"].getInt() shl 32 + inetstat_data["ThisMonth0"]["BytesSentLow"].getInt() - if inetstat_data["ThisMonth0"].hasKey("BytesReceivedHigh") and inetstat_data["ThisMonth0"].hasKey("BytesReceivedLow"): - month_receive = inetstat_data["ThisMonth0"]["BytesReceivedHigh"].getInt() shl 32 + inetstat_data["ThisMonth0"]["BytesReceivedLow"].getInt() - - #today - if inetstat_data["Today0"].hasKey("BytesSentHigh") and inetstat_data["Today0"].hasKey("BytesSentLow"): - today_transmit = inetstat_data["Today0"]["BytesSentHigh"].getInt() shl 32 + inetstat_data["Today0"]["BytesSentLow"].getInt() - if inetstat_data["Today0"].hasKey("BytesReceivedHigh") and inetstat_data["Today0"].hasKey("BytesReceivedLow"): - today_receive = inetstat_data["Today0"]["BytesReceivedHigh"].getInt() shl 32 + inetstat_data["Today0"]["BytesReceivedLow"].getInt() - - #total - res &= "fritzbox_network_transmit_bytes_total " & $(total_transmit) & " " & $(inetstat_lastUpdated * 1000) & "\n" - res &= "fritzbox_network_receive_bytes_total " & $(total_receive) & " " & $(inetstat_lastUpdated * 1000) & "\n" - - #month - res &= "fritzbox_network_transmit_bytes_month " & $(month_transmit) & " " & $(inetstat_lastUpdated * 1000) & "\n" - res &= "fritzbox_network_receive_bytes_month " & $(month_receive) & " " & $(inetstat_lastUpdated * 1000) & "\n" - - #today - res &= "fritzbox_network_transmit_bytes_today " & $(today_transmit) & " " & $(inetstat_lastUpdated * 1000) & "\n" - res &= "fritzbox_network_receive_bytes_today " & $(today_receive) & " " & $(inetstat_lastUpdated * 1000) & "\n" - - if mobiled_lastUpdated != 0: - if mobiled_data.hasKey("ue0"): - if mobiled_data["ue0"].hasKey("conn_rate_rx"): downstream = mobiled_data["ue0"]["conn_rate_rx"].getInt() - if mobiled_data["ue0"].hasKey("conn_rate_tx"): upstream = mobiled_data["ue0"]["conn_rate_tx"].getInt() - - if mobiled_data["ue0"].hasKey("conn_cell"): - let cells = mobiled_data["ue0"]["conn_cell"].getStr.split(",") - - var cell0 = "" - var cell1 = "" - - if cells[0] != "" and mobiled_data.hasKey("cell"&cells[0]): cell0 = "cell"&cells[0] - if cells[1] != "" and mobiled_data.hasKey("cell"&cells[1]): cell1 = "cell"&cells[1] - - if cell0 != "": - if mobiled_data[cell0].hasKey("technology"): cell0_technology = mobiled_data[cell0]["technology"].getStr() - if mobiled_data[cell0].hasKey("quality"): cell0_quality = mobiled_data[cell0]["quality"].getInt() - if mobiled_data[cell0].hasKey("band"): cell0_band = mobiled_data[cell0]["band"].getInt() - if mobiled_data[cell0].hasKey("distance"): cell0_distance = mobiled_data[cell0]["distance"].getInt() - - if cell1 != "": - if mobiled_data[cell1].hasKey("technology"): cell1_technology = mobiled_data[cell1]["technology"].getStr() - if mobiled_data[cell1].hasKey("quality"): cell1_quality = mobiled_data[cell1]["quality"].getInt() - if mobiled_data[cell1].hasKey("band"): cell1_band = mobiled_data[cell1]["band"].getInt() - if mobiled_data[cell1].hasKey("distance"): cell1_distance = mobiled_data[cell1]["distance"].getInt() - - - - res &= "fritzbox_network_downstram " & $(downstream) & " " & $(mobiled_lastUpdated * 1000) & "\n" - res &= "fritzbox_network_upstream " & $(upstream) & " " & $(mobiled_lastUpdated * 1000) & "\n" - - if cell0_band != 0: res &= "fritzbox_network_band{cell=\"0\"} " & $(cell0_band) & " " & $(mobiled_lastUpdated * 1000) & "\n" - if cell0_quality != 0: res &= "fritzbox_network_quality{cell=\"0\"} " & $(cell0_quality) & " " & $(mobiled_lastUpdated * 1000) & "\n" - if cell0_distance != 0: res &= "fritzbox_network_distance{cell=\"0\"} " & $(cell0_distance/1000) & " " & $(mobiled_lastUpdated * 1000) & "\n" - - if cell1_band != 0: res &= "fritzbox_network_band{cell=\"1\"} " & $(cell1_band) & " " & $(mobiled_lastUpdated * 1000) & "\n" - if cell1_quality != 0: res &= "fritzbox_network_quality{cell=\"1\"} " & $(cell1_quality) & " " & $(mobiled_lastUpdated * 1000) & "\n" - if cell1_distance != 0: res &= "fritzbox_network_distance{cell=\"1\"} " & $(cell1_distance/1000) & " " & $(mobiled_lastUpdated * 1000) & "\n" - - await req.respond(Http200, res) - elif req.url.path == "/inetstat.json": - await req.respond(Http200, $(%* inetstat_data)) - elif req.url.path == "/mobiled.json": - await req.respond(Http200, $(%* mobiled_data)) +proc getTxTraffic (data: JsonNode, mode: string): int = + if data[mode].hasKey("BytesSentHigh") and data[mode].hasKey("BytesSentLow"): + return data[mode]["BytesSentHigh"].getInt shl 32 + data[mode]["BytesSentLow"].getInt + +proc getRxTraffic (data: JsonNode, mode: string): int = + if data[mode].hasKey("BytesReceivedHigh") and data[mode].hasKey("BytesReceivedLow"): + return data[mode]["BytesReceivedHigh"].getInt shl 32 + data[mode]["BytesReceivedLow"].getInt + +proc prometheusResponse* (request: Request, state: JsonNode) {.async.} = + var res = "" + + if state["inetstat"]["lastUpdated"].getInt == 0 and state["mobiled"]["lastUpdated"].getInt == 0: + await request.respond(Http500, "500 No data yet") + + if state["inetstat"]["lastUpdated"].getInt != 0: + let data = state["inetstat"]["data"] + let lastUpdated = state["inetstat"]["lastUpdated"].getInt + + if data.hasKey("Total0"): + #total + res &= "fritzbox_network_transmit_bytes_total " & $(data.getTxTraffic("Total0")) & " " & $(lastUpdated * 1000) & "\n" + res &= "fritzbox_network_receive_bytes_total " & $(data.getRxTraffic("Total0")) & " " & $(lastUpdated * 1000) & "\n" + + #month + res &= "fritzbox_network_transmit_bytes_month " & $(data.getTxTraffic("ThisMonth0")) & " " & $(lastUpdated * 1000) & "\n" + res &= "fritzbox_network_receive_bytes_month " & $(data.getRxTraffic("ThisMonth0")) & " " & $(lastUpdated * 1000) & "\n" + + #today + res &= "fritzbox_network_transmit_bytes_today " & $(data.getTxTraffic("Today0")) & " " & $(lastUpdated * 1000) & "\n" + res &= "fritzbox_network_receive_bytes_today " & $(data.getRxTraffic("Today0")) & " " & $(lastUpdated * 1000) & "\n" + + + if state["mobiled"]["lastUpdated"].getInt != 0: + let data = state["mobiled"]["data"] + let lastUpdated = state["mobiled"]["lastUpdated"].getInt() + + if data.hasKey("ue0"): + if data["ue0"].hasKey("conn_rate_rx"): + let downstream = data["ue0"]["conn_rate_rx"].getInt() + res &= "fritzbox_network_downstram " & $(downstream) & " " & $(lastUpdated * 1000) & "\n" + + + if data["ue0"].hasKey("conn_rate_tx"): + let upstream = data["ue0"]["conn_rate_tx"].getInt() + res &= "fritzbox_network_upstream " & $(upstream) & " " & $(lastUpdated * 1000) & "\n" + + + if data["ue0"].hasKey("conn_cell"): + template parseCell(data: JsonNode, num: int) = + let cells = data["ue0"]["conn_cell"].getStr.split(",") + + if cells[num] != "" and data.hasKey("cell"&cells[num]): + let cell = "cell"&cells[num] + + if cell != "": + if data[cell].hasKey("technology"): + let cell_technology = data[cell]["technology"].getStr + + if data[cell].hasKey("quality"): + let cell_quality = data[cell]["quality"].getInt + res &= "fritzbox_network_quality{cell=\"" & $num & "\"} " & $(cell_quality) & " " & $(lastUpdated * 1000) & "\n" + + if data[cell].hasKey("band"): + let cell_band = data[cell]["band"].getInt + res &= "fritzbox_network_band{cell=\"" & $num & "\"} " & $(cell_band) & " " & $(lastUpdated * 1000) & "\n" + + if data[cell].hasKey("distance"): + let cell_distance = data[cell]["distance"].getInt + res &= "fritzbox_network_distance{cell=\"" & $num & "\"} " & $(cell_distance/1000) & " " & $(lastUpdated * 1000) & "\n" + + data.parseCell(0) + data.parseCell(1) + + await request.respond(Http200, res) + +proc main = # for gcsafe + setControlCHook(CtrlCHook) + + onSignal(SIGTERM): + echo "Got SIGTERM! \nStopping Server now!" + quit() + + let authToken = "penis123" + var server = newServer() # launch on http://localhost:5000 + var state = %* { + "mobiled": { + "data": {}, + "lastUpdated": 0, + }, + "mobiled": { + "data": {}, + "lastUpdated": 0, + } + } + + server.pages: + equals("/metrics"): + await request.prometheusResponse(state) + + equals("/inetstat.json"): + if state["inetstat"]["lastUpdated"].getInt != 0: + await request.respondJson(Http200, "success", "", state["inetstat"]["data"]) else: - await req.respond(Http404, "404 Not found") - else: - await req.respond(Http500, "500 No data yet") - - elif req.reqMethod == HttpPost: - if req.url.query == authToken: - if req.url.path == "/update/inetstat": - inetstat_data = parse_ctlmgr(req.body) - inetstat_lastUpdated = toUnix(getTime()) - await req.respond(Http200, "Noted, thanks") - elif req.url.path == "/update/mobiled": - mobiled_data = parse_ctlmgr(req.body) - mobiled_lastUpdated = toUnix(getTime()) - await req.respond(Http200, "Noted, thanks") + await request.respondJson(Http404, "error", "no data yet", newJObject()) + + equals("/mobiled.json"): + if state["mobiled"]["lastUpdated"].getInt != 0: + await request.respondJson(Http200, "success", "", state["mobiled"]["data"]) else: - await req.respond(Http404, "404 Not found") - else: - await req.respond(Http401, "401 Unauthorized") + await request.respondJson(Http404, "error", "no data yet", newJObject()) + + startsWith("/update/"): + if request.reqMethod == HttpPost: + if request.url.query == authToken: + if url == "inetstat": + state["inetstat"]["data"] = parseCtlMgr(request.body) + state["inetstat"]["lastUpdated"] = %toUnix(getTime()) + await request.respond(Http200, "Noted, thanks") + + elif url == "mobiled": + state["mobiled"]["data"] = parseCtlMgr(request.body) + state["mobiled"]["lastUpdated"] = %toUnix(getTime()) + await request.respond(Http200, "Noted, thanks") - else: - await req.respond(Http405, "405 Method Not Allowed") + else: + await request.respond(Http404, "404 Not found") + else: + await request.respond(Http401, "401 Unauthorized") -proc serveHttp*() {.async.} = - httpPort = 1234 - authToken = "penis123" - var httpServer = newAsyncHttpServer() - await httpServer.serve(Port(httpPort), processHttpClient) + server.start() -waitFor serveHttp() +main()+ \ No newline at end of file