Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -1992,10 +1992,11 @@ */ int cgi_http_server( int mnPort, int mxPort, /* Range of TCP ports to try */ const char *zBrowser, /* Run this browser, if not NULL */ const char *zIpAddr, /* Bind to this IP address, if not null */ + int iIdleTimeout, /* Stop after this many seconds of inactivity */ int flags /* HTTP_SERVER_* flags */ ){ #if defined(_WIN32) /* Use win32_http_server() instead */ fossil_exit(1); @@ -2008,11 +2009,12 @@ int child; /* PID of the child process */ int nchildren = 0; /* Number of child processes */ struct timeval delay; /* How long to wait inside select() */ struct sockaddr_in inaddr; /* The socket address */ int opt = 1; /* setsockopt flag */ - int iPort = mnPort; + int iPort = mnPort; /* TCP port to use */ + time_t stopTime = 0; /* When to timeout */ while( iPort<=mxPort ){ memset(&inaddr, 0, sizeof(inaddr)); inaddr.sin_family = AF_INET; if( zIpAddr ){ @@ -2070,10 +2072,11 @@ #endif if( system(zBrowser)<0 ){ fossil_warning("cannot start browser: %s\n", zBrowser); } } + if( iIdleTimeout>0 ) stopTime = time(0)+iIdleTimeout; while( 1 ){ #if FOSSIL_MAX_CONNECTIONS>0 while( nchildren>=FOSSIL_MAX_CONNECTIONS ){ if( wait(0)>=0 ) nchildren--; } @@ -2086,10 +2089,11 @@ select( listener+1, &readfds, 0, 0, &delay); if( FD_ISSET(listener, &readfds) ){ lenaddr = sizeof(inaddr); connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr); if( connection>=0 ){ + if( iIdleTimeout>0 ) stopTime = time(0)+iIdleTimeout; child = fork(); if( child!=0 ){ if( child>0 ){ nchildren++; nRequest++; @@ -2125,10 +2129,14 @@ fprintf(stderr, "/***** Child %d exited on signal %d (%s) *****/\n", x, WTERMSIG(iStatus), strsignal(WTERMSIG(iStatus))); } nchildren--; } + } + /* Stop serving if idle for too long */ + if( iIdleTimeout>0 && stopTime style_footer(); } +/* +** WEBPAGE: noop +** +** Send back an empty HTTP reply. Deliver no content. +*/ +void noop_page(void){ + fossil_free(style_csp(1)); + cgi_set_content_type("text/plain"); +} + /* ** Set the g.zBaseURL value to the full URL for the toplevel of ** the fossil tree. Set g.zTop to g.zBaseURL without the ** leading "http://" and the host and port. @@ -2361,10 +2371,11 @@ ** --files GLOB comma-separate glob patterns for static file to serve ** --host NAME specify hostname of the server ** --https signal a request coming in via https ** --in FILE Take input from FILE instead of standard input ** --ipaddr ADDR Assume the request comes from the given IP address +** --keep-alive Include "keepalive.js" in HTML pages ** --localauth enable automatic login for local connections ** --nocompress do not compress HTTP replies ** --nodelay omit backoffice processing if it would delay process exit ** --nojail drop root privilege but do not enter the chroot jail ** --nossl signal that no SSL connections are available @@ -2440,10 +2451,11 @@ zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */ cgi_replace_parameter("HTTPS","on"); } zHost = find_option("host", 0, 1); if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost); + if( find_option("keep-alive",0,0) ) style_load_js("keepalive.js"); #if defined(_WIN32) && USE_SEE zPidKey = find_option("usepidkey", 0, 1); if( zPidKey ){ DWORD processId = 0; @@ -2624,10 +2636,13 @@ ** Options: ** --baseurl URL Use URL as the base (useful for reverse proxies) ** --create Create a new REPOSITORY if it does not already exist ** --extroot DIR Document root for the /ext extension mechanism ** --files GLOBLIST Comma-separated list of glob patterns for static files +** --idle-timeout N Exit if no HTTP requests are received for N seconds. +** "0" means never. 0 is default for the "server" +** command and "60" is the default for the "ui" command. ** --localauth enable automatic login for requests from localhost ** --localhost listen on 127.0.0.1 only (always true for "ui") ** --https Indicates that the input is coming through a reverse ** proxy that has already translated HTTPS into HTTP. ** --max-latency N Do not let any single HTTP request run for more than N @@ -2663,10 +2678,12 @@ int allowRepoList; /* List repositories on URL "/" */ const char *zAltBase; /* Argument to the --baseurl option */ const char *zFileGlob; /* Static content must match this */ char *zIpAddr = 0; /* Bind to this IP address */ int fCreate = 0; /* The --create flag */ + const char *zIdleTimeout; /* Value of the --idle-timeout flag */ + int iIdle = 0; /* Idle timeout value */ const char *zInitPage = 0; /* Start on this page. --page option */ #if defined(_WIN32) && USE_SEE const char *zPidKey; #endif @@ -2696,11 +2713,17 @@ Th_InitTraceLog(); zPort = find_option("port", "P", 1); isUiCmd = g.argv[1][0]=='u'; if( isUiCmd ){ zInitPage = find_option("page", 0, 1); + iIdle = 60; + } + zIdleTimeout = find_option("idle-timeout",0,1); + if( zIdleTimeout ){ + iIdle = atoi(zIdleTimeout); } + zNotFound = find_option("notfound", 0, 1); allowRepoList = find_option("repolist",0,0)!=0; if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1; zAltBase = find_option("baseurl", 0, 1); fCreate = find_option("create",0,0)!=0; @@ -2794,11 +2817,11 @@ } } if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY; if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT; db_close(1); - if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, flags) ){ + if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, iIdle, flags) ){ fossil_fatal("unable to listen on TCP socket %d", iPort); } /* For the parent process, the cgi_http_server() command above never ** returns (except in the case of an error). Instead, for each incoming ** client connection, a child process is created, file descriptors 0 @@ -2834,10 +2857,11 @@ if( flags & HTTP_SERVER_SCGI ){ cgi_handle_scgi_request(); }else{ cgi_handle_http_request(0); } + if( iIdle>0 ) style_load_js("keepalive.js"); process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); if( g.fAnyTrace ){ fprintf(stderr, "/***** Webpage finished in subprocess %d *****/\n", getpid()); } @@ -2862,11 +2886,11 @@ if( allowRepoList ){ flags |= HTTP_SERVER_REPOLIST; } if( win32_http_service(iPort, zAltBase, zNotFound, zFileGlob, flags) ){ win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile, - zAltBase, zNotFound, zFileGlob, zIpAddr, flags); + zAltBase, zNotFound, zFileGlob, zIpAddr, iIdle, flags); } #endif } /* Index: src/main.mk ================================================================== --- src/main.mk +++ src/main.mk @@ -218,10 +218,11 @@ $(SRCDIR)/copybtn.js \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/graph.js \ $(SRCDIR)/href.js \ + $(SRCDIR)/keepalive.js \ $(SRCDIR)/login.js \ $(SRCDIR)/markdown.md \ $(SRCDIR)/menu.js \ $(SRCDIR)/sbsdiff.js \ $(SRCDIR)/scroll.js \ Index: src/winhttp.c ================================================================== --- src/winhttp.c +++ src/winhttp.c @@ -199,16 +199,18 @@ } return 1; }; /* -** Accepts connections on DualSocket. +** Accepts connections on DualSocket. Return */ -static void DualSocket_accept(DualSocket* pListen, DualSocket* pClient, +static int DualSocket_accept(DualSocket* pListen, DualSocket* pClient, DualAddr* pClientAddr){ fd_set rs; int rs_count = 0; + int rc = 0; + struct timeval delay; assert( pListen!=NULL && pClient!=NULL && pClientAddr!= NULL ); DualSocket_init(pClient); DualAddr_init(pClientAddr); FD_ZERO(&rs); if( pListen->s4!=INVALID_SOCKET ){ @@ -217,21 +219,22 @@ } if( pListen->s6!=INVALID_SOCKET ){ FD_SET(pListen->s6, &rs); ++rs_count; } - if( select(rs_count, &rs, 0, 0, 0 /*blocking*/)==SOCKET_ERROR ){ - return; - } + delay.tv_sec = 2; + delay.tv_usec = 0; + rc = select(rs_count, &rs, 0, 0, &delay); if( FD_ISSET(pListen->s4, &rs) ){ pClient->s4 = accept(pListen->s4, (struct sockaddr*)&pClientAddr->a4.addr, &pClientAddr->a4.len); } if( FD_ISSET(pListen->s6, &rs) ){ pClient->s6 = accept(pListen->s6, (struct sockaddr*)&pClientAddr->a6.addr, &pClientAddr->a6.len); } + return rc; } /* ** The HttpServer structure holds information about an instance of ** the HTTP server itself. @@ -514,15 +517,16 @@ ** that socket. */ void win32_http_server( int mnPort, int mxPort, /* Range of allowed TCP port numbers */ const char *zBrowser, /* Command to launch browser. (Or NULL) */ - const char *zStopper, /* Stop server when this file is exists (Or NULL) */ + const char *zStopper, /* Stop server when this file exists (Or NULL) */ const char *zBaseUrl, /* The --baseurl option, or NULL */ const char *zNotFound, /* The --notfound option, or NULL */ const char *zFileGlob, /* The --fileglob option, or NULL */ const char *zIpAddr, /* Bind to this IP address, if not NULL */ + int iIdleTimeout, /* Idle timeout in seconds. 0 means none */ int flags /* One or more HTTP_SERVER_ flags */ ){ HANDLE hStoppedEvent; WSADATA wd; DualSocket ds; @@ -529,10 +533,11 @@ int idCnt = 0; int iPort = mnPort; Blob options; wchar_t zTmpPath[MAX_PATH]; const char *zSkin; + time_t stopTime = 0; /* When to stop due to idle timeout */ #if USE_SEE const char *zSavedKey = 0; size_t savedKeySize = 0; #endif @@ -553,10 +558,13 @@ blob_appendf(&options, " --localauth"); } if( g.thTrace ){ blob_appendf(&options, " --th-trace"); } + if( iIdleTimeout>0 ){ + blob_appendf(&options, " --keep-alive"); + } if( flags & HTTP_SERVER_REPOLIST ){ blob_appendf(&options, " --repolist"); } zSkin = skin_in_use(); if( zSkin ){ @@ -632,18 +640,23 @@ _beginthread(win32_server_stopper, 0, (void*)pServer); } /* Set the service status to running and pass the listener socket to the ** service handling procedures. */ win32_http_service_running(&ds); + if( iIdleTimeout>0 ) stopTime = time(0) + iIdleTimeout; for(;;){ DualSocket client; DualAddr client_addr; HttpRequest *pRequest; int wsaError; + int nSock; - DualSocket_accept(&ds, &client, &client_addr); - if( client.s4==INVALID_SOCKET && client.s6==INVALID_SOCKET ){ + nSock = DualSocket_accept(&ds, &client, &client_addr); + if( nSock==SOCKET_ERROR + && client.s4==INVALID_SOCKET + && client.s6==INVALID_SOCKET + ){ /* If the service control handler has closed the listener socket, ** cleanup and return, otherwise report a fatal error. */ wsaError = WSAGetLastError(); DualSocket_close(&ds); if( (wsaError==WSAEINTR) || (wsaError==WSAENOTSOCK) ){ @@ -659,10 +672,11 @@ pRequest->id = ++idCnt; pRequest->s = client.s4; memcpy(&pRequest->addr, &client_addr.a4, sizeof(client_addr.a4)); pRequest->flags = flags; pRequest->zOptions = blob_str(&options); + if( iIdleTimeout>0 ) stopTime = time(0) + iIdleTimeout; if( flags & HTTP_SERVER_SCGI ){ _beginthread(win32_scgi_request, 0, (void*)pRequest); }else{ _beginthread(win32_http_request, 0, (void*)pRequest); } @@ -672,16 +686,18 @@ pRequest->id = ++idCnt; pRequest->s = client.s6; memcpy(&pRequest->addr, &client_addr.a6, sizeof(client_addr.a6)); pRequest->flags = flags; pRequest->zOptions = blob_str(&options); + if( iIdleTimeout>0 ) stopTime = time(0) + iIdleTimeout; if( flags & HTTP_SERVER_SCGI ){ _beginthread(win32_scgi_request, 0, (void*)pRequest); }else{ _beginthread(win32_http_request, 0, (void*)pRequest); } } + if( iIdleTimeout>0 && stopTime