!7 // for sub-bots please include function serveRedirect. please include function serve403. please include function serve404. please include function serve500. please include function serveFile. please include function serveFileWithName. please include function serveFile_maxCache. please include function serveByteArray. please include function serveByteArray_maxCache. please include function serveInputStream. sclass WebSocketWithWebRequest extends WebSocket { WebRequest request; *(NanoHTTPD.IHTTPSession session, WebRequest *request) { super(session); } IWebRequest webRequest() { ret request; } } sclass WebRequest implements IWebRequest { NanoHTTPD.IHTTPSession httpSession; S uri, subURI; SS params, files; S cookie, clientIP; Session session; bool isHttps, noSpam; *(NanoHTTPD.IHTTPSession *httpSession, S *uri, SS *params) { files = httpSession.getFiles(); clientIP = getClientIPFromHeaders(httpSession.getHeaders()); } public S uri() { ret uri; } public SS params() { ret params; } public SS files() { ret files; } public SS headers() { ret httpSession.getHeaders(); } public S cookie() { ret cookie; } public bool isHttps() { ret isHttps; } User loggedInUser() { ret session == null ? null : session.user(); } S googleClientID() { S domain = lower(domain()); File jsonFile = googleClientSecretFileForDomain(domain); if (!fileExists(jsonFile)) null; Map map = decodeJSONMap(loadTextFile(jsonFile)); map = (Map) map.get("web"); ret (S) map.get("client_id"); } public void noSpam { if (noSpam) ret; set noSpam; //print("noSpam count: " + simpleSpamClientDetect2_markNoSpam(clientIP, uri) + " (" + clientIP + "/" + uri + ")"); } } concept User > ConceptWithGlobalID { long lastSeen; } concept CookieUser > User { S cookie; toString { ret "[cookieUser " + md5(cookie) + "]"; } } concept GoogleUser > User { long googleLogInDate; S googleEmail, googleFirstName, googleLastName; bool googleEmailVerified; toString { ret googleFirstName + " " + googleLastName; } } concept Session { S cookie; User user; long accesses; User user() { if (user == null) cset(this, user := uniq CookieUser(+cookie)); ret user; } } abstract sclass DynEleuMultiIP extends DynPrintLogAndEnabled { transient autoDispose Set httpServers = syncLinkedHashSet(); switchable int httpPort = 8000; double clearSessionsInterval = 60.0; double clearSessionsTimeout = 300.0; switchable double maxModuleReloadWait = 20.0; switchable double strayWebSocketDelay = 5.0; // Eleu-wide sessions... I don't think anyone uses these anymore switchable bool useSessions; void start { super.start(); dbIndexing(Session, 'cookie, CookieUser, 'cookie, GoogleUser, 'googleEmail); dm_restartOnFieldChange enabled(); if (!enabled) ret; dm_startThread("Start Web Servers", r { start_webServers(ipsToBindTo()); }); dm_doEvery(clearSessionsInterval, r clearSessions); //ownResource(serverSocketFactory_autoUpdate()); } void clearSessions { Cl l = filter(list(Session), s -> s.accesses <= 1 && elapsedSeconds_timestamp(s.created) >= clearSessionsTimeout); if (nempty(l)) { print("Dropping " + nSessions(l)); deleteConcepts(l); } } WebSocket newWebSocket(NanoHTTPD.IHTTPSession handshake) enter { vmBus_send haveNewWebSocket(module(), handshake); S domain = mapGet(handshake.getHeaders(), "host"); S uri = handshake.getUri(); SS params = handshake.getParms(); print("Making WebSocket. domain: " + domain + ", uri: " + uri); WebRequest req = new(handshake, uri, params); WebSocket ws = new WebSocketWithWebRequest(handshake, req); S module = moduleForDomainAndURI(domain, uri); if (nempty(module) && !dm_moduleIsStartingOrReloading(module)) pcall { dm_callOpt(module, 'handleWebSocket, ws); } else { sleepSeconds(strayWebSocketDelay); ws.close(); // Hopefully just a reload } ret ws; } O webServe(S uri, SS params) { WebRequest req = new(NanoHTTPD.currentSession!, uri, params); print("IP: " + req.clientIP); /*Pair spamCheck = spamBlocker.checkRequest(req.uri, req.clientIP); if (spamCheck.b) { print("Request from blocked IP: " + req.clientIP); sleepSeconds(60.0); ret print("go away"); }*/ // fill more fields of WebRequest fillWebRequest(req); // log some printVars("webServe", +uri, userAgent := req.userAgent()); // Serve Let's encrypt challenges if (startsWith(uri, "/.well-known/")) { S fileName = "validation.txt"; /*if (swic(req.domain(), "www.")) fileName = "validation-www.txt";*/ ret loadTextFile(userDir(fileName)); } S module = moduleForDomainAndURI(req.domain(), uri); if (nempty(module)) { if (sleepWhile(() -> dm_moduleIsStartingOrReloading(module), max := maxModuleReloadWait)) ret subBot_serve500("Reload taking too long"); ret eleu_callModuleHTMLMethod(module, req); } ret subBot_serve404(); } // override me S moduleForDomain(S domain) { null; } // or me S moduleForDomainAndURI(S domain, S uri) { ret moduleForDomain(domain); } ServeHttp_CookieHandler makeCookieHandler() { ret nu ServeHttp_CookieHandler(verbose := true); } void fillWebRequest(WebRequest req) { req.cookie = makeCookieHandler().handle(); req.isHttps = false; // Get session req.session = nempty(req.cookie) && useSessions ? uniq Session(cookie := req.cookie) : null; if (req.session != null) cset(req.session, accesses := req.session.accesses+1); print(session := req.session); } /*void enhanceFrame(Container c) { super.enhanceFrame(c); internalFrameMenuItem(c, "Show blocked IPs", rEnterThread { print("Blocked IPs: " + spamBlocker.blockedIPs); }); }*/ // bind to all non-public IPs by default LS ipsToBindTo() { ret myLocalIPs(); } void start_webServers(LS ips) { if (empty(ips)) ret with print("No IPs to bind to"); forEach_pcall(ips, ip -> { var httpd = new WebSocketHTTPD(ip, httpPort, lambda1 newWebSocket); httpd.enter = lambda0 enter; httpd.serveFunction = func(S uri, SS parms) { webServe(uri, parms) }; ctex { httpd.start(); } httpServers.add(httpd); print("Web server started at http://" + ip + ":" + httpd.getPort()); }); } } // end of module