Uses 1658K of libraries. Click here for Pure Java version (31666L/210K).
1 | !7 |
2 | |
3 | // for sub-bots |
4 | please include function serveRedirect. |
5 | please include function serve403. |
6 | please include function serve404. |
7 | please include function serve500. |
8 | please include function serveFile. |
9 | please include function serveFileWithName. |
10 | please include function serveFile_maxCache. |
11 | please include function serveByteArray. |
12 | please include function serveByteArray_maxCache. |
13 | please include function serveInputStream. |
14 | please include function servePartialContent. |
15 | please include function serveRangeNotSatisfiable. |
16 | |
17 | sclass WebSocketWithWebRequest extends WebSocket { |
18 | WebRequest request; |
19 | |
20 | *(NanoHTTPD.IHTTPSession session, WebRequest *request) { |
21 | super(session); |
22 | } |
23 | |
24 | IWebRequest webRequest() { ret request; } |
25 | } |
26 | |
27 | sclass WebRequest implements IWebRequest { |
28 | NanoHTTPD.IHTTPSession httpSession; |
29 | S uri, subURI; |
30 | SS params, files; |
31 | S cookie, clientIP; |
32 | Session session; |
33 | bool isHttps, noSpam; |
34 | |
35 | *(NanoHTTPD.IHTTPSession *httpSession, S *uri, SS *params) { |
36 | files = httpSession.getFiles(); |
37 | clientIP = getClientIPFromHeaders(httpSession.getHeaders()); |
38 | } |
39 | |
40 | public S uri() { ret uri; } |
41 | public SS params() { ret params; } |
42 | public SS files() { ret files; } |
43 | |
44 | public SS headers() { ret httpSession.getHeaders(); } |
45 | |
46 | public S cookie() { ret cookie; } |
47 | |
48 | public bool isHttps() { ret isHttps; } |
49 | |
50 | public bool isPost() { ret httpSession.getMethod() == NanoHTTPD.Method.POST; } |
51 | |
52 | User loggedInUser() { |
53 | ret session == null ? null : session.user(); |
54 | } |
55 | |
56 | S googleClientID() { |
57 | S domain = lower(domain()); |
58 | File jsonFile = googleClientSecretFileForDomain(domain); |
59 | if (!fileExists(jsonFile)) null; |
60 | |
61 | Map map = decodeJSONMap(loadTextFile(jsonFile)); |
62 | map = (Map) map.get("web"); |
63 | ret (S) map.get("client_id"); |
64 | } |
65 | |
66 | public void noSpam { |
67 | if (noSpam) ret; |
68 | set noSpam; |
69 | print("noSpam count: " + simpleSpamClientDetect2_markNoSpam(clientIP, uri) + " (" + clientIP + "/" + uri + ")"); |
70 | } |
71 | } |
72 | |
73 | concept User > ConceptWithGlobalID { |
74 | long lastSeen; |
75 | } |
76 | |
77 | concept CookieUser > User { |
78 | S cookie; |
79 | |
80 | toString { ret "[cookieUser " + md5(cookie) + "]"; } |
81 | } |
82 | |
83 | concept GoogleUser > User { |
84 | long googleLogInDate; |
85 | S googleEmail, googleFirstName, googleLastName; |
86 | bool googleEmailVerified; |
87 | |
88 | toString { ret googleFirstName + " " + googleLastName; } |
89 | } |
90 | |
91 | concept Session { |
92 | S cookie; |
93 | User user; |
94 | long accesses; |
95 | |
96 | User user() { |
97 | if (user == null) |
98 | cset(this, user := uniq CookieUser(+cookie)); |
99 | ret user; |
100 | } |
101 | } |
102 | |
103 | abstract sclass DynEleu extends DynPrintLogAndEnabled { |
104 | !include #1029492 // HTTP+HTTPS servers with WebSockets |
105 | |
106 | double clearSessionsInterval = 60.0; |
107 | double clearSessionsTimeout = 300.0; |
108 | switchable double maxModuleReloadWait = 20.0; |
109 | switchable double strayWebSocketDelay = 5.0; |
110 | |
111 | // Eleu-wide sessions... I don't think anyone uses these anymore |
112 | switchable bool useSessions; |
113 | |
114 | switchable bool shareCookiesBetweenApexDomains = true; |
115 | |
116 | // for noticing certificate changes in JavaX-Secret |
117 | transient FileWatchService watchService; |
118 | |
119 | void start { |
120 | super.start(); |
121 | dbIndexing(Session, 'cookie, CookieUser, 'cookie, GoogleUser, 'googleEmail); |
122 | dm_restartOnFieldChange enabled(); |
123 | if (!enabled) ret; |
124 | dm_startThread("Start Web Servers", r { |
125 | start_webServers(serverSocketFactory_botCompanyEtc()); |
126 | onWebServersStarted(); |
127 | }); |
128 | dm_doEvery(clearSessionsInterval, r clearSessions); |
129 | |
130 | ownResource(serverSocketFactory_autoUpdate()); |
131 | } |
132 | |
133 | void clearSessions { |
134 | Cl<Session> l = filter(list(Session), |
135 | s -> s.accesses <= 1 && elapsedSeconds_timestamp(s.created) >= clearSessionsTimeout); |
136 | if (nempty(l)) { |
137 | print("Dropping " + nSessions(l)); |
138 | deleteConcepts(l); |
139 | } |
140 | } |
141 | |
142 | WebSocket newWebSocket(NanoHTTPD.IHTTPSession handshake) enter { |
143 | vmBus_send haveNewWebSocket(module(), handshake); |
144 | S domain = mapGet(handshake.getHeaders(), "host"); |
145 | S uri = handshake.getUri(); |
146 | SS params = handshake.getParms(); |
147 | print("Making WebSocket. domain: " + domain + ", uri: " + uri); |
148 | WebRequest req = createWebRequest(handshake, uri, params); |
149 | WebSocket ws = new WebSocketWithWebRequest(handshake, req); |
150 | |
151 | S module = moduleForDomainAndURI(domain, uri); |
152 | if (nempty(module) && !dm_moduleIsStartingOrReloading(module)) pcall { |
153 | dm_callOpt(module, 'handleWebSocket, ws); |
154 | } else { |
155 | sleepSeconds(strayWebSocketDelay); |
156 | ws.close(); // Hopefully just a reload |
157 | } |
158 | |
159 | ret ws; |
160 | } |
161 | |
162 | O webServe(S uri, SS params) { |
163 | WebRequest req = new(NanoHTTPD.currentSession!, uri, params); |
164 | print("IP: " + req.clientIP); |
165 | |
166 | Pair<Int, Bool> spamCheck = spamBlocker.checkRequest(req.uri, req.clientIP); |
167 | if (spamCheck.b) { |
168 | print("Request from blocked IP: " + req.clientIP); |
169 | sleepSeconds(60.0); |
170 | ret print("go away"); |
171 | } |
172 | |
173 | // fill more fields of WebRequest |
174 | |
175 | fillWebRequest(req); |
176 | |
177 | // log some |
178 | |
179 | printVars("webServe", +uri, userAgent := req.userAgent()); |
180 | |
181 | // Serve Let's encrypt challenges |
182 | |
183 | if (startsWith(uri, "/.well-known/")) { |
184 | S fileName = "validation.txt"; |
185 | /*if (swic(req.domain(), "www.")) |
186 | fileName = "validation-www.txt";*/ |
187 | ret loadTextFile(userDir(fileName)); |
188 | } |
189 | |
190 | // Serve Google verification |
191 | |
192 | ifndef NoGoogleVerification |
193 | if (eqic(uri, "/google-verify")) |
194 | ret serveGoogleVerify(req); |
195 | endifndef |
196 | |
197 | S module = moduleForDomainAndURI(req.domain(), uri); |
198 | if (nempty(module)) { |
199 | if (sleepWhile(() -> dm_moduleIsStartingOrReloading(module), max := maxModuleReloadWait)) |
200 | ret subBot_serve500("Reload taking too long"); |
201 | ret eleu_callModuleHTMLMethod(module, req); |
202 | } |
203 | |
204 | ret subBot_serve404(); |
205 | } |
206 | |
207 | // override me |
208 | S moduleForDomain(S domain) { null; } |
209 | |
210 | // or me |
211 | S moduleForDomainAndURI(S domain, S uri) { ret moduleForDomain(domain); } |
212 | |
213 | ifndef NoGoogleVerification |
214 | O serveGoogleVerify(WebRequest req) { |
215 | Payload payload = googleVerifyUserToken2(req.googleClientID(), req.params.get("token")); |
216 | S email = payload == null ? null : payload.getEmail(); |
217 | if (empty(email)) ret print("google-verify", "No"); |
218 | |
219 | // create/update an object for the user |
220 | GoogleUser user = uniqCI_sync GoogleUser(googleEmail := email); |
221 | cset(user, |
222 | googleEmailVerified := payload.getEmailVerified(), |
223 | googleFirstName := strOrNull(payload.get("given_name")), |
224 | googleLastName := strOrNull(payload.get("family_name")), |
225 | googleLogInDate := now()); |
226 | |
227 | // link user to session |
228 | cset(req.session, +user); |
229 | |
230 | ret print("google-verify", payload.getEmail() + " " + (payload.getEmailVerified() ? "(verified)" : "(not verified)")); |
231 | } |
232 | endifndef |
233 | |
234 | ServeHttp_CookieHandler makeCookieHandler() { |
235 | new ServeHttp_CookieHandler h; |
236 | h.verbose = true; |
237 | h.shareCookiesBetweenApexDomains = shareCookiesBetweenApexDomains; |
238 | ret h; |
239 | } |
240 | |
241 | void fillWebRequest(WebRequest req) { |
242 | req.cookie = makeCookieHandler().handle(); |
243 | |
244 | req.isHttps = WebSocketHTTPD_current! == httpsServer; |
245 | |
246 | // Get session |
247 | req.session = nempty(req.cookie) && useSessions ? uniq Session(cookie := req.cookie) : null; |
248 | if (req.session != null) |
249 | cset(req.session, accesses := req.session.accesses+1); |
250 | print(session := req.session); |
251 | } |
252 | |
253 | void enhanceFrame(Container c) { |
254 | super.enhanceFrame(c); |
255 | internalFrameMenuItem(c, "Show blocked IPs", rEnterThread { |
256 | print("Blocked IPs: " + spamBlocker.blockedIPs); |
257 | }); |
258 | } |
259 | |
260 | void onWebServersStarted() {} |
261 | |
262 | WebRequest createWebRequest(NanoHTTPD.IHTTPSession httpSession, |
263 | S uri, SS params) { |
264 | ret new WebRequest(httpSession, uri, params); |
265 | } |
266 | } // end of module |
Began life as a copy of #1028671
download show line numbers debug dex old transpilations
Travelled to 7 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv
No comments. add comment
Snippet ID: | #1029543 |
Snippet name: | DynEleu [web server as module] |
Eternal ID of this version: | #1029543/63 |
Text MD5: | 425b62a37a1b22e72b45bd79778f1cb6 |
Transpilation MD5: | cfff9e7f6b6deee143f0ae79a9755588 |
Author: | stefan |
Category: | javax |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2021-10-04 01:29:35 |
Source code size: | 7990 bytes / 266 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 466 / 995 |
Version history: | 62 change(s) |
Referenced in: | [show references] |