This adds a docker remote API proxy to the weaveexec image. The proxy has hooks for (post-) container start and (pre-) container create; the former attaches a weave interface to a container given a WEAVE_CIDR environment entry, and the latter adjusts the entrypoint of a container to wait for the ethwe interface. Notes: None of the hooks need to inspect/change large bodies. In order to avoid a crash when handling images larger than memory we need to ensure that the entire request body is never loaded into memory (i.e. No ioutils.ReadAll). When proxying a raw-stream response, we can't use http.WriteHeader. It sets the Transfer-Encoding to chunked, which will throw off the clients. Instead we have to hijack the socket, and write out http status code manually. For chunked transfer-encodings, clients (docker-py) depend on each http chunk containing exactly one JSON object. Therefore, we cannot re-chunk the body, and have to pass it across verbatim. Proxy needs to pass multi-cidrs separately when calling weave. Otherwise they get smashed together on the command line, and the call fails. When creating the image, if no entrypoint is specified on the container, it should use the entrypoint of the image. Code for this is based on powerstrip-weave. We handle WEAVE_CIDR='' by acting like it is not set. The reason being, that because WEAVE_CIDR is inherited from images (along with env vars), setting it to blank is equivalent to unsetting it.
172 lines
4.2 KiB
Go
172 lines
4.2 KiB
Go
package proxy
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
|
|
. "github.com/weaveworks/weave/common"
|
|
)
|
|
|
|
type client struct {
|
|
interceptor
|
|
Dial func() (net.Conn, error)
|
|
}
|
|
|
|
func newClient(dial func() (net.Conn, error), i interceptor) *client {
|
|
return &client{
|
|
interceptor: i,
|
|
Dial: dial,
|
|
}
|
|
}
|
|
|
|
func (c *client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
req, err := c.InterceptRequest(r)
|
|
if err == docker.ErrNoSuchImage {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
if err != nil {
|
|
http.Error(w, "Unable to create proxied request", http.StatusInternalServerError)
|
|
Warning.Print("Error intercepting request: ", err)
|
|
return
|
|
}
|
|
|
|
conn, err := c.Dial()
|
|
if err != nil {
|
|
http.Error(w, "Could not connect to target", http.StatusInternalServerError)
|
|
Warning.Print(err)
|
|
return
|
|
}
|
|
client := httputil.NewClientConn(conn, nil)
|
|
defer client.Close()
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil && err != httputil.ErrPersistEOF {
|
|
http.Error(w, fmt.Sprintf("Could not make request to target: %s", err.Error()), http.StatusInternalServerError)
|
|
Warning.Print("Error forwarding request: ", err)
|
|
return
|
|
}
|
|
resp, err = c.InterceptResponse(resp)
|
|
if err != nil {
|
|
http.Error(w, "Unable to intercept response", http.StatusInternalServerError)
|
|
Warning.Print("Error intercepting response: ", err)
|
|
return
|
|
}
|
|
|
|
hdr := w.Header()
|
|
for k, vs := range resp.Header {
|
|
for _, v := range vs {
|
|
hdr.Add(k, v)
|
|
}
|
|
}
|
|
Debug.Printf("Response from target: %s %v", resp.Status, w.Header())
|
|
|
|
if resp.Header.Get("Content-Type") == "application/vnd.docker.raw-stream" {
|
|
doRawStream(w, resp, client)
|
|
} else if resp.TransferEncoding != nil && resp.TransferEncoding[0] == "chunked" {
|
|
doChunkedResponse(w, resp, client)
|
|
} else {
|
|
w.WriteHeader(resp.StatusCode)
|
|
if _, err := io.Copy(w, resp.Body); err != nil {
|
|
Warning.Print(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func doRawStream(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) {
|
|
down, downBuf, up, rem, err := hijack(w, client)
|
|
if err != nil {
|
|
Error.Fatal(w, "Unable to hijack connection for raw stream mode", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer down.Close()
|
|
defer up.Close()
|
|
|
|
if _, err := down.Write([]byte("HTTP/1.1 200 OK\n")); err != nil {
|
|
Warning.Print(err)
|
|
return
|
|
}
|
|
|
|
if err := resp.Header.Write(down); err != nil {
|
|
Warning.Print(err)
|
|
return
|
|
}
|
|
|
|
if _, err := down.Write([]byte("\n")); err != nil {
|
|
Warning.Print(err)
|
|
return
|
|
}
|
|
|
|
end := make(chan bool)
|
|
go func() {
|
|
defer close(end)
|
|
if _, err := io.Copy(up, downBuf); err != nil {
|
|
Warning.Print(err)
|
|
}
|
|
err = up.(interface {
|
|
CloseWrite() error
|
|
}).CloseWrite()
|
|
if err != nil {
|
|
Debug.Printf("Error Closing upstream: %s", err)
|
|
}
|
|
}()
|
|
|
|
if _, err := io.Copy(down, io.MultiReader(rem, up)); err != nil {
|
|
Warning.Print(err)
|
|
}
|
|
err = down.(interface {
|
|
CloseWrite() error
|
|
}).CloseWrite()
|
|
if err != nil {
|
|
Debug.Printf("Error Closing downstream: %s", err)
|
|
}
|
|
|
|
<-end
|
|
}
|
|
|
|
func doChunkedResponse(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) {
|
|
// Because we can't go back to request/response after we
|
|
// hijack the connection, we need to close it and make the
|
|
// client open another.
|
|
w.Header().Add("Connection", "close")
|
|
w.WriteHeader(resp.StatusCode)
|
|
|
|
down, _, up, rem, err := hijack(w, client)
|
|
if err != nil {
|
|
Error.Fatal("Unable to hijack response stream for chunked response", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer up.Close()
|
|
defer down.Close()
|
|
// Copy the chunked response body to downstream,
|
|
// stopping at the end of the chunked section.
|
|
rawResponseBody := io.MultiReader(rem, up)
|
|
if _, err := io.Copy(ioutil.Discard, httputil.NewChunkedReader(io.TeeReader(rawResponseBody, down))); err != nil {
|
|
Error.Fatal("Error copying chunked response body", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
resp.Trailer.Write(down)
|
|
// a chunked response ends with a CRLF
|
|
down.Write([]byte("\r\n"))
|
|
}
|
|
|
|
func hijack(w http.ResponseWriter, client *httputil.ClientConn) (down net.Conn, downBuf *bufio.ReadWriter, up net.Conn, rem io.Reader, err error) {
|
|
hj, ok := w.(http.Hijacker)
|
|
if !ok {
|
|
err = errors.New("Unable to cast to Hijack")
|
|
return
|
|
}
|
|
down, downBuf, err = hj.Hijack()
|
|
if err != nil {
|
|
return
|
|
}
|
|
up, rem = client.Hijack()
|
|
return
|
|
}
|