Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package build // import "github.com/docker/docker/api/server/router/build" |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "bufio" |
|
| 4 | 5 |
"bytes" |
| 5 | 6 |
"context" |
| 6 | 7 |
"encoding/base64" |
| ... | ... |
@@ -192,8 +193,19 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r * |
| 192 | 192 |
|
| 193 | 193 |
w.Header().Set("Content-Type", "application/json")
|
| 194 | 194 |
|
| 195 |
- output := ioutils.NewWriteFlusher(w) |
|
| 195 |
+ var output writeCloseFlusher = ioutils.NewWriteFlusher(w) |
|
| 196 | 196 |
defer output.Close() |
| 197 |
+ |
|
| 198 |
+ body := r.Body |
|
| 199 |
+ if body != nil {
|
|
| 200 |
+ // there is a possibility that output is written before request body |
|
| 201 |
+ // has been fully read so we need to protect against it. |
|
| 202 |
+ // this can be removed when |
|
| 203 |
+ // https://github.com/golang/go/issues/15527 |
|
| 204 |
+ // https://github.com/golang/go/issues/22209 |
|
| 205 |
+ // has been fixed |
|
| 206 |
+ body, output = wrapOutputBufferedUntilRequestRead(body, output) |
|
| 207 |
+ } |
|
| 197 | 208 |
errf := func(err error) error {
|
| 198 | 209 |
if httputils.BoolValue(r, "q") && notVerboseBuffer.Len() > 0 {
|
| 199 | 210 |
output.Write(notVerboseBuffer.Bytes()) |
| ... | ... |
@@ -235,7 +247,7 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r * |
| 235 | 235 |
wantAux := versions.GreaterThanOrEqualTo(version, "1.30") |
| 236 | 236 |
|
| 237 | 237 |
imgID, err := br.backend.Build(ctx, backend.BuildConfig{
|
| 238 |
- Source: r.Body, |
|
| 238 |
+ Source: body, |
|
| 239 | 239 |
Options: buildOptions, |
| 240 | 240 |
ProgressWriter: buildProgressWriter(out, wantAux, createProgressReader), |
| 241 | 241 |
}) |
| ... | ... |
@@ -294,3 +306,92 @@ func buildProgressWriter(out io.Writer, wantAux bool, createProgressReader func( |
| 294 | 294 |
ProgressReaderFunc: createProgressReader, |
| 295 | 295 |
} |
| 296 | 296 |
} |
| 297 |
+ |
|
| 298 |
+type writeCloseFlusher interface {
|
|
| 299 |
+ Flush() |
|
| 300 |
+ Flushed() bool |
|
| 301 |
+ io.WriteCloser |
|
| 302 |
+} |
|
| 303 |
+ |
|
| 304 |
+func wrapOutputBufferedUntilRequestRead(rc io.ReadCloser, out writeCloseFlusher) (io.ReadCloser, writeCloseFlusher) {
|
|
| 305 |
+ w := &wcf{
|
|
| 306 |
+ buf: bytes.NewBuffer(nil), |
|
| 307 |
+ writeCloseFlusher: out, |
|
| 308 |
+ } |
|
| 309 |
+ r := bufio.NewReader(rc) |
|
| 310 |
+ _, err := r.Peek(1) |
|
| 311 |
+ if err != nil {
|
|
| 312 |
+ return rc, out |
|
| 313 |
+ } |
|
| 314 |
+ rc = &rcNotifier{
|
|
| 315 |
+ Reader: r, |
|
| 316 |
+ Closer: rc, |
|
| 317 |
+ notify: w.notify, |
|
| 318 |
+ } |
|
| 319 |
+ return rc, w |
|
| 320 |
+} |
|
| 321 |
+ |
|
| 322 |
+type rcNotifier struct {
|
|
| 323 |
+ io.Reader |
|
| 324 |
+ io.Closer |
|
| 325 |
+ notify func() |
|
| 326 |
+} |
|
| 327 |
+ |
|
| 328 |
+func (r *rcNotifier) Read(b []byte) (int, error) {
|
|
| 329 |
+ n, err := r.Reader.Read(b) |
|
| 330 |
+ if err != nil {
|
|
| 331 |
+ r.notify() |
|
| 332 |
+ } |
|
| 333 |
+ return n, err |
|
| 334 |
+} |
|
| 335 |
+ |
|
| 336 |
+type wcf struct {
|
|
| 337 |
+ writeCloseFlusher |
|
| 338 |
+ mu sync.Mutex |
|
| 339 |
+ ready bool |
|
| 340 |
+ buf *bytes.Buffer |
|
| 341 |
+ flushed bool |
|
| 342 |
+} |
|
| 343 |
+ |
|
| 344 |
+func (w *wcf) Flush() {
|
|
| 345 |
+ w.mu.Lock() |
|
| 346 |
+ w.flushed = true |
|
| 347 |
+ if !w.ready {
|
|
| 348 |
+ w.mu.Unlock() |
|
| 349 |
+ return |
|
| 350 |
+ } |
|
| 351 |
+ w.mu.Unlock() |
|
| 352 |
+ w.writeCloseFlusher.Flush() |
|
| 353 |
+} |
|
| 354 |
+ |
|
| 355 |
+func (w *wcf) Flushed() bool {
|
|
| 356 |
+ w.mu.Lock() |
|
| 357 |
+ b := w.flushed |
|
| 358 |
+ w.mu.Unlock() |
|
| 359 |
+ return b |
|
| 360 |
+} |
|
| 361 |
+ |
|
| 362 |
+func (w *wcf) Write(b []byte) (int, error) {
|
|
| 363 |
+ w.mu.Lock() |
|
| 364 |
+ if !w.ready {
|
|
| 365 |
+ n, err := w.buf.Write(b) |
|
| 366 |
+ w.mu.Unlock() |
|
| 367 |
+ return n, err |
|
| 368 |
+ } |
|
| 369 |
+ w.mu.Unlock() |
|
| 370 |
+ return w.writeCloseFlusher.Write(b) |
|
| 371 |
+} |
|
| 372 |
+ |
|
| 373 |
+func (w *wcf) notify() {
|
|
| 374 |
+ w.mu.Lock() |
|
| 375 |
+ if !w.ready {
|
|
| 376 |
+ if w.buf.Len() > 0 {
|
|
| 377 |
+ io.Copy(w.writeCloseFlusher, w.buf) |
|
| 378 |
+ } |
|
| 379 |
+ if w.flushed {
|
|
| 380 |
+ w.writeCloseFlusher.Flush() |
|
| 381 |
+ } |
|
| 382 |
+ w.ready = true |
|
| 383 |
+ } |
|
| 384 |
+ w.mu.Unlock() |
|
| 385 |
+} |
| ... | ... |
@@ -1,7 +1,6 @@ |
| 1 | 1 |
package buildkit |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "bufio" |
|
| 5 | 4 |
"io" |
| 6 | 5 |
"net/http" |
| 7 | 6 |
"strings" |
| ... | ... |
@@ -28,12 +27,9 @@ func newReqBodyHandler(rt http.RoundTripper) *reqBodyHandler {
|
| 28 | 28 |
} |
| 29 | 29 |
|
| 30 | 30 |
func (h *reqBodyHandler) newRequest(rc io.ReadCloser) (string, func()) {
|
| 31 |
- // handle expect-continue vs chunked output |
|
| 32 |
- r := bufio.NewReader(rc) |
|
| 33 |
- r.Peek(1) |
|
| 34 | 31 |
id := identity.NewID() |
| 35 | 32 |
h.mu.Lock() |
| 36 |
- h.requests[id] = &readCloser{Reader: r, Closer: rc}
|
|
| 33 |
+ h.requests[id] = rc |
|
| 37 | 34 |
h.mu.Unlock() |
| 38 | 35 |
return "http://" + urlPrefix + id, func() {
|
| 39 | 36 |
h.mu.Lock() |
| ... | ... |
@@ -58,12 +54,14 @@ func (h *reqBodyHandler) RoundTrip(req *http.Request) (*http.Response, error) {
|
| 58 | 58 |
return nil, errors.Errorf("context not found")
|
| 59 | 59 |
} |
| 60 | 60 |
|
| 61 |
- return &http.Response{
|
|
| 61 |
+ resp := &http.Response{
|
|
| 62 | 62 |
Status: "200 OK", |
| 63 | 63 |
StatusCode: 200, |
| 64 | 64 |
Body: rc, |
| 65 | 65 |
ContentLength: -1, |
| 66 |
- }, nil |
|
| 66 |
+ } |
|
| 67 |
+ |
|
| 68 |
+ return resp, nil |
|
| 67 | 69 |
} |
| 68 | 70 |
return h.rt.RoundTrip(req) |
| 69 | 71 |
} |