git-instaweb: add Python builtin http.server support
With this patch it is possible to launch git-instaweb by using Python http.server CGI handler via `-d python` option. git-instaweb generates a small wrapper around the http.server (in GIT_DIR/gitweb/) that address a limitation of the CGI handler where CGI scripts have to be in a cgi-bin subdirectory and directory index can't be easily changed. To keep the implementation small, gitweb is running on url `/cgi-bin/gitweb.cgi` and an automatic redirection is done when opening `/`. The generated wrapper is compatible with both Python 2 and 3. Python is by default installed on most modern Linux distributions which enables running `git instaweb -d python` without needing anything else. Signed-off-by: Arti Zirk <arti.zirk@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
							parent
							
								
									16a465bc01
								
							
						
					
					
						commit
						2eb14bb2d4
					
				|  | @ -29,7 +29,8 @@ OPTIONS | |||
| 	The HTTP daemon command-line that will be executed. | ||||
| 	Command-line options may be specified here, and the | ||||
| 	configuration file will be added at the end of the command-line. | ||||
| 	Currently apache2, lighttpd, mongoose, plackup and webrick are supported. | ||||
| 	Currently apache2, lighttpd, mongoose, plackup, python and | ||||
| 	webrick are supported. | ||||
| 	(Default: lighttpd) | ||||
|  | ||||
| -m:: | ||||
|  |  | |||
							
								
								
									
										127
									
								
								git-instaweb.sh
								
								
								
								
							
							
						
						
									
										127
									
								
								git-instaweb.sh
								
								
								
								
							|  | @ -67,6 +67,13 @@ resolve_full_httpd () { | |||
| 		httpd_only="${httpd%% *}" # cut on first space | ||||
| 		return | ||||
| 		;; | ||||
| 	*python*) | ||||
| 		# server is started by running via generated gitweb.py in | ||||
| 		# $fqgitdir/gitweb | ||||
| 		full_httpd="$fqgitdir/gitweb/gitweb.py" | ||||
| 		httpd_only="${httpd%% *}" # cut on first space | ||||
| 		return | ||||
| 		;; | ||||
| 	esac | ||||
|  | ||||
| 	httpd_only="$(echo $httpd | cut -f1 -d' ')" | ||||
|  | @ -110,7 +117,7 @@ start_httpd () { | |||
|  | ||||
| 	# don't quote $full_httpd, there can be arguments to it (-f) | ||||
| 	case "$httpd" in | ||||
| 	*mongoose*|*plackup*) | ||||
| 	*mongoose*|*plackup*|*python*) | ||||
| 		#These servers don't have a daemon mode so we'll have to fork it | ||||
| 		$full_httpd "$conf" & | ||||
| 		#Save the pid before doing anything else (we'll print it later) | ||||
|  | @ -595,6 +602,121 @@ EOF | |||
| 	rm -f "$conf" | ||||
| } | ||||
|  | ||||
| python_conf() { | ||||
| 	# Python's builtin http.server and its CGI support is very limited. | ||||
| 	# CGI handler is capable of running CGI script only from inside a directory. | ||||
| 	# Trying to set cgi_directories=["/"] will add double slash to SCRIPT_NAME | ||||
| 	# and that in turn breaks gitweb's relative link generation. | ||||
|  | ||||
| 	# create a simple web root where $fqgitdir/gitweb/$httpd_only is our root | ||||
| 	mkdir -p "$fqgitdir/gitweb/$httpd_only/cgi-bin" | ||||
| 	# Python http.server follows the symlinks | ||||
| 	ln -sf "$root/gitweb.cgi" "$fqgitdir/gitweb/$httpd_only/cgi-bin/gitweb.cgi" | ||||
| 	ln -sf "$root/static" "$fqgitdir/gitweb/$httpd_only/" | ||||
|  | ||||
| 	# generate a standalone 'python http.server' script in $fqgitdir/gitweb | ||||
| 	# This asumes that python is in user's $PATH | ||||
| 	# This script is Python 2 and 3 compatible | ||||
| 	cat > "$fqgitdir/gitweb/gitweb.py" <<EOF | ||||
| #!/usr/bin/env python | ||||
| import os | ||||
| import sys | ||||
|  | ||||
| # Open log file in line buffering mode | ||||
| accesslogfile = open("$fqgitdir/gitweb/access.log", 'a', buffering=1) | ||||
| errorlogfile = open("$fqgitdir/gitweb/error.log", 'a', buffering=1) | ||||
|  | ||||
| # and replace our stdout and stderr with log files | ||||
| # also do a lowlevel duplicate of the logfile file descriptors so that | ||||
| # our CGI child process writes any stderr warning also to the log file | ||||
| _orig_stdout_fd = sys.stdout.fileno() | ||||
| sys.stdout.close() | ||||
| os.dup2(accesslogfile.fileno(), _orig_stdout_fd) | ||||
| sys.stdout = accesslogfile | ||||
|  | ||||
| _orig_stderr_fd = sys.stderr.fileno() | ||||
| sys.stderr.close() | ||||
| os.dup2(errorlogfile.fileno(), _orig_stderr_fd) | ||||
| sys.stderr = errorlogfile | ||||
|  | ||||
| from functools import partial | ||||
|  | ||||
| if sys.version_info < (3, 0):  # Python 2 | ||||
| 	from CGIHTTPServer import CGIHTTPRequestHandler | ||||
| 	from BaseHTTPServer import HTTPServer as ServerClass | ||||
| else:  # Python 3 | ||||
| 	from http.server import CGIHTTPRequestHandler | ||||
| 	from http.server import HTTPServer as ServerClass | ||||
|  | ||||
|  | ||||
| # Those environment variables will be passed to the cgi script | ||||
| os.environ.update({ | ||||
| 	"GIT_EXEC_PATH": "$GIT_EXEC_PATH", | ||||
| 	"GIT_DIR": "$GIT_DIR", | ||||
| 	"GITWEB_CONFIG": "$GITWEB_CONFIG" | ||||
| }) | ||||
|  | ||||
|  | ||||
| class GitWebRequestHandler(CGIHTTPRequestHandler): | ||||
|  | ||||
| 	def log_message(self, format, *args): | ||||
| 		# Write access logs to stdout | ||||
| 		sys.stdout.write("%s - - [%s] %s\n" % | ||||
| 				(self.address_string(), | ||||
| 				self.log_date_time_string(), | ||||
| 				format%args)) | ||||
|  | ||||
| 	def do_HEAD(self): | ||||
| 		self.redirect_path() | ||||
| 		CGIHTTPRequestHandler.do_HEAD(self) | ||||
|  | ||||
| 	def do_GET(self): | ||||
| 		if self.path == "/": | ||||
| 			self.send_response(303, "See Other") | ||||
| 			self.send_header("Location", "/cgi-bin/gitweb.cgi") | ||||
| 			self.end_headers() | ||||
| 			return | ||||
| 		self.redirect_path() | ||||
| 		CGIHTTPRequestHandler.do_GET(self) | ||||
|  | ||||
| 	def do_POST(self): | ||||
| 		self.redirect_path() | ||||
| 		CGIHTTPRequestHandler.do_POST(self) | ||||
|  | ||||
| 	# rewrite path of every request that is not gitweb.cgi to out of cgi-bin | ||||
| 	def redirect_path(self): | ||||
| 		if not self.path.startswith("/cgi-bin/gitweb.cgi"): | ||||
| 			self.path = self.path.replace("/cgi-bin/", "/") | ||||
|  | ||||
| 	# gitweb.cgi is the only thing that is ever going to be run here. | ||||
| 	# Ignore everything else | ||||
| 	def is_cgi(self): | ||||
| 		result = False | ||||
| 		if self.path.startswith('/cgi-bin/gitweb.cgi'): | ||||
| 			result = CGIHTTPRequestHandler.is_cgi(self) | ||||
| 		return result | ||||
|  | ||||
|  | ||||
| bind = "127.0.0.1" | ||||
| if "$local" == "true": | ||||
| 	bind = "0.0.0.0" | ||||
|  | ||||
| # Set our http root directory | ||||
| # This is a work around for a missing directory argument in older Python versions | ||||
| # as this was added to SimpleHTTPRequestHandler in Python 3.7 | ||||
| os.chdir("$fqgitdir/gitweb/$httpd_only/") | ||||
|  | ||||
| GitWebRequestHandler.protocol_version = "HTTP/1.0" | ||||
| httpd = ServerClass((bind, $port), GitWebRequestHandler) | ||||
|  | ||||
| sa = httpd.socket.getsockname() | ||||
| print("Serving HTTP on", sa[0], "port", sa[1], "...") | ||||
| httpd.serve_forever() | ||||
| EOF | ||||
|  | ||||
| 	chmod a+x "$fqgitdir/gitweb/gitweb.py" | ||||
| } | ||||
|  | ||||
| gitweb_conf() { | ||||
| 	cat > "$fqgitdir/gitweb/gitweb_config.perl" <<EOF | ||||
| #!@@PERL@@ | ||||
|  | @ -623,6 +745,9 @@ configure_httpd() { | |||
| 	*plackup*) | ||||
| 		plackup_conf | ||||
| 		;; | ||||
| 	*python*) | ||||
| 		python_conf | ||||
| 		;; | ||||
| 	*) | ||||
| 		echo "Unknown httpd specified: $httpd" | ||||
| 		exit 1 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Arti Zirk
						Arti Zirk