1 | static Set<S> blockedIPs = synchroHashSet(); // important: cleaned regularly in #1009210 |
2 | static Set<S> whiteListedIPs = synchroHashSet(); |
3 | |
4 | //static int blockedIPDelay = -1; // forever |
5 | static int blockedIPDelay = 60; // seconds |
6 | |
7 | //static int spamInterval = 60000; |
8 | //static double spamPerSecondThreshold = 2.0; // per IP + URI |
9 | |
10 | // for sub-bots to call through reflection |
11 | please include function serveRedirect. |
12 | please include function serve403. |
13 | please include function serve404. |
14 | please include function serve500. |
15 | please include function serveFile. |
16 | please include function serveFileWithName. |
17 | please include function serveFile_maxCache. |
18 | please include function serveByteArray. |
19 | please include function serveByteArray_maxCache. |
20 | |
21 | static volatile int hitCount; |
22 | |
23 | // These are speaking URLs like "/images/" |
24 | static new SS botNames; // see e.g. #1013896 |
25 | |
26 | static S anonymousDialogID = "web-anon"; |
27 | |
28 | static S webLogProgramID = "#1002576"; |
29 | static S webLog = "webLog"; |
30 | |
31 | static int recentWebLogMax = 1000; |
32 | static new LinkedList<Map> recentWebLog; |
33 | |
34 | static S homePageBotID = "#1002771"; |
35 | |
36 | static void webInit() { |
37 | readLocally("hitCount"); |
38 | readLocally("homePageBotID"); |
39 | //readLocally("blockedIPs"); |
40 | //set NanoHTTPD_debug; |
41 | } |
42 | |
43 | static new Object webLock; |
44 | |
45 | static NanoHTTPD.Response serve(S uri, NanoHTTPD.Method method, |
46 | Map<S,S> header, Map<S,S> params, Map<S,S> files) { |
47 | |
48 | final long startTime = sysNow(); |
49 | temp tempAfterwards(r { print(formatDateAndTime() + " | " + (sysNow()-startTime) + " ms") }); |
50 | |
51 | SS headers = getSession().getHeaders(); |
52 | S clientIP = headers.get("remote-addr"); |
53 | |
54 | Pair<Int, Bool> pair = simpleSpamClientDetect2(clientIP, uri); |
55 | //Pair<S> pair = pair(clientIP, uri); |
56 | //int ipCount = toInt(ipsAndUris.get(pair)); |
57 | //ipsAndUris.put(pair, ipCount+1); |
58 | |
59 | bool whiteListed = whiteListedIPs.contains(clientIP); |
60 | if (whiteListed) pair.b = false; |
61 | |
62 | bool bad = eq(uri, "/simulate-bad-client"); |
63 | //int spamMax = iround(spamPerSecondThreshold*spamInterval/1000); |
64 | int ipCount = pair.a; |
65 | |
66 | if (!bad && pair.b) { |
67 | print("Blocking IP " + clientIP + ", count reached: " + ipCount); |
68 | addBlockedIP(clientIP); |
69 | bad = true; |
70 | } |
71 | |
72 | if (blockedIPs.contains(clientIP)) { |
73 | bad = true; |
74 | if (blockedIPDelay < 0) |
75 | print(formatDateAndTime() + " | Blocked IP!! " + clientIP + " " + l(nanoHttpd_badClients())); |
76 | else { |
77 | print(formatDateAndTime() + " | Blocked IP!! " + clientIP + (blockedIPDelay != 0 ? " Delaying " + blockedIPDelay + "s" : "")); |
78 | //sleepSeconds(blockedIPDelay); |
79 | } |
80 | //ret serveHTML("No"); |
81 | } |
82 | |
83 | if (bad) { |
84 | NanoHTTPD.IHTTPSession httpSession = NanoHTTPD.currentSession!; |
85 | if (httpSession == null || blockedIPDelay >= 0) { |
86 | if (httpSession == null) print("No HTML session?"); |
87 | sleepSeconds(blockedIPDelay); |
88 | ret serve500("go away"); |
89 | } |
90 | httpSession.badClient(true); |
91 | null; |
92 | } |
93 | |
94 | S host = dropFrom(header.get("host"), ":"); |
95 | |
96 | print(gmtWithSeconds() + ": Serving HTTP " + quote(headers.get("host") + /*colonPortUnless80(subBot_currentPort())*/ + uri) + " to " + clientIP + " (anti-spam count: " + ipCount + "/" + simpleSpamClientDetect2_spamMax + (whiteListed ? ", white-listed" : "") + ")"); |
97 | |
98 | int hitID; |
99 | |
100 | synchronized(webLock) { |
101 | ++hitCount; |
102 | hitID = hitCount; |
103 | if ((hitCount % 1000) == 1) { |
104 | hitCount = (hitCount+999)/1000*1000; |
105 | saveLocally("hitCount"); |
106 | hitCount = hitID; |
107 | } |
108 | } |
109 | |
110 | pcall { webLog(litmap("time", now(), "request", hitID, "uri", uri, "method", str(method), "header", header, "params", params, "files", files)); } |
111 | |
112 | try { |
113 | NanoHTTPD.Response response = serveNoLog(uri, method, header, params, files); |
114 | |
115 | // log that we responded |
116 | pcall { webLog(litmap("time", now(), "response", hitID)); } |
117 | |
118 | ret response; |
119 | } catch (Throwable e) { |
120 | // log that there was an error |
121 | pcall { webLog(litmap("time", now(), "response-error", hitID, getStackTrace(e))); } |
122 | throw asRuntimeException(e); |
123 | } |
124 | } |
125 | |
126 | static NanoHTTPD.Response serveNoLog(S uri, NanoHTTPD.Method method, |
127 | Map<S,S> header, Map<S,S> params, Map<S,S> files) { |
128 | |
129 | actualURI.set(uri); |
130 | if (nempty(files)) |
131 | print("URI: " + uri + ", files: " + files); |
132 | uploadFiles.set(files); |
133 | if (!startsWith(uri, "/")) ret serve404("Bad URI"); |
134 | uri = dropPrefixMandatory("/", uri); |
135 | |
136 | if (!loaded) { |
137 | int numLoaded = l(bots), total = l(botIDs); |
138 | |
139 | // query dispatcher for number of loaded sub-bots |
140 | |
141 | S lbi = loadingBotID; |
142 | if (sameSnippetID(lbi, "#1002317")) { |
143 | int[] x = (int[]) callOpt(loadingBot, "numLoaded"); |
144 | if (x != null) { |
145 | numLoaded += x[0]; |
146 | total += x[1]; |
147 | } |
148 | |
149 | lbi = or((S) getOpt(loadingBot, "loadingBotID"), lbi); |
150 | } |
151 | |
152 | S s = "LOADING, PLEASE TRY AGAIN. " + numLoaded + "/" + total + " bots loaded "; |
153 | if (lbi != null) |
154 | s += " (loading bot " + formatSnippetID(lbi) /*+ " - " + getSnippetTitle_overBot(lbi)*/ + ")"; |
155 | |
156 | ret serve500(s); |
157 | } |
158 | |
159 | // count and set cookie |
160 | O visitorsBot = getBot("#1002157"); |
161 | if (visitorsBot != null) |
162 | callHtmlMethod(visitorsBot, "/"); |
163 | |
164 | setDialogIDForWeb(); |
165 | |
166 | if (eq(uri, "") || eq(uri, "raw")) |
167 | ret serveHomePage("/", params); |
168 | |
169 | S host = dropFrom(header.get("host"), ":"); |
170 | |
171 | if (domainIsUnderOneOf(host, "code.botcompany.de", "tomii.me")) |
172 | ret serveHomePage(uri, params); // Blog Bot handles it |
173 | |
174 | if (eq(uri, "favicon.ico")) |
175 | ret serveFile(loadLibrary(or2(trimLoadTextFile(javaxDataDir("eleu-favicon.txt")), #1101146)), "image/x-icon"); |
176 | |
177 | int i = uri.indexOf('/'); |
178 | if (i < 0) { uri += "/"; i = l(uri)-1; } |
179 | S firstPart = uri.substring(0, i); |
180 | |
181 | //print("uri: " + uri + ", i: " + i + ", firstPart: " + firstPart); |
182 | if (botNames.containsKey(firstPart)) { |
183 | uri = dropPrefix("#", botNames.get(firstPart)) |
184 | + addPrefix("/", substring(uri, i)); |
185 | i = uri.indexOf('/'); |
186 | firstPart = i >= 0 ? uri.substring(0, i) : uri; |
187 | //print("now firstPart: " + firstPart + ", uri: " + uri + ", i: " + i); |
188 | } |
189 | |
190 | S rest = i >= 0 ? substr(uri, i) : "/"; // rest always starts with "/" |
191 | |
192 | if (possibleGlobalID(toLower(firstPart))) { |
193 | ret serveBot(#1007510, "/raw/" + uri, params); |
194 | /*AIConcept c = aiConceptsMap_cached().get(toLower(firstPart)); |
195 | if (c != null) |
196 | ret serveHTML("Concept found: " + c.globalID + " - " + c.name); |
197 | else |
198 | ret serveHTML("Concept not found: " + toLower(firstPart));*/ |
199 | } |
200 | |
201 | if (!isInteger(firstPart)) |
202 | ret serveHomePage("/" + uri, params); |
203 | |
204 | ret serveBot(firstPart, rest, params); |
205 | } |
206 | |
207 | static NanoHTTPD.Response serveBot(S botID, S subUri, Map<S, S> params) { |
208 | sleepWhile(() -> isTrue_callOpt(getDispatcher(), 'isReloading, botID)); |
209 | |
210 | Class bot = getBot(botID); |
211 | boolean raw = subUri.equals("/raw") || subUri.startsWith("/raw/"); |
212 | if (raw) { |
213 | subUri = substr(subUri, "/raw".length()); |
214 | if (subUri.length() == 0) subUri = "/"; |
215 | } |
216 | if (bot != null) { |
217 | //print("Bot " + botID + " methods: " + structure(allMethodNames(bot))); |
218 | O botOut; |
219 | try { |
220 | botOut = callExtendedHtmlMethod(bot, subUri, params); |
221 | } catch e { |
222 | callOpt(bot, 'print, "Error while serving " + subUri + ": " + stackTraceToString(e)); |
223 | throw rethrow(e); |
224 | } |
225 | |
226 | if (botOut instanceof NanoHTTPD.Response) |
227 | ret (NanoHTTPD.Response) botOut; |
228 | if (eq(getClassName(botOut), NanoHTTPD.Response.class.getName())) |
229 | fail("Wrong realm"); |
230 | |
231 | S botHtml = str(botOut); |
232 | S html; |
233 | if (raw) html = unnull(botHtml); |
234 | else { |
235 | if (botHtml == null) |
236 | botHtml = "Bot has no HTML output."; |
237 | S name = getSnippetTitle(botID); |
238 | S title = htmlencode(name) + " [" + formatSnippetID(botID) + "]"; |
239 | |
240 | html = botHtml; |
241 | Pair<S> pTitle = hextracttag(html, 'title); |
242 | if (pairBNotNull(pTitle)) title = join(contentsOfContainerTag(htmlTok(pTitle.b))); |
243 | html = pTitle.a; |
244 | html = "<html><head><title>" + title + "</title></head>" + |
245 | "<body><h3>Bot <a href=\"" + snippetLink(botID) + "\">" + formatSnippetID(botID) + "</a> - " + htmlencode(name) + "</h3>\n" + botHtml + |
246 | "\n</body></html>"; |
247 | } |
248 | ret serveHTMLNoCache(html); |
249 | } |
250 | |
251 | ret serve404(); |
252 | } |
253 | |
254 | static NanoHTTPD.Response serveHomePage(S uri, Map<S, S> params) { |
255 | if (nempty(homePageBotID)) { |
256 | O bot = getBot(homePageBotID); |
257 | if (bot != null) /*pcall*/ { |
258 | ret serveHTMLNoCache(callHtmlMethod2(bot, uri, params)); |
259 | } |
260 | } |
261 | |
262 | ret serveHTML(botsListCache!); |
263 | } |
264 | |
265 | static TimedCache<S> botsListCache = new(r serveBotsList, 10.0); |
266 | |
267 | static S serveBotsList() { |
268 | new StringBuilder buf; |
269 | buf.append("<html>"); |
270 | buf.append("<head><title>TinyBrain Chat Bots</title></head>"); |
271 | buf.append("<body>"); |
272 | buf.append("<h3><a href=" + htmlQuote("https://botcompany.de") + ">TinyBrain</a> Bots</h3>"); |
273 | buf.append("<ul>"); |
274 | |
275 | L<S> botsToList = litlist(getProgramID()); |
276 | O dispatcher = getDispatcher(); |
277 | if (dispatcher != null) |
278 | botsToList.addAll((L) call(dispatcher, "getSubBotIDs")); |
279 | botsToList.addAll(activeBots); |
280 | |
281 | for (S botID : botsToList) { |
282 | botID = formatSnippetID(botID); |
283 | S parsedID = "" + parseSnippetID(botID); |
284 | S title = "?"; |
285 | pcall { title = getSnippetTitle_overBot(botID); } |
286 | S name = botID + " - " + htmlencode(title); |
287 | buf.append("<li><a href=" + htmlQuote(parsedID) + ">" + name + "</a> ("); |
288 | buf.append("<a href=" + htmlQuote(snippetLink(parsedID)) + ">source</a>)</li>"); |
289 | } |
290 | buf.append("</ul>"); |
291 | buf.append("<p>Hit count: " + hitCount + "</p>"); |
292 | |
293 | buf.append("<p>Last interactions:</p>"); |
294 | |
295 | int maxInteractionsToPrint = 10; |
296 | |
297 | for (int i = l(history)-1; i >= 0 && i >= l(history)-maxInteractionsToPrint; i--) { |
298 | Map m = history.get(i); |
299 | buf.append("<p>" + htmlencode(m.get("question")) + "<br> " + htmlencode(m.get("answer")) + "</p>"); |
300 | } |
301 | |
302 | buf.append("</body>"); |
303 | buf.append("</html>"); |
304 | ret str(buf); |
305 | } |
306 | |
307 | // for sub-bots to call |
308 | static NanoHTTPD.IHTTPSession getSession() { |
309 | ret NanoHTTPD.currentSession.get(); |
310 | } |
311 | |
312 | // for sub-bots to call |
313 | static O getCookies() { |
314 | NanoHTTPD.IHTTPSession session = NanoHTTPD.currentSession.get(); |
315 | NanoHTTPD.CookieHandler cookies = session.getCookies(); |
316 | ret cookies; |
317 | } |
318 | |
319 | static void setDialogIDForWeb() { |
320 | O session = getSession(); |
321 | O cookieHandler = getCookies(); |
322 | S cookie = cast call(cookieHandler, "read", "cookie"); |
323 | //print("Web answer cookie: " + cookie); |
324 | //print("Web answer question: " + quote(s)); |
325 | S dialogID = nempty(cookie) ? "web-" + hashCookie(cookie) : anonymousDialogID; |
326 | main.dialogID.set(dialogID); |
327 | } |
328 | |
329 | static S webAnswer(S s) { |
330 | ret webAnswer(s, true); |
331 | } |
332 | |
333 | static S webAnswer(S s, boolean log) { |
334 | setDialogIDForWeb(); |
335 | |
336 | originalLine.set(s); |
337 | |
338 | // drop the attn prefix ("!") |
339 | s = dropPrefix("!", s).trim(); |
340 | |
341 | attn.set(true); |
342 | dedicated.set(true); |
343 | // user name is empty for now |
344 | |
345 | S a = null; |
346 | pcall { |
347 | a = answer(s, false); // generalRelease = false, all bots |
348 | print("Web answer: " + quote(a)); |
349 | if (empty(a)) |
350 | a = "Hello :)"; |
351 | } |
352 | if (empty(a)) a = "(no response)"; |
353 | |
354 | if (log) { |
355 | S user = getDialogID(); |
356 | Map logEntry = litmap("question", s, "user", user, "answer", a); |
357 | historyAdd(logEntry); |
358 | } |
359 | |
360 | ret a; |
361 | } |
362 | |
363 | static S hashCookie(S cookie) { |
364 | ret md5(cookie).substring(0, 10); |
365 | } |
366 | |
367 | static void webLog(Map data) { |
368 | synchronized(webLock) { |
369 | logStructure(getProgramFile(webLogProgramID, webLog), data); |
370 | if (recentWebLogMax > 0) { |
371 | if (l(recentWebLog) >= recentWebLogMax) |
372 | recentWebLog.remove(0); |
373 | recentWebLog.add(data); |
374 | } |
375 | } |
376 | } |
377 | |
378 | static L<Map> getRecentWebLog() { |
379 | synchronized(webLock) { |
380 | ret cloneList(recentWebLog); |
381 | } |
382 | } |
383 | |
384 | static synchronized void addBlockedIP(S ip) { |
385 | blockedIPs.add(ip); |
386 | //save("blockedIPs"); |
387 | } |
388 | |
389 | static synchronized void removeBlockedIP(S ip) { |
390 | blockedIPs.remove(ip); |
391 | //save("blockedIPs"); |
392 | } |
393 | |
394 | static synchronized void clearBlockedIPs() { |
395 | blockedIPs.clear(); |
396 | } |
download show line numbers debug dex old transpilations
Travelled to 16 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, irmadwmeruwu, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv
No comments. add comment
Snippet ID: | #1002576 |
Snippet name: | Eleu Web Serving (Include) |
Eternal ID of this version: | #1002576/130 |
Text MD5: | 2883bbcc8b0d27fde6205483dc26bfde |
Author: | stefan |
Category: | eleu |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2022-03-30 18:46:01 |
Source code size: | 12289 bytes / 396 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 1187 / 2670 |
Version history: | 129 change(s) |
Referenced in: | [show references] |