* [PATCH] Add persistent-https to contrib
@ 2012-05-23 17:06 Colby Ranger
2012-05-24 18:17 ` Junio C Hamano
2012-05-29 21:52 ` [PATCH v2] " Colby Ranger
0 siblings, 2 replies; 12+ messages in thread
From: Colby Ranger @ 2012-05-23 17:06 UTC (permalink / raw)
To: git; +Cc: gitster, Colby Ranger
Git over HTTPS has a high request startup latency, since the SSL
negotiation can take up to a second. In order to reduce this latency,
connections should be left open to the Git server across requests
(or invocations of the git commandline).
Reduce SSL startup latency by running a daemon job that keeps
connections open to a Git server. The daemon job
(git-remote-persistent-https--proxy) is started on the first request
through the client binary (git-remote-persistent-https) and remains
running for 24 hours after the last request, or until a new daemon
binary is placed in the PATH. The client determines the daemon's
HTTP address by communicating over a UNIX socket with the daemon.
From there, the rest of the Git protocol work is delegated to the
"git-remote-http" binary, with the environment's http_proxy set to
the daemon.
Signed-off-by: Colby Ranger <cranger@google.com>
---
contrib/persistent-https/COPYING | 202 +++++++++++++++++++++++++++++++++++
contrib/persistent-https/README | 62 +++++++++++
contrib/persistent-https/client.go | 178 ++++++++++++++++++++++++++++++
contrib/persistent-https/main.go | 82 ++++++++++++++
contrib/persistent-https/proxy.go | 190 ++++++++++++++++++++++++++++++++
contrib/persistent-https/release.sh | 45 ++++++++
contrib/persistent-https/socket.go | 97 +++++++++++++++++
contrib/persistent-https/tar.sh | 40 +++++++
8 files changed, 896 insertions(+)
create mode 100644 contrib/persistent-https/COPYING
create mode 100644 contrib/persistent-https/README
create mode 100644 contrib/persistent-https/client.go
create mode 100644 contrib/persistent-https/main.go
create mode 100644 contrib/persistent-https/proxy.go
create mode 100755 contrib/persistent-https/release.sh
create mode 100644 contrib/persistent-https/socket.go
create mode 100755 contrib/persistent-https/tar.sh
diff --git a/contrib/persistent-https/COPYING b/contrib/persistent-https/COPYING
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/contrib/persistent-https/COPYING
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/contrib/persistent-https/README b/contrib/persistent-https/README
new file mode 100644
index 0000000..e433c1a
--- /dev/null
+++ b/contrib/persistent-https/README
@@ -0,0 +1,62 @@
+git-remote-persistent-https
+
+The git-remote-persistent-https binary speeds up SSL operations
+by running a daemon job (git-remote-persistent-https--proxy) that
+keeps a connection open to a server.
+
+
+PRE-BUILT BINARIES
+
+Darwin amd64:
+https://commondatastorage.googleapis.com/git-remote-persistent-https/darwin_amd64.tar.gz
+
+Linux amd64:
+https://commondatastorage.googleapis.com/git-remote-persistent-https/linux_amd64.tar.gz
+
+
+INSTALLING
+
+Move all of the git-remote-persistent-http* binaries to a directory
+in PATH.
+
+
+USAGE
+
+HTTPS requests can be delegated to the proxy by using the
+"persistent-https" scheme, e.g.
+
+git clone persistent-https://kernel.googlesource.com/pub/scm/git/git
+
+Likewise, .gitconfig can be updated as follows to rewrite https urls
+to use persistent-https:
+
+[url "persistent-https"]
+ insteadof = https
+[url "persistent-http"]
+ insteadof = http
+
+
+#####################################################################
+# BUILDING FROM SOURCE
+#####################################################################
+
+LOCATION
+
+The source is available in the contrib/persistent-https directory of
+the Git source repository. The Git source repository is available at
+git://git.kernel.org/pub/scm/git/git.git/
+https://kernel.googlesource.com/pub/scm/git/git
+
+
+PREREQUISITES
+
+The code is written in Go (http://golang.org/) and the Go compiler is
+required. Currently, the compiler must be built and installed from tip
+of source, in order to include a fix in the reverse http proxy:
+http://code.google.com/p/go/source/detail?r=a615b796570a2cd8591884767a7d67ede74f6648
+
+
+BUILDING
+
+Run ./release.sh to build the binaries. See the section on
+INSTALLING above.
diff --git a/contrib/persistent-https/client.go b/contrib/persistent-https/client.go
new file mode 100644
index 0000000..96e161d
--- /dev/null
+++ b/contrib/persistent-https/client.go
@@ -0,0 +1,178 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "net"
+ "net/url"
+ "os"
+ "os/exec"
+ "strings"
+ "syscall"
+ "time"
+)
+
+type Client struct {
+ ProxyBin string
+ Args []string
+
+ insecure bool
+}
+
+func (c *Client) Run() error {
+ if err := c.resolveArgs(); err != nil {
+ return fmt.Errorf("resolveArgs() got error: %v", err)
+ }
+
+ // Connect to the proxy.
+ uconn, hconn, addr, err := c.connect()
+ if err != nil {
+ return fmt.Errorf("connect() got error: %v", err)
+ }
+ // Keep the unix socket connection open for the duration of the request.
+ defer uconn.Close()
+ // Keep a connection to the HTTP server open, so no other user can
+ // bind on the same address so long as the process is running.
+ defer hconn.Close()
+
+ // Start the git-remote-http subprocess.
+ cargs := []string{"-c", fmt.Sprintf("http.proxy=%v", addr), "remote-http"}
+ cargs = append(cargs, c.Args...)
+ cmd := exec.Command("git", cargs...)
+ if !c.insecure {
+ cmd.Env = append(os.Environ(), "GIT_GOOGLE_CREDENTIAL_CORPSSO_ENABLE=1")
+ }
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ if eerr, ok := err.(*exec.ExitError); ok {
+ if stat, ok := eerr.ProcessState.Sys().(syscall.WaitStatus); ok && stat.ExitStatus() != 0 {
+ os.Exit(stat.ExitStatus())
+ }
+ }
+ return fmt.Errorf("git-remote-http subprocess got error: %v", err)
+ }
+ return nil
+}
+
+func (c *Client) connect() (uconn net.Conn, hconn net.Conn, addr string, err error) {
+ uconn, err = DefaultSocket.Dial()
+ if err != nil {
+ if e, ok := err.(*net.OpError); ok && (os.IsNotExist(e.Err) || e.Err == syscall.ECONNREFUSED) {
+ if err = c.startProxy(); err == nil {
+ uconn, err = DefaultSocket.Dial()
+ }
+ }
+ if err != nil {
+ return
+ }
+ }
+
+ if addr, err = c.readAddr(uconn); err != nil {
+ return
+ }
+
+ // Open a tcp connection to the proxy.
+ if hconn, err = net.Dial("tcp", addr); err != nil {
+ return
+ }
+
+ // Verify the address hasn't changed ownership.
+ var addr2 string
+ if addr2, err = c.readAddr(uconn); err != nil {
+ return
+ } else if addr != addr2 {
+ err = fmt.Errorf("address changed after connect. got %q, want %q", addr2, addr)
+ return
+ }
+ return
+}
+
+func (c *Client) readAddr(conn net.Conn) (string, error) {
+ conn.SetDeadline(time.Now().Add(5 * time.Second))
+ data := make([]byte, 100)
+ n, err := conn.Read(data)
+ if err != nil {
+ return "", fmt.Errorf("error reading unix socket: %v", err)
+ } else if n == 0 {
+ return "", errors.New("empty data response")
+ }
+ conn.Write([]byte{1}) // Ack
+
+ var addr string
+ if addrs := strings.Split(string(data[:n]), "\n"); len(addrs) != 2 {
+ return "", fmt.Errorf("got %q, wanted 2 addresses", data[:n])
+ } else if c.insecure {
+ addr = addrs[1]
+ } else {
+ addr = addrs[0]
+ }
+ return addr, nil
+}
+
+func (c *Client) startProxy() error {
+ cmd := exec.Command(c.ProxyBin)
+ cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ return err
+ }
+ defer stdout.Close()
+ if err := cmd.Start(); err != nil {
+ return err
+ }
+ result := make(chan error)
+ go func() {
+ bytes, _, err := bufio.NewReader(stdout).ReadLine()
+ if line := string(bytes); err == nil && line != "OK" {
+ err = fmt.Errorf("proxy returned %q, want \"OK\"", line)
+ }
+ result <- err
+ }()
+ select {
+ case err := <-result:
+ return err
+ case <-time.After(5 * time.Second):
+ return errors.New("timeout waiting for proxy to start")
+ }
+ panic("not reachable")
+}
+
+func (c *Client) resolveArgs() error {
+ if nargs := len(c.Args); nargs == 0 {
+ return errors.New("remote needed")
+ } else if nargs > 2 {
+ return fmt.Errorf("want at most 2 args, got %v", c.Args)
+ }
+
+ // Rewrite the url scheme to be http.
+ idx := len(c.Args) - 1
+ rawurl := c.Args[idx]
+ rurl, err := url.Parse(rawurl)
+ if err != nil {
+ return fmt.Errorf("invalid remote: %v", err)
+ }
+ c.insecure = rurl.Scheme == "persistent-http"
+ rurl.Scheme = "http"
+ c.Args[idx] = rurl.String()
+ if idx != 0 && c.Args[0] == rawurl {
+ c.Args[0] = c.Args[idx]
+ }
+ return nil
+}
diff --git a/contrib/persistent-https/main.go b/contrib/persistent-https/main.go
new file mode 100644
index 0000000..56199ec
--- /dev/null
+++ b/contrib/persistent-https/main.go
@@ -0,0 +1,82 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// The git-remote-persistent-https binary speeds up SSL operations by running
+// a daemon job that keeps a connection open to a Git server. This ensures the
+// git-remote-persistent-https--proxy is running and delegating execution
+// to the git-remote-http binary with the http_proxy set to the daemon job.
+// A unix socket is used to authenticate the proxy and discover the
+// HTTP address. Note, both the client and proxy are included in the same
+// binary.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "strings"
+ "time"
+)
+
+var (
+ forceProxy = flag.Bool("proxy", false, "Whether to start the binary in proxy mode")
+ proxyBin = flag.String("proxy_bin", "git-remote-persistent-https--proxy", "Path to the proxy binary")
+ printLabel = flag.Bool("print_label", false, "Prints the build label for the binary")
+
+ // Variable that should be defined through the -X linker flag.
+ _BUILD_EMBED_LABEL string
+)
+
+const (
+ defaultMaxIdleDuration = 24 * time.Hour
+ defaultPollUpdateInterval = 15 * time.Minute
+)
+
+func main() {
+ flag.Parse()
+ if *printLabel {
+ // Short circuit execution to print the build label
+ fmt.Println(buildLabel())
+ return
+ }
+
+ var err error
+ if *forceProxy || strings.HasSuffix(os.Args[0], "--proxy") {
+ log.SetPrefix("git-remote-persistent-https--proxy: ")
+ proxy := &Proxy{
+ BuildLabel: buildLabel(),
+ MaxIdleDuration: defaultMaxIdleDuration,
+ PollUpdateInterval: defaultPollUpdateInterval,
+ }
+ err = proxy.Run()
+ } else {
+ log.SetPrefix("git-remote-persistent-https: ")
+ client := &Client{
+ ProxyBin: *proxyBin,
+ Args: flag.Args(),
+ }
+ err = client.Run()
+ }
+ if err != nil {
+ log.Fatalln(err)
+ }
+}
+
+func buildLabel() string {
+ if _BUILD_EMBED_LABEL == "" {
+ log.Println("unlabeled build; build with release.sh to label")
+ }
+ return _BUILD_EMBED_LABEL
+}
diff --git a/contrib/persistent-https/proxy.go b/contrib/persistent-https/proxy.go
new file mode 100644
index 0000000..bb0cdba
--- /dev/null
+++ b/contrib/persistent-https/proxy.go
@@ -0,0 +1,190 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "net/http/httputil"
+ "os"
+ "os/exec"
+ "os/signal"
+ "sync"
+ "syscall"
+ "time"
+)
+
+type Proxy struct {
+ BuildLabel string
+ MaxIdleDuration time.Duration
+ PollUpdateInterval time.Duration
+
+ ul net.Listener
+ httpAddr string
+ httpsAddr string
+}
+
+func (p *Proxy) Run() error {
+ hl, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ return fmt.Errorf("http listen failed: %v", err)
+ }
+ defer hl.Close()
+
+ hsl, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ return fmt.Errorf("https listen failed: %v", err)
+ }
+ defer hsl.Close()
+
+ p.ul, err = DefaultSocket.Listen()
+ if err != nil {
+ c, derr := DefaultSocket.Dial()
+ if derr == nil {
+ c.Close()
+ fmt.Println("OK\nA proxy is already running... exiting")
+ return nil
+ } else if e, ok := derr.(*net.OpError); ok && e.Err == syscall.ECONNREFUSED {
+ // Nothing is listening on the socket, unlink it and try again.
+ syscall.Unlink(DefaultSocket.Path())
+ p.ul, err = DefaultSocket.Listen()
+ }
+ if err != nil {
+ return fmt.Errorf("unix listen failed on %v: %v", DefaultSocket.Path(), err)
+ }
+ }
+ defer p.ul.Close()
+ go p.closeOnSignal()
+ go p.closeOnUpdate()
+
+ p.httpAddr = hl.Addr().String()
+ p.httpsAddr = hsl.Addr().String()
+ fmt.Printf("OK\nListening on unix socket=%v http=%v https=%v\n",
+ p.ul.Addr(), p.httpAddr, p.httpsAddr)
+
+ result := make(chan error, 2)
+ go p.serveUnix(result)
+ go func() {
+ result <- http.Serve(hl, &httputil.ReverseProxy{
+ FlushInterval: 500 * time.Millisecond,
+ Director: func(r *http.Request) {},
+ })
+ }()
+ go func() {
+ result <- http.Serve(hsl, &httputil.ReverseProxy{
+ FlushInterval: 500 * time.Millisecond,
+ Director: func(r *http.Request) {
+ r.URL.Scheme = "https"
+ },
+ })
+ }()
+ return <-result
+}
+
+type socketContext struct {
+ sync.WaitGroup
+ mutex sync.Mutex
+ last time.Time
+}
+
+func (sc *socketContext) Done() {
+ sc.mutex.Lock()
+ defer sc.mutex.Unlock()
+ sc.last = time.Now()
+ sc.WaitGroup.Done()
+}
+
+func (p *Proxy) serveUnix(result chan<- error) {
+ sockCtx := &socketContext{}
+ go p.closeOnIdle(sockCtx)
+
+ var err error
+ for {
+ var uconn net.Conn
+ uconn, err = p.ul.Accept()
+ if err != nil {
+ err = fmt.Errorf("accept failed: %v", err)
+ break
+ }
+ sockCtx.Add(1)
+ go p.handleUnixConn(sockCtx, uconn)
+ }
+ sockCtx.Wait()
+ result <- err
+}
+
+func (p *Proxy) handleUnixConn(sockCtx *socketContext, uconn net.Conn) {
+ defer sockCtx.Done()
+ defer uconn.Close()
+ data := []byte(fmt.Sprintf("%v\n%v", p.httpsAddr, p.httpAddr))
+ uconn.SetDeadline(time.Now().Add(5 * time.Second))
+ for i := 0; i < 2; i++ {
+ if n, err := uconn.Write(data); err != nil {
+ log.Printf("error sending http addresses: %+v\n", err)
+ return
+ } else if n != len(data) {
+ log.Printf("sent %d data bytes, wanted %d\n", n, len(data))
+ return
+ }
+ if _, err := uconn.Read([]byte{0, 0, 0, 0}); err != nil {
+ log.Printf("error waiting for Ack: %+v\n", err)
+ return
+ }
+ }
+ // Wait without a deadline for the client to finish via EOF
+ uconn.SetDeadline(time.Time{})
+ uconn.Read([]byte{0, 0, 0, 0})
+}
+
+func (p *Proxy) closeOnIdle(sockCtx *socketContext) {
+ for d := p.MaxIdleDuration; d > 0; {
+ time.Sleep(d)
+ sockCtx.Wait()
+ sockCtx.mutex.Lock()
+ if d = sockCtx.last.Add(p.MaxIdleDuration).Sub(time.Now()); d <= 0 {
+ log.Println("graceful shutdown from idle timeout")
+ p.ul.Close()
+ }
+ sockCtx.mutex.Unlock()
+ }
+}
+
+func (p *Proxy) closeOnUpdate() {
+ for {
+ time.Sleep(p.PollUpdateInterval)
+ if out, err := exec.Command(os.Args[0], "--print_label").Output(); err != nil {
+ log.Printf("error polling for updated binary: %v\n", err)
+ } else if s := string(out[:len(out)-1]); p.BuildLabel != s {
+ log.Printf("graceful shutdown from updated binary: %q --> %q\n", p.BuildLabel, s)
+ p.ul.Close()
+ break
+ }
+ }
+}
+
+func (p *Proxy) closeOnSignal() {
+ ch := make(chan os.Signal, 10)
+ signal.Notify(ch, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM), os.Signal(syscall.SIGHUP))
+ sig := <-ch
+ p.ul.Close()
+ switch sig {
+ case os.Signal(syscall.SIGHUP):
+ log.Printf("graceful shutdown from signal: %v\n", sig)
+ default:
+ log.Fatalf("exiting from signal: %v\n", sig)
+ }
+}
diff --git a/contrib/persistent-https/release.sh b/contrib/persistent-https/release.sh
new file mode 100755
index 0000000..9ce2fe2
--- /dev/null
+++ b/contrib/persistent-https/release.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+#
+# Copyright 2012 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Builds the binaries for release. The script is mainly used to set the
+#_BUILD_EMBED_LABEL to the current system time through a linker flag.
+
+set -e
+
+rm -f git-remote-persistent-https \
+ git-remote-persistent-https--proxy \
+ git-remote-persistent-http
+
+BUILD_LABEL=`date +"%s"`
+go build -o git-remote-persistent-https \
+ -ldflags "-X main._BUILD_EMBED_LABEL $BUILD_LABEL"
+
+chmod 555 git-remote-persistent-https
+ln -s git-remote-persistent-https git-remote-persistent-https--proxy
+ln -s git-remote-persistent-https git-remote-persistent-http
+
+LIVE_LABEL=`./git-remote-persistent-https --print_label`
+
+tput setaf 2 # green
+echo "Successfully built with label: $LIVE_LABEL"
+tput sgr0
+
+echo
+echo "Move the following binaries to a directory in your PATH:"
+echo " git-remote-persistent-https"
+echo " git-remote-persistent-https--proxy"
+echo " git-remote-persistent-http"
+
diff --git a/contrib/persistent-https/socket.go b/contrib/persistent-https/socket.go
new file mode 100644
index 0000000..193b911
--- /dev/null
+++ b/contrib/persistent-https/socket.go
@@ -0,0 +1,97 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "os"
+ "path/filepath"
+ "syscall"
+)
+
+// A Socket is a wrapper around a Unix socket that verifies directory
+// permissions.
+type Socket struct {
+ Dir string
+}
+
+func defaultDir() string {
+ sockPath := ".git-credential-cache"
+ if home := os.Getenv("HOME"); home != "" {
+ return filepath.Join(home, sockPath)
+ }
+ log.Printf("socket: cannot find HOME path. using relative directory %q for socket", sockPath)
+ return sockPath
+}
+
+// DefaultSocket is a Socket in the $HOME/.git-credential-cache directory.
+var DefaultSocket = Socket{Dir: defaultDir()}
+
+// Listen announces the local network address of the unix socket. The
+// permissions on the socket directory are verified before attempting
+// the actual listen.
+func (s Socket) Listen() (net.Listener, error) {
+ network, addr := "unix", s.Path()
+ if err := s.mkdir(); err != nil {
+ return nil, &net.OpError{Op: "listen", Net: network, Addr: &net.UnixAddr{Name: addr, Net: network}, Err: err}
+ }
+ return net.Listen(network, addr)
+}
+
+// Dial connects to the unix socket. The permissions on the socket directory
+// are verified before attempting the actual dial.
+func (s Socket) Dial() (net.Conn, error) {
+ network, addr := "unix", s.Path()
+ if err := s.checkPermissions(); err != nil {
+ return nil, &net.OpError{Op: "dial", Net: network, Addr: &net.UnixAddr{Name: addr, Net: network}, Err: err}
+ }
+ return net.Dial(network, addr)
+}
+
+// Path returns the fully specified file name of the unix socket.
+func (s Socket) Path() string {
+ return filepath.Join(s.Dir, "persistent-https-proxy-socket")
+}
+
+func (s Socket) mkdir() error {
+ if err := s.checkPermissions(); err == nil {
+ return nil
+ } else if !os.IsNotExist(err) {
+ return err
+ }
+ if err := os.MkdirAll(s.Dir, 0700); err != nil {
+ return err
+ }
+ return s.checkPermissions()
+}
+
+func (s Socket) checkPermissions() error {
+ fi, err := os.Stat(s.Dir)
+ if err != nil {
+ return err
+ }
+ if !fi.IsDir() {
+ return fmt.Errorf("socket: got file, want directory for %q", s.Dir)
+ }
+ if fi.Mode().Perm() != 0700 {
+ return fmt.Errorf("socket: got perm %o, want 700 for %q", fi.Mode().Perm(), s.Dir)
+ }
+ if st := fi.Sys().(*syscall.Stat_t); int(st.Uid) != os.Getuid() {
+ return fmt.Errorf("socket: got uid %d, want %d for %q", st.Uid, os.Getuid(), s.Dir)
+ }
+ return nil
+}
diff --git a/contrib/persistent-https/tar.sh b/contrib/persistent-https/tar.sh
new file mode 100755
index 0000000..0288753
--- /dev/null
+++ b/contrib/persistent-https/tar.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# Copyright 2012 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Builds the binaries for release and produces them in a tarball.
+
+set -e
+
+OUT=$(go env GOOS)_$(go env GOARCH).tar.gz
+
+rm -rf bin git-remote-persistent-https ${OUT}
+
+./release.sh
+
+echo
+tput setaf 2 # green
+echo "Creating ${OUT}:"
+tput sgr0
+
+mkdir bin
+cp COPYING README bin
+chmod 444 bin/*
+mv git-remote-persistent-http* bin
+mv bin git-remote-persistent-https
+
+tar -czvf ${OUT} git-remote-persistent-https
+
+rm -rf git-remote-persistent-https
--
1.7.9.2
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH] Add persistent-https to contrib
2012-05-23 17:06 [PATCH] Add persistent-https to contrib Colby Ranger
@ 2012-05-24 18:17 ` Junio C Hamano
2012-05-24 19:33 ` Shawn Pearce
2012-05-29 21:52 ` [PATCH v2] " Colby Ranger
1 sibling, 1 reply; 12+ messages in thread
From: Junio C Hamano @ 2012-05-24 18:17 UTC (permalink / raw)
To: Colby Ranger; +Cc: git, gitster
Colby Ranger <cranger@google.com> writes:
> Git over HTTPS has a high request startup latency, since the SSL
> negotiation can take up to a second. In order to reduce this latency,
> connections should be left open to the Git server across requests
> (or invocations of the git commandline).
>
> Reduce SSL startup latency by running a daemon job that keeps
> connections open to a Git server. The daemon job
> (git-remote-persistent-https--proxy) is started on the first request
> through the client binary (git-remote-persistent-https) and remains
> running for 24 hours after the last request, or until a new daemon
> binary is placed in the PATH. The client determines the daemon's
> HTTP address by communicating over a UNIX socket with the daemon.
>>From there, the rest of the Git protocol work is delegated to the
> "git-remote-http" binary, with the environment's http_proxy set to
> the daemon.
>
> Signed-off-by: Colby Ranger <cranger@google.com>
Clever. Do you have some numbers?
If this persistent proxy weren't in the picture, the git client would
directly delegate its communication with the origin server to
git-remote-https, and git-remote-https would interact with the credential
API to handle authentication for "https://origin.server.xz/repo".
How does the persistent proxy sitting in between your git client and the
origin server and/or the real proxy you have at the perimeter of your
network interact with respect to authentication/authorization to access
the repository or the real proxy? They will talk with the persistent
proxy, and the persistent proxy will talk with the git client via
git-remote-http, and I am assuming that this last git-remote-http will be
the one that uses the credential API. Would it ask credential for
"https://origin.server.xz/repo"? "http://origin.server.xz/repo"? Or
would it be "persistent-https://origin.server.xz.repo"?
> contrib/persistent-https/COPYING | 202 +++++++++++++++++++++++++++++++++++
I do not mind carrying this in the contrib/ area (I am assuming that
distributing Apache licensed software that does not link with GPLv2 core
is OK). It may be just me, but a file called COPYING that does not have
GPL text in it was a bit surprising. I wonder if it is more customary to
call it either LICENSE (or perhaps LICENSE-2.0)?
> contrib/persistent-https/README | 62 +++++++++++
> contrib/persistent-https/client.go | 178 ++++++++++++++++++++++++++++++
> contrib/persistent-https/main.go | 82 ++++++++++++++
> contrib/persistent-https/proxy.go | 190 ++++++++++++++++++++++++++++++++
> contrib/persistent-https/release.sh | 45 ++++++++
> contrib/persistent-https/socket.go | 97 +++++++++++++++++
> contrib/persistent-https/tar.sh | 40 +++++++
> 8 files changed, 896 insertions(+)
It might deserve its own contrib/persistent-https/Makefile in addition to
your internal "release" scripts, though.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] Add persistent-https to contrib
2012-05-24 18:17 ` Junio C Hamano
@ 2012-05-24 19:33 ` Shawn Pearce
2012-05-24 20:29 ` Jeff King
2012-05-24 20:51 ` Junio C Hamano
0 siblings, 2 replies; 12+ messages in thread
From: Shawn Pearce @ 2012-05-24 19:33 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Colby Ranger, git
On Thu, May 24, 2012 at 11:17 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Colby Ranger <cranger@google.com> writes:
>
>> Git over HTTPS has a high request startup latency, since the SSL
>> negotiation can take up to a second. In order to reduce this latency,
>> connections should be left open to the Git server across requests
>> (or invocations of the git commandline).
>>
>> Reduce SSL startup latency by running a daemon job that keeps
>> connections open to a Git server. The daemon job
>> (git-remote-persistent-https--proxy) is started on the first request
>> through the client binary (git-remote-persistent-https) and remains
>> running for 24 hours after the last request, or until a new daemon
>> binary is placed in the PATH. The client determines the daemon's
>> HTTP address by communicating over a UNIX socket with the daemon.
>>>From there, the rest of the Git protocol work is delegated to the
>> "git-remote-http" binary, with the environment's http_proxy set to
>> the daemon.
>>
>> Signed-off-by: Colby Ranger <cranger@google.com>
>
> Clever. Do you have some numbers?
$ (for i in {1..5}; do time git ls-remote
https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
>/dev/null;done) 2>&1 | grep real
real 0m0.193s
real 0m0.175s
real 0m0.191s
real 0m0.173s
real 0m0.194s
$ (for i in {1..5}; do time git ls-remote
persistent-https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
>/dev/null;done) 2>&1 | grep real
real 0m0.208s
real 0m0.085s
real 0m0.079s
real 0m0.067s
real 0m0.059s
That initial 208ms run was the local proxy starting, and the
connection being established. Its similar to the normal case.
Subsequent executions have lower latency.
> If this persistent proxy weren't in the picture, the git client would
> directly delegate its communication with the origin server to
> git-remote-https, and git-remote-https would interact with the credential
> API to handle authentication for "https://origin.server.xz/repo".
Yes.
> How does the persistent proxy sitting in between your git client and the
> origin server and/or the real proxy you have at the perimeter of your
> network interact with respect to authentication/authorization to access
> the repository or the real proxy? They will talk with the persistent
> proxy, and the persistent proxy will talk with the git client via
> git-remote-http, and I am assuming that this last git-remote-http will be
> the one that uses the credential API.
Yes.
> Would it ask credential for
> "https://origin.server.xz/repo"? "http://origin.server.xz/repo"? Or
> would it be "persistent-https://origin.server.xz.repo"?
IIRC it asks for "http://origin.server.xz/repo".
The persistent-https code tells the git credential helper the
connection is "secure" (that is the proxy will use SSL as it exits the
local machine) by setting GIT_GOOGLE_CREDENTIAL_CORPSSO_ENABLE=1 in
the environment. This leaked from our internal version of the proxy,
Colby was supposed to scrub this string before open sourcing. :-)
So now everyone knows $DAYJOB = Google, we have a credential helper,
and it supports some sort of corporate single sign on. Whee.
>> contrib/persistent-https/COPYING | 202 +++++++++++++++++++++++++++++++++++
>
> I do not mind carrying this in the contrib/ area (I am assuming that
> distributing Apache licensed software that does not link with GPLv2 core
> is OK). It may be just me, but a file called COPYING that does not have
> GPL text in it was a bit surprising. I wonder if it is more customary to
> call it either LICENSE (or perhaps LICENSE-2.0)?
Agreed, LICENSE or LICENSE-2.0 is the right name for this file, not COPYING.
>> contrib/persistent-https/README | 62 +++++++++++
>> contrib/persistent-https/client.go | 178 ++++++++++++++++++++++++++++++
>> contrib/persistent-https/main.go | 82 ++++++++++++++
>> contrib/persistent-https/proxy.go | 190 ++++++++++++++++++++++++++++++++
>> contrib/persistent-https/release.sh | 45 ++++++++
>> contrib/persistent-https/socket.go | 97 +++++++++++++++++
>> contrib/persistent-https/tar.sh | 40 +++++++
>> 8 files changed, 896 insertions(+)
>
> It might deserve its own contrib/persistent-https/Makefile in addition to
> your internal "release" scripts, though.
Fair point.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] Add persistent-https to contrib
2012-05-24 19:33 ` Shawn Pearce
@ 2012-05-24 20:29 ` Jeff King
2012-05-24 20:42 ` Shawn Pearce
2012-05-24 20:42 ` Daniel Stenberg
2012-05-24 20:51 ` Junio C Hamano
1 sibling, 2 replies; 12+ messages in thread
From: Jeff King @ 2012-05-24 20:29 UTC (permalink / raw)
To: Shawn Pearce; +Cc: Daniel Stenberg, Junio C Hamano, Colby Ranger, git
On Thu, May 24, 2012 at 12:33:08PM -0700, Shawn O. Pearce wrote:
> $ (for i in {1..5}; do time git ls-remote
> persistent-https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
> >/dev/null;done) 2>&1 | grep real
> real 0m0.208s
> real 0m0.085s
> real 0m0.079s
> real 0m0.067s
> real 0m0.059s
Nice numbers. And as clever as I find this helper-wrapping-a-helper
solution, I wonder if the right layer for a fix isn't inside curl. It
already keeps an ssl session-id cache in memory; how hard would it be to
turn that into an on-disk cache?
I don't think that is grounds for rejecting this patch; obviously it is
working for you guys, and it is available right now, and it is only
going into contrib/ anyway. But a curl solution seems like a cleaner
long-term fix, and would benefit all curl users. It is even mentioned in
curl's doc/TODO file. :)
-Peff
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] Add persistent-https to contrib
2012-05-24 20:29 ` Jeff King
@ 2012-05-24 20:42 ` Shawn Pearce
2012-05-24 20:46 ` Shawn Pearce
2012-05-24 20:42 ` Daniel Stenberg
1 sibling, 1 reply; 12+ messages in thread
From: Shawn Pearce @ 2012-05-24 20:42 UTC (permalink / raw)
To: Jeff King; +Cc: Daniel Stenberg, Junio C Hamano, Colby Ranger, git
On Thu, May 24, 2012 at 1:29 PM, Jeff King <peff@peff.net> wrote:
> On Thu, May 24, 2012 at 12:33:08PM -0700, Shawn O. Pearce wrote:
>
>> $ (for i in {1..5}; do time git ls-remote
>> persistent-https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
>> >/dev/null;done) 2>&1 | grep real
>> real 0m0.208s
>> real 0m0.085s
>> real 0m0.079s
>> real 0m0.067s
>> real 0m0.059s
>
> Nice numbers. And as clever as I find this helper-wrapping-a-helper
> solution, I wonder if the right layer for a fix isn't inside curl. It
> already keeps an ssl session-id cache in memory; how hard would it be to
> turn that into an on-disk cache?
>
> I don't think that is grounds for rejecting this patch; obviously it is
> working for you guys, and it is available right now, and it is only
> going into contrib/ anyway. But a curl solution seems like a cleaner
> long-term fix, and would benefit all curl users. It is even mentioned in
> curl's doc/TODO file. :)
Well, this helper "solution" also has the benefit of HTTP keep-alive
working across Git command invocations. Its common for servers to use
a 5 minute keep-alive on an HTTP 1.1 connection. Git-over-HTTP
commonly uses Transfer-Encoding: chunked on replies, so keep-alive
will generally just work, even though a pack stream's length isn't
known in advance. Because the helper is an external process holding
that connection open, we also benefit from being able to reuse an
existing TCP connection to the server.
But sure, it would be nice if libcurl was able to share SSL sessions
across Git command invocations without this black magic proxy thing.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] Add persistent-https to contrib
2012-05-24 20:42 ` Shawn Pearce
@ 2012-05-24 20:46 ` Shawn Pearce
2012-05-24 20:51 ` Jeff King
0 siblings, 1 reply; 12+ messages in thread
From: Shawn Pearce @ 2012-05-24 20:46 UTC (permalink / raw)
To: Jeff King; +Cc: Daniel Stenberg, Junio C Hamano, Colby Ranger, git
On Thu, May 24, 2012 at 1:42 PM, Shawn Pearce <spearce@spearce.org> wrote:
> On Thu, May 24, 2012 at 1:29 PM, Jeff King <peff@peff.net> wrote:
>> On Thu, May 24, 2012 at 12:33:08PM -0700, Shawn O. Pearce wrote:
>>
>>> $ (for i in {1..5}; do time git ls-remote
>>> persistent-https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
>>> >/dev/null;done) 2>&1 | grep real
>>> real 0m0.208s
>>> real 0m0.085s
>>> real 0m0.079s
>>> real 0m0.067s
>>> real 0m0.059s
>>
>> Nice numbers. And as clever as I find this helper-wrapping-a-helper
>> solution, I wonder if the right layer for a fix isn't inside curl. It
>> already keeps an ssl session-id cache in memory; how hard would it be to
>> turn that into an on-disk cache?
...
> Well, this helper "solution" also has the benefit of HTTP keep-alive
> working across Git command invocations.
Here is plaintext HTTP, where the benefit is from HTTP keep-alive:
(for i in {1..5}; do time git ls-remote
http://android.googlesource.com/tools/repo >/dev/null;done) 2>&1 |
grep real
real 0m0.098s
real 0m0.097s
real 0m0.106s
real 0m0.095s
real 0m0.105s
(for i in {1..5}; do time git ls-remote
persistent-http://android.googlesource.com/tools/repo >/dev/null;done)
2>&1 | grep real
real 0m0.134s
real 0m0.065s
real 0m0.063s
real 0m0.061s
real 0m0.067s
Notice we still save 30ms or so in this case. That is about the RTT
for my workstation to that server. :-)
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] Add persistent-https to contrib
2012-05-24 20:46 ` Shawn Pearce
@ 2012-05-24 20:51 ` Jeff King
0 siblings, 0 replies; 12+ messages in thread
From: Jeff King @ 2012-05-24 20:51 UTC (permalink / raw)
To: Shawn Pearce; +Cc: Daniel Stenberg, Junio C Hamano, Colby Ranger, git
On Thu, May 24, 2012 at 01:46:36PM -0700, Shawn O. Pearce wrote:
> >> Nice numbers. And as clever as I find this helper-wrapping-a-helper
> >> solution, I wonder if the right layer for a fix isn't inside curl. It
> >> already keeps an ssl session-id cache in memory; how hard would it be to
> >> turn that into an on-disk cache?
> ...
> > Well, this helper "solution" also has the benefit of HTTP keep-alive
> > working across Git command invocations.
True. It also works even when the server does not support ssl id
caching.
> Here is plaintext HTTP, where the benefit is from HTTP keep-alive:
>
> (for i in {1..5}; do time git ls-remote
> http://android.googlesource.com/tools/repo >/dev/null;done) 2>&1 |
> grep real
> real 0m0.098s
> real 0m0.097s
> real 0m0.106s
> real 0m0.095s
> real 0m0.105s
>
> (for i in {1..5}; do time git ls-remote
> persistent-http://android.googlesource.com/tools/repo >/dev/null;done)
> 2>&1 | grep real
> real 0m0.134s
> real 0m0.065s
> real 0m0.063s
> real 0m0.061s
> real 0m0.067s
>
> Notice we still save 30ms or so in this case. That is about the RTT
> for my workstation to that server. :-)
Nice. I assumed most of your speedup was coming from dropping the SSL
setup, but it seems that a good chunk of it is just the TCP setup.
Thanks for providing numbers.
-Peff
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] Add persistent-https to contrib
2012-05-24 20:29 ` Jeff King
2012-05-24 20:42 ` Shawn Pearce
@ 2012-05-24 20:42 ` Daniel Stenberg
1 sibling, 0 replies; 12+ messages in thread
From: Daniel Stenberg @ 2012-05-24 20:42 UTC (permalink / raw)
To: Jeff King; +Cc: Shawn Pearce, Junio C Hamano, Colby Ranger, git
On Thu, 24 May 2012, Jeff King wrote:
> Nice numbers. And as clever as I find this helper-wrapping-a-helper
> solution, I wonder if the right layer for a fix isn't inside curl. It
> already keeps an ssl session-id cache in memory; how hard would it be to
> turn that into an on-disk cache?
>
> I don't think that is grounds for rejecting this patch; obviously it is
> working for you guys, and it is available right now, and it is only going
> into contrib/ anyway. But a curl solution seems like a cleaner long-term
> fix, and would benefit all curl users. It is even mentioned in curl's
> doc/TODO file. :)
Yes, in the curl project we would certainly not object to being able to
export/import SSL-session-ids (which then could be used to implement a cache,
on disk or in memory). However, I don't know how easily done that is to
implement with the different TLS libraries in use.
--
/ daniel.haxx.se
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] Add persistent-https to contrib
2012-05-24 19:33 ` Shawn Pearce
2012-05-24 20:29 ` Jeff King
@ 2012-05-24 20:51 ` Junio C Hamano
2012-05-29 21:29 ` Colby Ranger
1 sibling, 1 reply; 12+ messages in thread
From: Junio C Hamano @ 2012-05-24 20:51 UTC (permalink / raw)
To: Shawn Pearce; +Cc: Colby Ranger, git
Shawn Pearce <spearce@spearce.org> writes:
> The persistent-https code tells the git credential helper the
> connection is "secure" (that is the proxy will use SSL as it exits the
> local machine) by setting GIT_GOOGLE_CREDENTIAL_CORPSSO_ENABLE=1 in
> the environment. This leaked from our internal version of the proxy,
> Colby was supposed to scrub this string before open sourcing. :-)
>
> So now everyone knows $DAYJOB = Google, we have a credential helper,
> and it supports some sort of corporate single sign on. Whee.
It is obviously needed to drop that bit from the public version (and have
you guys keep an internal fork to add it back), but I have to wonder if
this is an indication that something like that is useful in general.
More specifically, this environment variable is a way to tell the wrapped
helper who is wrapping it. Users outside Google's environment of the
persistent-https helper obviously would not care about the corporate
sanitary sewer overflow mechanism, but they may have a similar need to
tweak what happens inside the git-remote-http that is driven by the
persistent helper. They would not care about "we can enable corpsso", but
they would benefit from knowing that either:
(1) the connection is "secure" (by the definition above); or
(2) the connection is going to this particular helper.
Conceptually, an approach to allow chain of helpers tell which one(s) of
defined set of attributes (e.g. "secure") are in effect, e.g. (1), might
be cleaner, but it probably is a bit too early overengineering (I do not
think we know if there is a good set of common attributes various helpers
might want to implement upstream and pay attention downstream) at this
point. But at least it might not hurt to give the downstream to find out
what upstream is driving them.
Hrm?
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] Add persistent-https to contrib
2012-05-24 20:51 ` Junio C Hamano
@ 2012-05-29 21:29 ` Colby Ranger
0 siblings, 0 replies; 12+ messages in thread
From: Colby Ranger @ 2012-05-29 21:29 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Shawn Pearce, git
> I do not mind carrying this in the contrib/ area (I am assuming that
> distributing Apache licensed software that does not link with GPLv2 core
> is OK). It may be just me, but a file called COPYING that does not have
> GPL text in it was a bit surprising. I wonder if it is more customary to
> call it either LICENSE (or perhaps LICENSE-2.0)?
Agreed. I'll change the name to LICENSE.
> It might deserve its own contrib/persistent-https/Makefile in addition to
> your internal "release" scripts, though.
OK. I'll update the code to use a simple Makefile for everything,
instead of the shell scripts.
> More specifically, this environment variable is a way to tell the wrapped
> helper who is wrapping it. Users outside Google's environment of the
> persistent-https helper obviously would not care about the corporate
> sanitary sewer overflow mechanism, but they may have a similar need to
> tweak what happens inside the git-remote-http that is driven by the
> persistent helper. They would not care about "we can enable corpsso", but
> they would benefit from knowing that either:
>
> (1) the connection is "secure" (by the definition above); or
> (2) the connection is going to this particular helper.
Agreed, I'll go with approach (1) and change the name of the var to be
GIT_PERSISTENT_HTTPS_SECURE. Also, I will update the code to remove
any GIT_PERSISTENT_HTTPS_SECURE from the environment.
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2] Add persistent-https to contrib
2012-05-23 17:06 [PATCH] Add persistent-https to contrib Colby Ranger
2012-05-24 18:17 ` Junio C Hamano
@ 2012-05-29 21:52 ` Colby Ranger
2012-05-29 23:34 ` Junio C Hamano
1 sibling, 1 reply; 12+ messages in thread
From: Colby Ranger @ 2012-05-29 21:52 UTC (permalink / raw)
To: git; +Cc: gitster, Colby Ranger
Git over HTTPS has a high request startup latency, since the SSL
negotiation can take up to a second. In order to reduce this latency,
connections should be left open to the Git server across requests
(or invocations of the git commandline).
Reduce SSL startup latency by running a daemon job that keeps
connections open to a Git server. The daemon job
(git-remote-persistent-https--proxy) is started on the first request
through the client binary (git-remote-persistent-https) and remains
running for 24 hours after the last request, or until a new daemon
binary is placed in the PATH. The client determines the daemon's
HTTP address by communicating over a UNIX socket with the daemon.
From there, the rest of the Git protocol work is delegated to the
"git-remote-http" binary, with the environment's http_proxy set to
the daemon.
Signed-off-by: Colby Ranger <cranger@google.com>
---
contrib/persistent-https/LICENSE | 202 ++++++++++++++++++++++++++++++++++++
contrib/persistent-https/Makefile | 38 +++++++
contrib/persistent-https/README | 62 +++++++++++
contrib/persistent-https/client.go | 189 +++++++++++++++++++++++++++++++++
contrib/persistent-https/main.go | 82 +++++++++++++++
contrib/persistent-https/proxy.go | 190 +++++++++++++++++++++++++++++++++
contrib/persistent-https/socket.go | 97 +++++++++++++++++
7 files changed, 860 insertions(+)
create mode 100644 contrib/persistent-https/LICENSE
create mode 100644 contrib/persistent-https/Makefile
create mode 100644 contrib/persistent-https/README
create mode 100644 contrib/persistent-https/client.go
create mode 100644 contrib/persistent-https/main.go
create mode 100644 contrib/persistent-https/proxy.go
create mode 100644 contrib/persistent-https/socket.go
diff --git a/contrib/persistent-https/LICENSE b/contrib/persistent-https/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/contrib/persistent-https/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/contrib/persistent-https/Makefile b/contrib/persistent-https/Makefile
new file mode 100644
index 0000000..92baa3b
--- /dev/null
+++ b/contrib/persistent-https/Makefile
@@ -0,0 +1,38 @@
+# Copyright 2012 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+BUILD_LABEL=$(shell date +"%s")
+TAR_OUT=$(shell go env GOOS)_$(shell go env GOARCH).tar.gz
+
+all: git-remote-persistent-https git-remote-persistent-https--proxy \
+ git-remote-persistent-http
+
+git-remote-persistent-https--proxy: git-remote-persistent-https
+ ln -f -s git-remote-persistent-https git-remote-persistent-https--proxy
+
+git-remote-persistent-http: git-remote-persistent-https
+ ln -f -s git-remote-persistent-https git-remote-persistent-http
+
+git-remote-persistent-https:
+ go build -o git-remote-persistent-https \
+ -ldflags "-X main._BUILD_EMBED_LABEL $(BUILD_LABEL)"
+
+clean:
+ rm -f git-remote-persistent-http* *.tar.gz
+
+tar: clean all
+ @chmod 555 git-remote-persistent-https
+ @tar -czf $(TAR_OUT) git-remote-persistent-http* README LICENSE
+ @echo
+ @echo "Created $(TAR_OUT)"
diff --git a/contrib/persistent-https/README b/contrib/persistent-https/README
new file mode 100644
index 0000000..f784dd2
--- /dev/null
+++ b/contrib/persistent-https/README
@@ -0,0 +1,62 @@
+git-remote-persistent-https
+
+The git-remote-persistent-https binary speeds up SSL operations
+by running a daemon job (git-remote-persistent-https--proxy) that
+keeps a connection open to a server.
+
+
+PRE-BUILT BINARIES
+
+Darwin amd64:
+https://commondatastorage.googleapis.com/git-remote-persistent-https/darwin_amd64.tar.gz
+
+Linux amd64:
+https://commondatastorage.googleapis.com/git-remote-persistent-https/linux_amd64.tar.gz
+
+
+INSTALLING
+
+Move all of the git-remote-persistent-http* binaries to a directory
+in PATH.
+
+
+USAGE
+
+HTTPS requests can be delegated to the proxy by using the
+"persistent-https" scheme, e.g.
+
+git clone persistent-https://kernel.googlesource.com/pub/scm/git/git
+
+Likewise, .gitconfig can be updated as follows to rewrite https urls
+to use persistent-https:
+
+[url "persistent-https"]
+ insteadof = https
+[url "persistent-http"]
+ insteadof = http
+
+
+#####################################################################
+# BUILDING FROM SOURCE
+#####################################################################
+
+LOCATION
+
+The source is available in the contrib/persistent-https directory of
+the Git source repository. The Git source repository is available at
+git://git.kernel.org/pub/scm/git/git.git/
+https://kernel.googlesource.com/pub/scm/git/git
+
+
+PREREQUISITES
+
+The code is written in Go (http://golang.org/) and the Go compiler is
+required. Currently, the compiler must be built and installed from tip
+of source, in order to include a fix in the reverse http proxy:
+http://code.google.com/p/go/source/detail?r=a615b796570a2cd8591884767a7d67ede74f6648
+
+
+BUILDING
+
+Run "make" to build the binaries. See the section on
+INSTALLING above.
diff --git a/contrib/persistent-https/client.go b/contrib/persistent-https/client.go
new file mode 100644
index 0000000..71125b5
--- /dev/null
+++ b/contrib/persistent-https/client.go
@@ -0,0 +1,189 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "net"
+ "net/url"
+ "os"
+ "os/exec"
+ "strings"
+ "syscall"
+ "time"
+)
+
+type Client struct {
+ ProxyBin string
+ Args []string
+
+ insecure bool
+}
+
+func (c *Client) Run() error {
+ if err := c.resolveArgs(); err != nil {
+ return fmt.Errorf("resolveArgs() got error: %v", err)
+ }
+
+ // Connect to the proxy.
+ uconn, hconn, addr, err := c.connect()
+ if err != nil {
+ return fmt.Errorf("connect() got error: %v", err)
+ }
+ // Keep the unix socket connection open for the duration of the request.
+ defer uconn.Close()
+ // Keep a connection to the HTTP server open, so no other user can
+ // bind on the same address so long as the process is running.
+ defer hconn.Close()
+
+ // Start the git-remote-http subprocess.
+ cargs := []string{"-c", fmt.Sprintf("http.proxy=%v", addr), "remote-http"}
+ cargs = append(cargs, c.Args...)
+ cmd := exec.Command("git", cargs...)
+
+ for _, v := range os.Environ() {
+ if !strings.HasPrefix(v, "GIT_PERSISTENT_HTTPS_SECURE=") {
+ cmd.Env = append(cmd.Env, v)
+ }
+ }
+ // Set the GIT_PERSISTENT_HTTPS_SECURE environment variable when
+ // the proxy is using a SSL connection. This allows credential helpers
+ // to identify secure proxy connections, despite being passed an HTTP
+ // scheme.
+ if !c.insecure {
+ cmd.Env = append(cmd.Env, "GIT_PERSISTENT_HTTPS_SECURE=1")
+ }
+
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ if eerr, ok := err.(*exec.ExitError); ok {
+ if stat, ok := eerr.ProcessState.Sys().(syscall.WaitStatus); ok && stat.ExitStatus() != 0 {
+ os.Exit(stat.ExitStatus())
+ }
+ }
+ return fmt.Errorf("git-remote-http subprocess got error: %v", err)
+ }
+ return nil
+}
+
+func (c *Client) connect() (uconn net.Conn, hconn net.Conn, addr string, err error) {
+ uconn, err = DefaultSocket.Dial()
+ if err != nil {
+ if e, ok := err.(*net.OpError); ok && (os.IsNotExist(e.Err) || e.Err == syscall.ECONNREFUSED) {
+ if err = c.startProxy(); err == nil {
+ uconn, err = DefaultSocket.Dial()
+ }
+ }
+ if err != nil {
+ return
+ }
+ }
+
+ if addr, err = c.readAddr(uconn); err != nil {
+ return
+ }
+
+ // Open a tcp connection to the proxy.
+ if hconn, err = net.Dial("tcp", addr); err != nil {
+ return
+ }
+
+ // Verify the address hasn't changed ownership.
+ var addr2 string
+ if addr2, err = c.readAddr(uconn); err != nil {
+ return
+ } else if addr != addr2 {
+ err = fmt.Errorf("address changed after connect. got %q, want %q", addr2, addr)
+ return
+ }
+ return
+}
+
+func (c *Client) readAddr(conn net.Conn) (string, error) {
+ conn.SetDeadline(time.Now().Add(5 * time.Second))
+ data := make([]byte, 100)
+ n, err := conn.Read(data)
+ if err != nil {
+ return "", fmt.Errorf("error reading unix socket: %v", err)
+ } else if n == 0 {
+ return "", errors.New("empty data response")
+ }
+ conn.Write([]byte{1}) // Ack
+
+ var addr string
+ if addrs := strings.Split(string(data[:n]), "\n"); len(addrs) != 2 {
+ return "", fmt.Errorf("got %q, wanted 2 addresses", data[:n])
+ } else if c.insecure {
+ addr = addrs[1]
+ } else {
+ addr = addrs[0]
+ }
+ return addr, nil
+}
+
+func (c *Client) startProxy() error {
+ cmd := exec.Command(c.ProxyBin)
+ cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ return err
+ }
+ defer stdout.Close()
+ if err := cmd.Start(); err != nil {
+ return err
+ }
+ result := make(chan error)
+ go func() {
+ bytes, _, err := bufio.NewReader(stdout).ReadLine()
+ if line := string(bytes); err == nil && line != "OK" {
+ err = fmt.Errorf("proxy returned %q, want \"OK\"", line)
+ }
+ result <- err
+ }()
+ select {
+ case err := <-result:
+ return err
+ case <-time.After(5 * time.Second):
+ return errors.New("timeout waiting for proxy to start")
+ }
+ panic("not reachable")
+}
+
+func (c *Client) resolveArgs() error {
+ if nargs := len(c.Args); nargs == 0 {
+ return errors.New("remote needed")
+ } else if nargs > 2 {
+ return fmt.Errorf("want at most 2 args, got %v", c.Args)
+ }
+
+ // Rewrite the url scheme to be http.
+ idx := len(c.Args) - 1
+ rawurl := c.Args[idx]
+ rurl, err := url.Parse(rawurl)
+ if err != nil {
+ return fmt.Errorf("invalid remote: %v", err)
+ }
+ c.insecure = rurl.Scheme == "persistent-http"
+ rurl.Scheme = "http"
+ c.Args[idx] = rurl.String()
+ if idx != 0 && c.Args[0] == rawurl {
+ c.Args[0] = c.Args[idx]
+ }
+ return nil
+}
diff --git a/contrib/persistent-https/main.go b/contrib/persistent-https/main.go
new file mode 100644
index 0000000..fd1b107
--- /dev/null
+++ b/contrib/persistent-https/main.go
@@ -0,0 +1,82 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// The git-remote-persistent-https binary speeds up SSL operations by running
+// a daemon job that keeps a connection open to a Git server. This ensures the
+// git-remote-persistent-https--proxy is running and delegating execution
+// to the git-remote-http binary with the http_proxy set to the daemon job.
+// A unix socket is used to authenticate the proxy and discover the
+// HTTP address. Note, both the client and proxy are included in the same
+// binary.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "strings"
+ "time"
+)
+
+var (
+ forceProxy = flag.Bool("proxy", false, "Whether to start the binary in proxy mode")
+ proxyBin = flag.String("proxy_bin", "git-remote-persistent-https--proxy", "Path to the proxy binary")
+ printLabel = flag.Bool("print_label", false, "Prints the build label for the binary")
+
+ // Variable that should be defined through the -X linker flag.
+ _BUILD_EMBED_LABEL string
+)
+
+const (
+ defaultMaxIdleDuration = 24 * time.Hour
+ defaultPollUpdateInterval = 15 * time.Minute
+)
+
+func main() {
+ flag.Parse()
+ if *printLabel {
+ // Short circuit execution to print the build label
+ fmt.Println(buildLabel())
+ return
+ }
+
+ var err error
+ if *forceProxy || strings.HasSuffix(os.Args[0], "--proxy") {
+ log.SetPrefix("git-remote-persistent-https--proxy: ")
+ proxy := &Proxy{
+ BuildLabel: buildLabel(),
+ MaxIdleDuration: defaultMaxIdleDuration,
+ PollUpdateInterval: defaultPollUpdateInterval,
+ }
+ err = proxy.Run()
+ } else {
+ log.SetPrefix("git-remote-persistent-https: ")
+ client := &Client{
+ ProxyBin: *proxyBin,
+ Args: flag.Args(),
+ }
+ err = client.Run()
+ }
+ if err != nil {
+ log.Fatalln(err)
+ }
+}
+
+func buildLabel() string {
+ if _BUILD_EMBED_LABEL == "" {
+ log.Println(`unlabeled build; build with "make" to label`)
+ }
+ return _BUILD_EMBED_LABEL
+}
diff --git a/contrib/persistent-https/proxy.go b/contrib/persistent-https/proxy.go
new file mode 100644
index 0000000..bb0cdba
--- /dev/null
+++ b/contrib/persistent-https/proxy.go
@@ -0,0 +1,190 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "net/http/httputil"
+ "os"
+ "os/exec"
+ "os/signal"
+ "sync"
+ "syscall"
+ "time"
+)
+
+type Proxy struct {
+ BuildLabel string
+ MaxIdleDuration time.Duration
+ PollUpdateInterval time.Duration
+
+ ul net.Listener
+ httpAddr string
+ httpsAddr string
+}
+
+func (p *Proxy) Run() error {
+ hl, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ return fmt.Errorf("http listen failed: %v", err)
+ }
+ defer hl.Close()
+
+ hsl, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ return fmt.Errorf("https listen failed: %v", err)
+ }
+ defer hsl.Close()
+
+ p.ul, err = DefaultSocket.Listen()
+ if err != nil {
+ c, derr := DefaultSocket.Dial()
+ if derr == nil {
+ c.Close()
+ fmt.Println("OK\nA proxy is already running... exiting")
+ return nil
+ } else if e, ok := derr.(*net.OpError); ok && e.Err == syscall.ECONNREFUSED {
+ // Nothing is listening on the socket, unlink it and try again.
+ syscall.Unlink(DefaultSocket.Path())
+ p.ul, err = DefaultSocket.Listen()
+ }
+ if err != nil {
+ return fmt.Errorf("unix listen failed on %v: %v", DefaultSocket.Path(), err)
+ }
+ }
+ defer p.ul.Close()
+ go p.closeOnSignal()
+ go p.closeOnUpdate()
+
+ p.httpAddr = hl.Addr().String()
+ p.httpsAddr = hsl.Addr().String()
+ fmt.Printf("OK\nListening on unix socket=%v http=%v https=%v\n",
+ p.ul.Addr(), p.httpAddr, p.httpsAddr)
+
+ result := make(chan error, 2)
+ go p.serveUnix(result)
+ go func() {
+ result <- http.Serve(hl, &httputil.ReverseProxy{
+ FlushInterval: 500 * time.Millisecond,
+ Director: func(r *http.Request) {},
+ })
+ }()
+ go func() {
+ result <- http.Serve(hsl, &httputil.ReverseProxy{
+ FlushInterval: 500 * time.Millisecond,
+ Director: func(r *http.Request) {
+ r.URL.Scheme = "https"
+ },
+ })
+ }()
+ return <-result
+}
+
+type socketContext struct {
+ sync.WaitGroup
+ mutex sync.Mutex
+ last time.Time
+}
+
+func (sc *socketContext) Done() {
+ sc.mutex.Lock()
+ defer sc.mutex.Unlock()
+ sc.last = time.Now()
+ sc.WaitGroup.Done()
+}
+
+func (p *Proxy) serveUnix(result chan<- error) {
+ sockCtx := &socketContext{}
+ go p.closeOnIdle(sockCtx)
+
+ var err error
+ for {
+ var uconn net.Conn
+ uconn, err = p.ul.Accept()
+ if err != nil {
+ err = fmt.Errorf("accept failed: %v", err)
+ break
+ }
+ sockCtx.Add(1)
+ go p.handleUnixConn(sockCtx, uconn)
+ }
+ sockCtx.Wait()
+ result <- err
+}
+
+func (p *Proxy) handleUnixConn(sockCtx *socketContext, uconn net.Conn) {
+ defer sockCtx.Done()
+ defer uconn.Close()
+ data := []byte(fmt.Sprintf("%v\n%v", p.httpsAddr, p.httpAddr))
+ uconn.SetDeadline(time.Now().Add(5 * time.Second))
+ for i := 0; i < 2; i++ {
+ if n, err := uconn.Write(data); err != nil {
+ log.Printf("error sending http addresses: %+v\n", err)
+ return
+ } else if n != len(data) {
+ log.Printf("sent %d data bytes, wanted %d\n", n, len(data))
+ return
+ }
+ if _, err := uconn.Read([]byte{0, 0, 0, 0}); err != nil {
+ log.Printf("error waiting for Ack: %+v\n", err)
+ return
+ }
+ }
+ // Wait without a deadline for the client to finish via EOF
+ uconn.SetDeadline(time.Time{})
+ uconn.Read([]byte{0, 0, 0, 0})
+}
+
+func (p *Proxy) closeOnIdle(sockCtx *socketContext) {
+ for d := p.MaxIdleDuration; d > 0; {
+ time.Sleep(d)
+ sockCtx.Wait()
+ sockCtx.mutex.Lock()
+ if d = sockCtx.last.Add(p.MaxIdleDuration).Sub(time.Now()); d <= 0 {
+ log.Println("graceful shutdown from idle timeout")
+ p.ul.Close()
+ }
+ sockCtx.mutex.Unlock()
+ }
+}
+
+func (p *Proxy) closeOnUpdate() {
+ for {
+ time.Sleep(p.PollUpdateInterval)
+ if out, err := exec.Command(os.Args[0], "--print_label").Output(); err != nil {
+ log.Printf("error polling for updated binary: %v\n", err)
+ } else if s := string(out[:len(out)-1]); p.BuildLabel != s {
+ log.Printf("graceful shutdown from updated binary: %q --> %q\n", p.BuildLabel, s)
+ p.ul.Close()
+ break
+ }
+ }
+}
+
+func (p *Proxy) closeOnSignal() {
+ ch := make(chan os.Signal, 10)
+ signal.Notify(ch, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM), os.Signal(syscall.SIGHUP))
+ sig := <-ch
+ p.ul.Close()
+ switch sig {
+ case os.Signal(syscall.SIGHUP):
+ log.Printf("graceful shutdown from signal: %v\n", sig)
+ default:
+ log.Fatalf("exiting from signal: %v\n", sig)
+ }
+}
diff --git a/contrib/persistent-https/socket.go b/contrib/persistent-https/socket.go
new file mode 100644
index 0000000..193b911
--- /dev/null
+++ b/contrib/persistent-https/socket.go
@@ -0,0 +1,97 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "os"
+ "path/filepath"
+ "syscall"
+)
+
+// A Socket is a wrapper around a Unix socket that verifies directory
+// permissions.
+type Socket struct {
+ Dir string
+}
+
+func defaultDir() string {
+ sockPath := ".git-credential-cache"
+ if home := os.Getenv("HOME"); home != "" {
+ return filepath.Join(home, sockPath)
+ }
+ log.Printf("socket: cannot find HOME path. using relative directory %q for socket", sockPath)
+ return sockPath
+}
+
+// DefaultSocket is a Socket in the $HOME/.git-credential-cache directory.
+var DefaultSocket = Socket{Dir: defaultDir()}
+
+// Listen announces the local network address of the unix socket. The
+// permissions on the socket directory are verified before attempting
+// the actual listen.
+func (s Socket) Listen() (net.Listener, error) {
+ network, addr := "unix", s.Path()
+ if err := s.mkdir(); err != nil {
+ return nil, &net.OpError{Op: "listen", Net: network, Addr: &net.UnixAddr{Name: addr, Net: network}, Err: err}
+ }
+ return net.Listen(network, addr)
+}
+
+// Dial connects to the unix socket. The permissions on the socket directory
+// are verified before attempting the actual dial.
+func (s Socket) Dial() (net.Conn, error) {
+ network, addr := "unix", s.Path()
+ if err := s.checkPermissions(); err != nil {
+ return nil, &net.OpError{Op: "dial", Net: network, Addr: &net.UnixAddr{Name: addr, Net: network}, Err: err}
+ }
+ return net.Dial(network, addr)
+}
+
+// Path returns the fully specified file name of the unix socket.
+func (s Socket) Path() string {
+ return filepath.Join(s.Dir, "persistent-https-proxy-socket")
+}
+
+func (s Socket) mkdir() error {
+ if err := s.checkPermissions(); err == nil {
+ return nil
+ } else if !os.IsNotExist(err) {
+ return err
+ }
+ if err := os.MkdirAll(s.Dir, 0700); err != nil {
+ return err
+ }
+ return s.checkPermissions()
+}
+
+func (s Socket) checkPermissions() error {
+ fi, err := os.Stat(s.Dir)
+ if err != nil {
+ return err
+ }
+ if !fi.IsDir() {
+ return fmt.Errorf("socket: got file, want directory for %q", s.Dir)
+ }
+ if fi.Mode().Perm() != 0700 {
+ return fmt.Errorf("socket: got perm %o, want 700 for %q", fi.Mode().Perm(), s.Dir)
+ }
+ if st := fi.Sys().(*syscall.Stat_t); int(st.Uid) != os.Getuid() {
+ return fmt.Errorf("socket: got uid %d, want %d for %q", st.Uid, os.Getuid(), s.Dir)
+ }
+ return nil
+}
--
1.7.9.2
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v2] Add persistent-https to contrib
2012-05-29 21:52 ` [PATCH v2] " Colby Ranger
@ 2012-05-29 23:34 ` Junio C Hamano
0 siblings, 0 replies; 12+ messages in thread
From: Junio C Hamano @ 2012-05-29 23:34 UTC (permalink / raw)
To: Colby Ranger; +Cc: git
Colby Ranger <cranger@google.com> writes:
> Git over HTTPS has a high request startup latency, since the SSL
> negotiation can take up to a second. In order to reduce this latency,
> connections should be left open to the Git server across requests
> (or invocations of the git commandline).
>
> Reduce SSL startup latency by running a daemon job that keeps
> connections open to a Git server. The daemon job
> (git-remote-persistent-https--proxy) is started on the first request
> through the client binary (git-remote-persistent-https) and remains
> running for 24 hours after the last request, or until a new daemon
> binary is placed in the PATH. The client determines the daemon's
> HTTP address by communicating over a UNIX socket with the daemon.
> From there, the rest of the Git protocol work is delegated to the
> "git-remote-http" binary, with the environment's http_proxy set to
> the daemon.
>
> Signed-off-by: Colby Ranger <cranger@google.com>
> ---
Thanks. I'll add a few paragraphs taken from Shawn's message in
this thread before queuing, but it'll take some time until I get to
this patch (I am looking at a totally unrelated topic that brought an
unpleasant regression to cherry-pick).
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2012-05-29 23:34 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-05-23 17:06 [PATCH] Add persistent-https to contrib Colby Ranger
2012-05-24 18:17 ` Junio C Hamano
2012-05-24 19:33 ` Shawn Pearce
2012-05-24 20:29 ` Jeff King
2012-05-24 20:42 ` Shawn Pearce
2012-05-24 20:46 ` Shawn Pearce
2012-05-24 20:51 ` Jeff King
2012-05-24 20:42 ` Daniel Stenberg
2012-05-24 20:51 ` Junio C Hamano
2012-05-29 21:29 ` Colby Ranger
2012-05-29 21:52 ` [PATCH v2] " Colby Ranger
2012-05-29 23:34 ` Junio C Hamano
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).