Browse code

Separate API router from server.

Implement basic interfaces to write custom routers that can be plugged
to the server. Remove server coupling with the daemon.

Signed-off-by: David Calavera <david.calavera@gmail.com>

David Calavera authored on 2015/09/24 08:42:08
Showing 41 changed files
1 1
deleted file mode 100644
... ...
@@ -1,26 +0,0 @@
1
-package server
2
-
3
-import (
4
-	"encoding/json"
5
-	"net/http"
6
-
7
-	"github.com/docker/docker/api/types"
8
-	"github.com/docker/docker/cliconfig"
9
-	"golang.org/x/net/context"
10
-)
11
-
12
-func (s *Server) postAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
13
-	var config *cliconfig.AuthConfig
14
-	err := json.NewDecoder(r.Body).Decode(&config)
15
-	r.Body.Close()
16
-	if err != nil {
17
-		return err
18
-	}
19
-	status, err := s.daemon.RegistryService.Auth(config)
20
-	if err != nil {
21
-		return err
22
-	}
23
-	return writeJSON(w, http.StatusOK, &types.AuthResponse{
24
-		Status: status,
25
-	})
26
-}
27 1
deleted file mode 100644
... ...
@@ -1,506 +0,0 @@
1
-package server
2
-
3
-import (
4
-	"fmt"
5
-	"io"
6
-	"net/http"
7
-	"strconv"
8
-	"strings"
9
-	"syscall"
10
-	"time"
11
-
12
-	"github.com/Sirupsen/logrus"
13
-	"github.com/docker/distribution/registry/api/errcode"
14
-	"github.com/docker/docker/api/types"
15
-	"github.com/docker/docker/daemon"
16
-	derr "github.com/docker/docker/errors"
17
-	"github.com/docker/docker/pkg/ioutils"
18
-	"github.com/docker/docker/pkg/signal"
19
-	"github.com/docker/docker/runconfig"
20
-	"github.com/docker/docker/utils"
21
-	"golang.org/x/net/context"
22
-	"golang.org/x/net/websocket"
23
-)
24
-
25
-func (s *Server) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
26
-	if err := parseForm(r); err != nil {
27
-		return err
28
-	}
29
-
30
-	config := &daemon.ContainersConfig{
31
-		All:     boolValue(r, "all"),
32
-		Size:    boolValue(r, "size"),
33
-		Since:   r.Form.Get("since"),
34
-		Before:  r.Form.Get("before"),
35
-		Filters: r.Form.Get("filters"),
36
-	}
37
-
38
-	if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
39
-		limit, err := strconv.Atoi(tmpLimit)
40
-		if err != nil {
41
-			return err
42
-		}
43
-		config.Limit = limit
44
-	}
45
-
46
-	containers, err := s.daemon.Containers(config)
47
-	if err != nil {
48
-		return err
49
-	}
50
-
51
-	return writeJSON(w, http.StatusOK, containers)
52
-}
53
-
54
-func (s *Server) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
55
-	if err := parseForm(r); err != nil {
56
-		return err
57
-	}
58
-	if vars == nil {
59
-		return fmt.Errorf("Missing parameter")
60
-	}
61
-
62
-	stream := boolValueOrDefault(r, "stream", true)
63
-	var out io.Writer
64
-	if !stream {
65
-		w.Header().Set("Content-Type", "application/json")
66
-		out = w
67
-	} else {
68
-		out = ioutils.NewWriteFlusher(w)
69
-	}
70
-
71
-	var closeNotifier <-chan bool
72
-	if notifier, ok := w.(http.CloseNotifier); ok {
73
-		closeNotifier = notifier.CloseNotify()
74
-	}
75
-
76
-	config := &daemon.ContainerStatsConfig{
77
-		Stream:    stream,
78
-		OutStream: out,
79
-		Stop:      closeNotifier,
80
-		Version:   versionFromContext(ctx),
81
-	}
82
-
83
-	return s.daemon.ContainerStats(vars["name"], config)
84
-}
85
-
86
-func (s *Server) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
87
-	if err := parseForm(r); err != nil {
88
-		return err
89
-	}
90
-	if vars == nil {
91
-		return fmt.Errorf("Missing parameter")
92
-	}
93
-
94
-	// Args are validated before the stream starts because when it starts we're
95
-	// sending HTTP 200 by writing an empty chunk of data to tell the client that
96
-	// daemon is going to stream. By sending this initial HTTP 200 we can't report
97
-	// any error after the stream starts (i.e. container not found, wrong parameters)
98
-	// with the appropriate status code.
99
-	stdout, stderr := boolValue(r, "stdout"), boolValue(r, "stderr")
100
-	if !(stdout || stderr) {
101
-		return fmt.Errorf("Bad parameters: you must choose at least one stream")
102
-	}
103
-
104
-	var since time.Time
105
-	if r.Form.Get("since") != "" {
106
-		s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64)
107
-		if err != nil {
108
-			return err
109
-		}
110
-		since = time.Unix(s, 0)
111
-	}
112
-
113
-	var closeNotifier <-chan bool
114
-	if notifier, ok := w.(http.CloseNotifier); ok {
115
-		closeNotifier = notifier.CloseNotify()
116
-	}
117
-
118
-	c, err := s.daemon.Get(vars["name"])
119
-	if err != nil {
120
-		return err
121
-	}
122
-
123
-	outStream := ioutils.NewWriteFlusher(w)
124
-	// write an empty chunk of data (this is to ensure that the
125
-	// HTTP Response is sent immediately, even if the container has
126
-	// not yet produced any data)
127
-	outStream.Write(nil)
128
-
129
-	logsConfig := &daemon.ContainerLogsConfig{
130
-		Follow:     boolValue(r, "follow"),
131
-		Timestamps: boolValue(r, "timestamps"),
132
-		Since:      since,
133
-		Tail:       r.Form.Get("tail"),
134
-		UseStdout:  stdout,
135
-		UseStderr:  stderr,
136
-		OutStream:  outStream,
137
-		Stop:       closeNotifier,
138
-	}
139
-
140
-	if err := s.daemon.ContainerLogs(c, logsConfig); err != nil {
141
-		// The client may be expecting all of the data we're sending to
142
-		// be multiplexed, so send it through OutStream, which will
143
-		// have been set up to handle that if needed.
144
-		fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %s\n", utils.GetErrorMessage(err))
145
-	}
146
-
147
-	return nil
148
-}
149
-
150
-func (s *Server) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
151
-	if vars == nil {
152
-		return fmt.Errorf("Missing parameter")
153
-	}
154
-
155
-	return s.daemon.ContainerExport(vars["name"], w)
156
-}
157
-
158
-func (s *Server) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
159
-	if vars == nil {
160
-		return fmt.Errorf("Missing parameter")
161
-	}
162
-
163
-	// If contentLength is -1, we can assumed chunked encoding
164
-	// or more technically that the length is unknown
165
-	// https://golang.org/src/pkg/net/http/request.go#L139
166
-	// net/http otherwise seems to swallow any headers related to chunked encoding
167
-	// including r.TransferEncoding
168
-	// allow a nil body for backwards compatibility
169
-	var hostConfig *runconfig.HostConfig
170
-	if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) {
171
-		if err := checkForJSON(r); err != nil {
172
-			return err
173
-		}
174
-
175
-		c, err := runconfig.DecodeHostConfig(r.Body)
176
-		if err != nil {
177
-			return err
178
-		}
179
-
180
-		hostConfig = c
181
-	}
182
-
183
-	if err := s.daemon.ContainerStart(vars["name"], hostConfig); err != nil {
184
-		return err
185
-	}
186
-	w.WriteHeader(http.StatusNoContent)
187
-	return nil
188
-}
189
-
190
-func (s *Server) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
191
-	if err := parseForm(r); err != nil {
192
-		return err
193
-	}
194
-	if vars == nil {
195
-		return fmt.Errorf("Missing parameter")
196
-	}
197
-
198
-	seconds, _ := strconv.Atoi(r.Form.Get("t"))
199
-
200
-	if err := s.daemon.ContainerStop(vars["name"], seconds); err != nil {
201
-		return err
202
-	}
203
-	w.WriteHeader(http.StatusNoContent)
204
-
205
-	return nil
206
-}
207
-
208
-func (s *Server) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
209
-	if vars == nil {
210
-		return fmt.Errorf("Missing parameter")
211
-	}
212
-	if err := parseForm(r); err != nil {
213
-		return err
214
-	}
215
-
216
-	var sig syscall.Signal
217
-	name := vars["name"]
218
-
219
-	// If we have a signal, look at it. Otherwise, do nothing
220
-	if sigStr := r.Form.Get("signal"); sigStr != "" {
221
-		var err error
222
-		if sig, err = signal.ParseSignal(sigStr); err != nil {
223
-			return err
224
-		}
225
-	}
226
-
227
-	if err := s.daemon.ContainerKill(name, uint64(sig)); err != nil {
228
-		theErr, isDerr := err.(errcode.ErrorCoder)
229
-		isStopped := isDerr && theErr.ErrorCode() == derr.ErrorCodeNotRunning
230
-
231
-		// Return error that's not caused because the container is stopped.
232
-		// Return error if the container is not running and the api is >= 1.20
233
-		// to keep backwards compatibility.
234
-		version := versionFromContext(ctx)
235
-		if version.GreaterThanOrEqualTo("1.20") || !isStopped {
236
-			return fmt.Errorf("Cannot kill container %s: %v", name, err)
237
-		}
238
-	}
239
-
240
-	w.WriteHeader(http.StatusNoContent)
241
-	return nil
242
-}
243
-
244
-func (s *Server) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
245
-	if err := parseForm(r); err != nil {
246
-		return err
247
-	}
248
-	if vars == nil {
249
-		return fmt.Errorf("Missing parameter")
250
-	}
251
-
252
-	timeout, _ := strconv.Atoi(r.Form.Get("t"))
253
-
254
-	if err := s.daemon.ContainerRestart(vars["name"], timeout); err != nil {
255
-		return err
256
-	}
257
-
258
-	w.WriteHeader(http.StatusNoContent)
259
-
260
-	return nil
261
-}
262
-
263
-func (s *Server) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
264
-	if vars == nil {
265
-		return fmt.Errorf("Missing parameter")
266
-	}
267
-	if err := parseForm(r); err != nil {
268
-		return err
269
-	}
270
-
271
-	if err := s.daemon.ContainerPause(vars["name"]); err != nil {
272
-		return err
273
-	}
274
-
275
-	w.WriteHeader(http.StatusNoContent)
276
-
277
-	return nil
278
-}
279
-
280
-func (s *Server) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
281
-	if vars == nil {
282
-		return fmt.Errorf("Missing parameter")
283
-	}
284
-	if err := parseForm(r); err != nil {
285
-		return err
286
-	}
287
-
288
-	if err := s.daemon.ContainerUnpause(vars["name"]); err != nil {
289
-		return err
290
-	}
291
-
292
-	w.WriteHeader(http.StatusNoContent)
293
-
294
-	return nil
295
-}
296
-
297
-func (s *Server) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
298
-	if vars == nil {
299
-		return fmt.Errorf("Missing parameter")
300
-	}
301
-
302
-	status, err := s.daemon.ContainerWait(vars["name"], -1*time.Second)
303
-	if err != nil {
304
-		return err
305
-	}
306
-
307
-	return writeJSON(w, http.StatusOK, &types.ContainerWaitResponse{
308
-		StatusCode: status,
309
-	})
310
-}
311
-
312
-func (s *Server) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
313
-	if vars == nil {
314
-		return fmt.Errorf("Missing parameter")
315
-	}
316
-
317
-	changes, err := s.daemon.ContainerChanges(vars["name"])
318
-	if err != nil {
319
-		return err
320
-	}
321
-
322
-	return writeJSON(w, http.StatusOK, changes)
323
-}
324
-
325
-func (s *Server) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
326
-	if vars == nil {
327
-		return fmt.Errorf("Missing parameter")
328
-	}
329
-
330
-	if err := parseForm(r); err != nil {
331
-		return err
332
-	}
333
-
334
-	procList, err := s.daemon.ContainerTop(vars["name"], r.Form.Get("ps_args"))
335
-	if err != nil {
336
-		return err
337
-	}
338
-
339
-	return writeJSON(w, http.StatusOK, procList)
340
-}
341
-
342
-func (s *Server) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
343
-	if err := parseForm(r); err != nil {
344
-		return err
345
-	}
346
-	if vars == nil {
347
-		return fmt.Errorf("Missing parameter")
348
-	}
349
-
350
-	name := vars["name"]
351
-	newName := r.Form.Get("name")
352
-	if err := s.daemon.ContainerRename(name, newName); err != nil {
353
-		return err
354
-	}
355
-	w.WriteHeader(http.StatusNoContent)
356
-	return nil
357
-}
358
-
359
-func (s *Server) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
360
-	if err := parseForm(r); err != nil {
361
-		return err
362
-	}
363
-	if err := checkForJSON(r); err != nil {
364
-		return err
365
-	}
366
-
367
-	name := r.Form.Get("name")
368
-
369
-	config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
370
-	if err != nil {
371
-		return err
372
-	}
373
-	version := versionFromContext(ctx)
374
-	adjustCPUShares := version.LessThan("1.19")
375
-
376
-	ccr, err := s.daemon.ContainerCreate(name, config, hostConfig, adjustCPUShares)
377
-	if err != nil {
378
-		return err
379
-	}
380
-
381
-	return writeJSON(w, http.StatusCreated, ccr)
382
-}
383
-
384
-func (s *Server) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
385
-	if err := parseForm(r); err != nil {
386
-		return err
387
-	}
388
-	if vars == nil {
389
-		return fmt.Errorf("Missing parameter")
390
-	}
391
-
392
-	name := vars["name"]
393
-	config := &daemon.ContainerRmConfig{
394
-		ForceRemove:  boolValue(r, "force"),
395
-		RemoveVolume: boolValue(r, "v"),
396
-		RemoveLink:   boolValue(r, "link"),
397
-	}
398
-
399
-	if err := s.daemon.ContainerRm(name, config); err != nil {
400
-		// Force a 404 for the empty string
401
-		if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") {
402
-			return fmt.Errorf("no such id: \"\"")
403
-		}
404
-		return err
405
-	}
406
-
407
-	w.WriteHeader(http.StatusNoContent)
408
-
409
-	return nil
410
-}
411
-
412
-func (s *Server) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
413
-	if err := parseForm(r); err != nil {
414
-		return err
415
-	}
416
-	if vars == nil {
417
-		return fmt.Errorf("Missing parameter")
418
-	}
419
-
420
-	height, err := strconv.Atoi(r.Form.Get("h"))
421
-	if err != nil {
422
-		return err
423
-	}
424
-	width, err := strconv.Atoi(r.Form.Get("w"))
425
-	if err != nil {
426
-		return err
427
-	}
428
-
429
-	return s.daemon.ContainerResize(vars["name"], height, width)
430
-}
431
-
432
-func (s *Server) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
433
-	if err := parseForm(r); err != nil {
434
-		return err
435
-	}
436
-	if vars == nil {
437
-		return fmt.Errorf("Missing parameter")
438
-	}
439
-	containerName := vars["name"]
440
-
441
-	if !s.daemon.Exists(containerName) {
442
-		return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
443
-	}
444
-
445
-	inStream, outStream, err := hijackServer(w)
446
-	if err != nil {
447
-		return err
448
-	}
449
-	defer closeStreams(inStream, outStream)
450
-
451
-	if _, ok := r.Header["Upgrade"]; ok {
452
-		fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
453
-	} else {
454
-		fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
455
-	}
456
-
457
-	attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{
458
-		InStream:  inStream,
459
-		OutStream: outStream,
460
-		UseStdin:  boolValue(r, "stdin"),
461
-		UseStdout: boolValue(r, "stdout"),
462
-		UseStderr: boolValue(r, "stderr"),
463
-		Logs:      boolValue(r, "logs"),
464
-		Stream:    boolValue(r, "stream"),
465
-	}
466
-
467
-	if err := s.daemon.ContainerAttachWithLogs(containerName, attachWithLogsConfig); err != nil {
468
-		fmt.Fprintf(outStream, "Error attaching: %s\n", err)
469
-	}
470
-
471
-	return nil
472
-}
473
-
474
-func (s *Server) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
475
-	if err := parseForm(r); err != nil {
476
-		return err
477
-	}
478
-	if vars == nil {
479
-		return fmt.Errorf("Missing parameter")
480
-	}
481
-	containerName := vars["name"]
482
-
483
-	if !s.daemon.Exists(containerName) {
484
-		return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
485
-	}
486
-
487
-	h := websocket.Handler(func(ws *websocket.Conn) {
488
-		defer ws.Close()
489
-
490
-		wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{
491
-			InStream:  ws,
492
-			OutStream: ws,
493
-			ErrStream: ws,
494
-			Logs:      boolValue(r, "logs"),
495
-			Stream:    boolValue(r, "stream"),
496
-		}
497
-
498
-		if err := s.daemon.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil {
499
-			logrus.Errorf("Error attaching websocket: %s", err)
500
-		}
501
-	})
502
-	ws := websocket.Server{Handler: h, Handshake: nil}
503
-	ws.ServeHTTP(w, r)
504
-
505
-	return nil
506
-}
507 1
deleted file mode 100644
... ...
@@ -1,115 +0,0 @@
1
-package server
2
-
3
-import (
4
-	"encoding/base64"
5
-	"encoding/json"
6
-	"fmt"
7
-	"io"
8
-	"net/http"
9
-	"os"
10
-	"strings"
11
-
12
-	"github.com/docker/docker/api/types"
13
-	"golang.org/x/net/context"
14
-)
15
-
16
-// postContainersCopy is deprecated in favor of getContainersArchive.
17
-func (s *Server) postContainersCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
18
-	if vars == nil {
19
-		return fmt.Errorf("Missing parameter")
20
-	}
21
-
22
-	if err := checkForJSON(r); err != nil {
23
-		return err
24
-	}
25
-
26
-	cfg := types.CopyConfig{}
27
-	if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
28
-		return err
29
-	}
30
-
31
-	if cfg.Resource == "" {
32
-		return fmt.Errorf("Path cannot be empty")
33
-	}
34
-
35
-	data, err := s.daemon.ContainerCopy(vars["name"], cfg.Resource)
36
-	if err != nil {
37
-		if strings.Contains(strings.ToLower(err.Error()), "no such id") {
38
-			w.WriteHeader(http.StatusNotFound)
39
-			return nil
40
-		}
41
-		if os.IsNotExist(err) {
42
-			return fmt.Errorf("Could not find the file %s in container %s", cfg.Resource, vars["name"])
43
-		}
44
-		return err
45
-	}
46
-	defer data.Close()
47
-
48
-	w.Header().Set("Content-Type", "application/x-tar")
49
-	if _, err := io.Copy(w, data); err != nil {
50
-		return err
51
-	}
52
-
53
-	return nil
54
-}
55
-
56
-// // Encode the stat to JSON, base64 encode, and place in a header.
57
-func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Header) error {
58
-	statJSON, err := json.Marshal(stat)
59
-	if err != nil {
60
-		return err
61
-	}
62
-
63
-	header.Set(
64
-		"X-Docker-Container-Path-Stat",
65
-		base64.StdEncoding.EncodeToString(statJSON),
66
-	)
67
-
68
-	return nil
69
-}
70
-
71
-func (s *Server) headContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
72
-	v, err := archiveFormValues(r, vars)
73
-	if err != nil {
74
-		return err
75
-	}
76
-
77
-	stat, err := s.daemon.ContainerStatPath(v.name, v.path)
78
-	if err != nil {
79
-		return err
80
-	}
81
-
82
-	return setContainerPathStatHeader(stat, w.Header())
83
-}
84
-
85
-func (s *Server) getContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
86
-	v, err := archiveFormValues(r, vars)
87
-	if err != nil {
88
-		return err
89
-	}
90
-
91
-	tarArchive, stat, err := s.daemon.ContainerArchivePath(v.name, v.path)
92
-	if err != nil {
93
-		return err
94
-	}
95
-	defer tarArchive.Close()
96
-
97
-	if err := setContainerPathStatHeader(stat, w.Header()); err != nil {
98
-		return err
99
-	}
100
-
101
-	w.Header().Set("Content-Type", "application/x-tar")
102
-	_, err = io.Copy(w, tarArchive)
103
-
104
-	return err
105
-}
106
-
107
-func (s *Server) putContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
108
-	v, err := archiveFormValues(r, vars)
109
-	if err != nil {
110
-		return err
111
-	}
112
-
113
-	noOverwriteDirNonDir := boolValue(r, "noOverwriteDirNonDir")
114
-	return s.daemon.ContainerExtractToDir(v.name, v.path, noOverwriteDirNonDir, r.Body)
115
-}
116 1
deleted file mode 100644
... ...
@@ -1,180 +0,0 @@
1
-package server
2
-
3
-import (
4
-	"encoding/json"
5
-	"net/http"
6
-	"runtime"
7
-	"strconv"
8
-	"strings"
9
-	"time"
10
-
11
-	"github.com/Sirupsen/logrus"
12
-	"github.com/docker/docker/api"
13
-	"github.com/docker/docker/api/types"
14
-	"github.com/docker/docker/autogen/dockerversion"
15
-	"github.com/docker/docker/pkg/ioutils"
16
-	"github.com/docker/docker/pkg/jsonmessage"
17
-	"github.com/docker/docker/pkg/parsers/filters"
18
-	"github.com/docker/docker/pkg/parsers/kernel"
19
-	"github.com/docker/docker/utils"
20
-	"golang.org/x/net/context"
21
-)
22
-
23
-func (s *Server) getVersion(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
24
-	v := &types.Version{
25
-		Version:    dockerversion.VERSION,
26
-		APIVersion: api.Version,
27
-		GitCommit:  dockerversion.GITCOMMIT,
28
-		GoVersion:  runtime.Version(),
29
-		Os:         runtime.GOOS,
30
-		Arch:       runtime.GOARCH,
31
-		BuildTime:  dockerversion.BUILDTIME,
32
-	}
33
-
34
-	version := versionFromContext(ctx)
35
-
36
-	if version.GreaterThanOrEqualTo("1.19") {
37
-		v.Experimental = utils.ExperimentalBuild()
38
-	}
39
-
40
-	if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
41
-		v.KernelVersion = kernelVersion.String()
42
-	}
43
-
44
-	return writeJSON(w, http.StatusOK, v)
45
-}
46
-
47
-func (s *Server) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
48
-	info, err := s.daemon.SystemInfo()
49
-	if err != nil {
50
-		return err
51
-	}
52
-
53
-	return writeJSON(w, http.StatusOK, info)
54
-}
55
-
56
-func (s *Server) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
57
-	if err := parseForm(r); err != nil {
58
-		return err
59
-	}
60
-	var since int64 = -1
61
-	if r.Form.Get("since") != "" {
62
-		s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64)
63
-		if err != nil {
64
-			return err
65
-		}
66
-		since = s
67
-	}
68
-
69
-	var until int64 = -1
70
-	if r.Form.Get("until") != "" {
71
-		u, err := strconv.ParseInt(r.Form.Get("until"), 10, 64)
72
-		if err != nil {
73
-			return err
74
-		}
75
-		until = u
76
-	}
77
-
78
-	timer := time.NewTimer(0)
79
-	timer.Stop()
80
-	if until > 0 {
81
-		dur := time.Unix(until, 0).Sub(time.Now())
82
-		timer = time.NewTimer(dur)
83
-	}
84
-
85
-	ef, err := filters.FromParam(r.Form.Get("filters"))
86
-	if err != nil {
87
-		return err
88
-	}
89
-
90
-	isFiltered := func(field string, filter []string) bool {
91
-		if len(field) == 0 {
92
-			return false
93
-		}
94
-		if len(filter) == 0 {
95
-			return false
96
-		}
97
-		for _, v := range filter {
98
-			if v == field {
99
-				return false
100
-			}
101
-			if strings.Contains(field, ":") {
102
-				image := strings.Split(field, ":")
103
-				if image[0] == v {
104
-					return false
105
-				}
106
-			}
107
-		}
108
-		return true
109
-	}
110
-
111
-	d := s.daemon
112
-	es := d.EventsService
113
-	w.Header().Set("Content-Type", "application/json")
114
-
115
-	outStream := ioutils.NewWriteFlusher(w)
116
-	// Write an empty chunk of data.
117
-	// This is to ensure that the HTTP status code is sent immediately,
118
-	// so that it will not block the receiver.
119
-	outStream.Write(nil)
120
-	enc := json.NewEncoder(outStream)
121
-
122
-	getContainerID := func(cn string) string {
123
-		c, err := d.Get(cn)
124
-		if err != nil {
125
-			return ""
126
-		}
127
-		return c.ID
128
-	}
129
-
130
-	sendEvent := func(ev *jsonmessage.JSONMessage) error {
131
-		//incoming container filter can be name,id or partial id, convert and replace as a full container id
132
-		for i, cn := range ef["container"] {
133
-			ef["container"][i] = getContainerID(cn)
134
-		}
135
-
136
-		if isFiltered(ev.Status, ef["event"]) || (isFiltered(ev.ID, ef["image"]) &&
137
-			isFiltered(ev.From, ef["image"])) || isFiltered(ev.ID, ef["container"]) {
138
-			return nil
139
-		}
140
-
141
-		return enc.Encode(ev)
142
-	}
143
-
144
-	current, l := es.Subscribe()
145
-	if since == -1 {
146
-		current = nil
147
-	}
148
-	defer es.Evict(l)
149
-	for _, ev := range current {
150
-		if ev.Time < since {
151
-			continue
152
-		}
153
-		if err := sendEvent(ev); err != nil {
154
-			return err
155
-		}
156
-	}
157
-
158
-	var closeNotify <-chan bool
159
-	if closeNotifier, ok := w.(http.CloseNotifier); ok {
160
-		closeNotify = closeNotifier.CloseNotify()
161
-	}
162
-
163
-	for {
164
-		select {
165
-		case ev := <-l:
166
-			jev, ok := ev.(*jsonmessage.JSONMessage)
167
-			if !ok {
168
-				continue
169
-			}
170
-			if err := sendEvent(jev); err != nil {
171
-				return err
172
-			}
173
-		case <-timer.C:
174
-			return nil
175
-		case <-closeNotify:
176
-			logrus.Debug("Client disconnected, stop sending events")
177
-			return nil
178
-		}
179
-	}
180
-}
181 1
deleted file mode 100644
... ...
@@ -1,127 +0,0 @@
1
-package server
2
-
3
-import (
4
-	"encoding/json"
5
-	"fmt"
6
-	"io"
7
-	"net/http"
8
-	"strconv"
9
-
10
-	"github.com/Sirupsen/logrus"
11
-	"github.com/docker/docker/api/types"
12
-	"github.com/docker/docker/pkg/stdcopy"
13
-	"github.com/docker/docker/runconfig"
14
-	"golang.org/x/net/context"
15
-)
16
-
17
-func (s *Server) getExecByID(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
18
-	if vars == nil {
19
-		return fmt.Errorf("Missing parameter 'id'")
20
-	}
21
-
22
-	eConfig, err := s.daemon.ContainerExecInspect(vars["id"])
23
-	if err != nil {
24
-		return err
25
-	}
26
-
27
-	return writeJSON(w, http.StatusOK, eConfig)
28
-}
29
-
30
-func (s *Server) postContainerExecCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
31
-	if err := parseForm(r); err != nil {
32
-		return err
33
-	}
34
-	if err := checkForJSON(r); err != nil {
35
-		return err
36
-	}
37
-	name := vars["name"]
38
-
39
-	execConfig := &runconfig.ExecConfig{}
40
-	if err := json.NewDecoder(r.Body).Decode(execConfig); err != nil {
41
-		return err
42
-	}
43
-	execConfig.Container = name
44
-
45
-	if len(execConfig.Cmd) == 0 {
46
-		return fmt.Errorf("No exec command specified")
47
-	}
48
-
49
-	// Register an instance of Exec in container.
50
-	id, err := s.daemon.ContainerExecCreate(execConfig)
51
-	if err != nil {
52
-		logrus.Errorf("Error setting up exec command in container %s: %s", name, err)
53
-		return err
54
-	}
55
-
56
-	return writeJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{
57
-		ID: id,
58
-	})
59
-}
60
-
61
-// TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start.
62
-func (s *Server) postContainerExecStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
63
-	if err := parseForm(r); err != nil {
64
-		return err
65
-	}
66
-	var (
67
-		execName                  = vars["name"]
68
-		stdin, inStream           io.ReadCloser
69
-		stdout, stderr, outStream io.Writer
70
-	)
71
-
72
-	execStartCheck := &types.ExecStartCheck{}
73
-	if err := json.NewDecoder(r.Body).Decode(execStartCheck); err != nil {
74
-		return err
75
-	}
76
-
77
-	if !execStartCheck.Detach {
78
-		var err error
79
-		// Setting up the streaming http interface.
80
-		inStream, outStream, err = hijackServer(w)
81
-		if err != nil {
82
-			return err
83
-		}
84
-		defer closeStreams(inStream, outStream)
85
-
86
-		if _, ok := r.Header["Upgrade"]; ok {
87
-			fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
88
-		} else {
89
-			fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
90
-		}
91
-
92
-		stdin = inStream
93
-		stdout = outStream
94
-		if !execStartCheck.Tty {
95
-			stderr = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
96
-			stdout = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
97
-		}
98
-	} else {
99
-		outStream = w
100
-	}
101
-
102
-	// Now run the user process in container.
103
-	if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil {
104
-		fmt.Fprintf(outStream, "Error running exec in container: %v\n", err)
105
-	}
106
-	return nil
107
-}
108
-
109
-func (s *Server) postContainerExecResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
110
-	if err := parseForm(r); err != nil {
111
-		return err
112
-	}
113
-	if vars == nil {
114
-		return fmt.Errorf("Missing parameter")
115
-	}
116
-
117
-	height, err := strconv.Atoi(r.Form.Get("h"))
118
-	if err != nil {
119
-		return err
120
-	}
121
-	width, err := strconv.Atoi(r.Form.Get("w"))
122
-	if err != nil {
123
-		return err
124
-	}
125
-
126
-	return s.daemon.ContainerExecResize(vars["name"], height, width)
127
-}
128 1
deleted file mode 100644
... ...
@@ -1,57 +0,0 @@
1
-package server
2
-
3
-import (
4
-	"fmt"
5
-	"net/http"
6
-	"path/filepath"
7
-	"strconv"
8
-	"strings"
9
-)
10
-
11
-func boolValue(r *http.Request, k string) bool {
12
-	s := strings.ToLower(strings.TrimSpace(r.FormValue(k)))
13
-	return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none")
14
-}
15
-
16
-// boolValueOrDefault returns the default bool passed if the query param is
17
-// missing, otherwise it's just a proxy to boolValue above
18
-func boolValueOrDefault(r *http.Request, k string, d bool) bool {
19
-	if _, ok := r.Form[k]; !ok {
20
-		return d
21
-	}
22
-	return boolValue(r, k)
23
-}
24
-
25
-func int64ValueOrZero(r *http.Request, k string) int64 {
26
-	val, err := strconv.ParseInt(r.FormValue(k), 10, 64)
27
-	if err != nil {
28
-		return 0
29
-	}
30
-	return val
31
-}
32
-
33
-type archiveOptions struct {
34
-	name string
35
-	path string
36
-}
37
-
38
-func archiveFormValues(r *http.Request, vars map[string]string) (archiveOptions, error) {
39
-	if vars == nil {
40
-		return archiveOptions{}, fmt.Errorf("Missing parameter")
41
-	}
42
-	if err := parseForm(r); err != nil {
43
-		return archiveOptions{}, err
44
-	}
45
-
46
-	name := vars["name"]
47
-	path := filepath.FromSlash(r.Form.Get("path"))
48
-
49
-	switch {
50
-	case name == "":
51
-		return archiveOptions{}, fmt.Errorf("bad parameter: 'name' cannot be empty")
52
-	case path == "":
53
-		return archiveOptions{}, fmt.Errorf("bad parameter: 'path' cannot be empty")
54
-	}
55
-
56
-	return archiveOptions{name, path}, nil
57
-}
58 1
deleted file mode 100644
... ...
@@ -1,70 +0,0 @@
1
-package server
2
-
3
-import (
4
-	"net/http"
5
-	"net/url"
6
-	"testing"
7
-)
8
-
9
-func TestBoolValue(t *testing.T) {
10
-	cases := map[string]bool{
11
-		"":      false,
12
-		"0":     false,
13
-		"no":    false,
14
-		"false": false,
15
-		"none":  false,
16
-		"1":     true,
17
-		"yes":   true,
18
-		"true":  true,
19
-		"one":   true,
20
-		"100":   true,
21
-	}
22
-
23
-	for c, e := range cases {
24
-		v := url.Values{}
25
-		v.Set("test", c)
26
-		r, _ := http.NewRequest("POST", "", nil)
27
-		r.Form = v
28
-
29
-		a := boolValue(r, "test")
30
-		if a != e {
31
-			t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
32
-		}
33
-	}
34
-}
35
-
36
-func TestBoolValueOrDefault(t *testing.T) {
37
-	r, _ := http.NewRequest("GET", "", nil)
38
-	if !boolValueOrDefault(r, "queryparam", true) {
39
-		t.Fatal("Expected to get true default value, got false")
40
-	}
41
-
42
-	v := url.Values{}
43
-	v.Set("param", "")
44
-	r, _ = http.NewRequest("GET", "", nil)
45
-	r.Form = v
46
-	if boolValueOrDefault(r, "param", true) {
47
-		t.Fatal("Expected not to get true")
48
-	}
49
-}
50
-
51
-func TestInt64ValueOrZero(t *testing.T) {
52
-	cases := map[string]int64{
53
-		"":     0,
54
-		"asdf": 0,
55
-		"0":    0,
56
-		"1":    1,
57
-	}
58
-
59
-	for c, e := range cases {
60
-		v := url.Values{}
61
-		v.Set("test", c)
62
-		r, _ := http.NewRequest("POST", "", nil)
63
-		r.Form = v
64
-
65
-		a := int64ValueOrZero(r, "test")
66
-		if a != e {
67
-			t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
68
-		}
69
-	}
70
-}
71 1
new file mode 100644
... ...
@@ -0,0 +1,63 @@
0
+package httputils
1
+
2
+import (
3
+	"fmt"
4
+	"net/http"
5
+	"path/filepath"
6
+	"strconv"
7
+	"strings"
8
+)
9
+
10
+// BoolValue transforms a form value in different formats into a boolean type.
11
+func BoolValue(r *http.Request, k string) bool {
12
+	s := strings.ToLower(strings.TrimSpace(r.FormValue(k)))
13
+	return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none")
14
+}
15
+
16
+// BoolValueOrDefault returns the default bool passed if the query param is
17
+// missing, otherwise it's just a proxy to boolValue above
18
+func BoolValueOrDefault(r *http.Request, k string, d bool) bool {
19
+	if _, ok := r.Form[k]; !ok {
20
+		return d
21
+	}
22
+	return BoolValue(r, k)
23
+}
24
+
25
+// Int64ValueOrZero parses a form value into a int64 type.
26
+// It returns 0 if the parsing fails.
27
+func Int64ValueOrZero(r *http.Request, k string) int64 {
28
+	val, err := strconv.ParseInt(r.FormValue(k), 10, 64)
29
+	if err != nil {
30
+		return 0
31
+	}
32
+	return val
33
+}
34
+
35
+// ArchiveOptions stores archive information for different operations.
36
+type ArchiveOptions struct {
37
+	Name string
38
+	Path string
39
+}
40
+
41
+// ArchiveFormValues parses form values and turns them into ArchiveOptions.
42
+// It fails if the archive name and path are not in the request.
43
+func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions, error) {
44
+	if vars == nil {
45
+		return ArchiveOptions{}, fmt.Errorf("Missing parameter")
46
+	}
47
+	if err := ParseForm(r); err != nil {
48
+		return ArchiveOptions{}, err
49
+	}
50
+
51
+	name := vars["name"]
52
+	path := filepath.FromSlash(r.Form.Get("path"))
53
+
54
+	switch {
55
+	case name == "":
56
+		return ArchiveOptions{}, fmt.Errorf("bad parameter: 'name' cannot be empty")
57
+	case path == "":
58
+		return ArchiveOptions{}, fmt.Errorf("bad parameter: 'path' cannot be empty")
59
+	}
60
+
61
+	return ArchiveOptions{name, path}, nil
62
+}
0 63
new file mode 100644
... ...
@@ -0,0 +1,70 @@
0
+package httputils
1
+
2
+import (
3
+	"net/http"
4
+	"net/url"
5
+	"testing"
6
+)
7
+
8
+func TestBoolValue(t *testing.T) {
9
+	cases := map[string]bool{
10
+		"":      false,
11
+		"0":     false,
12
+		"no":    false,
13
+		"false": false,
14
+		"none":  false,
15
+		"1":     true,
16
+		"yes":   true,
17
+		"true":  true,
18
+		"one":   true,
19
+		"100":   true,
20
+	}
21
+
22
+	for c, e := range cases {
23
+		v := url.Values{}
24
+		v.Set("test", c)
25
+		r, _ := http.NewRequest("POST", "", nil)
26
+		r.Form = v
27
+
28
+		a := BoolValue(r, "test")
29
+		if a != e {
30
+			t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
31
+		}
32
+	}
33
+}
34
+
35
+func TestBoolValueOrDefault(t *testing.T) {
36
+	r, _ := http.NewRequest("GET", "", nil)
37
+	if !BoolValueOrDefault(r, "queryparam", true) {
38
+		t.Fatal("Expected to get true default value, got false")
39
+	}
40
+
41
+	v := url.Values{}
42
+	v.Set("param", "")
43
+	r, _ = http.NewRequest("GET", "", nil)
44
+	r.Form = v
45
+	if BoolValueOrDefault(r, "param", true) {
46
+		t.Fatal("Expected not to get true")
47
+	}
48
+}
49
+
50
+func TestInt64ValueOrZero(t *testing.T) {
51
+	cases := map[string]int64{
52
+		"":     0,
53
+		"asdf": 0,
54
+		"0":    0,
55
+		"1":    1,
56
+	}
57
+
58
+	for c, e := range cases {
59
+		v := url.Values{}
60
+		v.Set("test", c)
61
+		r, _ := http.NewRequest("POST", "", nil)
62
+		r.Form = v
63
+
64
+		a := Int64ValueOrZero(r, "test")
65
+		if a != e {
66
+			t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
67
+		}
68
+	}
69
+}
0 70
new file mode 100644
... ...
@@ -0,0 +1,180 @@
0
+package httputils
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"io"
6
+	"net/http"
7
+	"strings"
8
+
9
+	"golang.org/x/net/context"
10
+
11
+	"github.com/Sirupsen/logrus"
12
+	"github.com/docker/distribution/registry/api/errcode"
13
+	"github.com/docker/docker/api"
14
+	"github.com/docker/docker/pkg/version"
15
+	"github.com/docker/docker/utils"
16
+)
17
+
18
+// APIVersionKey is the client's requested API version.
19
+const APIVersionKey = "api-version"
20
+
21
+// APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
22
+// Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
23
+type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
24
+
25
+// HijackConnection interrupts the http response writer to get the
26
+// underlying connection and operate with it.
27
+func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
28
+	conn, _, err := w.(http.Hijacker).Hijack()
29
+	if err != nil {
30
+		return nil, nil, err
31
+	}
32
+	// Flush the options to make sure the client sets the raw mode
33
+	conn.Write([]byte{})
34
+	return conn, conn, nil
35
+}
36
+
37
+// CloseStreams ensures that a list for http streams are properly closed.
38
+func CloseStreams(streams ...interface{}) {
39
+	for _, stream := range streams {
40
+		if tcpc, ok := stream.(interface {
41
+			CloseWrite() error
42
+		}); ok {
43
+			tcpc.CloseWrite()
44
+		} else if closer, ok := stream.(io.Closer); ok {
45
+			closer.Close()
46
+		}
47
+	}
48
+}
49
+
50
+// CheckForJSON makes sure that the request's Content-Type is application/json.
51
+func CheckForJSON(r *http.Request) error {
52
+	ct := r.Header.Get("Content-Type")
53
+
54
+	// No Content-Type header is ok as long as there's no Body
55
+	if ct == "" {
56
+		if r.Body == nil || r.ContentLength == 0 {
57
+			return nil
58
+		}
59
+	}
60
+
61
+	// Otherwise it better be json
62
+	if api.MatchesContentType(ct, "application/json") {
63
+		return nil
64
+	}
65
+	return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
66
+}
67
+
68
+// ParseForm ensures the request form is parsed even with invalid content types.
69
+// If we don't do this, POST method without Content-type (even with empty body) will fail.
70
+func ParseForm(r *http.Request) error {
71
+	if r == nil {
72
+		return nil
73
+	}
74
+	if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
75
+		return err
76
+	}
77
+	return nil
78
+}
79
+
80
+// ParseMultipartForm ensure the request form is parsed, even with invalid content types.
81
+func ParseMultipartForm(r *http.Request) error {
82
+	if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
83
+		return err
84
+	}
85
+	return nil
86
+}
87
+
88
+// WriteError decodes a specific docker error and sends it in the response.
89
+func WriteError(w http.ResponseWriter, err error) {
90
+	if err == nil || w == nil {
91
+		logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
92
+		return
93
+	}
94
+
95
+	statusCode := http.StatusInternalServerError
96
+	errMsg := err.Error()
97
+
98
+	// Based on the type of error we get we need to process things
99
+	// slightly differently to extract the error message.
100
+	// In the 'errcode.*' cases there are two different type of
101
+	// error that could be returned. errocode.ErrorCode is the base
102
+	// type of error object - it is just an 'int' that can then be
103
+	// used as the look-up key to find the message. errorcode.Error
104
+	// extends errorcode.Error by adding error-instance specific
105
+	// data, like 'details' or variable strings to be inserted into
106
+	// the message.
107
+	//
108
+	// Ideally, we should just be able to call err.Error() for all
109
+	// cases but the errcode package doesn't support that yet.
110
+	//
111
+	// Additionally, in both errcode cases, there might be an http
112
+	// status code associated with it, and if so use it.
113
+	switch err.(type) {
114
+	case errcode.ErrorCode:
115
+		daError, _ := err.(errcode.ErrorCode)
116
+		statusCode = daError.Descriptor().HTTPStatusCode
117
+		errMsg = daError.Message()
118
+
119
+	case errcode.Error:
120
+		// For reference, if you're looking for a particular error
121
+		// then you can do something like :
122
+		//   import ( derr "github.com/docker/docker/errors" )
123
+		//   if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
124
+
125
+		daError, _ := err.(errcode.Error)
126
+		statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
127
+		errMsg = daError.Message
128
+
129
+	default:
130
+		// This part of will be removed once we've
131
+		// converted everything over to use the errcode package
132
+
133
+		// FIXME: this is brittle and should not be necessary.
134
+		// If we need to differentiate between different possible error types,
135
+		// we should create appropriate error types with clearly defined meaning
136
+		errStr := strings.ToLower(err.Error())
137
+		for keyword, status := range map[string]int{
138
+			"not found":             http.StatusNotFound,
139
+			"no such":               http.StatusNotFound,
140
+			"bad parameter":         http.StatusBadRequest,
141
+			"conflict":              http.StatusConflict,
142
+			"impossible":            http.StatusNotAcceptable,
143
+			"wrong login/password":  http.StatusUnauthorized,
144
+			"hasn't been activated": http.StatusForbidden,
145
+		} {
146
+			if strings.Contains(errStr, keyword) {
147
+				statusCode = status
148
+				break
149
+			}
150
+		}
151
+	}
152
+
153
+	if statusCode == 0 {
154
+		statusCode = http.StatusInternalServerError
155
+	}
156
+
157
+	logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": utils.GetErrorMessage(err)}).Error("HTTP Error")
158
+	http.Error(w, errMsg, statusCode)
159
+}
160
+
161
+// WriteJSON writes the value v to the http response stream as json with standard json encoding.
162
+func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
163
+	w.Header().Set("Content-Type", "application/json")
164
+	w.WriteHeader(code)
165
+	return json.NewEncoder(w).Encode(v)
166
+}
167
+
168
+// VersionFromContext returns an API version from the context using APIVersionKey.
169
+// It panics if the context value does not have version.Version type.
170
+func VersionFromContext(ctx context.Context) (ver version.Version) {
171
+	if ctx == nil {
172
+		return
173
+	}
174
+	val := ctx.Value(APIVersionKey)
175
+	if val == nil {
176
+		return
177
+	}
178
+	return val.(version.Version)
179
+}
0 180
deleted file mode 100644
... ...
@@ -1,437 +0,0 @@
1
-package server
2
-
3
-import (
4
-	"encoding/base64"
5
-	"encoding/json"
6
-	"errors"
7
-	"fmt"
8
-	"io"
9
-	"net/http"
10
-	"strings"
11
-
12
-	"github.com/Sirupsen/logrus"
13
-	"github.com/docker/docker/api/types"
14
-	"github.com/docker/docker/builder"
15
-	"github.com/docker/docker/cliconfig"
16
-	"github.com/docker/docker/graph"
17
-	"github.com/docker/docker/pkg/ioutils"
18
-	"github.com/docker/docker/pkg/parsers"
19
-	"github.com/docker/docker/pkg/streamformatter"
20
-	"github.com/docker/docker/pkg/ulimit"
21
-	"github.com/docker/docker/runconfig"
22
-	"github.com/docker/docker/utils"
23
-	"golang.org/x/net/context"
24
-)
25
-
26
-func (s *Server) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
27
-	if err := parseForm(r); err != nil {
28
-		return err
29
-	}
30
-
31
-	if err := checkForJSON(r); err != nil {
32
-		return err
33
-	}
34
-
35
-	cname := r.Form.Get("container")
36
-
37
-	pause := boolValue(r, "pause")
38
-	version := versionFromContext(ctx)
39
-	if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
40
-		pause = true
41
-	}
42
-
43
-	c, _, err := runconfig.DecodeContainerConfig(r.Body)
44
-	if err != nil && err != io.EOF { //Do not fail if body is empty.
45
-		return err
46
-	}
47
-
48
-	commitCfg := &builder.CommitConfig{
49
-		Pause:   pause,
50
-		Repo:    r.Form.Get("repo"),
51
-		Tag:     r.Form.Get("tag"),
52
-		Author:  r.Form.Get("author"),
53
-		Comment: r.Form.Get("comment"),
54
-		Changes: r.Form["changes"],
55
-		Config:  c,
56
-	}
57
-
58
-	imgID, err := builder.Commit(cname, s.daemon, commitCfg)
59
-	if err != nil {
60
-		return err
61
-	}
62
-
63
-	return writeJSON(w, http.StatusCreated, &types.ContainerCommitResponse{
64
-		ID: imgID,
65
-	})
66
-}
67
-
68
-// Creates an image from Pull or from Import
69
-func (s *Server) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
70
-	if err := parseForm(r); err != nil {
71
-		return err
72
-	}
73
-
74
-	var (
75
-		image   = r.Form.Get("fromImage")
76
-		repo    = r.Form.Get("repo")
77
-		tag     = r.Form.Get("tag")
78
-		message = r.Form.Get("message")
79
-	)
80
-	authEncoded := r.Header.Get("X-Registry-Auth")
81
-	authConfig := &cliconfig.AuthConfig{}
82
-	if authEncoded != "" {
83
-		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
84
-		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
85
-			// for a pull it is not an error if no auth was given
86
-			// to increase compatibility with the existing api it is defaulting to be empty
87
-			authConfig = &cliconfig.AuthConfig{}
88
-		}
89
-	}
90
-
91
-	var (
92
-		err    error
93
-		output = ioutils.NewWriteFlusher(w)
94
-	)
95
-
96
-	w.Header().Set("Content-Type", "application/json")
97
-
98
-	if image != "" { //pull
99
-		if tag == "" {
100
-			image, tag = parsers.ParseRepositoryTag(image)
101
-		}
102
-		metaHeaders := map[string][]string{}
103
-		for k, v := range r.Header {
104
-			if strings.HasPrefix(k, "X-Meta-") {
105
-				metaHeaders[k] = v
106
-			}
107
-		}
108
-
109
-		imagePullConfig := &graph.ImagePullConfig{
110
-			MetaHeaders: metaHeaders,
111
-			AuthConfig:  authConfig,
112
-			OutStream:   output,
113
-		}
114
-
115
-		err = s.daemon.Repositories().Pull(image, tag, imagePullConfig)
116
-	} else { //import
117
-		if tag == "" {
118
-			repo, tag = parsers.ParseRepositoryTag(repo)
119
-		}
120
-
121
-		src := r.Form.Get("fromSrc")
122
-
123
-		// 'err' MUST NOT be defined within this block, we need any error
124
-		// generated from the download to be available to the output
125
-		// stream processing below
126
-		var newConfig *runconfig.Config
127
-		newConfig, err = builder.BuildFromConfig(s.daemon, &runconfig.Config{}, r.Form["changes"])
128
-		if err != nil {
129
-			return err
130
-		}
131
-
132
-		err = s.daemon.Repositories().Import(src, repo, tag, message, r.Body, output, newConfig)
133
-	}
134
-	if err != nil {
135
-		if !output.Flushed() {
136
-			return err
137
-		}
138
-		sf := streamformatter.NewJSONStreamFormatter()
139
-		output.Write(sf.FormatError(err))
140
-	}
141
-
142
-	return nil
143
-}
144
-
145
-func (s *Server) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
146
-	if vars == nil {
147
-		return fmt.Errorf("Missing parameter")
148
-	}
149
-
150
-	metaHeaders := map[string][]string{}
151
-	for k, v := range r.Header {
152
-		if strings.HasPrefix(k, "X-Meta-") {
153
-			metaHeaders[k] = v
154
-		}
155
-	}
156
-	if err := parseForm(r); err != nil {
157
-		return err
158
-	}
159
-	authConfig := &cliconfig.AuthConfig{}
160
-
161
-	authEncoded := r.Header.Get("X-Registry-Auth")
162
-	if authEncoded != "" {
163
-		// the new format is to handle the authConfig as a header
164
-		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
165
-		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
166
-			// to increase compatibility to existing api it is defaulting to be empty
167
-			authConfig = &cliconfig.AuthConfig{}
168
-		}
169
-	} else {
170
-		// the old format is supported for compatibility if there was no authConfig header
171
-		if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
172
-			return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err)
173
-		}
174
-	}
175
-
176
-	name := vars["name"]
177
-	output := ioutils.NewWriteFlusher(w)
178
-	imagePushConfig := &graph.ImagePushConfig{
179
-		MetaHeaders: metaHeaders,
180
-		AuthConfig:  authConfig,
181
-		Tag:         r.Form.Get("tag"),
182
-		OutStream:   output,
183
-	}
184
-
185
-	w.Header().Set("Content-Type", "application/json")
186
-
187
-	if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil {
188
-		if !output.Flushed() {
189
-			return err
190
-		}
191
-		sf := streamformatter.NewJSONStreamFormatter()
192
-		output.Write(sf.FormatError(err))
193
-	}
194
-	return nil
195
-}
196
-
197
-func (s *Server) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
198
-	if vars == nil {
199
-		return fmt.Errorf("Missing parameter")
200
-	}
201
-	if err := parseForm(r); err != nil {
202
-		return err
203
-	}
204
-
205
-	w.Header().Set("Content-Type", "application/x-tar")
206
-
207
-	output := ioutils.NewWriteFlusher(w)
208
-	var names []string
209
-	if name, ok := vars["name"]; ok {
210
-		names = []string{name}
211
-	} else {
212
-		names = r.Form["names"]
213
-	}
214
-
215
-	if err := s.daemon.Repositories().ImageExport(names, output); err != nil {
216
-		if !output.Flushed() {
217
-			return err
218
-		}
219
-		sf := streamformatter.NewJSONStreamFormatter()
220
-		output.Write(sf.FormatError(err))
221
-	}
222
-	return nil
223
-}
224
-
225
-func (s *Server) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
226
-	return s.daemon.Repositories().Load(r.Body, w)
227
-}
228
-
229
-func (s *Server) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
230
-	if err := parseForm(r); err != nil {
231
-		return err
232
-	}
233
-	if vars == nil {
234
-		return fmt.Errorf("Missing parameter")
235
-	}
236
-
237
-	name := vars["name"]
238
-
239
-	if name == "" {
240
-		return fmt.Errorf("image name cannot be blank")
241
-	}
242
-
243
-	force := boolValue(r, "force")
244
-	prune := !boolValue(r, "noprune")
245
-
246
-	list, err := s.daemon.ImageDelete(name, force, prune)
247
-	if err != nil {
248
-		return err
249
-	}
250
-
251
-	return writeJSON(w, http.StatusOK, list)
252
-}
253
-
254
-func (s *Server) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
255
-	if vars == nil {
256
-		return fmt.Errorf("Missing parameter")
257
-	}
258
-
259
-	imageInspect, err := s.daemon.Repositories().Lookup(vars["name"])
260
-	if err != nil {
261
-		return err
262
-	}
263
-
264
-	return writeJSON(w, http.StatusOK, imageInspect)
265
-}
266
-
267
-func (s *Server) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
268
-	var (
269
-		authConfigs        = map[string]cliconfig.AuthConfig{}
270
-		authConfigsEncoded = r.Header.Get("X-Registry-Config")
271
-		buildConfig        = builder.NewBuildConfig()
272
-	)
273
-
274
-	if authConfigsEncoded != "" {
275
-		authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
276
-		if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
277
-			// for a pull it is not an error if no auth was given
278
-			// to increase compatibility with the existing api it is defaulting
279
-			// to be empty.
280
-		}
281
-	}
282
-
283
-	w.Header().Set("Content-Type", "application/json")
284
-
285
-	version := versionFromContext(ctx)
286
-	if boolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
287
-		buildConfig.Remove = true
288
-	} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
289
-		buildConfig.Remove = true
290
-	} else {
291
-		buildConfig.Remove = boolValue(r, "rm")
292
-	}
293
-	if boolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
294
-		buildConfig.Pull = true
295
-	}
296
-
297
-	output := ioutils.NewWriteFlusher(w)
298
-	buildConfig.Stdout = output
299
-	buildConfig.Context = r.Body
300
-
301
-	buildConfig.RemoteURL = r.FormValue("remote")
302
-	buildConfig.DockerfileName = r.FormValue("dockerfile")
303
-	buildConfig.RepoName = r.FormValue("t")
304
-	buildConfig.SuppressOutput = boolValue(r, "q")
305
-	buildConfig.NoCache = boolValue(r, "nocache")
306
-	buildConfig.ForceRemove = boolValue(r, "forcerm")
307
-	buildConfig.AuthConfigs = authConfigs
308
-	buildConfig.MemorySwap = int64ValueOrZero(r, "memswap")
309
-	buildConfig.Memory = int64ValueOrZero(r, "memory")
310
-	buildConfig.CPUShares = int64ValueOrZero(r, "cpushares")
311
-	buildConfig.CPUPeriod = int64ValueOrZero(r, "cpuperiod")
312
-	buildConfig.CPUQuota = int64ValueOrZero(r, "cpuquota")
313
-	buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
314
-	buildConfig.CPUSetMems = r.FormValue("cpusetmems")
315
-	buildConfig.CgroupParent = r.FormValue("cgroupparent")
316
-
317
-	var buildUlimits = []*ulimit.Ulimit{}
318
-	ulimitsJSON := r.FormValue("ulimits")
319
-	if ulimitsJSON != "" {
320
-		if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
321
-			return err
322
-		}
323
-		buildConfig.Ulimits = buildUlimits
324
-	}
325
-
326
-	var buildArgs = map[string]string{}
327
-	buildArgsJSON := r.FormValue("buildargs")
328
-	if buildArgsJSON != "" {
329
-		if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
330
-			return err
331
-		}
332
-	}
333
-	buildConfig.BuildArgs = buildArgs
334
-
335
-	// Job cancellation. Note: not all job types support this.
336
-	if closeNotifier, ok := w.(http.CloseNotifier); ok {
337
-		finished := make(chan struct{})
338
-		defer close(finished)
339
-		go func() {
340
-			select {
341
-			case <-finished:
342
-			case <-closeNotifier.CloseNotify():
343
-				logrus.Infof("Client disconnected, cancelling job: build")
344
-				buildConfig.Cancel()
345
-			}
346
-		}()
347
-	}
348
-
349
-	if err := builder.Build(s.daemon, buildConfig); err != nil {
350
-		// Do not write the error in the http output if it's still empty.
351
-		// This prevents from writing a 200(OK) when there is an interal error.
352
-		if !output.Flushed() {
353
-			return err
354
-		}
355
-		sf := streamformatter.NewJSONStreamFormatter()
356
-		w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
357
-	}
358
-	return nil
359
-}
360
-
361
-func (s *Server) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
362
-	if err := parseForm(r); err != nil {
363
-		return err
364
-	}
365
-
366
-	// FIXME: The filter parameter could just be a match filter
367
-	images, err := s.daemon.Repositories().Images(r.Form.Get("filters"), r.Form.Get("filter"), boolValue(r, "all"))
368
-	if err != nil {
369
-		return err
370
-	}
371
-
372
-	return writeJSON(w, http.StatusOK, images)
373
-}
374
-
375
-func (s *Server) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
376
-	if vars == nil {
377
-		return fmt.Errorf("Missing parameter")
378
-	}
379
-
380
-	name := vars["name"]
381
-	history, err := s.daemon.Repositories().History(name)
382
-	if err != nil {
383
-		return err
384
-	}
385
-
386
-	return writeJSON(w, http.StatusOK, history)
387
-}
388
-
389
-func (s *Server) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
390
-	if err := parseForm(r); err != nil {
391
-		return err
392
-	}
393
-	if vars == nil {
394
-		return fmt.Errorf("Missing parameter")
395
-	}
396
-
397
-	repo := r.Form.Get("repo")
398
-	tag := r.Form.Get("tag")
399
-	force := boolValue(r, "force")
400
-	name := vars["name"]
401
-	if err := s.daemon.Repositories().Tag(repo, tag, name, force); err != nil {
402
-		return err
403
-	}
404
-	s.daemon.EventsService.Log("tag", utils.ImageReference(repo, tag), "")
405
-	w.WriteHeader(http.StatusCreated)
406
-	return nil
407
-}
408
-
409
-func (s *Server) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
410
-	if err := parseForm(r); err != nil {
411
-		return err
412
-	}
413
-	var (
414
-		config      *cliconfig.AuthConfig
415
-		authEncoded = r.Header.Get("X-Registry-Auth")
416
-		headers     = map[string][]string{}
417
-	)
418
-
419
-	if authEncoded != "" {
420
-		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
421
-		if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
422
-			// for a search it is not an error if no auth was given
423
-			// to increase compatibility with the existing api it is defaulting to be empty
424
-			config = &cliconfig.AuthConfig{}
425
-		}
426
-	}
427
-	for k, v := range r.Header {
428
-		if strings.HasPrefix(k, "X-Meta-") {
429
-			headers[k] = v
430
-		}
431
-	}
432
-	query, err := s.daemon.RegistryService.Search(r.Form.Get("term"), config, headers)
433
-	if err != nil {
434
-		return err
435
-	}
436
-	return writeJSON(w, http.StatusOK, query.Results)
437
-}
438 1
deleted file mode 100644
... ...
@@ -1,35 +0,0 @@
1
-package server
2
-
3
-import (
4
-	"fmt"
5
-	"net/http"
6
-
7
-	"golang.org/x/net/context"
8
-)
9
-
10
-// getContainersByName inspects containers configuration and serializes it as json.
11
-func (s *Server) getContainersByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
12
-	if vars == nil {
13
-		return fmt.Errorf("Missing parameter")
14
-	}
15
-
16
-	var json interface{}
17
-	var err error
18
-
19
-	version := versionFromContext(ctx)
20
-
21
-	switch {
22
-	case version.LessThan("1.20"):
23
-		json, err = s.daemon.ContainerInspectPre120(vars["name"])
24
-	case version.Equal("1.20"):
25
-		json, err = s.daemon.ContainerInspect120(vars["name"])
26
-	default:
27
-		json, err = s.daemon.ContainerInspect(vars["name"])
28
-	}
29
-
30
-	if err != nil {
31
-		return err
32
-	}
33
-
34
-	return writeJSON(w, http.StatusOK, json)
35
-}
... ...
@@ -7,21 +7,19 @@ import (
7 7
 
8 8
 	"github.com/Sirupsen/logrus"
9 9
 	"github.com/docker/docker/api"
10
+	"github.com/docker/docker/api/server/httputils"
10 11
 	"github.com/docker/docker/autogen/dockerversion"
11 12
 	"github.com/docker/docker/errors"
12 13
 	"github.com/docker/docker/pkg/version"
13 14
 	"golang.org/x/net/context"
14 15
 )
