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: | 709 / 1297 |
| Version history: | 62 change(s) |
| Referenced in: | [show references] |