github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/internal/vcweb/hg.go (about)

     1  // Copyright 2022 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package vcweb
     6  
     7  import (
     8  	"bufio"
     9  	"errors"
    10  	"io"
    11  	"log"
    12  	"net/http"
    13  	"net/http/httputil"
    14  	"net/url"
    15  	"os"
    16  	"os/exec"
    17  	"strings"
    18  	"sync"
    19  )
    20  
    21  type hgHandler struct {
    22  	once      sync.Once
    23  	hgPath    string
    24  	hgPathErr error
    25  }
    26  
    27  func (h *hgHandler) Available() bool {
    28  	h.once.Do(func() {
    29  		h.hgPath, h.hgPathErr = exec.LookPath("hg")
    30  	})
    31  	return h.hgPathErr == nil
    32  }
    33  
    34  func (h *hgHandler) Handler(dir string, env []string, logger *log.Logger) (http.Handler, error) {
    35  	if !h.Available() {
    36  		return nil, ServerNotInstalledError{name: "hg"}
    37  	}
    38  
    39  	handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    40  		// Mercurial has a CGI server implementation (called hgweb). In theory we
    41  		// could use that — however, assuming that hgweb is even installed, the
    42  		// configuration for hgweb varies by Python version (2 vs 3), and we would
    43  		// rather not go rooting around trying to find the right Python version to
    44  		// run.
    45  		//
    46  		// Instead, we'll take a somewhat more roundabout approach: we assume that
    47  		// if "hg" works at all then "hg serve" works too, and we'll execute that as
    48  		// a subprocess, using a reverse proxy to forward the request and response.
    49  
    50  		cmd := exec.Command(h.hgPath, "serve", "--port", "0", "--address", "localhost", "--accesslog", os.DevNull, "--name", "vcweb", "--print-url")
    51  		cmd.Dir = dir
    52  		cmd.Env = append(env[:len(env):len(env)], "PWD="+dir)
    53  
    54  		stderr := new(strings.Builder)
    55  		cmd.Stderr = stderr
    56  
    57  		stdout, err := cmd.StdoutPipe()
    58  		if err != nil {
    59  			http.Error(w, err.Error(), http.StatusInternalServerError)
    60  			return
    61  		}
    62  		readDone := make(chan struct{})
    63  		defer func() {
    64  			stdout.Close()
    65  			<-readDone
    66  		}()
    67  
    68  		hgURL := make(chan *url.URL, 1)
    69  		hgURLError := make(chan error, 1)
    70  		go func() {
    71  			defer close(readDone)
    72  			r := bufio.NewReader(stdout)
    73  			for {
    74  				line, err := r.ReadString('\n')
    75  				if err != nil {
    76  					return
    77  				}
    78  				u, err := url.Parse(strings.TrimSpace(line))
    79  				if err == nil {
    80  					hgURL <- u
    81  				} else {
    82  					hgURLError <- err
    83  				}
    84  				break
    85  			}
    86  			io.Copy(io.Discard, r)
    87  		}()
    88  
    89  		if err := cmd.Start(); err != nil {
    90  			http.Error(w, err.Error(), http.StatusInternalServerError)
    91  			return
    92  		}
    93  		defer func() {
    94  			if err := cmd.Process.Signal(os.Interrupt); err != nil && !errors.Is(err, os.ErrProcessDone) {
    95  				cmd.Process.Kill()
    96  			}
    97  			err := cmd.Wait()
    98  			if out := strings.TrimSuffix(stderr.String(), "interrupted!\n"); out != "" {
    99  				logger.Printf("%v: %v\n%s", cmd, err, out)
   100  			} else {
   101  				logger.Printf("%v", cmd)
   102  			}
   103  		}()
   104  
   105  		select {
   106  		case <-req.Context().Done():
   107  			logger.Printf("%v: %v", req.Context().Err(), cmd)
   108  			http.Error(w, req.Context().Err().Error(), http.StatusBadGateway)
   109  			return
   110  		case err := <-hgURLError:
   111  			logger.Printf("%v: %v", cmd, err)
   112  			http.Error(w, err.Error(), http.StatusBadGateway)
   113  			return
   114  		case url := <-hgURL:
   115  			logger.Printf("proxying hg request to %s", url)
   116  			httputil.NewSingleHostReverseProxy(url).ServeHTTP(w, req)
   117  		}
   118  	})
   119  
   120  	return handler, nil
   121  }