15 16
 
16
-// apiVersionKey is the client's requested API version.
17
-const apiVersionKey = "api-version"
18
-
19 17
 // middleware is an adapter to allow the use of ordinary functions as Docker API filters.
20 18
 // Any function that has the appropriate signature can be register as a middleware.
21
-type middleware func(handler HTTPAPIFunc) HTTPAPIFunc
19
+type middleware func(handler httputils.APIFunc) httputils.APIFunc
22 20
 
23 21
 // loggingMiddleware logs each request when logging is enabled.
24
-func (s *Server) loggingMiddleware(handler HTTPAPIFunc) HTTPAPIFunc {
22
+func (s *Server) loggingMiddleware(handler httputils.APIFunc) httputils.APIFunc {
25 23
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
26 24
 		if s.cfg.Logging {
27 25
 			logrus.Infof("%s %s", r.Method, r.RequestURI)
... ...
@@ -31,7 +29,7 @@ func (s *Server) loggingMiddleware(handler HTTPAPIFunc) HTTPAPIFunc {
31 31
 }
32 32
 
33 33
 // userAgentMiddleware checks the User-Agent header looking for a valid docker client spec.
34
-func (s *Server) userAgentMiddleware(handler HTTPAPIFunc) HTTPAPIFunc {
34
+func (s *Server) userAgentMiddleware(handler httputils.APIFunc) httputils.APIFunc {
35 35
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
36 36
 		if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
37 37
 			dockerVersion := version.Version(s.cfg.Version)
... ...
@@ -53,7 +51,7 @@ func (s *Server) userAgentMiddleware(handler HTTPAPIFunc) HTTPAPIFunc {
53 53
 }
54 54
 
55 55
 // corsMiddleware sets the CORS header expectations in the server.
56
-func (s *Server) corsMiddleware(handler HTTPAPIFunc) HTTPAPIFunc {
56
+func (s *Server) corsMiddleware(handler httputils.APIFunc) httputils.APIFunc {
57 57
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
58 58
 		// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
59 59
 		// otherwise, all head values will be passed to HTTP handler
... ...
@@ -70,7 +68,7 @@ func (s *Server) corsMiddleware(handler HTTPAPIFunc) HTTPAPIFunc {
70 70
 }
71 71
 
72 72
 // versionMiddleware checks the api version requirements before passing the request to the server handler.
73
-func versionMiddleware(handler HTTPAPIFunc) HTTPAPIFunc {
73
+func versionMiddleware(handler httputils.APIFunc) httputils.APIFunc {
74 74
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
75 75
 		apiVersion := version.Version(vars["version"])
76 76
 		if apiVersion == "" {
... ...
@@ -85,7 +83,7 @@ func versionMiddleware(handler HTTPAPIFunc) HTTPAPIFunc {
85 85
 		}
86 86
 
87 87
 		w.Header().Set("Server", "Docker/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
88
-		ctx = context.WithValue(ctx, apiVersionKey, apiVersion)
88
+		ctx = context.WithValue(ctx, httputils.APIVersionKey, apiVersion)
89 89
 		return handler(ctx, w, r, vars)
90 90
 	}
91 91
 }
... ...
@@ -103,7 +101,8 @@ func versionMiddleware(handler HTTPAPIFunc) HTTPAPIFunc {
103 103
 //			)
104 104
 //		)
105 105
 //	)
106
-func (s *Server) handleWithGlobalMiddlewares(handler HTTPAPIFunc) HTTPAPIFunc {
106
+// )
107
+func (s *Server) handleWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc {
107 108
 	middlewares := []middleware{
108 109
 		versionMiddleware,
109 110
 		s.corsMiddleware,
... ...
@@ -117,16 +116,3 @@ func (s *Server) handleWithGlobalMiddlewares(handler HTTPAPIFunc) HTTPAPIFunc {
117 117
 	}
118 118
 	return h
119 119
 }
120
-
121
-// versionFromContext returns an API version from the context using apiVersionKey.
122
-// It panics if the context value does not have version.Version type.
123
-func versionFromContext(ctx context.Context) (ver version.Version) {
124
-	if ctx == nil {
125
-		return
126
-	}
127
-	val := ctx.Value(apiVersionKey)
128
-	if val == nil {
129
-		return
130
-	}
131
-	return val.(version.Version)
132
-}
... ...
@@ -6,13 +6,14 @@ import (
6 6
 	"testing"
7 7
 
8 8
 	"github.com/docker/distribution/registry/api/errcode"
9
+	"github.com/docker/docker/api/server/httputils"
9 10
 	"github.com/docker/docker/errors"
10 11
 	"golang.org/x/net/context"
11 12
 )
12 13
 
13 14
 func TestVersionMiddleware(t *testing.T) {
14 15
 	handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
15
-		if versionFromContext(ctx) == "" {
16
+		if httputils.VersionFromContext(ctx) == "" {
16 17
 			t.Fatalf("Expected version, got empty string")
17 18
 		}
18 19
 		return nil
... ...
@@ -30,7 +31,7 @@ func TestVersionMiddleware(t *testing.T) {
30 30
 
31 31
 func TestVersionMiddlewareWithErrors(t *testing.T) {
32 32
 	handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
33
-		if versionFromContext(ctx) == "" {
33
+		if httputils.VersionFromContext(ctx) == "" {
34 34
 			t.Fatalf("Expected version, got empty string")
35 35
 		}
36 36
 		return nil
37 37
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+package local
1
+
2
+import (
3
+	"encoding/json"
4
+	"net/http"
5
+
6
+	"github.com/docker/docker/api/server/httputils"
7
+	"github.com/docker/docker/api/types"
8
+	"github.com/docker/docker/cliconfig"
9
+	"golang.org/x/net/context"
10
+)
11
+
12
+func (s *router) postAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
13
+	var config *cliconfig.AuthConfig
14
+	err := json.NewDecoder(r.Body).Decode(&config)
15
+	r.Body.Close()
16
+	if err != nil {
17
+		return err
18
+	}
19
+	status, err := s.daemon.RegistryService.Auth(config)
20
+	if err != nil {
21
+		return err
22
+	}
23
+	return httputils.WriteJSON(w, http.StatusOK, &types.AuthResponse{
24
+		Status: status,
25
+	})
26
+}
0 27
new file mode 100644
... ...
@@ -0,0 +1,507 @@
0
+package local
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+	"net/http"
6
+	"strconv"
7
+	"strings"
8
+	"syscall"
9
+	"time"
10
+
11
+	"github.com/Sirupsen/logrus"
12
+	"github.com/docker/distribution/registry/api/errcode"
13
+	"github.com/docker/docker/api/server/httputils"
14
+	"github.com/docker/docker/api/types"
15
+	"github.com/docker/docker/daemon"
16
+	derr "github.com/docker/docker/errors"
17
+	"github.com/docker/docker/pkg/ioutils"
18
+	"github.com/docker/docker/pkg/signal"
19
+	"github.com/docker/docker/runconfig"
20
+	"github.com/docker/docker/utils"
21
+	"golang.org/x/net/context"
22
+	"golang.org/x/net/websocket"
23
+)
24
+
25
+func (s *router) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
26
+	if err := httputils.ParseForm(r); err != nil {
27
+		return err
28
+	}
29
+
30
+	config := &daemon.ContainersConfig{
31
+		All:     httputils.BoolValue(r, "all"),
32
+		Size:    httputils.BoolValue(r, "size"),
33
+		Since:   r.Form.Get("since"),
34
+		Before:  r.Form.Get("before"),
35
+		Filters: r.Form.Get("filters"),
36
+	}
37
+
38
+	if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
39
+		limit, err := strconv.Atoi(tmpLimit)
40
+		if err != nil {
41
+			return err
42
+		}
43
+		config.Limit = limit
44
+	}
45
+
46
+	containers, err := s.daemon.Containers(config)
47
+	if err != nil {
48
+		return err
49
+	}
50
+
51
+	return httputils.WriteJSON(w, http.StatusOK, containers)
52
+}
53
+
54
+func (s *router) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
55
+	if err := httputils.ParseForm(r); err != nil {
56
+		return err
57
+	}
58
+	if vars == nil {
59
+		return fmt.Errorf("Missing parameter")
60
+	}
61
+
62
+	stream := httputils.BoolValueOrDefault(r, "stream", true)
63
+	var out io.Writer
64
+	if !stream {
65
+		w.Header().Set("Content-Type", "application/json")
66
+		out = w
67
+	} else {
68
+		out = ioutils.NewWriteFlusher(w)
69
+	}
70
+
71
+	var closeNotifier <-chan bool
72
+	if notifier, ok := w.(http.CloseNotifier); ok {
73
+		closeNotifier = notifier.CloseNotify()
74
+	}
75
+
76
+	config := &daemon.ContainerStatsConfig{
77
+		Stream:    stream,
78
+		OutStream: out,
79
+		Stop:      closeNotifier,
80
+		Version:   httputils.VersionFromContext(ctx),
81
+	}
82
+
83
+	return s.daemon.ContainerStats(vars["name"], config)
84
+}
85
+
86
+func (s *router) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
87
+	if err := httputils.ParseForm(r); err != nil {
88
+		return err
89
+	}
90
+	if vars == nil {
91
+		return fmt.Errorf("Missing parameter")
92
+	}
93
+
94
+	// Args are validated before the stream starts because when it starts we're
95
+	// sending HTTP 200 by writing an empty chunk of data to tell the client that
96
+	// daemon is going to stream. By sending this initial HTTP 200 we can't report
97
+	// any error after the stream starts (i.e. container not found, wrong parameters)
98
+	// with the appropriate status code.
99
+	stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
100
+	if !(stdout || stderr) {
101
+		return fmt.Errorf("Bad parameters: you must choose at least one stream")
102
+	}
103
+
104
+	var since time.Time
105
+	if r.Form.Get("since") != "" {
106
+		s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64)
107
+		if err != nil {
108
+			return err
109
+		}
110
+		since = time.Unix(s, 0)
111
+	}
112
+
113
+	var closeNotifier <-chan bool
114
+	if notifier, ok := w.(http.CloseNotifier); ok {
115
+		closeNotifier = notifier.CloseNotify()
116
+	}
117
+
118
+	c, err := s.daemon.Get(vars["name"])
119
+	if err != nil {
120
+		return err
121
+	}
122
+
123
+	outStream := ioutils.NewWriteFlusher(w)
124
+	// write an empty chunk of data (this is to ensure that the
125
+	// HTTP Response is sent immediately, even if the container has
126
+	// not yet produced any data)
127
+	outStream.Write(nil)
128
+
129
+	logsConfig := &daemon.ContainerLogsConfig{
130
+		Follow:     httputils.BoolValue(r, "follow"),
131
+		Timestamps: httputils.BoolValue(r, "timestamps"),
132
+		Since:      since,
133
+		Tail:       r.Form.Get("tail"),
134
+		UseStdout:  stdout,
135
+		UseStderr:  stderr,
136
+		OutStream:  outStream,
137
+		Stop:       closeNotifier,
138
+	}
139
+
140
+	if err := s.daemon.ContainerLogs(c, logsConfig); err != nil {
141
+		// The client may be expecting all of the data we're sending to
142
+		// be multiplexed, so send it through OutStream, which will
143
+		// have been set up to handle that if needed.
144
+		fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %s\n", utils.GetErrorMessage(err))
145
+	}
146
+
147
+	return nil
148
+}
149
+
150
+func (s *router) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
151
+	if vars == nil {
152
+		return fmt.Errorf("Missing parameter")
153
+	}
154
+
155
+	return s.daemon.ContainerExport(vars["name"], w)
156
+}
157
+
158
+func (s *router) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
159
+	if vars == nil {
160
+		return fmt.Errorf("Missing parameter")
161
+	}
162
+
163
+	// If contentLength is -1, we can assumed chunked encoding
164
+	// or more technically that the length is unknown
165
+	// https://golang.org/src/pkg/net/http/request.go#L139
166
+	// net/http otherwise seems to swallow any headers related to chunked encoding
167
+	// including r.TransferEncoding
168
+	// allow a nil body for backwards compatibility
169
+	var hostConfig *runconfig.HostConfig
170
+	if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) {
171
+		if err := httputils.CheckForJSON(r); err != nil {
172
+			return err
173
+		}
174
+
175
+		c, err := runconfig.DecodeHostConfig(r.Body)
176
+		if err != nil {
177
+			return err
178
+		}
179
+
180
+		hostConfig = c
181
+	}
182
+
183
+	if err := s.daemon.ContainerStart(vars["name"], hostConfig); err != nil {
184
+		return err
185
+	}
186
+	w.WriteHeader(http.StatusNoContent)
187
+	return nil
188
+}
189
+
190
+func (s *router) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
191
+	if err := httputils.ParseForm(r); err != nil {
192
+		return err
193
+	}
194
+	if vars == nil {
195
+		return fmt.Errorf("Missing parameter")
196
+	}
197
+
198
+	seconds, _ := strconv.Atoi(r.Form.Get("t"))
199
+
200
+	if err := s.daemon.ContainerStop(vars["name"], seconds); err != nil {
201
+		return err
202
+	}
203
+	w.WriteHeader(http.StatusNoContent)
204
+
205
+	return nil
206
+}
207
+
208
+func (s *router) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
209
+	if vars == nil {
210
+		return fmt.Errorf("Missing parameter")
211
+	}
212
+	if err := httputils.ParseForm(r); err != nil {
213
+		return err
214
+	}
215
+
216
+	var sig syscall.Signal
217
+	name := vars["name"]
218
+
219
+	// If we have a signal, look at it. Otherwise, do nothing
220
+	if sigStr := r.Form.Get("signal"); sigStr != "" {
221
+		var err error
222
+		if sig, err = signal.ParseSignal(sigStr); err != nil {
223
+			return err
224
+		}
225
+	}
226
+
227
+	if err := s.daemon.ContainerKill(name, uint64(sig)); err != nil {
228
+		theErr, isDerr := err.(errcode.ErrorCoder)
229
+		isStopped := isDerr && theErr.ErrorCode() == derr.ErrorCodeNotRunning
230
+
231
+		// Return error that's not caused because the container is stopped.
232
+		// Return error if the container is not running and the api is >= 1.20
233
+		// to keep backwards compatibility.
234
+		version := httputils.VersionFromContext(ctx)
235
+		if version.GreaterThanOrEqualTo("1.20") || !isStopped {
236
+			return fmt.Errorf("Cannot kill container %s: %v", name, err)
237
+		}
238
+	}
239
+
240
+	w.WriteHeader(http.StatusNoContent)
241
+	return nil
242
+}
243
+
244
+func (s *router) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
245
+	if err := httputils.ParseForm(r); err != nil {
246
+		return err
247
+	}
248
+	if vars == nil {
249
+		return fmt.Errorf("Missing parameter")
250
+	}
251
+
252
+	timeout, _ := strconv.Atoi(r.Form.Get("t"))
253
+
254
+	if err := s.daemon.ContainerRestart(vars["name"], timeout); err != nil {
255
+		return err
256
+	}
257
+
258
+	w.WriteHeader(http.StatusNoContent)
259
+
260
+	return nil
261
+}
262
+
263
+func (s *router) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
264
+	if vars == nil {
265
+		return fmt.Errorf("Missing parameter")
266
+	}
267
+	if err := httputils.ParseForm(r); err != nil {
268
+		return err
269
+	}
270
+
271
+	if err := s.daemon.ContainerPause(vars["name"]); err != nil {
272
+		return err
273
+	}
274
+
275
+	w.WriteHeader(http.StatusNoContent)
276
+
277
+	return nil
278
+}
279
+
280
+func (s *router) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
281
+	if vars == nil {
282
+		return fmt.Errorf("Missing parameter")
283
+	}
284
+	if err := httputils.ParseForm(r); err != nil {
285
+		return err
286
+	}
287
+
288
+	if err := s.daemon.ContainerUnpause(vars["name"]); err != nil {
289
+		return err
290
+	}
291
+
292
+	w.WriteHeader(http.StatusNoContent)
293
+
294
+	return nil
295
+}
296
+
297
+func (s *router) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
298
+	if vars == nil {
299
+		return fmt.Errorf("Missing parameter")
300
+	}
301
+
302
+	status, err := s.daemon.ContainerWait(vars["name"], -1*time.Second)
303
+	if err != nil {
304
+		return err
305
+	}
306
+
307
+	return httputils.WriteJSON(w, http.StatusOK, &types.ContainerWaitResponse{
308
+		StatusCode: status,
309
+	})
310
+}
311
+
312
+func (s *router) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
313
+	if vars == nil {
314
+		return fmt.Errorf("Missing parameter")
315
+	}
316
+
317
+	changes, err := s.daemon.ContainerChanges(vars["name"])
318
+	if err != nil {
319
+		return err
320
+	}
321
+
322
+	return httputils.WriteJSON(w, http.StatusOK, changes)
323
+}
324
+
325
+func (s *router) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
326
+	if vars == nil {
327
+		return fmt.Errorf("Missing parameter")
328
+	}
329
+
330
+	if err := httputils.ParseForm(r); err != nil {
331
+		return err
332
+	}
333
+
334
+	procList, err := s.daemon.ContainerTop(vars["name"], r.Form.Get("ps_args"))
335
+	if err != nil {
336
+		return err
337
+	}
338
+
339
+	return httputils.WriteJSON(w, http.StatusOK, procList)
340
+}
341
+
342
+func (s *router) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
343
+	if err := httputils.ParseForm(r); err != nil {
344
+		return err
345
+	}
346
+	if vars == nil {
347
+		return fmt.Errorf("Missing parameter")
348
+	}
349
+
350
+	name := vars["name"]
351
+	newName := r.Form.Get("name")
352
+	if err := s.daemon.ContainerRename(name, newName); err != nil {
353
+		return err
354
+	}
355
+	w.WriteHeader(http.StatusNoContent)
356
+	return nil
357
+}
358
+
359
+func (s *router) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
360
+	if err := httputils.ParseForm(r); err != nil {
361
+		return err
362
+	}
363
+	if err := httputils.CheckForJSON(r); err != nil {
364
+		return err
365
+	}
366
+
367
+	name := r.Form.Get("name")
368
+
369
+	config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
370
+	if err != nil {
371
+		return err
372
+	}
373
+	version := httputils.VersionFromContext(ctx)
374
+	adjustCPUShares := version.LessThan("1.19")
375
+
376
+	ccr, err := s.daemon.ContainerCreate(name, config, hostConfig, adjustCPUShares)
377
+	if err != nil {
378
+		return err
379
+	}
380
+
381
+	return httputils.WriteJSON(w, http.StatusCreated, ccr)
382
+}
383
+
384
+func (s *router) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
385
+	if err := httputils.ParseForm(r); err != nil {
386
+		return err
387
+	}
388
+	if vars == nil {
389
+		return fmt.Errorf("Missing parameter")
390
+	}
391
+
392
+	name := vars["name"]
393
+	config := &daemon.ContainerRmConfig{
394
+		ForceRemove:  httputils.BoolValue(r, "force"),
395
+		RemoveVolume: httputils.BoolValue(r, "v"),
396
+		RemoveLink:   httputils.BoolValue(r, "link"),
397
+	}
398
+
399
+	if err := s.daemon.ContainerRm(name, config); err != nil {
400
+		// Force a 404 for the empty string
401
+		if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") {
402
+			return fmt.Errorf("no such id: \"\"")
403
+		}
404
+		return err
405
+	}
406
+
407
+	w.WriteHeader(http.StatusNoContent)
408
+
409
+	return nil
410
+}
411
+
412
+func (s *router) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
413
+	if err := httputils.ParseForm(r); err != nil {
414
+		return err
415
+	}
416
+	if vars == nil {
417
+		return fmt.Errorf("Missing parameter")
418
+	}
419
+
420
+	height, err := strconv.Atoi(r.Form.Get("h"))
421
+	if err != nil {
422
+		return err
423
+	}
424
+	width, err := strconv.Atoi(r.Form.Get("w"))
425
+	if err != nil {
426
+		return err
427
+	}
428
+
429
+	return s.daemon.ContainerResize(vars["name"], height, width)
430
+}
431
+
432
+func (s *router) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
433
+	if err := httputils.ParseForm(r); err != nil {
434
+		return err
435
+	}
436
+	if vars == nil {
437
+		return fmt.Errorf("Missing parameter")
438
+	}
439
+	containerName := vars["name"]
440
+
441
+	if !s.daemon.Exists(containerName) {
442
+		return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
443
+	}
444
+
445
+	inStream, outStream, err := httputils.HijackConnection(w)
446
+	if err != nil {
447
+		return err
448
+	}
449
+	defer httputils.CloseStreams(inStream, outStream)
450
+
451
+	if _, ok := r.Header["Upgrade"]; ok {
452
+		fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
453
+	} else {
454
+		fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
455
+	}
456
+
457
+	attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{
458
+		InStream:  inStream,
459
+		OutStream: outStream,
460
+		UseStdin:  httputils.BoolValue(r, "stdin"),
461
+		UseStdout: httputils.BoolValue(r, "stdout"),
462
+		UseStderr: httputils.BoolValue(r, "stderr"),
463
+		Logs:      httputils.BoolValue(r, "logs"),
464
+		Stream:    httputils.BoolValue(r, "stream"),
465
+	}
466
+
467
+	if err := s.daemon.ContainerAttachWithLogs(containerName, attachWithLogsConfig); err != nil {
468
+		fmt.Fprintf(outStream, "Error attaching: %s\n", err)
469
+	}
470
+
471
+	return nil
472
+}
473
+
474
+func (s *router) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
475
+	if err := httputils.ParseForm(r); err != nil {
476
+		return err
477
+	}
478
+	if vars == nil {
479
+		return fmt.Errorf("Missing parameter")
480
+	}
481
+	containerName := vars["name"]
482
+
483
+	if !s.daemon.Exists(containerName) {
484
+		return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
485
+	}
486
+
487
+	h := websocket.Handler(func(ws *websocket.Conn) {
488
+		defer ws.Close()
489
+
490
+		wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{
491
+			InStream:  ws,
492
+			OutStream: ws,
493
+			ErrStream: ws,
494
+			Logs:      httputils.BoolValue(r, "logs"),
495
+			Stream:    httputils.BoolValue(r, "stream"),
496
+		}
497
+
498
+		if err := s.daemon.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil {
499
+			logrus.Errorf("Error attaching websocket: %s", err)
500
+		}
501
+	})
502
+	ws := websocket.Server{Handler: h, Handshake: nil}
503
+	ws.ServeHTTP(w, r)
504
+
505
+	return nil
506
+}
0 507
new file mode 100644
... ...
@@ -0,0 +1,116 @@
0
+package local
1
+
2
+import (
3
+	"encoding/base64"
4
+	"encoding/json"
5
+	"fmt"
6
+	"io"
7
+	"net/http"
8
+	"os"
9
+	"strings"
10
+
11
+	"github.com/docker/docker/api/server/httputils"
12
+	"github.com/docker/docker/api/types"
13
+	"golang.org/x/net/context"
14
+)
15
+
16
+// postContainersCopy is deprecated in favor of getContainersArchive.
17
+func (s *router) postContainersCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
18
+	if vars == nil {
19
+		return fmt.Errorf("Missing parameter")
20
+	}
21
+
22
+	if err := httputils.CheckForJSON(r); err != nil {
23
+		return err
24
+	}
25
+
26
+	cfg := types.CopyConfig{}
27
+	if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
28
+		return err
29
+	}
30
+
31
+	if cfg.Resource == "" {
32
+		return fmt.Errorf("Path cannot be empty")
33
+	}
34
+
35
+	data, err := s.daemon.ContainerCopy(vars["name"], cfg.Resource)
36
+	if err != nil {
37
+		if strings.Contains(strings.ToLower(err.Error()), "no such id") {
38
+			w.WriteHeader(http.StatusNotFound)
39
+			return nil
40
+		}
41
+		if os.IsNotExist(err) {
42
+			return fmt.Errorf("Could not find the file %s in container %s", cfg.Resource, vars["name"])
43
+		}
44
+		return err
45
+	}
46
+	defer data.Close()
47
+
48
+	w.Header().Set("Content-Type", "application/x-tar")
49
+	if _, err := io.Copy(w, data); err != nil {
50
+		return err
51
+	}
52
+
53
+	return nil
54
+}
55
+
56
+// // Encode the stat to JSON, base64 encode, and place in a header.
57
+func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Header) error {
58
+	statJSON, err := json.Marshal(stat)
59
+	if err != nil {
60
+		return err
61
+	}
62
+
63
+	header.Set(
64
+		"X-Docker-Container-Path-Stat",
65
+		base64.StdEncoding.EncodeToString(statJSON),
66
+	)
67
+
68
+	return nil
69
+}
70
+
71
+func (s *router) headContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
72
+	v, err := httputils.ArchiveFormValues(r, vars)
73
+	if err != nil {
74
+		return err
75
+	}
76
+
77
+	stat, err := s.daemon.ContainerStatPath(v.Name, v.Path)
78
+	if err != nil {
79
+		return err
80
+	}
81
+
82
+	return setContainerPathStatHeader(stat, w.Header())
83
+}
84
+
85
+func (s *router) getContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
86
+	v, err := httputils.ArchiveFormValues(r, vars)
87
+	if err != nil {
88
+		return err
89
+	}
90
+
91
+	tarArchive, stat, err := s.daemon.ContainerArchivePath(v.Name, v.Path)
92
+	if err != nil {
93
+		return err
94
+	}
95
+	defer tarArchive.Close()
96
+
97
+	if err := setContainerPathStatHeader(stat, w.Header()); err != nil {
98
+		return err
99
+	}
100
+
101
+	w.Header().Set("Content-Type", "application/x-tar")
102
+	_, err = io.Copy(w, tarArchive)
103
+
104
+	return err
105
+}
106
+
107
+func (s *router) putContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
108
+	v, err := httputils.ArchiveFormValues(r, vars)
109
+	if err != nil {
110
+		return err
111
+	}
112
+
113
+	noOverwriteDirNonDir := httputils.BoolValue(r, "noOverwriteDirNonDir")
114
+	return s.daemon.ContainerExtractToDir(v.Name, v.Path, noOverwriteDirNonDir, r.Body)
115
+}
0 116
new file mode 100644
... ...
@@ -0,0 +1,128 @@
0
+package local
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"io"
6
+	"net/http"
7
+	"strconv"
8
+
9
+	"github.com/Sirupsen/logrus"
10
+	"github.com/docker/docker/api/server/httputils"
11
+	"github.com/docker/docker/api/types"
12
+	"github.com/docker/docker/pkg/stdcopy"
13
+	"github.com/docker/docker/runconfig"
14
+	"golang.org/x/net/context"
15
+)
16
+
17
+func (s *router) getExecByID(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
18
+	if vars == nil {
19
+		return fmt.Errorf("Missing parameter 'id'")
20
+	}
21
+
22
+	eConfig, err := s.daemon.ContainerExecInspect(vars["id"])
23
+	if err != nil {
24
+		return err
25
+	}
26
+
27
+	return httputils.WriteJSON(w, http.StatusOK, eConfig)
28
+}
29
+
30
+func (s *router) postContainerExecCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
31
+	if err := httputils.ParseForm(r); err != nil {
32
+		return err
33
+	}
34
+	if err := httputils.CheckForJSON(r); err != nil {
35
+		return err
36
+	}
37
+	name := vars["name"]
38
+
39
+	execConfig := &runconfig.ExecConfig{}
40
+	if err := json.NewDecoder(r.Body).Decode(execConfig); err != nil {
41
+		return err
42
+	}
43
+	execConfig.Container = name
44
+
45
+	if len(execConfig.Cmd) == 0 {
46
+		return fmt.Errorf("No exec command specified")
47
+	}
48
+
49
+	// Register an instance of Exec in container.
50
+	id, err := s.daemon.ContainerExecCreate(execConfig)
51
+	if err != nil {
52
+		logrus.Errorf("Error setting up exec command in container %s: %s", name, err)
53
+		return err
54
+	}
55
+
56
+	return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{
57
+		ID: id,
58
+	})
59
+}
60
+
61
+// TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start.
62
+func (s *router) postContainerExecStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
63
+	if err := httputils.ParseForm(r); err != nil {
64
+		return err
65
+	}
66
+	var (
67
+		execName                  = vars["name"]
68
+		stdin, inStream           io.ReadCloser
69
+		stdout, stderr, outStream io.Writer
70
+	)
71
+
72
+	execStartCheck := &types.ExecStartCheck{}
73
+	if err := json.NewDecoder(r.Body).Decode(execStartCheck); err != nil {
74
+		return err
75
+	}
76
+
77
+	if !execStartCheck.Detach {
78
+		var err error
79
+		// Setting up the streaming http interface.
80
+		inStream, outStream, err = httputils.HijackConnection(w)
81
+		if err != nil {
82
+			return err
83
+		}
84
+		defer httputils.CloseStreams(inStream, outStream)
85
+
86
+		if _, ok := r.Header["Upgrade"]; ok {
87
+			fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
88
+		} else {
89
+			fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
90
+		}
91
+
92
+		stdin = inStream
93
+		stdout = outStream
94
+		if !execStartCheck.Tty {
95
+			stderr = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
96
+			stdout = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
97
+		}
98
+	} else {
99
+		outStream = w
100
+	}
101
+
102
+	// Now run the user process in container.
103
+	if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil {
104
+		fmt.Fprintf(outStream, "Error running exec in container: %v\n", err)
105
+	}
106
+	return nil
107
+}
108
+
109
+func (s *router) postContainerExecResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
110
+	if err := httputils.ParseForm(r); err != nil {
111
+		return err
112
+	}
113
+	if vars == nil {
114
+		return fmt.Errorf("Missing parameter")
115
+	}
116
+
117
+	height, err := strconv.Atoi(r.Form.Get("h"))
118
+	if err != nil {
119
+		return err
120
+	}
121
+	width, err := strconv.Atoi(r.Form.Get("w"))
122
+	if err != nil {
123
+		return err
124
+	}
125
+
126
+	return s.daemon.ContainerExecResize(vars["name"], height, width)
127
+}
0 128
new file mode 100644
... ...
@@ -0,0 +1,438 @@
0
+package local
1
+
2
+import (
3
+	"encoding/base64"
4
+	"encoding/json"
5
+	"errors"
6
+	"fmt"
7
+	"io"
8
+	"net/http"
9
+	"strings"
10
+
11
+	"github.com/Sirupsen/logrus"
12
+	"github.com/docker/docker/api/server/httputils"
13
+	"github.com/docker/docker/api/types"
14
+	"github.com/docker/docker/builder"
15
+	"github.com/docker/docker/cliconfig"
16
+	"github.com/docker/docker/graph"
17
+	"github.com/docker/docker/pkg/ioutils"
18
+	"github.com/docker/docker/pkg/parsers"
19
+	"github.com/docker/docker/pkg/streamformatter"
20
+	"github.com/docker/docker/pkg/ulimit"
21
+	"github.com/docker/docker/runconfig"
22
+	"github.com/docker/docker/utils"
23
+	"golang.org/x/net/context"
24
+)
25
+
26
+func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
27
+	if err := httputils.ParseForm(r); err != nil {
28
+		return err
29
+	}
30
+
31
+	if err := httputils.CheckForJSON(r); err != nil {
32
+		return err
33
+	}
34
+
35
+	cname := r.Form.Get("container")
36
+
37
+	pause := httputils.BoolValue(r, "pause")
38
+	version := httputils.VersionFromContext(ctx)
39
+	if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
40
+		pause = true
41
+	}
42
+
43
+	c, _, err := runconfig.DecodeContainerConfig(r.Body)
44
+	if err != nil && err != io.EOF { //Do not fail if body is empty.
45
+		return err
46
+	}
47
+
48
+	commitCfg := &builder.CommitConfig{
49
+		Pause:   pause,
50
+		Repo:    r.Form.Get("repo"),
51
+		Tag:     r.Form.Get("tag"),
52
+		Author:  r.Form.Get("author"),
53
+		Comment: r.Form.Get("comment"),
54
+		Changes: r.Form["changes"],
55
+		Config:  c,
56
+	}
57
+
58
+	imgID, err := builder.Commit(cname, s.daemon, commitCfg)
59
+	if err != nil {
60
+		return err
61
+	}
62
+
63
+	return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerCommitResponse{
64
+		ID: imgID,
65
+	})
66
+}
67
+
68
+// Creates an image from Pull or from Import
69
+func (s *router) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
70
+	if err := httputils.ParseForm(r); err != nil {
71
+		return err
72
+	}
73
+
74
+	var (
75
+		image   = r.Form.Get("fromImage")
76
+		repo    = r.Form.Get("repo")
77
+		tag     = r.Form.Get("tag")
78
+		message = r.Form.Get("message")
79
+	)
80
+	authEncoded := r.Header.Get("X-Registry-Auth")
81
+	authConfig := &cliconfig.AuthConfig{}
82
+	if authEncoded != "" {
83
+		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
84
+		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
85
+			// for a pull it is not an error if no auth was given
86
+			// to increase compatibility with the existing api it is defaulting to be empty
87
+			authConfig = &cliconfig.AuthConfig{}
88
+		}
89
+	}
90
+
91
+	var (
92
+		err    error
93
+		output = ioutils.NewWriteFlusher(w)
94
+	)
95
+
96
+	w.Header().Set("Content-Type", "application/json")
97
+
98
+	if image != "" { //pull
99
+		if tag == "" {
100
+			image, tag = parsers.ParseRepositoryTag(image)
101
+		}
102
+		metaHeaders := map[string][]string{}
103
+		for k, v := range r.Header {
104
+			if strings.HasPrefix(k, "X-Meta-") {
105
+				metaHeaders[k] = v
106
+			}
107
+		}
108
+
109
+		imagePullConfig := &graph.ImagePullConfig{
110
+			MetaHeaders: metaHeaders,
111
+			AuthConfig:  authConfig,
112
+			OutStream:   output,
113
+		}
114
+
115
+		err = s.daemon.Repositories().Pull(image, tag, imagePullConfig)
116
+	} else { //import
117
+		if tag == "" {
118
+			repo, tag = parsers.ParseRepositoryTag(repo)
119
+		}
120
+
121
+		src := r.Form.Get("fromSrc")
122
+
123
+		// 'err' MUST NOT be defined within this block, we need any error
124
+		// generated from the download to be available to the output
125
+		// stream processing below
126
+		var newConfig *runconfig.Config
127
+		newConfig, err = builder.BuildFromConfig(s.daemon, &runconfig.Config{}, r.Form["changes"])
128
+		if err != nil {
129
+			return err
130
+		}
131
+
132
+		err = s.daemon.Repositories().Import(src, repo, tag, message, r.Body, output, newConfig)
133
+	}
134
+	if err != nil {
135
+		if !output.Flushed() {
136
+			return err
137
+		}
138
+		sf := streamformatter.NewJSONStreamFormatter()
139
+		output.Write(sf.FormatError(err))
140
+	}
141
+
142
+	return nil
143
+}
144
+
145
+func (s *router) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
146
+	if vars == nil {
147
+		return fmt.Errorf("Missing parameter")
148
+	}
149
+
150
+	metaHeaders := map[string][]string{}
151
+	for k, v := range r.Header {
152
+		if strings.HasPrefix(k, "X-Meta-") {
153
+			metaHeaders[k] = v
154
+		}
155
+	}
156
+	if err := httputils.ParseForm(r); err != nil {
157
+		return err
158
+	}
159
+	authConfig := &cliconfig.AuthConfig{}
160
+
161
+	authEncoded := r.Header.Get("X-Registry-Auth")
162
+	if authEncoded != "" {
163
+		// the new format is to handle the authConfig as a header
164
+		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
165
+		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
166
+			// to increase compatibility to existing api it is defaulting to be empty
167
+			authConfig = &cliconfig.AuthConfig{}
168
+		}
169
+	} else {
170
+		// the old format is supported for compatibility if there was no authConfig header
171
+		if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
172
+			return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err)
173
+		}
174
+	}
175
+
176
+	name := vars["name"]
177
+	output := ioutils.NewWriteFlusher(w)
178
+	imagePushConfig := &graph.ImagePushConfig{
179
+		MetaHeaders: metaHeaders,
180
+		AuthConfig:  authConfig,
181
+		Tag:         r.Form.Get("tag"),
182
+		OutStream:   output,
183
+	}
184
+
185
+	w.Header().Set("Content-Type", "application/json")
186
+
187
+	if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil {
188
+		if !output.Flushed() {
189
+			return err
190
+		}
191
+		sf := streamformatter.NewJSONStreamFormatter()
192
+		output.Write(sf.FormatError(err))
193
+	}
194
+	return nil
195
+}
196
+
197
+func (s *router) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
198
+	if vars == nil {
199
+		return fmt.Errorf("Missing parameter")
200
+	}
201
+	if err := httputils.ParseForm(r); err != nil {
202
+		return err
203
+	}
204
+
205
+	w.Header().Set("Content-Type", "application/x-tar")
206
+
207
+	output := ioutils.NewWriteFlusher(w)
208
+	var names []string
209
+	if name, ok := vars["name"]; ok {
210
+		names = []string{name}
211
+	} else {
212
+		names = r.Form["names"]
213
+	}
214
+
215
+	if err := s.daemon.Repositories().ImageExport(names, output); err != nil {
216
+		if !output.Flushed() {
217
+			return err
218
+		}
219
+		sf := streamformatter.NewJSONStreamFormatter()
220
+		output.Write(sf.FormatError(err))
221
+	}
222
+	return nil
223
+}
224
+
225
+func (s *router) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
226
+	return s.daemon.Repositories().Load(r.Body, w)
227
+}
228
+
229
+func (s *router) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
230
+	if err := httputils.ParseForm(r); err != nil {
231
+		return err
232
+	}
233
+	if vars == nil {
234
+		return fmt.Errorf("Missing parameter")
235
+	}
236
+
237
+	name := vars["name"]
238
+
239
+	if name == "" {
240
+		return fmt.Errorf("image name cannot be blank")
241
+	}
242
+
243
+	force := httputils.BoolValue(r, "force")
244
+	prune := !httputils.BoolValue(r, "noprune")
245
+
246
+	list, err := s.daemon.ImageDelete(name, force, prune)
247
+	if err != nil {
248
+		return err
249
+	}
250
+
251
+	return httputils.WriteJSON(w, http.StatusOK, list)
252
+}
253
+
254
+func (s *router) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
255
+	if vars == nil {
256
+		return fmt.Errorf("Missing parameter")
257
+	}
258
+
259
+	imageInspect, err := s.daemon.Repositories().Lookup(vars["name"])
260
+	if err != nil {
261
+		return err
262
+	}
263
+
264
+	return httputils.WriteJSON(w, http.StatusOK, imageInspect)
265
+}
266
+
267
+func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
268
+	var (
269
+		authConfigs        = map[string]cliconfig.AuthConfig{}
270
+		authConfigsEncoded = r.Header.Get("X-Registry-Config")
271
+		buildConfig        = builder.NewBuildConfig()
272
+	)
273
+
274
+	if authConfigsEncoded != "" {
275
+		authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
276
+		if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
277
+			// for a pull it is not an error if no auth was given
278
+			// to increase compatibility with the existing api it is defaulting
279
+			// to be empty.
280
+		}
281
+	}
282
+
283
+	w.Header().Set("Content-Type", "application/json")
284
+
285
+	version := httputils.VersionFromContext(ctx)
286
+	if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
287
+		buildConfig.Remove = true
288
+	} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
289
+		buildConfig.Remove = true
290
+	} else {
291
+		buildConfig.Remove = httputils.BoolValue(r, "rm")
292
+	}
293
+	if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
294
+		buildConfig.Pull = true
295
+	}
296
+
297
+	output := ioutils.NewWriteFlusher(w)
298
+	buildConfig.Stdout = output
299
+	buildConfig.Context = r.Body
300
+
301
+	buildConfig.RemoteURL = r.FormValue("remote")
302
+	buildConfig.DockerfileName = r.FormValue("dockerfile")
303
+	buildConfig.RepoName = r.FormValue("t")
304
+	buildConfig.SuppressOutput = httputils.BoolValue(r, "q")
305
+	buildConfig.NoCache = httputils.BoolValue(r, "nocache")
306
+	buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm")
307
+	buildConfig.AuthConfigs = authConfigs
308
+	buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
309
+	buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory")
310
+	buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
311
+	buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
312
+	buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
313
+	buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
314
+	buildConfig.CPUSetMems = r.FormValue("cpusetmems")
315
+	buildConfig.CgroupParent = r.FormValue("cgroupparent")
316
+
317
+	var buildUlimits = []*ulimit.Ulimit{}
318
+	ulimitsJSON := r.FormValue("ulimits")
319
+	if ulimitsJSON != "" {
320
+		if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
321
+			return err
322
+		}
323
+		buildConfig.Ulimits = buildUlimits
324
+	}
325
+
326
+	var buildArgs = map[string]string{}
327
+	buildArgsJSON := r.FormValue("buildargs")
328
+	if buildArgsJSON != "" {
329
+		if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
330
+			return err
331
+		}
332
+	}
333
+	buildConfig.BuildArgs = buildArgs
334
+
335
+	// Job cancellation. Note: not all job types support this.
336
+	if closeNotifier, ok := w.(http.CloseNotifier); ok {
337
+		finished := make(chan struct{})
338
+		defer close(finished)
339
+		go func() {
340
+			select {
341
+			case <-finished:
342
+			case <-closeNotifier.CloseNotify():
343
+				logrus.Infof("Client disconnected, cancelling job: build")
344
+				buildConfig.Cancel()
345
+			}
346
+		}()
347
+	}
348
+
349
+	if err := builder.Build(s.daemon, buildConfig); err != nil {
350
+		// Do not write the error in the http output if it's still empty.
351
+		// This prevents from writing a 200(OK) when there is an interal error.
352
+		if !output.Flushed() {
353
+			return err
354
+		}
355
+		sf := streamformatter.NewJSONStreamFormatter()
356
+		w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
357
+	}
358
+	return nil
359
+}
360
+
361
+func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
362
+	if err := httputils.ParseForm(r); err != nil {
363
+		return err
364
+	}
365
+
366
+	// FIXME: The filter parameter could just be a match filter
367
+	images, err := s.daemon.Repositories().Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
368
+	if err != nil {
369
+		return err
370
+	}
371
+
372
+	return httputils.WriteJSON(w, http.StatusOK, images)
373
+}
374
+
375
+func (s *router) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
376
+	if vars == nil {
377
+		return fmt.Errorf("Missing parameter")
378
+	}
379
+
380
+	name := vars["name"]
381
+	history, err := s.daemon.Repositories().History(name)
382
+	if err != nil {
383
+		return err
384
+	}
385
+
386
+	return httputils.WriteJSON(w, http.StatusOK, history)
387
+}
388
+
389
+func (s *router) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
390
+	if err := httputils.ParseForm(r); err != nil {
391
+		return err
392
+	}
393
+	if vars == nil {
394
+		return fmt.Errorf("Missing parameter")
395
+	}
396
+
397
+	repo := r.Form.Get("repo")
398
+	tag := r.Form.Get("tag")
399
+	force := httputils.BoolValue(r, "force")
400
+	name := vars["name"]
401
+	if err := s.daemon.Repositories().Tag(repo, tag, name, force); err != nil {
402
+		return err
403
+	}
404
+	s.daemon.EventsService.Log("tag", utils.ImageReference(repo, tag), "")
405
+	w.WriteHeader(http.StatusCreated)
406
+	return nil
407
+}
408
+
409
+func (s *router) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
410
+	if err := httputils.ParseForm(r); err != nil {
411
+		return err
412
+	}
413
+	var (
414
+		config      *cliconfig.AuthConfig
415
+		authEncoded = r.Header.Get("X-Registry-Auth")
416
+		headers     = map[string][]string{}
417
+	)
418
+
419
+	if authEncoded != "" {
420
+		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
421
+		if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
422
+			// for a search it is not an error if no auth was given
423
+			// to increase compatibility with the existing api it is defaulting to be empty
424
+			config = &cliconfig.AuthConfig{}
425
+		}
426
+	}
427
+	for k, v := range r.Header {
428
+		if strings.HasPrefix(k, "X-Meta-") {
429
+			headers[k] = v
430
+		}
431
+	}
432
+	query, err := s.daemon.RegistryService.Search(r.Form.Get("term"), config, headers)
433
+	if err != nil {
434
+		return err
435
+	}
436
+	return httputils.WriteJSON(w, http.StatusOK, query.Results)
437
+}
0 438
new file mode 100644
... ...
@@ -0,0 +1,181 @@
0
+package local
1
+
2
+import (
3
+	"encoding/json"
4
+	"net/http"
5
+	"runtime"
6
+	"strconv"
7
+	"strings"
8
+	"time"
9
+
10
+	"github.com/Sirupsen/logrus"
11
+	"github.com/docker/docker/api"
12
+	"github.com/docker/docker/api/server/httputils"
13
+	"github.com/docker/docker/api/types"
14
+	"github.com/docker/docker/autogen/dockerversion"
15
+	"github.com/docker/docker/pkg/ioutils"
16
+	"github.com/docker/docker/pkg/jsonmessage"
17
+	"github.com/docker/docker/pkg/parsers/filters"
18
+	"github.com/docker/docker/pkg/parsers/kernel"
19
+	"github.com/docker/docker/utils"
20
+	"golang.org/x/net/context"
21
+)
22
+
23
+func (s *router) getVersion(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
24
+	v := &types.Version{
25
+		Version:    dockerversion.VERSION,
26
+		APIVersion: api.Version,
27
+		GitCommit:  dockerversion.GITCOMMIT,
28
+		GoVersion:  runtime.Version(),
29
+		Os:         runtime.GOOS,
30
+		Arch:       runtime.GOARCH,
31
+		BuildTime:  dockerversion.BUILDTIME,
32
+	}
33
+
34
+	version := httputils.VersionFromContext(ctx)
35
+
36
+	if version.GreaterThanOrEqualTo("1.19") {
37
+		v.Experimental = utils.ExperimentalBuild()
38
+	}
39
+
40
+	if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
41
+		v.KernelVersion = kernelVersion.String()
42
+	}
43
+
44
+	return httputils.WriteJSON(w, http.StatusOK, v)
45
+}
46
+
47
+func (s *router) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
48
+	info, err := s.daemon.SystemInfo()
49
+	if err != nil {
50
+		return err
51
+	}
52
+
53
+	return httputils.WriteJSON(w, http.StatusOK, info)
54
+}
55
+
56
+func (s *router) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
57
+	if err := httputils.ParseForm(r); err != nil {
58
+		return err
59
+	}
60
+	var since int64 = -1
61
+	if r.Form.Get("since") != "" {
62
+		s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64)
63
+		if err != nil {
64
+			return err
65
+		}
66
+		since = s
67
+	}
68
+
69
+	var until int64 = -1
70
+	if r.Form.Get("until") != "" {
71
+		u, err := strconv.ParseInt(r.Form.Get("until"), 10, 64)
72
+		if err != nil {
73
+			return err
74
+		}
75
+		until = u
76
+	}
77
+
78
+	timer := time.NewTimer(0)
79
+	timer.Stop()
80
+	if until > 0 {
81
+		dur := time.Unix(until, 0).Sub(time.Now())
82
+		timer = time.NewTimer(dur)
83
+	}
84
+
85
+	ef, err := filters.FromParam(r.Form.Get("filters"))
86
+	if err != nil {
87
+		return err
88
+	}
89
+
90
+	isFiltered := func(field string, filter []string) bool {
91
+		if len(field) == 0 {
92
+			return false
93
+		}
94
+		if len(filter) == 0 {
95
+			return false
96
+		}
97
+		for _, v := range filter {
98
+			if v == field {
99
+				return false
100
+			}
101
+			if strings.Contains(field, ":") {
102
+				image := strings.Split(field, ":")
103
+				if image[0] == v {
104
+					return false
105
+				}
106
+			}
107
+		}
108
+		return true
109
+	}
110
+
111
+	d := s.daemon
112
+	es := d.EventsService
113
+	w.Header().Set("Content-Type", "application/json")
114
+
115
+	outStream := ioutils.NewWriteFlusher(w)
116
+	// Write an empty chunk of data.
117
+	// This is to ensure that the HTTP status code is sent immediately,
118
+	// so that it will not block the receiver.
119
+	outStream.Write(nil)
120
+	enc := json.NewEncoder(outStream)
121
+
122
+	getContainerID := func(cn string) string {
123
+		c, err := d.Get(cn)
124
+		if err != nil {
125
+			return ""
126
+		}
127
+		return c.ID
128
+	}
129
+
130
+	sendEvent := func(ev *jsonmessage.JSONMessage) error {
131
+		//incoming container filter can be name,id or partial id, convert and replace as a full container id
132
+		for i, cn := range ef["container"] {
133
+			ef["container"][i] = getContainerID(cn)
134
+		}
135
+
136
+		if isFiltered(ev.Status, ef["event"]) || (isFiltered(ev.ID, ef["image"]) &&
137
+			isFiltered(ev.From, ef["image"])) || isFiltered(ev.ID, ef["container"]) {
138
+			return nil
139
+		}
140
+
141
+		return enc.Encode(ev)
142
+	}
143
+
144
+	current, l := es.Subscribe()
145
+	if since == -1 {
146
+		current = nil
147
+	}
148
+	defer es.Evict(l)
149
+	for _, ev := range current {
150
+		if ev.Time < since {
151
+			continue
152
+		}
153
+		if err := sendEvent(ev); err != nil {
154
+			return err
155
+		}
156
+	}
157
+
158
+	var closeNotify <-chan bool
159
+	if closeNotifier, ok := w.(http.CloseNotifier); ok {
160
+		closeNotify = closeNotifier.CloseNotify()
161
+	}
162
+
163
+	for {
164
+		select {
165
+		case ev := <-l:
166
+			jev, ok := ev.(*jsonmessage.JSONMessage)
167
+			if !ok {
168
+				continue
169
+			}
170
+			if err := sendEvent(jev); err != nil {
171
+				return err
172
+			}
173
+		case <-timer.C:
174
+			return nil
175
+		case <-closeNotify:
176
+			logrus.Debug("Client disconnected, stop sending events")
177
+			return nil
178
+		}
179
+	}
180
+}
0 181
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+package local
1
+
2
+import (
3
+	"fmt"
4
+	"net/http"
5
+
6
+	"github.com/docker/docker/api/server/httputils"
7
+	"golang.org/x/net/context"
8
+)
9
+
10
+// getContainersByName inspects containers configuration and serializes it as json.
11
+func (s *router) getContainersByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
12
+	if vars == nil {
13
+		return fmt.Errorf("Missing parameter")
14
+	}
15
+
16
+	var json interface{}
17
+	var err error
18
+
19
+	version := httputils.VersionFromContext(ctx)
20
+
21
+	switch {
22
+	case version.LessThan("1.20"):
23
+		json, err = s.daemon.ContainerInspectPre120(vars["name"])
24
+	case version.Equal("1.20"):
25
+		json, err = s.daemon.ContainerInspect120(vars["name"])
26
+	default:
27
+		json, err = s.daemon.ContainerInspect(vars["name"])
28
+	}
29
+
30
+	if err != nil {
31
+		return err
32
+	}
33
+
34
+	return httputils.WriteJSON(w, http.StatusOK, json)
35
+}
0 36
new file mode 100644
... ...
@@ -0,0 +1,161 @@
0
+package local
1
+
2
+import (
3
+	"net/http"
4
+
5
+	"golang.org/x/net/context"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+	"github.com/docker/docker/api/server/httputils"
9
+	dkrouter "github.com/docker/docker/api/server/router"
10
+	"github.com/docker/docker/daemon"
11
+	"github.com/gorilla/mux"
12
+)
13
+
14
+// router is a docker router that talks with the local docker daemon.
15
+type router struct {
16
+	daemon *daemon.Daemon
17
+	routes []dkrouter.Route
18
+}
19
+
20
+// localRoute defines an individual API route to connect with the docker daemon.
21
+// It implements router.Route.
22
+type localRoute struct {
23
+	method  string
24
+	path    string
25
+	handler httputils.APIFunc
26
+}
27
+
28
+// Handler returns the APIFunc to let the server wrap it in middlewares
29
+func (l localRoute) Handler() httputils.APIFunc {
30
+	return l.handler
31
+}
32
+
33
+// Register adds the filtered handler to the mux.
34
+func (l localRoute) Register(m *mux.Router, handler http.Handler) {
35
+	logrus.Debugf("Registering %s, %s", l.method, l.path)
36
+	m.Path(dkrouter.VersionMatcher + l.path).Methods(l.method).Handler(handler)
37
+	m.Path(l.path).Methods(l.method).Handler(handler)
38
+}
39
+
40
+// NewRoute initialies a new local route for the reouter
41
+func NewRoute(method, path string, handler httputils.APIFunc) dkrouter.Route {
42
+	return localRoute{method, path, handler}
43
+}
44
+
45
+// NewGetRoute initializes a new route with the http method GET.
46
+func NewGetRoute(path string, handler httputils.APIFunc) dkrouter.Route {
47
+	return NewRoute("GET", path, handler)
48
+}
49
+
50
+// NewPostRoute initializes a new route with the http method POST.
51
+func NewPostRoute(path string, handler httputils.APIFunc) dkrouter.Route {
52
+	return NewRoute("POST", path, handler)
53
+}
54
+
55
+// NewPutRoute initializes a new route with the http method PUT.
56
+func NewPutRoute(path string, handler httputils.APIFunc) dkrouter.Route {
57
+	return NewRoute("PUT", path, handler)
58
+}
59
+
60
+// NewDeleteRoute initializes a new route with the http method DELETE.
61
+func NewDeleteRoute(path string, handler httputils.APIFunc) dkrouter.Route {
62
+	return NewRoute("DELETE", path, handler)
63
+}
64
+
65
+// NewOptionsRoute initializes a new route with the http method OPTIONS
66
+func NewOptionsRoute(path string, handler httputils.APIFunc) dkrouter.Route {
67
+	return NewRoute("OPTIONS", path, handler)
68
+}
69
+
70
+// NewHeadRoute initializes a new route with the http method HEAD.
71
+func NewHeadRoute(path string, handler httputils.APIFunc) dkrouter.Route {
72
+	return NewRoute("HEAD", path, handler)
73
+}
74
+
75
+// NewRouter initializes a local router with a new daemon.
76
+func NewRouter(daemon *daemon.Daemon) dkrouter.Router {
77
+	r := &router{
78
+		daemon: daemon,
79
+	}
80
+	r.initRoutes()
81
+	return r
82
+}
83
+
84
+// Routes returns the list of routes registered in the router.
85
+func (r *router) Routes() []dkrouter.Route {
86
+	return r.routes
87
+}
88
+
89
+// initRoutes initializes the routes in this router
90
+func (r *router) initRoutes() {
91
+	r.routes = []dkrouter.Route{
92
+		// HEAD
93
+		NewHeadRoute("/containers/{name:.*}/archive", r.headContainersArchive),
94
+		// OPTIONS
95
+		NewOptionsRoute("/", optionsHandler),
96
+		// GET
97
+		NewGetRoute("/_ping", pingHandler),
98
+		NewGetRoute("/events", r.getEvents),
99
+		NewGetRoute("/info", r.getInfo),
100
+		NewGetRoute("/version", r.getVersion),
101
+		NewGetRoute("/images/json", r.getImagesJSON),
102
+		NewGetRoute("/images/search", r.getImagesSearch),
103
+		NewGetRoute("/images/get", r.getImagesGet),
104
+		NewGetRoute("/images/{name:.*}/get", r.getImagesGet),
105
+		NewGetRoute("/images/{name:.*}/history", r.getImagesHistory),
106
+		NewGetRoute("/images/{name:.*}/json", r.getImagesByName),
107
+		NewGetRoute("/containers/json", r.getContainersJSON),
108
+		NewGetRoute("/containers/{name:.*}/export", r.getContainersExport),
109
+		NewGetRoute("/containers/{name:.*}/changes", r.getContainersChanges),
110
+		NewGetRoute("/containers/{name:.*}/json", r.getContainersByName),
111
+		NewGetRoute("/containers/{name:.*}/top", r.getContainersTop),
112
+		NewGetRoute("/containers/{name:.*}/logs", r.getContainersLogs),
113
+		NewGetRoute("/containers/{name:.*}/stats", r.getContainersStats),
114
+		NewGetRoute("/containers/{name:.*}/attach/ws", r.wsContainersAttach),
115
+		NewGetRoute("/exec/{id:.*}/json", r.getExecByID),
116
+		NewGetRoute("/containers/{name:.*}/archive", r.getContainersArchive),
117
+		NewGetRoute("/volumes", r.getVolumesList),
118
+		NewGetRoute("/volumes/{name:.*}", r.getVolumeByName),
119
+		// POST
120
+		NewPostRoute("/auth", r.postAuth),
121
+		NewPostRoute("/commit", r.postCommit),
122
+		NewPostRoute("/build", r.postBuild),
123
+		NewPostRoute("/images/create", r.postImagesCreate),
124
+		NewPostRoute("/images/load", r.postImagesLoad),
125
+		NewPostRoute("/images/{name:.*}/push", r.postImagesPush),
126
+		NewPostRoute("/images/{name:.*}/tag", r.postImagesTag),
127
+		NewPostRoute("/containers/create", r.postContainersCreate),
128
+		NewPostRoute("/containers/{name:.*}/kill", r.postContainersKill),
129
+		NewPostRoute("/containers/{name:.*}/pause", r.postContainersPause),
130
+		NewPostRoute("/containers/{name:.*}/unpause", r.postContainersUnpause),
131
+		NewPostRoute("/containers/{name:.*}/restart", r.postContainersRestart),
132
+		NewPostRoute("/containers/{name:.*}/start", r.postContainersStart),
133
+		NewPostRoute("/containers/{name:.*}/stop", r.postContainersStop),
134
+		NewPostRoute("/containers/{name:.*}/wait", r.postContainersWait),
135
+		NewPostRoute("/containers/{name:.*}/resize", r.postContainersResize),
136
+		NewPostRoute("/containers/{name:.*}/attach", r.postContainersAttach),
137
+		NewPostRoute("/containers/{name:.*}/copy", r.postContainersCopy),
138
+		NewPostRoute("/containers/{name:.*}/exec", r.postContainerExecCreate),
139
+		NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart),
140
+		NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize),
141
+		NewPostRoute("/containers/{name:.*}/rename", r.postContainerRename),
142
+		NewPostRoute("/volumes", r.postVolumesCreate),
143
+		// PUT
144
+		NewPutRoute("/containers/{name:.*}/archive", r.putContainersArchive),
145
+		// DELETE
146
+		NewDeleteRoute("/containers/{name:.*}", r.deleteContainers),
147
+		NewDeleteRoute("/images/{name:.*}", r.deleteImages),
148
+		NewDeleteRoute("/volumes/{name:.*}", r.deleteVolumes),
149
+	}
150
+}
151
+
152
+func optionsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
153
+	w.WriteHeader(http.StatusOK)
154
+	return nil
155
+}
156
+
157
+func pingHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
158
+	_, err := w.Write([]byte{'O', 'K'})
159
+	return err
160
+}
0 161
new file mode 100644
... ...
@@ -0,0 +1,66 @@
0
+package local
1
+
2
+import (
3
+	"encoding/json"
4
+	"net/http"
5
+
6
+	"github.com/docker/docker/api/server/httputils"
7
+	"github.com/docker/docker/api/types"
8
+	"golang.org/x/net/context"
9
+)
10
+
11
+func (s *router) getVolumesList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
12
+	if err := httputils.ParseForm(r); err != nil {
13
+		return err
14
+	}
15
+
16
+	volumes, err := s.daemon.Volumes(r.Form.Get("filters"))
17
+	if err != nil {
18
+		return err
19
+	}
20
+	return httputils.WriteJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes})
21
+}
22
+
23
+func (s *router) getVolumeByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
24
+	if err := httputils.ParseForm(r); err != nil {
25
+		return err
26
+	}
27
+
28
+	v, err := s.daemon.VolumeInspect(vars["name"])
29
+	if err != nil {
30
+		return err
31
+	}
32
+	return httputils.WriteJSON(w, http.StatusOK, v)
33
+}
34
+
35
+func (s *router) postVolumesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
36
+	if err := httputils.ParseForm(r); err != nil {
37
+		return err
38
+	}
39
+
40
+	if err := httputils.CheckForJSON(r); err != nil {
41
+		return err
42
+	}
43
+
44
+	var req types.VolumeCreateRequest
45
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
46
+		return err
47
+	}
48
+
49
+	volume, err := s.daemon.VolumeCreate(req.Name, req.Driver, req.DriverOpts)
50
+	if err != nil {
51
+		return err
52
+	}
53
+	return httputils.WriteJSON(w, http.StatusCreated, volume)
54
+}
55
+
56
+func (s *router) deleteVolumes(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
57
+	if err := httputils.ParseForm(r); err != nil {
58
+		return err
59
+	}
60
+	if err := s.daemon.VolumeRm(vars["name"]); err != nil {
61
+		return err
62
+	}
63
+	w.WriteHeader(http.StatusNoContent)
64
+	return nil
65
+}
0 66
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package network
1
+
2
+import (
3
+	"github.com/docker/docker/api/server/httputils"
4
+	"github.com/docker/docker/api/server/router"
5
+)
6
+
7
+// networkRouter is a router to talk with the network controller
8
+type networkRouter struct {
9
+	routes []router.Route
10
+}
11
+
12
+// Routes returns the available routes to the network controller
13
+func (n networkRouter) Routes() []router.Route {
14
+	return n.routes
15
+}
16
+
17
+type networkRoute struct {
18
+	path    string
19
+	handler httputils.APIFunc
20
+}
21
+
22
+// Handler returns the APIFunc to let the server wrap it in middlewares
23
+func (l networkRoute) Handler() httputils.APIFunc {
24
+	return l.handler
25
+}
0 26
new file mode 100644
... ...
@@ -0,0 +1,51 @@
0
+// +build experimental
1
+
2
+package network
3
+
4
+import (
5
+	"net/http"
6
+
7
+	"golang.org/x/net/context"
8
+
9
+	"github.com/Sirupsen/logrus"
10
+	"github.com/docker/docker/api/server/router"
11
+	"github.com/docker/docker/daemon"
12
+	"github.com/docker/libnetwork/api"
13
+	"github.com/gorilla/mux"
14
+)
15
+
16
+var httpMethods = []string{"GET", "POST", "PUT", "DELETE"}
17
+
18
+// NewRouter initializes a new network router
19
+func NewRouter(d *daemon.Daemon) router.Router {
20
+	c := d.NetworkController()
21
+	if c == nil {
22
+		return networkRouter{}
23
+	}
24
+
25
+	var routes []router.Route
26
+	netHandler := api.NewHTTPHandler(c)
27
+
28
+	// TODO: libnetwork should stop hijacking request/response.
29
+	// It should define API functions to add normally to the router.
30
+	handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
31
+		netHandler(w, r)
32
+		return nil
33
+	}
34
+
35
+	for _, path := range []string{"/networks", "/services", "/sandboxes"} {
36
+		routes = append(routes, networkRoute{path, handler})
37
+	}
38
+
39
+	return networkRouter{routes}
40
+}
41
+
42
+// Register adds the filtered handler to the mux.
43
+func (n networkRoute) Register(m *mux.Router, handler http.Handler) {
44
+	logrus.Debugf("Registering %s, %v", n.path, httpMethods)
45
+	subrouter := m.PathPrefix(router.VersionMatcher + n.path).Subrouter()
46
+	subrouter.Methods(httpMethods...).Handler(handler)
47
+
48
+	subrouter = m.PathPrefix(n.path).Subrouter()
49
+	subrouter.Methods(httpMethods...).Handler(handler)
50
+}
0 51
new file mode 100644
... ...
@@ -0,0 +1,20 @@
0
+// +build !experimental
1
+
2
+package network
3
+
4
+import (
5
+	"net/http"
6
+
7
+	"github.com/docker/docker/api/server/router"
8
+	"github.com/docker/docker/daemon"
9
+	"github.com/gorilla/mux"
10
+)
11
+
12
+// NewRouter initializes a new network router
13
+func NewRouter(d *daemon.Daemon) router.Router {
14
+	return networkRouter{}
15
+}
16
+
17
+// Register adds the filtered handler to the mux.
18
+func (n networkRoute) Register(m *mux.Router, handler http.Handler) {
19
+}
0 20
new file mode 100644
... ...
@@ -0,0 +1,25 @@
0
+package router
1
+
2
+import (
3
+	"net/http"
4
+
5
+	"github.com/docker/docker/api/server/httputils"
6
+	"github.com/gorilla/mux"
7
+)
8
+
9
+// VersionMatcher defines a variable matcher to be parsed by the router
10
+// when a request is about to be served.
11
+const VersionMatcher = "/v{version:[0-9.]+}"
12
+
13
+// Router defines an interface to specify a group of routes to add the the docker server.
14
+type Router interface {
15
+	Routes() []Route
16
+}
17
+
18
+// Route defines an individual API route in the docker server.
19
+type Route interface {
20
+	// Register adds the handler route to the docker mux.
21
+	Register(*mux.Router, http.Handler)
22
+	// Handler returns the raw function to create the http handler.
23
+	Handler() httputils.APIFunc
24
+}
... ...
@@ -2,17 +2,17 @@ package server
2 2
 
