Libraryless. Click here for Pure Java version (29713L/194K).
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 | |
15 | sclass WebSocketWithWebRequest extends WebSocket { |
16 | WebRequest request; |
17 | |
18 | *(NanoHTTPD.IHTTPSession session, WebRequest *request) { |
19 | super(session); |
20 | } |
21 | |
22 | IWebRequest webRequest() { ret request; } |
23 | } |
24 | |
25 | sclass WebRequest implements IWebRequest { |
26 | NanoHTTPD.IHTTPSession httpSession; |
27 | S uri, subURI; |
28 | SS params, files; |
29 | S cookie, clientIP; |
30 | Session session; |
31 | bool isHttps, noSpam; |
32 | |
33 | *(NanoHTTPD.IHTTPSession *httpSession, S *uri, SS *params) { |
34 | files = httpSession.getFiles(); |
35 | clientIP = getClientIPFromHeaders(httpSession.getHeaders()); |
36 | } |
37 | |
38 | public S uri() { ret uri; } |
39 | public SS params() { ret params; } |
40 | public SS files() { ret files; } |
41 | |
42 | public SS headers() { ret httpSession.getHeaders(); } |
43 | |
44 | public S cookie() { ret cookie; } |
45 | |
46 | public bool isHttps() { ret isHttps; } |
47 | |
48 | User loggedInUser() { |
49 | ret session == null ? null : session.user(); |
50 | } |
51 | |
52 | S googleClientID() { |
53 | S domain = lower(domain()); |
54 | File jsonFile = googleClientSecretFileForDomain(domain); |
55 | if (!fileExists(jsonFile)) null; |
56 | |
57 | Map map = decodeJSONMap(loadTextFile(jsonFile)); |
58 | map = (Map) map.get("web"); |
59 | ret (S) map.get("client_id"); |
60 | } |
61 | |
62 | public void noSpam { |
63 | if (noSpam) ret; |
64 | set noSpam; |
65 | //print("noSpam count: " + simpleSpamClientDetect2_markNoSpam(clientIP, uri) + " (" + clientIP + "/" + uri + ")"); |
66 | } |
67 | } |
68 | |
69 | concept User > ConceptWithGlobalID { |
70 | long lastSeen; |
71 | } |
72 | |
73 | concept CookieUser > User { |
74 | S cookie; |
75 | |
76 | toString { ret "[cookieUser " + md5(cookie) + "]"; } |
77 | } |
78 | |
79 | concept GoogleUser > User { |
80 | long googleLogInDate; |
81 | S googleEmail, googleFirstName, googleLastName; |
82 | bool googleEmailVerified; |
83 | |
84 | toString { ret googleFirstName + " " + googleLastName; } |
85 | } |
86 | |
87 | concept Session { |
88 | S cookie; |
89 | User user; |
90 | long accesses; |
91 | |
92 | User user() { |
93 | if (user == null) |
94 | cset(this, user := uniq CookieUser(+cookie)); |
95 | ret user; |
96 | } |
97 | } |
98 | |
99 | abstract sclass DynEleuMultiIP extends DynPrintLogAndEnabled { |
100 | transient autoDispose Set<WebSocketHTTPD> httpServers = syncLinkedHashSet(); |
101 | switchable int httpPort = 8000; |
102 | |
103 | double clearSessionsInterval = 60.0; |
104 | double clearSessionsTimeout = 300.0; |
105 | switchable double maxModuleReloadWait = 20.0; |
106 | switchable double strayWebSocketDelay = 5.0; |
107 | |
108 | // Eleu-wide sessions... I don't think anyone uses these anymore |
109 | switchable bool useSessions; |
110 | |
111 | void start { |
112 | super.start(); |
113 | dbIndexing(Session, 'cookie, CookieUser, 'cookie, GoogleUser, 'googleEmail); |
114 | dm_restartOnFieldChange enabled(); |
115 | if (!enabled) ret; |
116 | dm_startThread("Start Web Servers", r { |
117 | start_webServers(ipsToBindTo()); |
118 | }); |
119 | dm_doEvery(clearSessionsInterval, r clearSessions); |
120 | |
121 | //ownResource(serverSocketFactory_autoUpdate()); |
122 | } |
123 | |
124 | void clearSessions { |
125 | Cl<Session> l = filter(list(Session), |
126 | s -> s.accesses <= 1 && elapsedSeconds_timestamp(s.created) >= clearSessionsTimeout); |
127 | if (nempty(l)) { |
128 | print("Dropping " + nSessions(l)); |
129 | deleteConcepts(l); |
130 | } |
131 | } |
132 | |
133 | WebSocket newWebSocket(NanoHTTPD.IHTTPSession handshake) enter { |
134 | vmBus_send haveNewWebSocket(module(), handshake); |
135 | S domain = mapGet(handshake.getHeaders(), "host"); |
136 | S uri = handshake.getUri(); |
137 | SS params = handshake.getParms(); |
138 | print("Making WebSocket. domain: " + domain + ", uri: " + uri); |
139 | WebRequest req = new(handshake, uri, params); |
140 | WebSocket ws = new WebSocketWithWebRequest(handshake, req); |
141 | |
142 | S module = moduleForDomainAndURI(domain, uri); |
143 | if (nempty(module) && !dm_moduleIsStartingOrReloading(module)) pcall { |
144 | dm_callOpt(module, 'handleWebSocket, ws); |
145 | } else { |
146 | sleepSeconds(strayWebSocketDelay); |
147 | ws.close(); // Hopefully just a reload |
148 | } |
149 | |
150 | ret ws; |
151 | } |
152 | |
153 | O webServe(S uri, SS params) { |
154 | WebRequest req = new(NanoHTTPD.currentSession!, uri, params); |
155 | print("IP: " + req.clientIP); |
156 | |
157 | /*Pair<Int, Bool> spamCheck = spamBlocker.checkRequest(req.uri, req.clientIP); |
158 | if (spamCheck.b) { |
159 | print("Request from blocked IP: " + req.clientIP); |
160 | sleepSeconds(60.0); |
161 | ret print("go away"); |
162 | }*/ |
163 | |
164 | // fill more fields of WebRequest |
165 | |
166 | fillWebRequest(req); |
167 | |
168 | // log some |
169 | |
170 | printVars("webServe", +uri, userAgent := req.userAgent()); |
171 | |
172 | // Serve Let's encrypt challenges |
173 | |
174 | if (startsWith(uri, "/.well-known/")) { |
175 | S fileName = "validation.txt"; |
176 | /*if (swic(req.domain(), "www.")) |
177 | fileName = "validation-www.txt";*/ |
178 | ret loadTextFile(userDir(fileName)); |
179 | } |
180 | |
181 | S module = moduleForDomainAndURI(req.domain(), uri); |
182 | if (nempty(module)) { |
183 | if (sleepWhile(() -> dm_moduleIsStartingOrReloading(module), max := maxModuleReloadWait)) |
184 | ret subBot_serve500("Reload taking too long"); |
185 | ret eleu_callModuleHTMLMethod(module, req); |
186 | } |
187 | |
188 | ret subBot_serve404(); |
189 | } |
190 | |
191 | // override me |
192 | S moduleForDomain(S domain) { null; } |
193 | |
194 | // or me |
195 | S moduleForDomainAndURI(S domain, S uri) { ret moduleForDomain(domain); } |
196 | |
197 | ServeHttp_CookieHandler makeCookieHandler() { |
198 | ret nu ServeHttp_CookieHandler(verbose := true); |
199 | } |
200 | |
201 | void fillWebRequest(WebRequest req) { |
202 | req.cookie = makeCookieHandler().handle(); |
203 | req.isHttps = false; |
204 | |
205 | // Get session |
206 | req.session = nempty(req.cookie) && useSessions ? uniq Session(cookie := req.cookie) : null; |
207 | if (req.session != null) |
208 | cset(req.session, accesses := req.session.accesses+1); |
209 | print(session := req.session); |
210 | } |
211 | |
212 | /*void enhanceFrame(Container c) { |
213 | super.enhanceFrame(c); |
214 | internalFrameMenuItem(c, "Show blocked IPs", rEnterThread { |
215 | print("Blocked IPs: " + spamBlocker.blockedIPs); |
216 | }); |
217 | }*/ |
218 | |
219 | // bind to all non-public IPs by default |
220 | LS ipsToBindTo() { |
221 | ret myLocalIPs(); |
222 | } |
223 | |
224 | void start_webServers(LS ips) { |
225 | if (empty(ips)) |
226 | ret with print("No IPs to bind to"); |
227 | |
228 | forEach_pcall(ips, ip -> { |
229 | var httpd = new WebSocketHTTPD(ip, httpPort, lambda1 newWebSocket); |
230 | httpd.enter = lambda0 enter; |
231 | httpd.serveFunction = func(S uri, SS parms) { webServe(uri, parms) }; |
232 | |
233 | ctex { httpd.start(); } |
234 | httpServers.add(httpd); |
235 | print("Web server started at http://" + ip + ":" + httpd.getPort()); |
236 | }); |
237 | } |
238 | } // end of module |
Began life as a copy of #1029543
download show line numbers debug dex old transpilations
Travelled to 3 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx
No comments. add comment
Snippet ID: | #1032107 |
Snippet name: | DynEleuMultiIP for multiple IPs, no https (e.g. in LAN) |
Eternal ID of this version: | #1032107/20 |
Text MD5: | b5392dbc99baed6abefedbdd044868c6 |
Transpilation MD5: | cf89701f52b57d0cc3ea07932c1ae1b4 |
Author: | stefan |
Category: | javax |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2021-08-14 01:33:12 |
Source code size: | 6899 bytes / 238 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 183 / 345 |
Version history: | 19 change(s) |
Referenced in: | [show references] |