static Set blockedIPs = synchroHashSet(); // important: cleaned regularly in #1009210 static Set whiteListedIPs = synchroHashSet(); //static int blockedIPDelay = -1; // forever static int blockedIPDelay = 60; // seconds //static int spamInterval = 60000; //static double spamPerSecondThreshold = 2.0; // per IP + URI // for sub-bots to call through reflection please include function serveRedirect. please include function serve403. meta-comment { please include function serve404. please include function serve500. please include function serveFile. please include function serveFileWithName. please include function serveFile_maxCache. please include function serveByteArray. please include function serveByteArray_maxCache. static volatile int hitCount; // These are speaking URLs like "/images/" static new SS botNames; // see e.g. #1013896 static S anonymousDialogID = "web-anon"; static S webLogProgramID = "#1002576"; static S webLog = "webLog"; static int recentWebLogMax = 1000; static new LinkedList recentWebLog; static S homePageBotID = "#1002771"; static void webInit() { readLocally("hitCount"); readLocally("homePageBotID"); //readLocally("blockedIPs"); //set NanoHTTPD_debug; } static new Object webLock; static NanoHTTPD.Response serve(S uri, NanoHTTPD.Method method, Map header, Map params, Map files) { final long startTime = sysNow(); temp tempAfterwards(r { print(formatDateAndTime() + " | " + (sysNow()-startTime) + " ms") }); SS headers = getSession().getHeaders(); S clientIP = headers.get("remote-addr"); Pair pair = simpleSpamClientDetect2(clientIP, uri); //Pair pair = pair(clientIP, uri); //int ipCount = toInt(ipsAndUris.get(pair)); //ipsAndUris.put(pair, ipCount+1); bool whiteListed = whiteListedIPs.contains(clientIP); if (whiteListed) pair.b = false; bool bad = eq(uri, "/simulate-bad-client"); //int spamMax = iround(spamPerSecondThreshold*spamInterval/1000); int ipCount = pair.a; if (!bad && pair.b) { print("Blocking IP " + clientIP + ", count reached: " + ipCount); addBlockedIP(clientIP); bad = true; } if (blockedIPs.contains(clientIP)) { bad = true; if (blockedIPDelay < 0) print(formatDateAndTime() + " | Blocked IP!! " + clientIP + " " + l(nanoHttpd_badClients())); else { print(formatDateAndTime() + " | Blocked IP!! " + clientIP + (blockedIPDelay != 0 ? " Delaying " + blockedIPDelay + "s" : "")); //sleepSeconds(blockedIPDelay); } //ret serveHTML("No"); } if (bad) { NanoHTTPD.IHTTPSession httpSession = NanoHTTPD.currentSession!; if (httpSession == null || blockedIPDelay >= 0) { if (httpSession == null) print("No HTML session?"); sleepSeconds(blockedIPDelay); ret serve500("go away"); } httpSession.badClient(true); null; } S host = dropFrom(header.get("host"), ":"); print(gmtWithSeconds() + ": Serving HTTP " + quote(headers.get("host") + /*colonPortUnless80(subBot_currentPort())*/ + uri) + " to " + clientIP + " (anti-spam count: " + ipCount + "/" + simpleSpamClientDetect2_spamMax + (whiteListed ? ", white-listed" : "") + ")"); int hitID; synchronized(webLock) { ++hitCount; hitID = hitCount; if ((hitCount % 1000) == 1) { hitCount = (hitCount+999)/1000*1000; saveLocally("hitCount"); hitCount = hitID; } } pcall { webLog(litmap("time", now(), "request", hitID, "uri", uri, "method", str(method), "header", header, "params", params, "files", files)); } try { NanoHTTPD.Response response = serveNoLog(uri, method, header, params, files); // log that we responded pcall { webLog(litmap("time", now(), "response", hitID)); } ret response; } catch (Throwable e) { // log that there was an error pcall { webLog(litmap("time", now(), "response-error", hitID, getStackTrace(e))); } throw asRuntimeException(e); } } static NanoHTTPD.Response serveNoLog(S uri, NanoHTTPD.Method method, Map header, Map params, Map files) { actualURI.set(uri); if (nempty(files)) print("URI: " + uri + ", files: " + files); uploadFiles.set(files); if (!startsWith(uri, "/")) ret serve404("Bad URI"); uri = dropPrefixMandatory("/", uri); if (!loaded) { int numLoaded = l(bots), total = l(botIDs); // query dispatcher for number of loaded sub-bots S lbi = loadingBotID; if (sameSnippetID(lbi, "#1002317")) { int[] x = (int[]) callOpt(loadingBot, "numLoaded"); if (x != null) { numLoaded += x[0]; total += x[1]; } lbi = or((S) getOpt(loadingBot, "loadingBotID"), lbi); } S s = "LOADING, PLEASE TRY AGAIN. " + numLoaded + "/" + total + " bots loaded "; if (lbi != null) s += " (loading bot " + formatSnippetID(lbi) /*+ " - " + getSnippetTitle_overBot(lbi)*/ + ")"; ret serve500(s); } // count and set cookie O visitorsBot = getBot("#1002157"); if (visitorsBot != null) callHtmlMethod(visitorsBot, "/"); setDialogIDForWeb(); if (eq(uri, "") || eq(uri, "raw")) ret serveHomePage("/", params); S host = dropFrom(header.get("host"), ":"); if (domainIsUnderOneOf(host, "code.botcompany.de", "tomii.me")) ret serveHomePage(uri, params); // Blog Bot handles it if (eq(uri, "favicon.ico")) ret serveFile(loadLibrary(or2(trimLoadTextFile(javaxDataDir("eleu-favicon.txt")), #1101146)), "image/x-icon"); int i = uri.indexOf('/'); if (i < 0) { uri += "/"; i = l(uri)-1; } S firstPart = uri.substring(0, i); //print("uri: " + uri + ", i: " + i + ", firstPart: " + firstPart); if (botNames.containsKey(firstPart)) { uri = dropPrefix("#", botNames.get(firstPart)) + addPrefix("/", substring(uri, i)); i = uri.indexOf('/'); firstPart = i >= 0 ? uri.substring(0, i) : uri; //print("now firstPart: " + firstPart + ", uri: " + uri + ", i: " + i); } S rest = i >= 0 ? substr(uri, i) : "/"; // rest always starts with "/" if (possibleGlobalID(toLower(firstPart))) { ret serveBot(#1007510, "/raw/" + uri, params); /*AIConcept c = aiConceptsMap_cached().get(toLower(firstPart)); if (c != null) ret serveHTML("Concept found: " + c.globalID + " - " + c.name); else ret serveHTML("Concept not found: " + toLower(firstPart));*/ } if (!isInteger(firstPart)) ret serveHomePage("/" + uri, params); ret serveBot(firstPart, rest, params); } static NanoHTTPD.Response serveBot(S botID, S subUri, Map params) { sleepWhile(() -> isTrue_callOpt(getDispatcher(), 'isReloading, botID)); Class bot = getBot(botID); boolean raw = subUri.equals("/raw") || subUri.startsWith("/raw/"); if (raw) { subUri = substr(subUri, "/raw".length()); if (subUri.length() == 0) subUri = "/"; } if (bot != null) { //print("Bot " + botID + " methods: " + structure(allMethodNames(bot))); O botOut; try { botOut = callExtendedHtmlMethod(bot, subUri, params); } catch e { callOpt(bot, 'print, "Error while serving " + subUri + ": " + stackTraceToString(e)); throw rethrow(e); } if (botOut instanceof NanoHTTPD.Response) ret (NanoHTTPD.Response) botOut; if (eq(getClassName(botOut), NanoHTTPD.Response.class.getName())) fail("Wrong realm"); S botHtml = str(botOut); S html; if (raw) html = unnull(botHtml); else { if (botHtml == null) botHtml = "Bot has no HTML output."; S name = getSnippetTitle(botID); S title = htmlencode(name) + " [" + formatSnippetID(botID) + "]"; html = botHtml; Pair pTitle = hextracttag(html, 'title); if (pairBNotNull(pTitle)) title = join(contentsOfContainerTag(htmlTok(pTitle.b))); html = pTitle.a; html = "" + title + "" + "

