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