3 3
 import (
4 4
 	"crypto/tls"
5
-	"encoding/json"
6 5
 	"fmt"
7
-	"io"
8 6
 	"net"
9 7
 	"net/http"
10 8
 	"os"
11 9
 	"strings"
12 10
 
13 11
 	"github.com/Sirupsen/logrus"
14
-	"github.com/docker/distribution/registry/api/errcode"
15
-	"github.com/docker/docker/api"
12
+	"github.com/docker/docker/api/server/httputils"
13
+	"github.com/docker/docker/api/server/router"
14
+	"github.com/docker/docker/api/server/router/local"
15
+	"github.com/docker/docker/api/server/router/network"
16 16
 	"github.com/docker/docker/daemon"
17 17
 	"github.com/docker/docker/pkg/sockets"
18 18
 	"github.com/docker/docker/utils"
... ...
@@ -32,21 +32,18 @@ type Config struct {
32 32
 
33 33
 // Server contains instance details for the server
34 34
 type Server struct {
35
-	daemon  *daemon.Daemon
36 35
 	cfg     *Config
37
-	router  *mux.Router
38 36
 	start   chan struct{}
39 37
 	servers []serverCloser
38
+	routers []router.Router
40 39
 }
41 40
 
42 41
 // New returns a new instance of the server based on the specified configuration.
43 42
 func New(cfg *Config) *Server {
44
-	srv := &Server{
43
+	return &Server{
45 44
 		cfg:   cfg,
46 45
 		start: make(chan struct{}),
47 46
 	}
48
-	srv.router = createRouter(srv)
49
-	return srv
50 47
 }
51 48
 
52 49
 // Close closes servers and thus stop receiving requests
... ...
@@ -118,152 +115,6 @@ func (s *HTTPServer) Close() error {
118 118
 	return s.l.Close()
119 119
 }
120 120
 
121
-// HTTPAPIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
122
-// Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
123
-type HTTPAPIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
124
-
125
-func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
126
-	conn, _, err := w.(http.Hijacker).Hijack()
127
-	if err != nil {
128
-		return nil, nil, err
129
-	}
130
-	// Flush the options to make sure the client sets the raw mode
131
-	conn.Write([]byte{})
132
-	return conn, conn, nil
133
-}
134
-
135
-func closeStreams(streams ...interface{}) {
136
-	for _, stream := range streams {
137
-		if tcpc, ok := stream.(interface {
138
-			CloseWrite() error
139
-		}); ok {
140
-			tcpc.CloseWrite()
141
-		} else if closer, ok := stream.(io.Closer); ok {
142
-			closer.Close()
143
-		}
144
-	}
145
-}
146
-
147
-// checkForJSON makes sure that the request's Content-Type is application/json.
148
-func checkForJSON(r *http.Request) error {
149
-	ct := r.Header.Get("Content-Type")
150
-
151
-	// No Content-Type header is ok as long as there's no Body
152
-	if ct == "" {
153
-		if r.Body == nil || r.ContentLength == 0 {
154
-			return nil
155
-		}
156
-	}
157
-
158
-	// Otherwise it better be json
159
-	if api.MatchesContentType(ct, "application/json") {
160
-		return nil
161
-	}
162
-	return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
163
-}
164
-
165
-//If we don't do this, POST method without Content-type (even with empty body) will fail
166
-func parseForm(r *http.Request) error {
167
-	if r == nil {
168
-		return nil
169
-	}
170
-	if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
171
-		return err
172
-	}
173
-	return nil
174
-}
175
-
176
-func parseMultipartForm(r *http.Request) error {
177
-	if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
178
-		return err
179
-	}
180
-	return nil
181
-}
182
-
183
-func httpError(w http.ResponseWriter, err error) {
184
-	if err == nil || w == nil {
185
-		logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
186
-		return
187
-	}
188
-
189
-	statusCode := http.StatusInternalServerError
190
-	errMsg := err.Error()
191
-
192
-	// Based on the type of error we get we need to process things
193
-	// slightly differently to extract the error message.
194
-	// In the 'errcode.*' cases there are two different type of
195
-	// error that could be returned. errocode.ErrorCode is the base
196
-	// type of error object - it is just an 'int' that can then be
197
-	// used as the look-up key to find the message. errorcode.Error
198
-	// extends errorcode.Error by adding error-instance specific
199
-	// data, like 'details' or variable strings to be inserted into
200
-	// the message.
201
-	//
202
-	// Ideally, we should just be able to call err.Error() for all
203
-	// cases but the errcode package doesn't support that yet.
204
-	//
205
-	// Additionally, in both errcode cases, there might be an http
206
-	// status code associated with it, and if so use it.
207
-	switch err.(type) {
208
-	case errcode.ErrorCode:
209
-		daError, _ := err.(errcode.ErrorCode)
210
-		statusCode = daError.Descriptor().HTTPStatusCode
211
-		errMsg = daError.Message()
212
-
213
-	case errcode.Error:
214
-		// For reference, if you're looking for a particular error
215
-		// then you can do something like :
216
-		//   import ( derr "github.com/docker/docker/errors" )
217
-		//   if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
218
-
219
-		daError, _ := err.(errcode.Error)
220
-		statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
221
-		errMsg = daError.Message
222
-
223
-	default:
224
-		// This part of will be removed once we've
225
-		// converted everything over to use the errcode package
226
-
227
-		// FIXME: this is brittle and should not be necessary.
228
-		// If we need to differentiate between different possible error types,
229
-		// we should create appropriate error types with clearly defined meaning
230
-		errStr := strings.ToLower(err.Error())
231
-		for keyword, status := range map[string]int{
232
-			"not found":             http.StatusNotFound,
233
-			"no such":               http.StatusNotFound,
234
-			"bad parameter":         http.StatusBadRequest,
235
-			"conflict":              http.StatusConflict,
236
-			"impossible":            http.StatusNotAcceptable,
237
-			"wrong login/password":  http.StatusUnauthorized,
238
-			"hasn't been activated": http.StatusForbidden,
239
-		} {
240
-			if strings.Contains(errStr, keyword) {
241
-				statusCode = status
242
-				break
243
-			}
244
-		}
245
-	}
246
-
247
-	if statusCode == 0 {
248
-		statusCode = http.StatusInternalServerError
249
-	}
250
-
251
-	logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": utils.GetErrorMessage(err)}).Error("HTTP Error")
252
-	http.Error(w, errMsg, statusCode)
253
-}
254
-
255
-// writeJSON writes the value v to the http response stream as json with standard
256
-// json encoding.
257
-func writeJSON(w http.ResponseWriter, code int, v interface{}) error {
258
-	w.Header().Set("Content-Type", "application/json")
259
-	w.WriteHeader(code)
260
-	return json.NewEncoder(w).Encode(v)
261
-}
262
-
263
-func (s *Server) optionsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
264
-	w.WriteHeader(http.StatusOK)
265
-	return nil
266
-}
267 121
 func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) {
268 122
 	logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders)
