func handleReverse(w http.ResponseWriter, r *http.Request) { if r.TLS == nil { http.Error(w, "buildlet registration requires SSL", http.StatusInternalServerError) return } // Check build keys. modes := r.Header["X-Go-Builder-Type"] gobuildkeys := r.Header["X-Go-Builder-Key"] if len(modes) == 0 || len(modes) != len(gobuildkeys) { http.Error(w, fmt.Sprintf("need at least one mode and matching key, got %d/%d", len(modes), len(gobuildkeys)), http.StatusPreconditionFailed) return } for i, m := range modes { if gobuildkeys[i] != builderKey(m) { http.Error(w, fmt.Sprintf("bad key for mode %q", m), http.StatusPreconditionFailed) return } } conn, bufrw, err := w.(http.Hijacker).Hijack() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } hostname := r.Header.Get("X-Go-Builder-Hostname") revDialer := revdial.NewDialer(bufrw, conn) log.Printf("Registering reverse buildlet %q (%s) for modes %v", hostname, r.RemoteAddr, modes) (&http.Response{StatusCode: http.StatusSwitchingProtocols, Proto: "HTTP/1.1"}).Write(conn) client := buildlet.NewClient(hostname, buildlet.NoKeyPair) client.SetHTTPClient(&http.Client{ Transport: &http.Transport{ Dial: func(network, addr string) (net.Conn, error) { return revDialer.Dial() }, }, }) client.SetDescription(fmt.Sprintf("reverse peer %s/%s for modes %v", hostname, r.RemoteAddr, modes)) var isDead struct { sync.Mutex v bool } client.SetOnHeartbeatFailure(func() { isDead.Lock() isDead.v = true isDead.Unlock() conn.Close() reversePool.nukeBuildlet(client) }) // If the reverse dialer (which is always reading from the // conn) detects that the remote went away, close the buildlet // client proactively show go func() { <-revDialer.Done() isDead.Lock() defer isDead.Unlock() if !isDead.v { client.Close() } }() tstatus := time.Now() status, err := client.Status() if err != nil { log.Printf("Reverse connection %s/%s for modes %v did not answer status after %v: %v", hostname, r.RemoteAddr, modes, time.Since(tstatus), err) conn.Close() return } if status.Version < minBuildletVersion { log.Printf("Buildlet too old: %s, %+v", r.RemoteAddr, status) conn.Close() return } log.Printf("Buildlet %s/%s: %+v for %s", hostname, r.RemoteAddr, status, modes) now := time.Now() b := &reverseBuildlet{ hostname: hostname, version: r.Header.Get("X-Go-Builder-Version"), modes: modes, client: client, conn: conn, inUseTime: now, regTime: now, } reversePool.addBuildlet(b) registerBuildlet(modes) // testing only }
func handleReverse(w http.ResponseWriter, r *http.Request) { if r.TLS == nil { http.Error(w, "buildlet registration requires SSL", http.StatusInternalServerError) return } // Check build keys. modes := r.Header["X-Go-Builder-Type"] gobuildkeys := r.Header["X-Go-Builder-Key"] if len(modes) == 0 || len(modes) != len(gobuildkeys) { http.Error(w, fmt.Sprintf("need at least one mode and matching key, got %d/%d", len(modes), len(gobuildkeys)), http.StatusPreconditionFailed) return } for i, m := range modes { if gobuildkeys[i] != builderKey(m) { http.Error(w, fmt.Sprintf("bad key for mode %q", m), http.StatusPreconditionFailed) return } } conn, bufrw, err := w.(http.Hijacker).Hijack() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } log.Printf("Registering reverse buildlet %s for modes %v", r.RemoteAddr, modes) // The server becomes a (very simple) http client. (&http.Response{StatusCode: 200, Proto: "HTTP/1.1"}).Write(conn) client := buildlet.NewClient("none", buildlet.NoKeyPair) client.SetHTTPClient(&http.Client{ Transport: newRoundTripper(client, conn, bufrw), }) client.SetDescription(fmt.Sprintf("reverse peer %s for modes %v", r.RemoteAddr, modes)) tstatus := time.Now() status, err := client.Status() if err != nil { log.Printf("Reverse connection %s for modes %v did not answer status after %v: %v", r.RemoteAddr, modes, time.Since(tstatus), err) conn.Close() return } if status.Version < minBuildletVersion { log.Printf("Buildlet too old: %s, %+v", r.RemoteAddr, status) conn.Close() return } log.Printf("Buildlet %s: %+v for %s", r.RemoteAddr, status, modes) // TODO(crawshaw): unregister buildlet when it disconnects. Maybe just // periodically request Status, and if there's no response unregister. reversePool.mu.Lock() defer reversePool.mu.Unlock() b := &reverseBuildlet{ modes: modes, client: client, conn: conn, inUseTime: time.Now(), } reversePool.buildlets = append(reversePool.buildlets, b) registerBuildlet(modes) }