Bot " + formatSnippetID(botID) + " - " + htmlencode(name) + "

\n" + botHtml + "\n"; } ret serveHTMLNoCache(html); } ret serve404(); } static NanoHTTPD.Response serveHomePage(S uri, Map params) { if (nempty(homePageBotID)) { O bot = getBot(homePageBotID); if (bot != null) /*pcall*/ { ret serveHTMLNoCache(callHtmlMethod2(bot, uri, params)); } } ret serveHTML(botsListCache!); } static TimedCache botsListCache = new(f serveBotsList, 10.0); static S serveBotsList() { new StringBuilder buf; buf.append(""); buf.append("TinyBrain Chat Bots"); buf.append(""); buf.append("

TinyBrain Bots

"); buf.append("
    "); L botsToList = litlist(getProgramID()); O dispatcher = getDispatcher(); if (dispatcher != null) botsToList.addAll((L) call(dispatcher, "getSubBotIDs")); botsToList.addAll(activeBots); for (S botID : botsToList) { botID = formatSnippetID(botID); S parsedID = "" + parseSnippetID(botID); S title = "?"; pcall { title = getSnippetTitle_overBot(botID); } S name = botID + " - " + htmlencode(title); buf.append("
  • " + name + " ("); buf.append("source)
  • "); } buf.append("