269 123
 	w.Header().Add("Access-Control-Allow-Origin", corsHeaders)
... ...
@@ -271,11 +122,6 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string
271 271
 	w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS")
272 272
 }
273 273
 
274
-func (s *Server) ping(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
275
-	_, err := w.Write([]byte{'O', 'K'})
276
-	return err
277
-}
278
-
279 274
 func (s *Server) initTCPSocket(addr string) (l net.Listener, err error) {
280 275
 	if s.cfg.TLSConfig == nil || s.cfg.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert {
281 276
 		logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
... ...
@@ -289,10 +135,10 @@ func (s *Server) initTCPSocket(addr string) (l net.Listener, err error) {
289 289
 	return
290 290
 }
291 291
 
292
-func (s *Server) makeHTTPHandler(localMethod string, localRoute string, localHandler HTTPAPIFunc) http.HandlerFunc {
292
+func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc {
293 293
 	return func(w http.ResponseWriter, r *http.Request) {
294
-		// log the handler generation
295
-		logrus.Debugf("Calling %s %s", localMethod, localRoute)
294
+		// log the handler call
295
+		logrus.Debugf("Calling %s %s", r.Method, r.URL.Path)
296 296
 
297 297
 		// Define the context that we'll pass around to share info
298 298
 		// like the docker-request-id.
... ...
@@ -302,108 +148,53 @@ func (s *Server) makeHTTPHandler(localMethod string, localRoute string, localHan
302 302
 		// immediate function being called should still be passed
303 303
 		// as 'args' on the function call.
304 304
 		ctx := context.Background()
305
-		handlerFunc := s.handleWithGlobalMiddlewares(localHandler)
305
+		handlerFunc := s.handleWithGlobalMiddlewares(handler)
306 306
 
307 307
 		if err := handlerFunc(ctx, w, r, mux.Vars(r)); err != nil {
308
-			logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, utils.GetErrorMessage(err))
309
-			httpError(w, err)
308
+			logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.URL.Path, utils.GetErrorMessage(err))
309
+			httputils.WriteError(w, err)
310 310
 		}
311 311
 	}
312 312
 }
313 313
 
314
-// createRouter initializes the main router the server uses.
314
+// InitRouters initializes a list of routers for the server.
315
+func (s *Server) InitRouters(d *daemon.Daemon) {
316
+	s.addRouter(local.NewRouter(d))
317
+	s.addRouter(network.NewRouter(d))
318
+}
319
+
320
+// addRouter adds a new router to the server.
321
+func (s *Server) addRouter(r router.Router) {
322
+	s.routers = append(s.routers, r)
323
+}
324
+
325
+// CreateMux initializes the main router the server uses.
315 326
 // we keep enableCors just for legacy usage, need to be removed in the future
316
-func createRouter(s *Server) *mux.Router {
317
-	r := mux.NewRouter()
327
+func (s *Server) CreateMux() *mux.Router {
328
+	m := mux.NewRouter()
318 329
 	if os.Getenv("DEBUG") != "" {
319
-		profilerSetup(r, "/debug/")
330
+		profilerSetup(m, "/debug/")
320 331
 	}
321
-	m := map[string]map[string]HTTPAPIFunc{
322
-		"HEAD": {
323
-			"/containers/{name:.*}/archive": s.headContainersArchive,
324
-		},
325
-		"GET": {
326
-			"/_ping":                          s.ping,
327
-			"/events":                         s.getEvents,
328
-			"/info":                           s.getInfo,
329
-			"/version":                        s.getVersion,
330
-			"/images/json":                    s.getImagesJSON,
331
-			"/images/search":                  s.getImagesSearch,
332
-			"/images/get":                     s.getImagesGet,
333
-			"/images/{name:.*}/get":           s.getImagesGet,
334
-			"/images/{name:.*}/history":       s.getImagesHistory,
335
-			"/images/{name:.*}/json":          s.getImagesByName,
336
-			"/containers/json":                s.getContainersJSON,
337
-			"/containers/{name:.*}/export":    s.getContainersExport,
338
-			"/containers/{name:.*}/changes":   s.getContainersChanges,
339
-			"/containers/{name:.*}/json":      s.getContainersByName,
340
-			"/containers/{name:.*}/top":       s.getContainersTop,
341
-			"/containers/{name:.*}/logs":      s.getContainersLogs,
342
-			"/containers/{name:.*}/stats":     s.getContainersStats,
343
-			"/containers/{name:.*}/attach/ws": s.wsContainersAttach,
344
-			"/exec/{id:.*}/json":              s.getExecByID,
345
-			"/containers/{name:.*}/archive":   s.getContainersArchive,
346
-			"/volumes":                        s.getVolumesList,
347
-			"/volumes/{name:.*}":              s.getVolumeByName,
348
-		},
349
-		"POST": {
350
-			"/auth":                         s.postAuth,
351
-			"/commit":                       s.postCommit,
352
-			"/build":                        s.postBuild,
353
-			"/images/create":                s.postImagesCreate,
354
-			"/images/load":                  s.postImagesLoad,
355
-			"/images/{name:.*}/push":        s.postImagesPush,
356
-			"/images/{name:.*}/tag":         s.postImagesTag,
357
-			"/containers/create":            s.postContainersCreate,
358
-			"/containers/{name:.*}/kill":    s.postContainersKill,
359
-			"/containers/{name:.*}/pause":   s.postContainersPause,
360
-			"/containers/{name:.*}/unpause": s.postContainersUnpause,
361
-			"/containers/{name:.*}/restart": s.postContainersRestart,
362
-			"/containers/{name:.*}/start":   s.postContainersStart,
363
-			"/containers/{name:.*}/stop":    s.postContainersStop,
364
-			"/containers/{name:.*}/wait":    s.postContainersWait,
365
-			"/containers/{name:.*}/resize":  s.postContainersResize,
366
-			"/containers/{name:.*}/attach":  s.postContainersAttach,
367
-			"/containers/{name:.*}/copy":    s.postContainersCopy,
368
-			"/containers/{name:.*}/exec":    s.postContainerExecCreate,
369
-			"/exec/{name:.*}/start":         s.postContainerExecStart,
370
-			"/exec/{name:.*}/resize":        s.postContainerExecResize,
371
-			"/containers/{name:.*}/rename":  s.postContainerRename,
372
-			"/volumes":                      s.postVolumesCreate,
373
-		},
374
-		"PUT": {
375
-			"/containers/{name:.*}/archive": s.putContainersArchive,
376
-		},
377
-		"DELETE": {
378
-			"/containers/{name:.*}": s.deleteContainers,
379
-			"/images/{name:.*}":     s.deleteImages,
380
-			"/volumes/{name:.*}":    s.deleteVolumes,
381
-		},
382
-		"OPTIONS": {
383
-			"": s.optionsHandler,
384
-		},
385
-	}
386
-
387
-	for method, routes := range m {
388
-		for route, fct := range routes {
389
-			logrus.Debugf("Registering %s, %s", method, route)
390
-			// NOTE: scope issue, make sure the variables are local and won't be changed
391
-			localRoute := route
392
-			localFct := fct
393
-			localMethod := method
394 332
 
395
-			// build the handler function
396
-			f := s.makeHTTPHandler(localMethod, localRoute, localFct)
397
-
398
-			// add the new route
399
-			if localRoute == "" {
400
-				r.Methods(localMethod).HandlerFunc(f)
401
-			} else {
402
-				r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
403
-				r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
404
-			}
333
+	logrus.Debugf("Registering routers")
334
+	for _, router := range s.routers {
335
+		for _, r := range router.Routes() {
336
+			f := s.makeHTTPHandler(r.Handler())
337
+			r.Register(m, f)
405 338
 		}
406 339
 	}
407 340
 
408
-	return r
341
+	return m
342
+}
343
+
344
+// AcceptConnections allows clients to connect to the API server.
345
+// Referenced Daemon is notified about this server, and waits for the
346
+// daemon acknowledgement before the incoming connections are accepted.
347
+func (s *Server) AcceptConnections() {
348
+	// close the lock so the listeners start accepting connections
349
+	select {
350
+	case <-s.start:
351
+	default:
352
+		close(s.start)
353
+	}
409 354
 }
410 355
deleted file mode 100644
... ...
@@ -1,22 +0,0 @@
1
-// +build experimental,!windows
2
-
3
-package server
4
-
5
-func (s *Server) registerSubRouter() {
6
-	httpHandler := s.daemon.NetworkAPIRouter()
7
-
8
-	subrouter := s.router.PathPrefix("/v{version:[0-9.]+}/networks").Subrouter()
9
-	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
10
-	subrouter = s.router.PathPrefix("/networks").Subrouter()
11
-	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
12
-
13
-	subrouter = s.router.PathPrefix("/v{version:[0-9.]+}/services").Subrouter()
14
-	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
15
-	subrouter = s.router.PathPrefix("/services").Subrouter()
16
-	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
17
-
18
-	subrouter = s.router.PathPrefix("/v{version:[0-9.]+}/sandboxes").Subrouter()
19
-	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
20
-	subrouter = s.router.PathPrefix("/sandboxes").Subrouter()
21
-	subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
22
-}
23 1
deleted file mode 100644
... ...
@@ -1,6 +0,0 @@
1
-// +build !experimental windows
2
-
3
-package server
4
-
5
-func (s *Server) registerSubRouter() {
6
-}
... ...
@@ -5,6 +5,8 @@ import (
5 5
 	"net/http/httptest"
6 6
 	"testing"
7 7
 
8
+	"github.com/docker/docker/api/server/httputils"
9
+
8 10
 	"golang.org/x/net/context"
9 11
 )
10 12
 
... ...
@@ -19,7 +21,7 @@ func TestMiddlewares(t *testing.T) {
19 19
 	ctx := context.Background()
20 20
 
21 21
 	localHandler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
22
-		if versionFromContext(ctx) == "" {
22
+		if httputils.VersionFromContext(ctx) == "" {
23 23
 			t.Fatalf("Expected version, got empty string")
24 24
 		}
25 25
 		return nil
... ...
@@ -8,12 +8,10 @@ import (
8 8
 	"net/http"
9 9
 	"strconv"
10 10
 
11
-	"github.com/docker/docker/daemon"
12 11
 	"github.com/docker/docker/pkg/sockets"
13 12
 	"github.com/docker/libnetwork/portallocator"
14 13
 
15 14
 	systemdActivation "github.com/coreos/go-systemd/activation"
16
-	systemdDaemon "github.com/coreos/go-systemd/daemon"
17 15
 )
18 16
 
19 17
 // newServer sets up the required serverClosers and does protocol specific checking.
... ...
@@ -52,7 +50,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
52 52
 		res = append(res, &HTTPServer{
53 53
 			&http.Server{
54 54
 				Addr:    addr,
55
-				Handler: s.router,
55
+				Handler: s.CreateMux(),
56 56
 			},
57 57
 			l,
58 58
 		})
... ...
@@ -60,22 +58,6 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
60 60
 	return res, nil
61 61
 }
62 62
 
63
-// AcceptConnections allows clients to connect to the API server.
64
-// Referenced Daemon is notified about this server, and waits for the
65
-// daemon acknowledgement before the incoming connections are accepted.
66
-func (s *Server) AcceptConnections(d *daemon.Daemon) {
67
-	// Tell the init daemon we are accepting requests
68
-	s.daemon = d
69
-	s.registerSubRouter()
70
-	go systemdDaemon.SdNotify("READY=1")
71
-	// close the lock so the listeners start accepting connections
72
-	select {
73
-	case <-s.start:
74
-	default:
75
-		close(s.start)
76
-	}
77
-}
78
-
79 63
 func allocateDaemonPort(addr string) error {
80 64
 	host, port, err := net.SplitHostPort(addr)
81 65
 	if err != nil {
... ...
@@ -6,8 +6,6 @@ import (
6 6
 	"errors"
7 7
 	"net"
8 8
 	"net/http"
9
-
10
-	"github.com/docker/docker/daemon"
11 9
 )
12 10
 
13 11
 // NewServer sets up the required Server and does protocol specific checking.
... ...
@@ -32,7 +30,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
32 32
 		res = append(res, &HTTPServer{
33 33
 			&http.Server{
34 34
 				Addr:    addr,
35
-				Handler: s.router,
35
+				Handler: s.CreateMux(),
36 36
 			},
37 37
 			l,
38 38
 		})
... ...
@@ -41,18 +39,6 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
41 41
 
42 42
 }
43 43
 
44
-// AcceptConnections allows router to start listening for the incoming requests.
45
-func (s *Server) AcceptConnections(d *daemon.Daemon) {
46
-	s.daemon = d
47
-	s.registerSubRouter()
48
-	// close the lock so the listeners start accepting connections
49
-	select {
50
-	case <-s.start:
51
-	default:
52
-		close(s.start)
53
-	}
54
-}
55
-
56 44
 func allocateDaemonPort(addr string) error {
57 45
 	return nil
58 46
 }
59 47
deleted file mode 100644
... ...
@@ -1,65 +0,0 @@
1
-package server
2
-
3
-import (
4
-	"encoding/json"
5
-	"net/http"
6
-
7
-	"github.com/docker/docker/api/types"
8
-	"golang.org/x/net/context"
9
-)
10
-
11
-func (s *Server) getVolumesList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
12
-	if err := parseForm(r); err != nil {
13
-		return err
14
-	}
15
-
16
-	volumes, err := s.daemon.Volumes(r.Form.Get("filters"))
17
-	if err != nil {
18
-		return err
19
-	}
20
-	return writeJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes})
21
-}
22
-
23
-func (s *Server) getVolumeByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
24
-	if err := parseForm(r); err != nil {
25
-		return err
26
-	}
27
-
28
-	v, err := s.daemon.VolumeInspect(vars["name"])
29
-	if err != nil {
30
-		return err
31
-	}
32
-	return writeJSON(w, http.StatusOK, v)
33
-}
34
-
35
-func (s *Server) postVolumesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
36
-	if err := parseForm(r); err != nil {
37
-		return err
38
-	}
39
-
40
-	if err := checkForJSON(r); err != nil {
41
-		return err
42
-	}
43
-
44
-	var req types.VolumeCreateRequest
45
-	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
46
-		return err
47
-	}
48
-
49
-	volume, err := s.daemon.VolumeCreate(req.Name, req.Driver, req.DriverOpts)
50
-	if err != nil {
51
-		return err
52
-	}
53
-	return writeJSON(w, http.StatusCreated, volume)
54
-}
55
-
56
-func (s *Server) deleteVolumes(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
57
-	if err := parseForm(r); err != nil {
58
-		return err
59
-	}
60
-	if err := s.daemon.VolumeRm(vars["name"]); err != nil {
61
-		return err
62
-	}
63
-	w.WriteHeader(http.StatusNoContent)
64
-	return nil
65
-}
... ...
@@ -1131,6 +1131,11 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig,
1131 1131
 	return verifyPlatformContainerSettings(daemon, hostConfig, config)
1132 1132
 }
