Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

266
LINES

< > BotCompany Repo | #1029543 // DynEleu [web server as module]

JavaX fragment (include) [tags: use-pretranspiled]

Uses 1658K of libraries. Click here for Pure Java version (31666L/210K).

!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.
please include function servePartialContent.
please include function serveRangeNotSatisfiable.

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; }
  
  public bool isPost() { ret httpSession.getMethod() == NanoHTTPD.Method.POST; }
  
  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 DynEleu extends DynPrintLogAndEnabled {
  !include #1029492 // HTTP+HTTPS servers with WebSockets
  
  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;
  
  switchable bool shareCookiesBetweenApexDomains = true;
  
  // for noticing certificate changes in JavaX-Secret
  transient FileWatchService watchService;
  
  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(serverSocketFactory_botCompanyEtc());
      onWebServersStarted();
    });
    dm_doEvery(clearSessionsInterval, r clearSessions);
    
    ownResource(serverSocketFactory_autoUpdate());
  }
  
  void clearSessions {
    Cl<Session> 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 = createWebRequest(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<Int, Bool> 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));
    }
      
    // Serve Google verification
    
    ifndef NoGoogleVerification
    if (eqic(uri, "/google-verify"))
      ret serveGoogleVerify(req);
    endifndef
      
    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); }

  ifndef NoGoogleVerification
  O serveGoogleVerify(WebRequest req) {
    Payload payload = googleVerifyUserToken2(req.googleClientID(), req.params.get("token"));
    S email = payload == null ? null : payload.getEmail();
    if (empty(email)) ret print("google-verify", "No");
    
    // create/update an object for the user
    GoogleUser user = uniqCI_sync GoogleUser(googleEmail := email);
    cset(user,
      googleEmailVerified := payload.getEmailVerified(),
      googleFirstName := strOrNull(payload.get("given_name")),
      googleLastName := strOrNull(payload.get("family_name")),
      googleLogInDate := now());
    
    // link user to session
    cset(req.session, +user);
      
    ret print("google-verify", payload.getEmail() + " " + (payload.getEmailVerified() ? "(verified)" : "(not verified)"));
  }
  endifndef

  ServeHttp_CookieHandler makeCookieHandler() {
    new ServeHttp_CookieHandler h;
    h.verbose = true;
    h.shareCookiesBetweenApexDomains = shareCookiesBetweenApexDomains;
    ret h;
  }

  void fillWebRequest(WebRequest req) {  
    req.cookie = makeCookieHandler().handle();
    
    req.isHttps = WebSocketHTTPD_current! == httpsServer;
    
    // 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);
    });
  }

  void onWebServersStarted() {}
  
  WebRequest createWebRequest(NanoHTTPD.IHTTPSession httpSession,
    S uri, SS params) {
    ret new WebRequest(httpSession, uri, params);
  }
} // end of module

Author comment

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: 467 / 995
Version history: 62 change(s)
Referenced in: #1032107 - DynEleuMultiIP for multiple IPs, no https (e.g. in LAN)
#1034167 - Standard Classes + Interfaces (LIVE, continuation of #1003674)