"); buf.append("

Hit count: " + hitCount + "

"); buf.append("

Last interactions:

"); int maxInteractionsToPrint = 10; for (int i = l(history)-1; i >= 0 && i >= l(history)-maxInteractionsToPrint; i--) { Map m = history.get(i); buf.append("

" + htmlencode(m.get("question")) + "
    " + htmlencode(m.get("answer")) + "

"); } buf.append(""); buf.append(""); ret str(buf); } // for sub-bots to call static NanoHTTPD.IHTTPSession getSession() { ret NanoHTTPD.currentSession.get(); } // for sub-bots to call static O getCookies() { NanoHTTPD.IHTTPSession session = NanoHTTPD.currentSession.get(); NanoHTTPD.CookieHandler cookies = session.getCookies(); ret cookies; } static void setDialogIDForWeb() { O session = getSession(); O cookieHandler = getCookies(); S cookie = cast call(cookieHandler, "read", "cookie"); //print("Web answer cookie: " + cookie); //print("Web answer question: " + quote(s)); S dialogID = nempty(cookie) ? "web-" + hashCookie(cookie) : anonymousDialogID; main.dialogID.set(dialogID); } static S webAnswer(S s) { ret webAnswer(s, true); } static S webAnswer(S s, boolean log) { setDialogIDForWeb(); originalLine.set(s); // drop the attn prefix ("!") s = dropPrefix("!", s).trim(); attn.set(true); dedicated.set(true); // user name is empty for now S a = null; pcall { a = answer(s, false); // generalRelease = false, all bots print("Web answer: " + quote(a)); if (empty(a)) a = "Hello :)"; } if (empty(a)) a = "(no response)"; if (log) { S user = getDialogID(); Map logEntry = litmap("question", s, "user", user, "answer", a); historyAdd(logEntry); } ret a; } static S hashCookie(S cookie) { ret md5(cookie).substring(0, 10); } static void webLog(Map data) { synchronized(webLock) { logStructure(getProgramFile(webLogProgramID, webLog), data); if (recentWebLogMax > 0) { if (l(recentWebLog) >= recentWebLogMax) recentWebLog.remove(0); recentWebLog.add(data); } } } static L getRecentWebLog() { synchronized(webLock) { ret cloneList(recentWebLog); } } static synchronized void addBlockedIP(S ip) { blockedIPs.add(ip); //save("blockedIPs"); } static synchronized void removeBlockedIP(S ip) { blockedIPs.remove(ip); //save("blockedIPs"); } static synchronized void clearBlockedIPs() { blockedIPs.clear(); } } // end of comment