1133 1133
 
1134
+// NetworkController exposes the libnetwork interface to manage networks.
1135
+func (daemon *Daemon) NetworkController() libnetwork.NetworkController {
1136
+	return daemon.netController
1137
+}
1138
+
1134 1139
 func configureVolumes(config *Config) (*store.VolumeStore, error) {
1135 1140
 	volumesDriver, err := local.New(config.Root)
1136 1141
 	if err != nil {
... ...
@@ -5,7 +5,6 @@ package daemon
5 5
 import (
6 6
 	"fmt"
7 7
 	"net"
8
-	"net/http"
9 8
 	"os"
10 9
 	"path/filepath"
11 10
 	"strings"
... ...
@@ -22,7 +21,6 @@ import (
22 22
 	"github.com/docker/docker/runconfig"
23 23
 	"github.com/docker/docker/utils"
24 24
 	"github.com/docker/libnetwork"
25
-	nwapi "github.com/docker/libnetwork/api"
26 25
 	nwconfig "github.com/docker/libnetwork/config"
27 26
 	"github.com/docker/libnetwork/netlabel"
28 27
 	"github.com/docker/libnetwork/options"
... ...
@@ -489,12 +487,6 @@ func setupInitLayer(initLayer string) error {
489 489
 	return nil
490 490
 }
491 491
 
492
-// NetworkAPIRouter implements a feature for server-experimental,
493
-// directly calling into libnetwork.
494
-func (daemon *Daemon) NetworkAPIRouter() func(w http.ResponseWriter, req *http.Request) {
495
-	return nwapi.NewHTTPHandler(daemon.netController)
496
-}
497
-
498 492
 // registerLinks writes the links to a file.
499 493
 func (daemon *Daemon) registerLinks(container *Container, hostConfig *runconfig.HostConfig) error {
500 494
 	if hostConfig == nil || hostConfig.Links == nil {
... ...
@@ -224,21 +224,6 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
224 224
 		serverConfig.TLSConfig = tlsConfig
225 225
 	}
226 226
 
227
-	api := apiserver.New(serverConfig)
228
-
229
-	// The serve API routine never exits unless an error occurs
230
-	// We need to start it as a goroutine and wait on it so
231
-	// daemon doesn't exit
232
-	serveAPIWait := make(chan error)
233
-	go func() {
234
-		if err := api.ServeAPI(commonFlags.Hosts); err != nil {
235
-			logrus.Errorf("ServeAPI error: %v", err)
236
-			serveAPIWait <- err
237
-			return
238
-		}
239
-		serveAPIWait <- nil
240
-	}()
241
-
242 227
 	if err := migrateKey(); err != nil {
243 228
 		logrus.Fatal(err)
244 229
 	}
... ...
@@ -264,6 +249,22 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
264 264
 		"graphdriver": d.GraphDriver().String(),
265 265
 	}).Info("Docker daemon")
266 266
 
267
+	api := apiserver.New(serverConfig)
268
+	api.InitRouters(d)
269
+
270
+	// The serve API routine never exits unless an error occurs
271
+	// We need to start it as a goroutine and wait on it so
272
+	// daemon doesn't exit
273
+	serveAPIWait := make(chan error)
274
+	go func() {
275
+		if err := api.ServeAPI(commonFlags.Hosts); err != nil {
276
+			logrus.Errorf("ServeAPI error: %v", err)
277
+			serveAPIWait <- err
278
+			return
279
+		}
280
+		serveAPIWait <- nil
281
+	}()
282
+
267 283
 	signal.Trap(func() {
268 284
 		api.Close()
269 285
 		<-serveAPIWait
... ...
@@ -277,7 +278,8 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
277 277
 
278 278
 	// after the daemon is done setting up we can tell the api to start
279 279
 	// accepting connections with specified daemon
280
-	api.AcceptConnections(d)
280
+	notifySystem()
281
+	api.AcceptConnections()
281 282
 
282 283
 	// Daemon is fully initialized and handling API traffic
283 284
 	// Wait for serve API to complete
284 285
new file mode 100644
... ...
@@ -0,0 +1,7 @@
0
+// +build daemon
1
+
2
+package docker
3
+
4
+// notifySystem sends a message to the host when the server is ready to be used
5
+func notifySystem() {
6
+}
... ...
@@ -3,5 +3,12 @@
3 3
 package main
4 4
 
5 5
 import (
6
+	systemdDaemon "github.com/coreos/go-systemd/daemon"
6 7
 	_ "github.com/docker/docker/daemon/execdriver/lxc"
7 8
 )
9
+
10
+// notifySystem sends a message to the host when the server is ready to be used
11
+func notifySystem() {
12
+	// Tell the init daemon we are accepting requests
13
+	go systemdDaemon.SdNotify("READY=1")
14
+}
... ...
@@ -10,3 +10,7 @@ var daemonCli cli.Handler
10 10
 
11 11
 // TODO: remove once `-d` is retired
12 12
 func handleGlobalDaemonFlag() {}
13
+
14
+// notifySystem sends a message to the host when the server is ready to be used
15
+func notifySystem() {
16
+}
... ...
@@ -27,3 +27,7 @@ func setDefaultUmask() error {
27 27
 func getDaemonConfDir() string {
28 28
 	return os.Getenv("PROGRAMDATA") + `\docker\config`
29 29
 }
30
+
31
+// notifySystem sends a message to the host when the server is ready to be used
32
+func notifySystem() {
33
+}